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>
53 lines
1.8 KiB
Markdown
53 lines
1.8 KiB
Markdown
# 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.
|