D4: exception inbox (dead-letter + webhook overlay + R1/R2 reconciliation) - #146
Merged
Conversation
…nciliation (D4) GET /v1/ops/exceptions, GET /v1/ops/exceptions/stats, POST .../acknowledge, POST .../resolve (ops-surfaces-spec.md §4): one triage feed aggregating dead-letter events (native, read-only mirror of the existing store), dead webhook deliveries and R1 journal_vs_store / R2 stuck_replay reconciliation findings (both overlay-backed via the new ops_exception_triage table, control- plane state class 7, both adapters). - store.py: TriageState + ensure/list/upsert/auto-resolve/set-state/count port methods, plus dead-letter/webhook reads for the inbox and stuck_replay_threshold_seconds() (reuses AGENTFLOW_CONTROLPLANE_LEASE_SECONDS). - embedded.py/postgres.py: ops_exception_triage schema + full method set; the resolved-row reopen decision runs in SQL (not Python) so DuckDB's naive-local timestamp coercion and PostgreSQL's TIMESTAMPTZ both compare consistently regardless of whether the caller's seen_at is aware or naive. - reconciliation.py: pure R1 (serving store behind its journal stage) / R2 (dead-letter stuck in replay_pending) checks, read-only over the QueryEngine/ControlPlaneStore ports (I10). - duckdb_backend.py: dead_letter_events store counterparts for the two already-seeded evt-004/evt-009 journal rows, so the demo inbox is non-empty (I7) — control-plane state on the shared connection, seeded once regardless of SERVING_BACKEND. - manual_resolutions (§4.5) counts only operator acknowledge/resolve actions, not system auto-resolves (AUTO_RESOLVE_NOTE sentinel). Tests: 16 new unit cases for the store/overlay methods, a new test_exceptions_inbox.py integration suite (demo pins I7, stable ids I5, dl: 409 guard I6, R1/R2 detection, idempotent reads I10, auto-resolve, ack/resolve lifecycle, manual_resolutions re-pin I9), plus a tenant-scoping case in test_tenant_isolation.py (I8). Full unit 1620 passed, integration 240 passed/52 skipped (docker unavailable on this host, pre-existing), ruff+mypy strict clean, openapi/agent-tools regenerated. Co-Authored-By: Claude Sonnet 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.
Summary
plan_endgame_02_07_26.md: the exception inbox perdocs/ops-surfaces-spec.md§4 —GET /v1/ops/exceptions,GET /v1/ops/exceptions/stats,POST /v1/ops/exceptions/{id}/acknowledge,POST /v1/ops/exceptions/{id}/resolve.journal_vs_store)/R2 (stuck_replay) reconciliation findings — the latter two backed by a newops_exception_triageoverlay table (control-plane state class 7, both the embedded DuckDB and PostgreSQL adapters).dead_letter_eventsstore counterparts for the already-seededevt-004/evt-009journal rows, so the inbox is non-empty out of the box (I7).manual_resolutions(§4.5) counts only operator acknowledge/resolve actions, not system auto-resolves.Test plan
pytest tests/unit— 1620 passedpytest tests/integration/— 240 passed, 52 skipped (Docker-gated Kafka/Iceberg tests, unavailable on this Windows host — pre-existing, unrelated to this change)ruff check/ruff format --check— cleanmypy src/ --ignore-missing-imports— cleanpython scripts/export_openapi.py --check— clean after regeneration🤖 Generated with Claude Code