Type to search pages and rules.

Browse pages

Concurrency and Idempotency

Concurrency control protects invariants when accepted writes overlap.

Markdown30 provisions

Intent#

Concurrency control protects invariants when accepted writes overlap. Idempotency protects an operation from duplicate execution after a retry or uncertain network response.

Activation#

Activation scope: local.

Applicable specification kinds: module, business-record, use-case.

The consumer enables concurrency when accepted writes can conflict or retries can duplicate irreversible or externally visible effects.

Baseline relationship#

This extension replaces no baseline provision. litespec/ext/concurrency#return-precondition-outcomes extends litespec/backend/api#use-consistent-status-codes with the 428 row for a missing precondition.

This extension switches on limit. litespec/ext/concurrency#set-key-retention records key retention as a Limit record.

Standards#

Document write conflict behavior#

ruleguidance
litespec/ext/concurrency#document-write-conflict-behavior
Requirement

A conflicting-write use case MUST document conflicting operations, invalid combined result, expected winner behavior, and user-visible conflict response.

Why

The specification identifies the invariant that version coordination protects.

Checka reviewer answers

Does the use case name conflicts, invalid state, winner behavior, and caller response?

Marked guidance: a reviewer answers this question.

Limit version checks#

rulecheckable
litespec/ext/concurrency#limit-version-checks
Requirement

An implementation MUST NOT add version checks without a documented conflicting-write case.

Why

Unneeded version checks create rejection behavior without a protected business outcome.

Checkchecked by a tool

ConcurrencyVersionTests link every version check to a documented conflicting-write case.

Marked checkable: the tool named here checks it.

Compare expected aggregate versions#

rulecheckable
litespec/ext/concurrency#compare-expected-aggregate-versions
Requirement

A conflicting-write implementation MUST store and compare the aggregate version through its selected persistence model.

Why

The persistence version detects a competing accepted write against the same aggregate state.

Checkchecked by a tool

ConcurrencyVersionTests race conflicting aggregate writes with expected versions.

Marked checkable: the tool named here checks it.

Map failed expected versions#

rulecheckable
litespec/ext/concurrency#map-failed-expected-versions
Requirement

An HTTP API MUST map a failed expected version to status 409 Problem Details with a stable code.

Why

A stable response lets callers distinguish a concurrency conflict from validation or authorization failure.

Checkchecked by a tool

ConcurrencyVersionTests assert a failed expected version returns 409 Problem Details with its stable code.

Marked checkable: the tool named here checks it.

Reject silent overwrites#

rulecheckable
litespec/ext/concurrency#reject-silent-overwrites
Requirement

A conflicting-write implementation MUST NOT silently overwrite a conflicting accepted change.

Why

Silent replacement can violate the invariant named by the use-case specification.

Checkchecked by a tool

ConcurrencyVersionTests assert conflicting accepted writes preserve the winner without silent replacement.

Marked checkable: the tool named here checks it.

Apply HTTP version preconditions#

rulecheckable
litespec/ext/concurrency#apply-http-version-preconditions
Requirement

An HTTP operation requiring a caller version MUST return a strong persistence-derived ETag and require If-Match on writes.

Why

HTTP preconditions let a caller send the version that it read.

Example

A current ETag permits the write; a changed entity version rejects it.

Checkchecked by a tool

ConcurrencyVersionTests require and validate a strong persistence-derived ETag.

Marked checkable: the tool named here checks it.

Return precondition outcomes#

rulecheckable
litespec/ext/concurrency#return-precondition-outcomes
Requirement

An HTTP API MUST return 428 precondition_required for missing If-Match and 409 version_conflict for stale versions.

Why

Distinct outcomes show whether a caller omitted or supplied an obsolete version. This extends the outcome table of litespec/backend/api#use-consistent-status-codes.

Checkchecked by a tool

ConcurrencyVersionTests return 428 for a missing If-Match and 409 for a stale version.

Marked checkable: the tool named here checks it.

Hide provider version values#

rulecheckable
litespec/ext/concurrency#hide-provider-version-values
Requirement

An HTTP API MUST NOT expose a provider-specific version value in another contract field.

Why

The ETag is the contract boundary for persistence-version coordination.

Checkchecked by a tool

ConcurrencyVersionTests assert public transport models expose no provider-specific version field.

Marked checkable: the tool named here checks it.

Scope client keys#

rulecheckable
litespec/ext/concurrency#scope-client-keys
Requirement

An idempotent operation MUST scope its client key to the tenant, authenticated actor, and operation.

Why

Tenant, actor, and operation scope prevents one caller's key from replaying another operation. A project without the tenancy extension uses one fixed tenant value.

Checkchecked by a tool

IdempotencyKeyTests assert keys from different tenants, actors, or operations do not replay each other.

Marked checkable: the tool named here checks it.

Store idempotency state atomically#

rulecheckable
litespec/ext/concurrency#store-idempotency-state-atomically
Requirement

An idempotent operation MUST store its fingerprint, state, response status, response body, and expiry with its business change in one provider transaction.

Why

One transaction connects the accepted change to the result that later calls replay. It leaves no completed record without its change and no change without its replay record.

Checkchecked by a tool

IdempotencyKeyTests commit fingerprint, state, response, and expiry with the accepted business change.

Marked checkable: the tool named here checks it.

Reject conflicting key reuse#

rulecheckable
litespec/ext/concurrency#reject-conflicting-key-reuse
Requirement

An idempotent operation MUST reject reuse of one key with a different request fingerprint.

Why

One client key can represent only one accepted request intent.

Checkchecked by a tool

IdempotencyKeyTests assert reused keys with changed input return the documented rejection.

Marked checkable: the tool named here checks it.

Enforce key uniqueness#

rulecheckable
litespec/ext/concurrency#enforce-key-uniqueness
Requirement

An idempotency store MUST enforce a unique constraint on tenant, actor, operation, and client key.

Why

The constraint selects one accepted winner when concurrent requests use the same key. A project without the tenancy extension uses one fixed tenant value.

Checkchecked by a tool

IdempotencyKeyTests assert concurrent inserts prove the tenant-actor-operation-key uniqueness constraint.

Marked checkable: the tool named here checks it.

Canonicalize request fingerprints#

rulecheckable
litespec/ext/concurrency#canonicalize-request-fingerprints
Requirement

An idempotency implementation MUST derive its fingerprint from canonical mapped command input, route identity, and the authenticated actor identifier, excluding credentials and the client key.

Why

Equivalent requests produce one comparison value without embedding authentication material.

The actor identifier is in the fingerprint. The credential that proved it is not in the fingerprint. Two actors can present the same client key. A fingerprint without the actor lets one actor's replay return another actor's stored response. A bearer token, a cookie, and a signature are the credential, and each one changes between two requests the rule has to treat as equal.

Checkchecked by a tool

IdempotencyKeyTests assert equivalent mapped requests produce one fingerprint without credentials or client key.

Marked checkable: the tool named here checks it.

Protect fingerprint source data#

ruleguidance
litespec/ext/concurrency#protect-fingerprint-source-data
Requirement

An idempotency implementation MUST store a cryptographic hash when support does not require original sensitive request content.

Why

A hash preserves comparison behavior while reducing retained sensitive data.

Checka reviewer answers

Is sensitive fingerprint source data hashed wherever support needs no original content?

Marked guidance: a reviewer answers this question.

Replay completed results#

rulecheckable
litespec/ext/concurrency#replay-completed-results
Requirement

A completed idempotent retry MUST return the original response status and body.

Why

A retry receives the accepted result rather than repeating its state change.

Checkchecked by a tool

IdempotencyReplayTests return the original accepted status and body.

Marked checkable: the tool named here checks it.

Define concurrent retry behavior#

rulecheckable
litespec/ext/concurrency#define-concurrent-retry-behavior
Requirement

An idempotent use case MUST define whether a concurrent request waits, reports in-progress, or returns a stable conflict.

Why

Callers need one documented result while an identical request is still active.

Checkchecked by a tool

IdempotencyReplayTests prove the documented wait, in-progress, or conflict behavior.

Marked checkable: the tool named here checks it.

Reload concurrent winners#

rulecheckable
litespec/ext/concurrency#reload-concurrent-winners
Requirement

A losing concurrent idempotency request MUST reload and replay the winning record when its fingerprint matches.

Why

The unique-key winner provides the accepted result for all matching retries.

Checkchecked by a tool

IdempotencyReplayTests reload and replay the matching winner record.

Marked checkable: the tool named here checks it.

Discard failed replay state#

rulecheckable
litespec/ext/concurrency#discard-failed-replay-state
Requirement

A failed pre-commit idempotent request MUST leave neither the business change nor a completed replay record.

Why

A later retry can safely perform the uncommitted operation.

Checkchecked by a tool

IdempotencyReplayTests leave no business change or completed replay state.

Marked checkable: the tool named here checks it.

Delay irreversible provider calls#

ruleguidance
litespec/ext/concurrency#delay-irreversible-provider-calls
Requirement

An idempotent operation MUST NOT perform an irreversible provider call before its business transaction commits.

Why

An uncommitted operation cannot safely claim an irreversible external effect.

Checka reviewer answers

Does every irreversible provider call run only after its business transaction commits?

Marked guidance: a reviewer answers this question.

Route required external effects durably#

ruleguidance
litespec/ext/concurrency#route-required-external-effects-durably
Requirement

An idempotent operation MUST use the outbox extension or a documented provider idempotency contract for required external effects.

Why

Durable routing or provider replay protection aligns external effects with retries.

Checka reviewer answers

Does each required effect use the outbox or a documented provider idempotency contract?

Marked guidance: a reviewer answers this question.

Set key retention#

ruleguidance
litespec/ext/concurrency#set-key-retention
Requirement

An idempotency owner MUST record key retention as a Limit record set from the caller retry window and business risk.

Why

Retention lasts through the period in which callers can safely retry.

Checka reviewer answers

Does the Limit record cover the caller retry window and the business risk?

Marked guidance: a reviewer answers this question.

Bound expired-record cleanup#

rulecheckable
litespec/ext/concurrency#bound-expired-record-cleanup
Requirement

An idempotency owner MUST remove expired records through a bounded maintenance process.

Why

Bounded maintenance prevents cleanup from overwhelming normal application work.

Checkchecked by a tool

IdempotencyRetentionTests show bounded deletion of expired idempotency records.

Marked checkable: the tool named here checks it.

Retain safe retry keys#

rulecheckable
litespec/ext/concurrency#retain-safe-retry-keys
Requirement

An idempotency owner MUST NOT expire a key before callers can safely retry.

Why

Early expiration can turn a retry into duplicate business work.

Checkchecked by a tool

IdempotencyRetentionTests reject expiration before the documented safe retry time.

Marked checkable: the tool named here checks it.

Declare replayed transport outcomes#

rulecheckable
litespec/ext/concurrency#declare-replayed-transport-outcomes
Requirement

An idempotent use case MUST list the statuses and headers stored for replay.

Why

The list defines the transport result that a completed retry can reproduce.

Checkchecked by a tool

IdempotencyOutcomeTests match replayed statuses and headers against the declared list.

Marked checkable: the tool named here checks it.

Restrict replay data#

ruleguidance
litespec/ext/concurrency#restrict-replay-data
Requirement

An idempotent implementation MUST store only safe response fields required to reproduce an accepted result.

Why

Replay data needs enough information for callers without retaining unnecessary sensitive content.

Checka reviewer answers

Does each stored replay field serve the accepted result callers reproduce?

Marked guidance: a reviewer answers this question.

Exclude unsafe replay outcomes#

rulecheckable
litespec/ext/concurrency#exclude-unsafe-replay-outcomes
Requirement

An idempotent implementation MUST NOT replay authentication failures, transient server failures, Set-Cookie, Cache-Control, Vary, Expires, Age, hop-by-hop headers, tokens, or secrets.

Why

These values are unsafe, transient, or unrelated to the accepted operation result. The caching headers are computed for the response that produced them. Replaying them hands a later caller freshness instructions from an earlier moment. RFC 9111 makes each one a property of the exchange rather than of the result. The replayed response computes its own.

Checkchecked by a tool

IdempotencyOutcomeTests exclude authentication, transient failures, cookies, hop-by-hop headers, tokens, and secrets.

Marked checkable: the tool named here checks it.

Conventions#

Use the standard idempotency header#

conventioncheckable
litespec/ext/concurrency#use-the-standard-idempotency-header
Default

Use Idempotency-Key for HTTP commands that accept client keys, bounded to 255 characters of visible ASCII.

ReplacementA consumer records a departure from this default as an Exception record with reason, review date, and removal condition.

Why

The standard header makes client-key behavior recognizable at the HTTP boundary. The IETF draft leaves the value opaque. A server then stores whatever a client sends as a key. A bound stops an unbounded key from becoming an unbounded store entry. A character set stops a key from carrying a control character into a log line.

Checkchecked by a tool

ConcurrencyTests assert applicable operations accept Idempotency-Key or record an Exception.

Marked checkable: the tool named here checks it.

Keep idempotency persistence in Infrastructure#

conventioncheckable
litespec/ext/concurrency#keep-idempotency-persistence-in-infrastructure
Default

Keep idempotency persistence and transaction handling in Infrastructure.

ReplacementA consumer records a departure from this default as an Exception record with reason, review date, and removal condition.

Why

Provider transactions and constraints belong outside Application and Domain.

Checkchecked by a tool

ConcurrencyTests assert idempotency persistence and transaction handling live in Infrastructure.

Marked checkable: the tool named here checks it.

Map replay responses at the API boundary#

conventioncheckable
litespec/ext/concurrency#map-replay-responses-at-the-api-boundary
Default

Keep replay response mapping at the API boundary.

ReplacementA consumer records a departure from this default as an Exception record with reason, review date, and removal condition.

Why

HTTP statuses and headers are transport concerns.

Checkchecked by a tool

ConcurrencyTests assert replay response mapping lives at the API boundary.

Marked checkable: the tool named here checks it.

Declare applicable headers#

conventioncheckable
litespec/ext/concurrency#declare-applicable-headers
Default

Declare the idempotency header as required on each applicable HTTP operation.

ReplacementA consumer records a departure from this default as an Exception record with reason, review date, and removal condition.

Why

Generated API contracts expose the requirement through litespec/backend/api#publish-precise-complete-schemas.

Checkchecked by a tool

ConcurrencyTests asserts generated OpenAPI declares the header for every applicable operation.

Marked checkable: the tool named here checks it.

Dependencies#

The baseline implementation requires no additional package.

Verification#

IDMarkEvidence
litespec/ext/concurrency#document-write-conflict-behaviorguidanceDoes the use case name conflicts, invalid state, winner behavior, and caller response?
litespec/ext/concurrency#limit-version-checkscheckableConcurrencyVersionTests link every version check to a documented conflicting-write case.
litespec/ext/concurrency#compare-expected-aggregate-versionscheckableConcurrencyVersionTests race conflicting aggregate writes with expected versions.
litespec/ext/concurrency#map-failed-expected-versionscheckableConcurrencyVersionTests assert a failed expected version returns 409 Problem Details with its stable code.
litespec/ext/concurrency#reject-silent-overwritescheckableConcurrencyVersionTests assert conflicting accepted writes preserve the winner without silent replacement.
litespec/ext/concurrency#apply-http-version-preconditionscheckableConcurrencyVersionTests require and validate a strong persistence-derived ETag.
litespec/ext/concurrency#return-precondition-outcomescheckableConcurrencyVersionTests return 428 for a missing If-Match and 409 for a stale version.
litespec/ext/concurrency#hide-provider-version-valuescheckableConcurrencyVersionTests assert public transport models expose no provider-specific version field.
litespec/ext/concurrency#scope-client-keyscheckableIdempotencyKeyTests assert keys from different tenants, actors, or operations do not replay each other.
litespec/ext/concurrency#store-idempotency-state-atomicallycheckableIdempotencyKeyTests commit fingerprint, state, response, and expiry with the accepted business change.
litespec/ext/concurrency#reject-conflicting-key-reusecheckableIdempotencyKeyTests assert reused keys with changed input return the documented rejection.
litespec/ext/concurrency#enforce-key-uniquenesscheckableIdempotencyKeyTests assert concurrent inserts prove the tenant-actor-operation-key uniqueness constraint.
litespec/ext/concurrency#canonicalize-request-fingerprintscheckableIdempotencyKeyTests assert equivalent mapped requests produce one fingerprint without credentials or client key.
litespec/ext/concurrency#protect-fingerprint-source-dataguidanceIs sensitive fingerprint source data hashed wherever support needs no original content?
litespec/ext/concurrency#replay-completed-resultscheckableIdempotencyReplayTests return the original accepted status and body.
litespec/ext/concurrency#define-concurrent-retry-behaviorcheckableIdempotencyReplayTests prove the documented wait, in-progress, or conflict behavior.
litespec/ext/concurrency#reload-concurrent-winnerscheckableIdempotencyReplayTests reload and replay the matching winner record.
litespec/ext/concurrency#discard-failed-replay-statecheckableIdempotencyReplayTests leave no business change or completed replay state.
litespec/ext/concurrency#delay-irreversible-provider-callsguidanceDoes every irreversible provider call run only after its business transaction commits?
litespec/ext/concurrency#route-required-external-effects-durablyguidanceDoes each required effect use the outbox or a documented provider idempotency contract?
litespec/ext/concurrency#set-key-retentionguidanceDoes the Limit record cover the caller retry window and the business risk?
litespec/ext/concurrency#bound-expired-record-cleanupcheckableIdempotencyRetentionTests show bounded deletion of expired idempotency records.
litespec/ext/concurrency#retain-safe-retry-keyscheckableIdempotencyRetentionTests reject expiration before the documented safe retry time.
litespec/ext/concurrency#declare-replayed-transport-outcomescheckableIdempotencyOutcomeTests match replayed statuses and headers against the declared list.
litespec/ext/concurrency#restrict-replay-dataguidanceDoes each stored replay field serve the accepted result callers reproduce?
litespec/ext/concurrency#exclude-unsafe-replay-outcomescheckableIdempotencyOutcomeTests exclude authentication, transient failures, cookies, hop-by-hop headers, tokens, and secrets.
litespec/ext/concurrency#use-the-standard-idempotency-headercheckableConcurrencyTests assert applicable operations accept Idempotency-Key or record an Exception.
litespec/ext/concurrency#keep-idempotency-persistence-in-infrastructurecheckableConcurrencyTests assert idempotency persistence and transaction handling live in Infrastructure.
litespec/ext/concurrency#map-replay-responses-at-the-api-boundarycheckableConcurrencyTests assert replay response mapping lives at the API boundary.
litespec/ext/concurrency#declare-applicable-headerscheckableConcurrencyTests asserts generated OpenAPI declares the header for every applicable operation.