Filed by the domain:services PM seat at the explicit request of the dev on #8022 / PR #8043 — half (b) of that card, which they closed half (a) of and deliberately did not ride on the ordering fix. Unassigned, no pm:queue, for triage to grade.
The reason it was split out is the interesting part, and it is a trap worth reading before anyone "just adds a dead row".
The gap
When plugin-webhooks' auto-enqueuer cannot resolve a webhook's encrypted signing secret, it drops the subscription — correctly, fail-closed, rather than delivering unsigned (#7799). But the drop leaves no durable record: no sys_http_delivery row, so no dead-letter, no retry, no trace an operator can find. #8043 fixed the boot-ordering cause; it did not make the drop durable.
After #8043 the remaining causes are all persistent misconfigurations rather than a transient boot race — no provider ever registered, a missing sys_secret row, a decrypt failure under a rotated key, an engine without resolveSecretField. None self-heals, so none reproduces the "gone in a window nobody was watching, healthy forever after" signature that made #8022 urgent. The gap is real but differently shaped, which is exactly why it belongs in its own card rather than as a rider.
⚠️ The trap — why the obvious fix is worse than the gap
The obvious version is "write the drop as a dead row so an operator can see it and retry". Measured by the dev on #8043, that is actively unsafe:
IHttpOutbox (packages/services/service-messaging/src/http-outbox.ts) has exactly one write door — enqueue(), which mints pending.- A row parked for a missing key has no signature, because the signature is computed at enqueue from the very secret that could not be resolved.
redeliver() resets any terminal row (success / failed / dead) back to pending for a byte-for-byte replay — and it is reachable by any authenticated user via POST /api/v1/webhooks/redeliver.
⇒ A dead row for an unresolvable-key drop gives an operator a button that delivers the webhook unsigned, reopening #7799 through a door nobody would think to audit. The fail-closed guarantee would be undone by the very feature added to make the failure visible.
What a correct fix needs (⛔ not decided here)
- A never-sendable state for such a row — a new column on the
sys_http_delivery platform object, so migration-visible; and - a
redeliver() refusal for any row that requires a signature and does not have one.
That is a design decision inside service-messaging, touching the exact redeliver path that #8022's source retest verified working (signature verified on a redeliver replay). It wants review on its own merits.
Interim state — observability, ⛔ not durability
#8043 did make the drop loud rather than silent, cheaply and in-file: it now reports at error level with the consequence and the remedy in the message, carrying an ADR-0112 code/status pair (INTERNAL_ERROR / 500) in its metadata — the same pair the seeder's refusal for the same cause already used. Said once per outage per webhook (not every 60 s forever) per AGENTS.md "Degradation log levels"; a webhook that recovers and breaks again is loud again.
That is observability, not durability — the dev said so explicitly and it does not substitute for this card. An operator reading logs can now find it; an operator reading sys_http_delivery still cannot.
Explicitly NOT claimed
Evidence
Measured 2026-08-12 by the dev on PR #8043 (<!-- os-dev-report --> comment 5267475217 on #8022), reading packages/services/service-messaging/src/http-outbox.ts and the redeliver path.
Source
Split out of #8022 (PR #8043) at the implementing dev's request — half (b), deliberately not ridden on the ordering fix.
Filed by the
domain:servicesPM seat at the explicit request of the dev on #8022 / PR #8043 — half (b) of that card, which they closed half (a) of and deliberately did not ride on the ordering fix. Unassigned, nopm:queue, for triage to grade.The reason it was split out is the interesting part, and it is a trap worth reading before anyone "just adds a dead row".
The gap
When
plugin-webhooks' auto-enqueuer cannot resolve a webhook's encrypted signing secret, it drops the subscription — correctly, fail-closed, rather than delivering unsigned (#7799). But the drop leaves no durable record: nosys_http_deliveryrow, so no dead-letter, no retry, no trace an operator can find. #8043 fixed the boot-ordering cause; it did not make the drop durable.After #8043 the remaining causes are all persistent misconfigurations rather than a transient boot race — no provider ever registered, a missing
sys_secretrow, a decrypt failure under a rotated key, an engine withoutresolveSecretField. None self-heals, so none reproduces the "gone in a window nobody was watching, healthy forever after" signature that made #8022 urgent. The gap is real but differently shaped, which is exactly why it belongs in its own card rather than as a rider.The obvious version is "write the drop as a
deadrow so an operator can see it and retry". Measured by the dev on #8043, that is actively unsafe:IHttpOutbox(packages/services/service-messaging/src/http-outbox.ts) has exactly one write door —enqueue(), which mintspending.redeliver()resets any terminal row (success/failed/dead) back topendingfor a byte-for-byte replay — and it is reachable by any authenticated user viaPOST /api/v1/webhooks/redeliver.⇒ A
deadrow for an unresolvable-key drop gives an operator a button that delivers the webhook unsigned, reopening #7799 through a door nobody would think to audit. The fail-closed guarantee would be undone by the very feature added to make the failure visible.What a correct fix needs (⛔ not decided here)
sys_http_deliveryplatform object, so migration-visible; andredeliver()refusal for any row that requires a signature and does not have one.That is a design decision inside
service-messaging, touching the exactredeliverpath that #8022's source retest verified working (signature verified on aredeliverreplay). It wants review on its own merits.Interim state — observability, ⛔ not durability
#8043 did make the drop loud rather than silent, cheaply and in-file: it now reports at
errorlevel with the consequence and the remedy in the message, carrying an ADR-0112code/statuspair (INTERNAL_ERROR/ 500) in its metadata — the same pair the seeder's refusal for the same cause already used. Said once per outage per webhook (not every 60 s forever) per AGENTS.md "Degradation log levels"; a webhook that recovers and breaks again is loud again.That is observability, not durability — the dev said so explicitly and it does not substitute for this card. An operator reading logs can now find it; an operator reading
sys_http_deliverystill cannot.Explicitly NOT claimed
sys_http_deliveryrow, while it still readsactive:true#8022 sense. No silent self-healing window remains; the remaining triggers are persistent and loud.Evidence
Measured 2026-08-12 by the dev on PR #8043 (
<!-- os-dev-report -->comment 5267475217 on #8022), readingpackages/services/service-messaging/src/http-outbox.tsand theredeliverpath.Source
Split out of #8022 (PR #8043) at the implementing dev's request — half (b), deliberately not ridden on the ordering fix.