# ADR 0002: Domain Eventing with Outbox/Inbox Reliability Pattern

- Status: Accepted
- Date: 2026-03-10
- Owners: Architecture Group
- Related:
  - `docs/adr/0001-app-boundaries.md`
  - `docs/domain-map.md`
  - `docs/non-functional-slo.md`

## Context
The platform is event-driven in the private secure zone and integrates with external providers. Financial consistency must never depend on best-effort event delivery.

Using direct publish calls without durability can lose events during crashes or partition scenarios. External callbacks and retries can also introduce duplicate processing.

## Decision
Adopt a transactional outbox/inbox pattern for all domain-to-domain and domain-to-external event delivery paths where reliability matters.

Core decisions:
1. Write business state and outbox event atomically in one DB transaction.
2. Dispatch outbox events asynchronously with retry and backoff.
3. Consumers persist inbox records and deduplicate by message id.
4. Side effects must be idempotent and replay-safe.
5. Event schemas are versioned and backward compatible.

## Event Model
Required event metadata:
- `event_id` (UUID)
- `event_name`
- `event_version`
- `occurred_at`
- `producer_app`
- `correlation_id`
- `causation_id`
- `aggregate_type`
- `aggregate_id`
- `payload`
- `headers`

Event naming:
- Past tense semantic name plus version suffix, e.g. `TransferCompleted.v1`.

Versioning rules:
- Non-breaking additions allowed within version.
- Breaking changes require new version (`.v2`).

## Outbox Contract
Outbox lifecycle states:
- `pending`
- `dispatching`
- `dispatched`
- `failed`
- `dead_letter`

Dispatch rules:
1. Ordered by `occurred_at`, then primary key.
2. Exponential backoff with jitter on transient failure.
3. Move to `dead_letter` after max attempts.
4. Emit audit and metrics on each state transition.

## Inbox Contract
Inbox fields:
- `message_id`
- `consumer_app`
- `first_seen_at`
- `processed_at`
- `status`
- `error`

Consumer rules:
1. Check dedup key before processing side effects.
2. Mark processed atomically with effect completion marker.
3. Ignore duplicates by returning prior result where possible.

## Delivery Semantics
- Internal and external event transport is at-least-once.
- Business handlers are designed for effectively-once outcomes through dedup + idempotency.
- Financial posting remains command-transactional, not event-eventual.

## Security and Compliance
- Events must never contain plaintext sensitive secrets.
- PII in payloads must follow data minimization and encryption policy.
- Audit events required for publish failures, dead letters, and replay operations.

## Observability Requirements
Mandatory metrics:
- outbox pending count
- outbox dispatch latency
- outbox retry count
- dead-letter count
- inbox duplicate count
- inbox processing latency

Mandatory tracing:
- propagate `correlation_id` and `causation_id` through command -> event -> consumer path.

## Consequences
Positive:
- Reliable inter-app event delivery under failures.
- Stronger auditability and replay capability.
- Safer integrations with external systems.

Trade-offs:
- Additional storage and worker complexity.
- More operational monitoring requirements.

## Implementation Notes
- Implement reusable outbox/inbox behaviors in `wallet_events`.
- Domain apps publish via `wallet_events` public interface only.
- Add contract tests for event schema and consumer compatibility.
- Include replay tooling for operational recovery.

## Acceptance Criteria
1. Domain transaction and outbox insert are atomic.
2. Duplicate event consumption does not duplicate side effects.
3. Dead-letter operational workflow is documented and tested.
4. SLO-E1 and SLO-E2 in `docs/non-functional-slo.md` are measurable in dashboards.
