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>
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
DISPATCHINGpast 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.