fix(ops): bound the stuck-orders and exception-inbox reads, report truncation (S-8) - #217
Merged
Merged
Conversation
…uncation (S-8) fetch_orders_by_status now requires a row cap (SQL LIMIT, deterministic ORDER BY primary key) and the exception inbox's two store reads (dead-letter rows, dead webhook deliveries) accept one — before, a large tenant's worklist read materialised its entire open-orders set and every dead row in memory on a worker thread (security pre-audit S-8). Per product decision the bound is honest pagination, not a silent cut: every capped caller probes with cap+1 and reports 'scan_truncated' in the response (stuck-orders, exceptions list, exceptions stats) when the window overflowed. Caps are env-tunable size safety nets in the journal_scan_limit mould: AGENTFLOW_OPS_ORDERS_SCAN_LIMIT and AGENTFLOW_OPS_INBOX_SCAN_LIMIT, both defaulting to 20000 — demo scale never gets near them. Two correctness guards that fall out of truncation: - a truncated webhook scan skips auto_resolve_missing_triage_findings — an incomplete seen-set cannot prove absence, and a wrongly resolved row whose updated_at never advances could not be reopened by upsert; - R1 journal-vs-store keeps its false-negative-only failure mode: an order outside the bounded window falls into the existing 'missing from the store - tolerated' branch, never a phantom finding. OpenAPI snapshot regenerated (three additive optional fields). Tests: engine LIMIT + determinism unit tests, newest-N store tests for both reads, and integration tests pinning the truncation flag on all three surfaces plus the auto-resolve guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DORA Metrics
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes security pre-audit S-8 (P3, unbounded reads):
fetch_orders_by_statushad noLIMITand the exception inbox materialised every dead-letter row and dead webhook delivery in memory on a worker thread.Semantics (per product decision): honest pagination, never a silent cut.
fetch_orders_by_statusnow requires a row cap (SQLLIMIT, deterministicORDER BYprimary key). The inbox's two store reads (list_dead_letter_events_for_inbox,list_dead_webhook_deliveries) accept an optional cap in both adapters (DuckDB + PostgreSQL), newest-N.scan_truncatedin the response —/v1/ops/stuck-orders,/v1/ops/exceptions,/v1/ops/exceptions/stats(additive optional fields; OpenAPI snapshot regenerated).journal_scan_limitmould:AGENTFLOW_OPS_ORDERS_SCAN_LIMIT,AGENTFLOW_OPS_INBOX_SCAN_LIMIT(default 20 000 — demo scale never gets near them).Correctness guards under truncation:
auto_resolve_missing_triage_findings: an incomplete seen-set cannot prove absence, and a wrongly resolved row whoseupdated_atnever advances could not be reopened by the upsert.Tests: engine LIMIT+determinism unit tests; newest-N store tests for both reads; integration tests pinning the truncation flag on all three surfaces and the auto-resolve guard. Locally: full unit suite 2026 passed, contract suite 17 passed (incl. live-vs-snapshot), ruff clean, bandit ratchet untouched (the reads stay parameterized/int-rendered — no new
nosec).🤖 Generated with Claude Code