Files
clean-architecture-backend-…/docs/notification/adr/NOTIF-ADR-002-event-ledger-projection.md
T
DongHyeonkaandClaude Opus 5 701ba67456 feat(notification): implement the notification delivery platform
Maps the 31-module plan onto the registry's 19 leaves as packages; the two
edges the registry forbids (provider->httpclient, inbox->messaging) are
replaced by application-owned ports. See docs/notification/module-mapping.md.

Acceptance is not delivery: ProviderSubmissionResult refuses to carry a
delivery outcome, and AMBIGUOUS is a first-class terminal state that blocks
automatic retry and fallback until reconciliation resolves it.

Providers: SES (SigV4 + SNS callback), Twilio (X-Twilio-Signature +
reconciliation), FCM (FID-primary batch), APNs, Web Push (RFC 8030/8291/8292),
SMTP and webhook. Contact points are AES-256-GCM encrypted with a separate
HMAC lookup fingerprint; nothing raw reaches a log, metric tag or exception.

Dispatch commits the attempt row, calls the provider with no transaction open,
then records the outcome; the durable queue uses FOR UPDATE SKIP LOCKED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 13:57:27 +09:00

1.1 KiB

NOTIF-ADR-002 — append-only event ledger with channel projectors

Status

Accepted.

Context

A single linear delivery status has to be updated in place, which forces a rule for deciding whether a new event outranks the stored one. The natural rule — compare ordinals — is wrong for real provider traffic. Twilio does not guarantee callback ordering, so sent arrives after delivered. Email generates complaints after deliveries. Both cases lose information under an ordinal rule.

Decision

Provider events are appended to an immutable ledger before any projection runs. Channel-specific projectors merge events into SubmissionOutcome, DeliveryOutcome, EvidenceLevel, EngagementFacts and SuppressionFacts using explicit transition tables. Projection is idempotent and can be replayed from the ledger.

Consequences

Duplicate, out-of-order and late events are normal inputs rather than defects. A projector bug is recoverable, because the events it mis-projected are still stored. Projector versions can be migrated by replay. The cost is a second write per event and a projection that can lag its ledger.