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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3b5aee50e3
commit
701ba67456
@@ -0,0 +1,52 @@
|
||||
# Callbacks and reconciliation
|
||||
|
||||
## Ingestion order
|
||||
|
||||
```text
|
||||
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.
|
||||
Reference in New Issue
Block a user