Skip to main content
InternalMarkdown

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, plus UNIQUE (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.