DLQ forensic durability & idempotency
## Summary Child epic of &74 (reliability-primitive hardening). The #1181 audit finding (DLQ handler log-and-acks = forensic loss; `dead_letter_audit` lacks a dedup key = threshold inflation) turned out to be un-implementable as a single weight-3 issue: an external stop-ship review + six source-verification passes established that 1. `dead_letter_audit` is **ADR-058 audit-class** ("never hard-deleted while unarchived") — the obvious dedup-backfill `DELETE` is illegal, and a plain unique key would also suppress *genuine* re-occurrences, not just phantom ack-failure redeliveries; 2. the proposed `(envelope_id, original_queue)` key is **incoherent**: the broker DLX path stamps the consumer queue (`subscriber.rs:124`) while the inbox retry-cap path stamps the producer `source_service` (`inbox.rs:317`) — the real consumer-queue identity is dropped at the `handler(envelope)` boundary; 3. a correct retry bound (AC1) needs a durable capture-before-ack tier, an MQ-layer error taxonomy, a settlement-failure outcome that closes the session (today ack/nack errors warn-and-continue and leak prefetch credit), and an *enforced* queue-type/delivery-limit policy (currently default-behavior-in-absence-of-config only). **Core architectural decision (approved 2026-07-28):** idempotency is a property of an *occurrence*. `dead_letter_audit` stays the single append-only source of truth and gains a nullable `occurrence_token` + partial unique index with `ON CONFLICT DO NOTHING` (a no-op insert — zero backfill/UPDATE/DELETE, ADR-058-clean). The token is minted at dead-lettering time: a phantom redelivery reuses it (deduped), a genuine re-dead-lettering mints fresh (recorded + counted). Threshold counting stays on the now per-occurrence-idempotent log; no projection table. ## Plan [dlq-forensic-durability.adoc](docs/modules/ROOT/pages/plans/dlq-forensic-durability.adoc) (lands with the C1 MR; per-child plans authored just-in-time per standing convention). ## Children - [x] #1196 fix(mq): thread real consumer-queue identity to the dead-letter path (weight: 3) — !1098, f45fd445 - [x] #1181 fix(mq,security): per-occurrence dead-letter idempotency + correct threshold (weight: 5) - [x] #1197 fix(mq): bounded DLQ retry with durable capture + error taxonomy (weight: 8) - [x] #1198 chore(mq): pin queue-type & delivery-limit policy + enforcement (weight: 3) - [x] #1199 feat(mq): DLQ observability + triage runbook + false-alert disclosure (weight: 3) - [x] #1202 chore(mq): per-service broker credentials + least-privilege permissions (weight: 8) — addendum; !1106, ba88360d - [x] #1203 fix(mq): handler panic kills the consumer supervisor; convert to Err (weight: 3) — addendum Sequencing: C4 ∥ C1 → C2 (#1181) → C3 → C5. The malformed-envelope ack+drop branch (silent forensic discard, `subscriber.rs:558-565`) folds into C3 as the *permanent* error class — no separate issue. --- **COMPLETE (2026-07-30)** — all seven children merged; MR !1106 (ba88360d) closed the final child #1202. Plan archived (`plans/archive/dlq-forensic-durability.adoc`). Follow-ups: #1205–#1211.
epic