ADR 0010: Postgres transactional outbox and leased workers
Status
Accepted
Context
Duckbill sync and automation must survive process restarts. Canonical writes and downstream work cannot diverge. A separate broker was still an open choice for Sprint 03.
Decision
Keep the queue in Postgres for this increment:
- Write
domain_event_outboxin the same database transaction as canonical upserts. - Dispatch
sync_work_itemswith priority P0–P5, worker lease, and heartbeat. - Recover crashed leases when
leased_untilis in the past (FOR UPDATE SKIP LOCKED). - Deduplicate side-effecting handlers with
processed_events (event_id, handler). - Publish failures retry by leaving
published_atnull; they do not rewrite domain rows.
Do not introduce Redis, SQS, or another broker until volume or isolation requires it.
Alternatives
- Immediate in-process emit after commit — lost on crash.
- External queue in Sprint 03 — extra moving part before the first mailbox works.
Consequences
Workers and the API share one database. Ops health can read unpublished outbox, open DLQ items, and pause rows without another system. A later broker can subscribe to the outbox without changing canonical writes.
