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 outbox table in the same transaction as the state change, then published asynchronously by the relay to the queue.
  • Consumers record inbox deduplication 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).