ADR 0006: Events — transactional outbox, inbox deduplication, versioned schemas
Status: Accepted
Date: 2026-07-19
Context
Integration events must survive crashes, never duplicate business effects, and evolve safely.
Decision
- Domain events stay inside the module transaction (in-memory dispatch).
- Integration events are written to the
outboxtable in the same transaction as the state change, then published asynchronously by the relay to the queue.
- Consumers record
inboxdeduplication entries (event id + consumer) and are idempotent by contract.
- Event envelopes carry
id,type(module.entity.verb, semver'd),tenantId,actor,occurredAt,correlationId,causationId,version, and a payload of IDs/references — never secrets or large mutable snapshots.
- Failed deliveries land in a dead-letter workflow with replay (
apps/control→ Jobs).
- Schema changes to published events require compatibility tests (
pnpm test --filter */contract).
Consequences
- Exactly-once *business effect* with at-least-once delivery; duplicates are provably harmless (reference journey 8).