Report #012 proved that a version-bound PostgreSQL mutation can allow one writer while rejecting a stale competitor. Report #013 keeps the race identical and changes only the isolation level.
The control still duplicates.
Two independent READ COMMITTED connections both observed ABSENT / version=100. With unconditional writes, both committed: final state COMMITTED / version=102 / effects=2.
Observed safe matrix
READ COMMITTEDWinner committed. Loser received a zero-row conditional update / precondition failure. Final effects: 1.
REPEATABLE READWinner committed. Loser received serialization failure, SQLSTATE 40001. Final effects: 1.
SERIALIZABLEWinner committed. Loser received serialization failure, SQLSTATE 40001. Final effects: 1.
READ COMMITTED · conflict as zero rows.
Both workers first observed ABSENT / v100. One transaction won the conditional update and committed v101. The losing conditional update matched no row and was classified as PRECONDITION_FAILED.
REPEATABLE READ · conflict as 40001.
Under REPEATABLE READ, the stale transaction did not return a zero-row result. PostgreSQL aborted it with serialization_failure / SQLSTATE 40001. After rollback, a fresh connection reconciled COMMITTED / v101 / effects=1.
SERIALIZABLE · conflict as 40001.
The SERIALIZABLE race produced the same losing signal in this run: serialization_failure / SQLSTATE 40001. Again, reconciliation from a fresh transaction showed the intended effect was already committed.
The TTP retry law
PostgreSQL documentation requires applications using Repeatable Read or Serializable to be prepared to retry transactions after serialization failures. TTP adds the application-level boundary: replay starts from fresh state and fresh authorization, not from the old mutation payload.
New invariant
A zero-row conditional update and SQLSTATE 40001 are different database signals, but neither proves that the intended consequential effect is absent.
Score · 10/10
Real PostgreSQL + unsafe baseline · 2/2Unconditional concurrent writes reproduced two effects.
READ COMMITTED · 2/2One winner, one zero-row stale writer, one final effect.
REPEATABLE READ · 2/2One winner, one SQLSTATE 40001, one final effect.
SERIALIZABLE · 2/2One winner, one SQLSTATE 40001, one final effect.
TTP recovery law · 2/2Every loser reconciled COMMITTED / v101 / effects=1 before any retry decision.
Interpretation boundary
This is one deterministic concurrency shape on PostgreSQL 17.6. It does not certify every isolation-level anomaly, PostgreSQL configuration, failover topology, distributed transaction, external API side effect or arbitrary agent system.
Reproduce / inspect
- Transactional Trust Protocol v1.0
- Canonical GitHub Actions run
- Benchmark harness
- Machine-readable result
- PostgreSQL 17 transaction isolation documentation
Evidence artifact digest: sha256:b22f5d36ed8a8aee6d751c639f48f8b760b2237ed7edf0e86c4a455f449743a0.
Verdict: the database signal changed with isolation level, but the safe application rule did not: end the stale transaction, reconcile current business state, and only re-execute if the action is still absent and legal.