Defines version-based optimistic concurrency, conflict responses, safe retries, and a zero-downtime migration path for mutable records where lost updates matter.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Add a monotonically changing version to each protected aggregate and require compare-and-set updates. Return a conflict containing the current version and safe comparison data; never hide a conflict with an automatic blind retry.
Scope
- Version token semantics, update/delete guards, conflict payload, client merge, retries, events, and schema migration.
- Aggregates with relatively infrequent conflicting writes.
- Backfill and mixed-version rollout for existing records.
Outside this block
- Pessimistic locking for scarce resources or invariants spanning many aggregates.
- Automatic semantic merging of arbitrary user content.
Contract
- Every successful mutation changes the version exactly once within the same transaction.
- Update and delete statements include both aggregate ID and expected version in their predicate.
- Zero affected rows causes a not-found or conflict distinction without guessing from client input.
- Conflict responses expose the current version and fields allowed for reconciliation, not secret or unauthorized state.
- Side effects and events occur only for the winning update and carry the resulting version.
- Retries are allowed only after a fresh read or for an idempotent command whose precondition is recomputed.
Implementation guidance
- Backfill a non-null version in batches, dual-write it before requiring clients to send it, then enforce the precondition.
- Map the version to an ETag or explicit API field consistently.
- Keep merge UI based on base, local draft, and authorized current representation.
- Test affected-row behavior through the real data access layer.
Failure handling and safeguards
- If the version is missing during the compatibility window, apply only the documented legacy policy and observe its use.
- If a conflict lookup is unauthorized, return the normal concealment response rather than current data.
- If event publication fails after commit, use an outbox or replayable record so the winning version is not published twice as two logical changes.
Verification and operations
- Measure conflict rate by aggregate and client version, missing-precondition use, merge abandonment, and retry success.
- Alert on updates that change data without changing version and on duplicate events for one aggregate version.
- Remove the legacy path only after observed clients have migrated.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.