WEB / WEB-19.S01
Races, transactions, and idempotency
Inspect a public-note revision and retry result at owner boundaries; keep stale decisions, commit, and caller knowledge separate.
1. Core · Mechanism
Read every scenario without motion
Commit result
- Operation intent
The caller prepares a public-note operation with declared purpose and an inert key alias; the key is not a credential.
- Caller / application
- Known public note operation
- Selected service
- Selected request scope
- SQLite note owner
- Tea note/version1 committed
- Scoped result record
- No saved result
- Normalize scope
The service matches caller/purpose/key and note/title parameters to the selected result contract.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Tea note/version1 committed
- Scoped result record
- No saved result
- Pending mutation
The known rename is pending in the transaction; an independent observer still sees the previously committed note.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Pending Updated note/version2
- Scoped result record
- No saved result
- Commit result
Saved parameters/result and note mutation commit in one SQL transaction; external effects are excluded.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Updated note/version2 committed
- Scoped result record
- Committed result Updated note/version2
- Result known
The caller learns noteId/title/version at selected local completion; real HTTP arrival is not claimed.
- Caller / application
- Updated note/version2; mutationCount1
- Selected service
- Scope+parameters selected
- SQLite note owner
- Updated note/version2 committed
- Scoped result record
- Committed result Updated note/version2
Stale condition
- Operation intent
The caller prepares a public-note operation with declared purpose and an inert key alias; the key is not a credential.
- Caller / application
- Known public note operation
- Selected service
- Selected request scope
- SQLite note owner
- Tea note/version1 committed
- Scoped result record
- No saved result
- Read version1
The application reads revision1 without a guarantee that state will be unchanged at later use.
- Caller / application
- Read expectation1
- Selected service
- Selected request scope
- SQLite note owner
- Tea note/version1 committed
- Scoped result record
- No saved result
- Ordinary update
An ordinary authored independent change makes owner Coffee note/version2; no concurrent attack workflow executes.
- Caller / application
- Read expectation1
- Selected service
- Selected request scope
- SQLite note owner
- Coffee note/version2
- Scoped result record
- No saved result
- Condition selected
The service binds expected1 to the authoritative UPDATE condition instead of relying on a UI check.
- Caller / application
- Read expectation1
- Selected service
- Expected1/current2 condition
- SQLite note owner
- Coffee note/version2
- Scoped result record
- No saved result
- Reject stale
The guarded statement affects zero rows and preserves current state; no bypass or vulnerable branch exists.
- Caller / application
- Read expectation1
- Selected service
- Expected1/current2 condition
- SQLite note owner
- Coffee note/version2; changedRows0
- Scoped result record
- No saved result
- Conflict outcome
The caller receives the selected conflict; stale work is not counted as a business mutation.
- Caller / application
- Conflict; current state preserved
- Selected service
- Expected1/current2 condition
- SQLite note owner
- Coffee note/version2; changedRows0
- Scoped result record
- No saved result
Unknown + retry
- Operation intent
The caller prepares a public-note operation with declared purpose and an inert key alias; the key is not a credential.
- Caller / application
- Known public note operation
- Selected service
- Selected request scope
- SQLite note owner
- Tea note/version1 committed
- Scoped result record
- No saved result
- Normalize scope
The service matches caller/purpose/key and note/title parameters to the selected result contract.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Tea note/version1 committed
- Scoped result record
- No saved result
- Pending mutation
The known rename is pending in the transaction; an independent observer still sees the previously committed note.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Pending Updated note/version2
- Scoped result record
- No saved result
- Commit result
Saved parameters/result and note mutation commit in one SQL transaction; external effects are excluded.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Updated note/version2 committed
- Scoped result record
- Committed result Updated note/version2
- Unknown response
An injected caller-knowledge event leaves first completion unknown; committed data is not assumed rolled back.
- Caller / application
- First result knowledge unknown
- Selected service
- Scope+parameters selected
- SQLite note owner
- Updated note/version2 committed
- Scoped result record
- Committed result Updated note/version2
- Same-scope retry
A finite retry with the same purpose/key and parameters looks up the saved result; no timeout traffic executes.
- Caller / application
- First result knowledge unknown
- Selected service
- Same scope+parameters retry
- SQLite note owner
- Updated note/version2 committed
- Scoped result record
- Committed result Updated note/version2
- Reuse saved result
The stored result is found again; no second note mutation occurs and record count stays1.
- Caller / application
- First result knowledge unknown
- Selected service
- Same scope+parameters retry
- SQLite note owner
- Updated note/version2 committed
- Scoped result record
- Result reused; mutationCount1
- Result known
The caller learns noteId/title/version at selected local completion; real HTTP arrival is not claimed.
- Caller / application
- Updated note/version2; mutationCount1
- Selected service
- Same scope+parameters retry
- SQLite note owner
- Updated note/version2 committed
- Scoped result record
- Result reused; mutationCount1
Rollback result
- Operation intent
The caller prepares a public-note operation with declared purpose and an inert key alias; the key is not a credential.
- Caller / application
- Known public note operation
- Selected service
- Selected request scope
- SQLite note owner
- Tea note/version1 committed
- Scoped result record
- No saved result
- Normalize scope
The service matches caller/purpose/key and note/title parameters to the selected result contract.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Tea note/version1 committed
- Scoped result record
- No saved result
- Pending mutation
The known rename is pending in the transaction; an independent observer still sees the previously committed note.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Pending Updated note/version2
- Scoped result record
- No saved result
- Result constraint
A harmless NOT NULL saved-result candidate fails; explicit rollback clears mutation and record together.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Pending Updated note/version2
- Scoped result record
- Constraint failure; no record
- Explicit rollback
The database preserves previous Tea note/version1; no operation-result record committed.
- Caller / application
- Known public note operation
- Selected service
- Scope+parameters selected
- SQLite note owner
- Tea note/version1 restored
- Scoped result record
- Result record0
- Failure known
The caller knows the selected failure; this is not the unknown-response-aftercommit outcome.
- Caller / application
- Failed; rollback observed
- Selected service
- Scope+parameters selected
- SQLite note owner
- Tea note/version1 restored
- Scoped result record
- Result record0
ACTORS · STATE · DATA FLOW
A read does not promise unchanged state at use
An application reads a public note at revision=1; the owner can change it before use. TOCTOU describes that knowledge gap between a check and use. Races are not all the same: order-dependent behavior, lost updates, and invariant violations can differ. This lesson races no vulnerable endpoint. It shows a guarded update rejecting old expectation=1 after an ordinary authored revision=2 transition.
The control evaluates an operation condition at the selected authoritative boundary, rather than repeating a UI check. The fixture binds note ID and expected revision in its UPDATE. A mismatch affects zero rows and preserves current revision/title. SQLite transaction and isolation settings define the selected mechanism scope. Starting a transaction alone does not fix a wrong predicate; explicitly define the invariant rows, fields, and operation.
A separate connection reads completed transactions; under the selected SQLite settings an uncommitted update is hidden from its observer. Visibility differs from predicate correctness and HTTP receipt. A sequential native test observes current revision and affected-row count through independent SELECTs. It uses no real threads, timing harness, concurrent traffic, or third-party target: evidence covers finite ordinary events.
Retry identity includes result and purpose
If a caller receives no completion response, it cannot infer that nothing executed. Here note rename commits while response knowledge stays unknown; a retry with the same request alias reuses the committed result. The alias is no credential or bearer token. Application idempotency scope includes caller alias, operation purpose, and key, with normalized note ID/title parameters. The same scope/key with different parameters yields a selected conflict rather than another operation result.
The idempotency record and business mutation commit in one SQLite transaction; a harmless NOT NULL failure rolls both back. A record alone is no database guarantee: the server defines meaning, validation, retention, and key reuse. Stripe saved status/body and parameter comparison belong to its own API contract; pruning or failure-result retention can differ in other products. Our finite ledger recommends no indefinite production retention.
HTTP safe/idempotent methods do not implement this application retry contract. Endpoint names, a key header, or a database transaction do not automatically provide end-to-end exactly-once effects. External emails, files, and network effects stay outside the selected SQL transaction. AWS retry guidance distinguishes caller intent and parameter mismatch; no cloud API is called here. Unknown-response is an injected knowledge event, not an actual socket timeout.
2. Red · Trust & Impact
Read every scenario without motion
Old revision
- The caller first read version 1
ReadVersion=1 identifies the copy used to form the rename intent. It does not establish that the owner still holds version 1.
- Rename intent
- readVersion=1
- Record owner
- Current revision not read yet
- Admission contract
- Update decision pending
- The record is version 2 before use
CurrentBeforeUse=2 and the title is Coffee note. The earlier read differs from the owner’s current revision.
- Rename intent
- expectedVersion=1
- Record owner
- Coffee note / currentVersion=2
- Admission contract
- Revision condition required
- Decision and write share one statement
The fixed UPDATE evaluates the version condition alongside id. ExpectedVersion=1 does not match the currentVersion=2 record, so the write is not admitted.
- Rename intent
- expectedVersion=1
- Record owner
- affectedRowsOnStale=0
- Admission contract
- revision mismatch
- Coffee note is preserved
The after record remains Coffee note at version=2. AffectedRows=0 shows the guarded update was not applied; it does not erase the earlier read.
- Rename intent
- readVersion=1
- Record owner
- after: Coffee note / version=2
- Admission contract
- stale update not applied
- Parameter binding does not make this decision
Title and noteId are bound as data; the revision guard separately constrains applicability to the current record. Data/query separation and freshness answer different questions.
- Rename intent
- parameters remain data
- Record owner
- affectedRowsOnStale=0
- Admission contract
- Binding != revision agreement
- Report the scoped result
The verified result is that this update with an old revision condition preserved the record. It does not establish the same concurrency guarantee for every distributed writer or store.
- Rename intent
- readVersion=1
- Record owner
- Coffee note / version=2
- Admission contract
- guarded update rejected
Parameter change
- Establish the operation scope
Caller-demo, rename-public-note purpose and request-a key are evaluated together. Knowing the key alone does not prove identity or permission.
- Rename intent
- caller-demo / rename-public-note / request-a
- Record owner
- Saved result not inspected yet
- Admission contract
- keyIsCredential=false
- The first operation has a saved result
The normal result records tea as Updated note at version=2. It belongs to the first operation’s parameters within the selected scope.
- Rename intent
- title=Updated note
- Record owner
- saved result: Updated note / version=2
- Admission contract
- Original parameters bound
- The same key carries a different title
The new candidate requests Different note title while sameScopeKey=true. The same key label does not make different parameters the same operation.
- Rename intent
- newTitle=Different note title
- Record owner
- saved result unchanged
- Admission contract
- sameScopeKey=true
- A parameter conflict decision
The outcome is parameter-conflict. The application does not apply a candidate with different parameters as a new rename under the same scope and key.
- Rename intent
- new parameters differ
- Record owner
- existingResultPreserved=true
- Admission contract
- outcome=parameter-conflict
- The previous result was not erased
ExistingResultPreserved=true records preservation of the prior outcome. Conflict does not mean the first operation rolled back or its result was erased.
- Rename intent
- parameter conflict
- Record owner
- saved result preserved
- Admission contract
- No second rename admitted
- New work needs its own contract
Another purpose or different parameters can define new work; its permission and identity scope need separate evaluation. An idempotency key cannot substitute for authorization.
- Rename intent
- separatePurposeIsSeparateScope=true
- Record owner
- existing result preserved
- Admission contract
- keyIsCredential=false
ASSUMPTION · BOUNDARY · CONTROL
An earlier check has limited knowledge scope
The controlled ledger separates readVersion1 from currentVersion2 at use. The condition is treating stale knowledge as current state; a possible impact is an old decision overwriting a newer title. This lesson runs only the correct guarded condition, observing zero affected rows and preserved state. It provides no bypass, vulnerability reproduction, or race-winning timing instructions. Inspection finds the authoritative statement that evaluates expected revision.
A transaction declaration does not prove the application invariant is covered. Another row, external effect, or owner can exceed the SQLite boundary. The selected transaction covers mutation, result, and record together; independent reads see no uncommitted state. Generalizing to every isolation level or distributed topology is incorrect. Fixed statements and bound values here are not a query-injection lesson.
An unknown response is not a failed operation
Caller response loss and store rollback have different ledger states. Running committed work with a new alias can express another intent; using the same alias and parameters retrieves the stored result under the selected contract. Reusing an alias for another title or purpose needs a correctness decision. An idempotency key carries no browser identity or permission, so success is no authorization outcome.
A record with only a success flag may not identify the completed note/revision. The saved result includes noteId, title, and revision; mismatch preserves it. A sent flag for an external effect could describe another receipt outside SQL. Review tests exactly-once claims against outcome owners; this native work proves only ordinary local state transitions.
3. Blue · Secure development & defense
Read every scenario without motion
Unknown result
- The first result is unknown to the caller
Before/after revisions and affectedRows come from a local SQLite statement and independent SELECT reads; caller UNKNOWN is a separately supplied scenario condition. CallerFirstKnowledge=unknown describes the caller’s missing knowledge. It is not evidence that the database did not commit.
- Caller observation
- callerFirstKnowledge=unknown
- Operation result
- Commit not inspected yet
- Evidence interpretation
- Outcome evidence pending
- Database commit was observed separately
FirstCommitted=true accompanies a saved Updated note result at version=2. Caller uncertainty can coexist with a durable commit.
- Caller observation
- callerFirstKnowledge=unknown
- Operation result
- firstCommitted=true / result version=2
- Evidence interpretation
- Commit != caller receipt
- The retry carries the same parameters
SameParameters=true and the same caller, purpose and key scope correlate the retry to the original operation. Similar titles alone do not establish that relation.
- Caller observation
- same scope / sameParameters=true
- Operation result
- saved result version=2
- Evidence interpretation
- Same operation correlated
- The saved result was reused
ResultReused=true and mutationCount=1 show that the retry used the original outcome without a second rename.
- Caller observation
- retry correlated
- Operation result
- resultReused=true / mutationCount=1
- Evidence interpretation
- One mutation observed
- Do not classify unknown as failure
The evidence shows the first operation committed and the retry created no additional effect. Earlier caller uncertainty cannot be rewritten as first-operation failure.
- Caller observation
- first caller knowledge unknown
- Operation result
- firstCommitted=true / mutationCount=1
- Evidence interpretation
- Unknown knowledge != failed operation
- Keep the verified boundary visible
This example reuses a saved result. External API effects do not join the SQLite transaction; a local record does not guarantee exactly-once behavior for the whole system.
- Caller observation
- same operation
- Operation result
- mutationCount=1
- Evidence interpretation
- externalEffectsAtomic=false
Known rollback
- Read the revision before the transaction
Before/after revisions and affectedRows come from a local SQLite statement and independent SELECT reads; caller UNKNOWN is a separately supplied scenario condition. The rollback fixture has beforeVersion=1. Establish this starting point before comparing the same transaction’s outcome.
- Caller observation
- Caller outcome not known yet
- Operation result
- beforeVersion=1
- Evidence interpretation
- Transaction outcome pending
- The result record hit a constraint
A harmless NOT NULL result candidate fails and explicit rollback follows. This is a transaction outcome, unlike merely absent caller observation.
- Caller observation
- Caller outcome evaluated separately
- Operation result
- constraint failure / explicit rollback
- Evidence interpretation
- Known rollback event
- Revision and result were inspected together
AfterVersion=1 and resultRecords=0 show the prior revision preserved and no committed result from this transaction.
- Caller observation
- Caller outcome evaluated separately
- Operation result
- afterVersion=1 / resultRecords=0
- Evidence interpretation
- No committed result from this transaction
- This branch has rollback evidence
Explicit rollback and the independent after record support a known unsuccessful transaction. FirstCommitted=true from the unknown-response branch cannot be copied here.
- Caller observation
- different branch: unknown response
- Operation result
- afterVersion=1 / resultRecords=0
- Evidence interpretation
- Known rollback != unknown commit
- Missing observation is a third state
Without observations needed for commit or rollback, neither branch can be selected. Zero selector matches do not complete a missing transaction record.
- Caller observation
- Missing outcome remains unknown
- Operation result
- rollback fixture separately verified
- Evidence interpretation
- No branch borrowed for absent evidence
- Tie retry decisions to known state
Evaluate a saved result for the same operation, account for absent durable results after known rollback, and require outcome evidence when state is unknown. Key possession makes none of these decisions alone.
- Caller observation
- keyIsCredential=false
- Operation result
- rollback resultRecords=0
- Evidence interpretation
- Result / rollback / unknown separate
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Evaluate the invariant in an authoritative statement
Before/after revisions and affectedRows come from a local SQLite statement and independent SELECT reads; caller UNKNOWN is a separately supplied scenario condition. The invariant is that a title changes only when current revision equals expected revision. Benign expected1/current2 mismatch yields rowcount0 and leaves current title/revision unchanged. Independent SELECTs compare before and after state without assuming the UI copy improved. Transaction visibility and operation condition are different controls, together supporting the selected outcome.
The SQLite fixture uses explicit transaction control, an owned file, and two independent connections with recorded settings. It does not benchmark contention or timeouts. Predicates and markers cover known public-note inputs without real targets or secrets. A wider invariant requires its row set, locking/condition semantics, and failure handling to be defined using the chosen database documentation.
Commit mutation and saved result together
The scope tuple caller-demo / rename-public-note / request-a stores normalized tea +Updated note parameters. Normal response and same-parameter retry produce the same result with one business mutation. The key is an instructional alias, not entropy, production-retention, or authentication guidance. Native checks cover parameter conflict within a scope and a separate purpose namespace.
Rollback preserves the original revision and leaves no result record. Under UNKNOWN caller response, commit survives and retry finds the saved result. These cases may both trigger retry intent while having different known business states. Preserve that distinction; unobserved browser receipts or external effects retain their own UNKNOWN outcomes.
NOW APPLY IT TO THE CASE
Which evidence distinguishes an old revision, repeated operation and unknown outcome?
Duru wants to rename Atlas’s public tea note. The application separately checks a revision condition and an operation record bound to caller, purpose and key. A result can be committed even when it is unknown to the caller.
Read accepted, delivery and committed-effect records at their respective owners. Do not rename unknown caller outcome as business rollback.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Public note condition/retry result ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"invariant": {
"noteId": "tea",
"readVersion": 1,
"currentBeforeUse": 2,
"affectedRowsOnStale": 0,
"after": {
"title": "Coffee note",
"version": 2
},
"statement": "UPDATE notes SET title=?,version=version+1 WHERE id=? AND version=?",
"parameters": [
"Updated note",
"tea",
1
]
},
"scope": {
"callerAlias": "caller-demo",
"purpose": "rename-public-note",
"keyAlias": "request-a",
"parameters": {
"noteId": "tea",
"title": "Updated note"
},
"keyIsCredential": false,
"separatePurposeIsSeparateScope": true
},
"normal": {
"result": {
"noteId": "tea",
"title": "Updated note",
"version": 2
},
"mutationCount": 1,
"resultRecords": 1
},
"retryUnknown": {
"firstCommitted": true,
"callerFirstKnowledge": "unknown",
"sameParameters": true,
"resultReused": true,
"result": {
"noteId": "tea",
"title": "Updated note",
"version": 2
},
"mutationCount": 1
},
"mismatch": {
"sameScopeKey": true,
"newTitle": "Different note title",
"outcome": "parameter-conflict",
"existingResultPreserved": true
},
"rollback": {
"beforeVersion": 1,
"afterVersion": 1,
"resultRecords": 0,
"reason": "benign NOT NULL result candidate; explicit rollback"
},
"limits": {
"concurrentRequestsExecuted": false,
"unknownResponse": "injected caller knowledge event, not socket loss",
"externalEffectsAtomic": false,
"universalExactlyOnce": false
},
"selector": {
"language": "XPath",
"xml": "<audit><revision expected=\"1\" current=\"2\" mutationAccepted=\"false\"/></audit>",
"xpath": "count(/audit/revision[@expected != @current and @mutationAccepted='true'])",
"expected": 0,
"positiveFixtureXml": "<audit><revision expected=\"1\" current=\"2\" mutationAccepted=\"true\"/></audit>",
"positiveExpected": 1,
"scope": "Only authored protocol-decision observations, not product log field names or a live HTTP capture.",
"missingFieldBehavior": "Missing observation is insufficient evidence; zero matches is not proof of correct runtime behavior."
}
}- invariant
- Fixed guarded SQL and observed stale rejection.
- scope
- Caller/purpose/key and normalized parameters.
- retryUnknown/rollback
- Preserved commit and rollback are distinct outcomes.
- selector
- Synthetic stale-mutation acceptance decision.
Selected own SQLite and finite sequential public-note operations. No vulnerable reproduction, concurrency harness, real network loss, or external-effect guarantee.
Two reality checks
Starting a transaction resolves all application races and invariants.
Isolation, predicates, and row scope are separate choices; a transaction declaration alone is no correct business condition.
No response means the operation rolled back.
Caller knowledge can stay unknown while a committed result is reused by scoped retry; rollback needs separate evidence.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
The read used version=1, but current version=2 at use. UPDATE requires version=1. Is the new title written?
- Yes. Once read, the record is locked against every later state change.
- No. affectedRowsOnStale=0; Coffee note version=2 is retained.
Read the explained answer
No. affectedRowsOnStale=0; Coffee note version=2 is retained. — The version condition is reapplied at write time. An old read does not permit overwriting the current record.
The caller does not know the first result, but it committed. Why does a retry with the same scoped key and parameters not create a second mutation?
Selected lesson artifact fields; teaching example
- retryUnknown.firstCommitted
- true
- retryUnknown.callerFirstKnowledge
- unknown
- retryUnknown.sameParameters
- true
- retryUnknown.resultReused
- true
- retryUnknown.mutationCount
- 1
- The previous result is reused; mutationCount stays one.
- An unknown caller result rolls back all earlier server writes.
Read the explained answer
The previous result is reused; mutationCount stays one. — callerFirstKnowledge=unknown is not absence of a server commit. Same-parameter dedupe returns the retained result without repeating the effect.
The same scope/key with a different title produces parameter-conflict. What boundaries apply to idempotency scope, transactions and real concurrency?
Read the explained answer
The key identifies the same operation within caller, purpose and parameter bindings; a changed title does not overwrite the retained result. The key is neither a credential nor authorization. A local transaction can protect the write and result record together; failed result creation needs explicit rollback. This sequential example measures neither parallel-request isolation nor external-effect atomicity and gives no universal exactly-once guarantee.
□ I bound the idempotency key to purpose/parameters rather than permission.
□ I inferred no concurrency or external-atomicity guarantee from local result reuse.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- MITRE CWE-367 — TOCTOUDescription · Potential Mitigations
- SQLite — Transaction2. Transactions · 2.1. Read transactions versus write transactions · 2.2. DEFERRED, IMMEDIATE, and EXCLUSIVE transactions · 2.3. Implicit versus explicit transactions
- SQLite — IsolationIsolation Between Database Connections · Isolation And Concurrency
- Python 3.14 — sqlite3Tutorial · sqlite3.connect · Cursor.execute · How to use placeholders to bind values in SQL queries · Transaction control via the isolation_level attribute · How to use the connection context manager
- Stripe — Idempotent requestsIdempotent requests: stored status/body · Parameter comparison and key retention · Validation/concurrent request exclusions
- Amazon Builders Library — Making retries safe with idempotent APIsRetrying and side effects · Retries and semantic equivalence · Late arriving requests · Same client request ID, different intent