Files
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.8 KiB

Callbacks and reconciliation

Ingestion order

body size limit
  → content type
  → profile lookup
  → signature verification
  → append to the ledger
  → duplicate detection
  → normalization
  → attempt resolution
  → projection
  → side effects
  → 2xx

Appending before projecting is what makes a fast 2xx honest. The provider is told the event is recorded, and a projector defect becomes a replay problem rather than a lost event.

A rejected signature is recorded in the security audit, never in the provider event ledger. Writing it to the ledger would let anyone who can reach the endpoint fill a delivery history with noise.

Duplicates and ordering

Duplicate suppression uses (providerProfileId, providerEventId) where the provider supplies an event id, and a deterministic fingerprint over profile, request id, event type, occurrence time and payload digest where it does not. A duplicate is acknowledged and projected exactly once.

Out-of-order callbacks are normal. Ordering is resolved by event semantics, not by arrival time.

Unknown fields

Callback parsers tolerate unknown JSON fields. Normalization only rejects a payload when a field required to identify the attempt is missing. Providers add fields; that must not stop ingestion.

Reconciliation

Reconciliation targets:

  • attempts stuck in DISPATCHING past their lease
  • ambiguous submissions
  • accepted attempts whose callback SLA has expired
  • unmatched provider events

A confirmed query result is appended to the same ledger with source = RECONCILIATION and projected by the same projector, so projection replay stays possible: there is no privileged second path that writes projections directly.

Where a provider has no status-query capability, the platform records Unsupported and leaves the attempt ambiguous. It does not infer a final status.