Make Retries Safe with a Transactional Idempotency Record
A durable idempotency pattern for retries, including canonicalization version rollouts and cross-version response replay.
Make retries safe with a transactional idempotency record
Scope
Use this for a retried mutation whose effect and idempotency outcome can commit in one durable database transaction. The caller supplies one operation key per logical mutation and retains it across retries. Scope a unique constraint by authenticated caller and operation family, but never by fingerprint or canonicalization version.
Committed record
Store the operation key, a request fingerprint, the canonicalization algorithm version that produced it, and replayable outcome facts. The outcome may be a saved response or immutable facts sufficient to render the supported response versions. The fingerprint covers all effect-relevant request inputs under its algorithm version. An old record without a version may be marked as legacy only when its provenance proves which algorithm produced it; otherwise mark it unknown and do not guess.
Execute and replay
The winning attempt inserts the key, applies the mutation, and stores its outcome in one transaction. The unique constraint coordinates concurrent attempts. A loser waits for settlement or receives an explicit retry-later response. After a commit, it reads the committed record from an authoritative store. After rollback, a retry may become the winner.
For an existing key, compare the incoming request using the record's stored canonicalization version, even if the current default has changed. Use a documented adapter from the incoming schema to that version. The adapter must preserve every distinction that could change the old operation. A match replays the saved outcome; a mismatch is a conflict. If no safe adapter exists, reject or route to explicit reconciliation rather than declaring a match or executing again.
Canonicalization rollout
Retain old canonicalizers and compatibility adapters for the full retry horizon. New attempts store the new version. For a legacy record, a new client may match only if its request can be translated into the precise legacy intent. A new field with an effect cannot be silently dropped by the legacy canonicalizer. For example, if the old request implicitly meant no gift wrap, a new request with gift wrap disabled can match after explicit normalization, while gift wrap enabled conflicts.
The reverse direction also matters. An old client retrying a new record can match only when the new record's additional fields equal documented legacy defaults. If an old and new client race for an absent key, the unique constraint selects one winner; compare the loser against the winner's stored version. Equivalent intents replay one outcome regardless of arrival order. Different intents conflict regardless of arrival order.
Response compatibility
Track response representation separately from request fingerprint version. Preserve immutable outcome facts and render the requested supported representation, or keep deterministic adapters from stored response versions. Do not infer the historical outcome from mutable current resource state. If a representation cannot be produced safely, report an explicit compatibility failure without reapplying the mutation.
Limits
A committed effect and its replay record must commit together. A timeout alone cannot establish failure. Effects outside the transaction require their own deduplication protocol or a transactional outbox with an idempotent consumer. Retain records for the supported retry horizon and reject reuse of a key with changed inputs. The procedure is a design argument; it has not been established by executed tests.