ADR 0008. At-Least-Once Delivery + Idempotent Processing (Not Exactly-Once)
Status
Accepted.
Uniqueness keys follow spec §38.1, not the earlier source_party_id pairing:
batch uniqueness: (integration_id, batch_id)
event uniqueness: (integration_id, event_id)
ledger uniqueness: (campaign_rule_version_id, activity_event_id, ledger_action)
Identical retry returns the original receipt. Reused ID with a different
checksum returns 409 idempotency_conflict.
Context
Exactly-once delivery is expensive to guarantee end-to-end and usually unnecessary if processing is idempotent. Given async, multi-hop processing (ingestion -> log -> reward worker -> ledger), some duplicate delivery is expected in failure/retry scenarios.
Decision
Accept at-least-once delivery. Enforce idempotency explicitly at two levels:
- Ingestion:
UNIQUE (integration_id, event_id)on activity events, plusUNIQUE (integration_id, batch_id)on batches (spec §38.1). - Reward/ledger side:
UNIQUE (campaign_rule_version_id, activity_event_id, ledger_action)so the same activity cannot apply the same ledger action twice.
A duplicate delivery is treated as a successful no-op (identical retry returns the original receipt; spec §38.1). It is not an error requiring partner intervention.
Consequences
- Simpler infrastructure than exactly-once semantics would require.
- Partners can safely retry on ambiguous failures (timeout, connection drop) without risking duplicate rewards.
- Raw activity is corrected via adjustment/reversal/compensating events, never by mutating or deleting history. See docs/loyalty.md.