From the 2026-09-10 repository review (R04), re-verified on v5.8.2. Ruling 2026-09-15: at-least-once delivery; a lost confirmation may re-air an announcement once, never silently drop it.
Problem.
AnnouncementAiredEventSink.cs:38 — TryWrite on the bounded channel; queue-full drops the engine-confirmed TrackAired signal.
AnnouncementAiredDrainService.cs:12 — a failed MarkAiredAsync write is logged and dropped, "never retried here".
AnnouncementLifecycleGuardianService.cs — the row stays claimed; after the grace period the guardian re-arms it to pending, and the announcement airs again.
- A restart between air and persist loses the in-memory queue.
Scope (at-least-once).
- Bounded retry with backoff in the drain service; idempotent
MarkAiredAsync (a second confirmation is a no-op, not a second lifecycle transition or booth-log line).
- Queue-full: retry the enqueue briefly rather than drop; log at Warning with the announcement id if it still cannot land.
- Booth-log append failure after a successful lifecycle write is handled separately from the lifecycle write failure.
- Operator wording: lifecycle states say "confirmed" only when the write landed.
Acceptance.
- Test: claim → genuine aired event → store fails once → store recovers → row ends
aired, guardian never re-arms it.
- Test: duplicate confirmation for one id causes one transition and one booth-log line.
- Test: restart between air and persist → announcement airs again at most once (documented, not silent).
- No synchronous DB work on the audio callback.
Out of scope: MediaRotationDrainService (rotation ledger drops are approximate accounting and acceptable); a durable local journal (rejected 2026-09-15 as more machinery than a small community needs).
Preserve gh-#612's engine-confirmed-airing behaviour.
From the 2026-09-10 repository review (R04), re-verified on v5.8.2. Ruling 2026-09-15: at-least-once delivery; a lost confirmation may re-air an announcement once, never silently drop it.
Problem.
AnnouncementAiredEventSink.cs:38—TryWriteon the bounded channel; queue-full drops the engine-confirmedTrackAiredsignal.AnnouncementAiredDrainService.cs:12— a failedMarkAiredAsyncwrite is logged and dropped, "never retried here".AnnouncementLifecycleGuardianService.cs— the row staysclaimed; after the grace period the guardian re-arms it topending, and the announcement airs again.Scope (at-least-once).
MarkAiredAsync(a second confirmation is a no-op, not a second lifecycle transition or booth-log line).Acceptance.
aired, guardian never re-arms it.Out of scope:
MediaRotationDrainService(rotation ledger drops are approximate accounting and acceptable); a durable local journal (rejected 2026-09-15 as more machinery than a small community needs).Preserve gh-#612's engine-confirmed-airing behaviour.