ADR-0001: Transactional outbox for side effects
Status
Accepted. Reconstructed from the implementation and from AGENTS.md. The original discussion was not in the repository.
Context
Order creation must record fulfillment work and a wallet debit, and stock changes must reach channels. Those effects cannot run only as in-process events on the API. The API process does not load the outbox dispatcher, and an in-process emit is lost if the process stops.
Decision
Writers call OutboxWriterService.enqueue with the Prisma transaction client. The worker’s OutboxDispatcherService claims rows and emits the event type through EventEmitter2 inside tenant context.
Bull remains a second mechanism for work that is queued directly (sync, labels, email, inventory flush, webhooks).
Alternatives
Not recorded. The workspace README sentence that says events are dispatched via Bull workers does not match the dispatcher, which polls OutboxEvent. Both mechanisms exist. The outbox is not a Bull queue.
Consequences
- A throw before commit rolls the business row and the outbox row back together.
- A throw in a listener retries the event, up to 10 times, then
DEAD. Listeners that are not idempotent can double-apply. - Emitting from order command with
EventEmitter2skips this path.AGENTS.mdforbids that for order side effects. - There is no replay tool in the code that was read.