WEB / WEB-12.04
Client and server state ownership
A note can be visible in the Browser, saved on the server, or still queued. State ownership determines which copy can make which decision.
1. Core · Mechanism
Read every scenario without motion
Base version matches
- Draft not sent yet
The Browser makes a new note from a v3 snapshot; the server record remains v3.
- Browser / Duru
- Base: v3; draft unsaved
- Application
- No request
- Authoritative record
- Current: v3
- Update request arrived
The Application receives new text and If-Match v3. Request arrival does not yet mean acceptance.
- Browser / Duru
- Base: v3; waiting
- Application
- Received: If-Match v3; checks pending
- Authoritative record
- Current: v3
- Trusted checks pass
The supplied authenticated context, authorization, and input are valid. No mutation has occurred.
- Browser / Duru
- Base: v3; waiting
- Application
- Trusted checks passed; conditional commit pending
- Authoritative record
- Current: v3
- Conditional local commit
Current v3 matches; check and write produce v4 at one boundary. This is a logical in-process operation, without a wire.
- Browser / Duru
- Base: v3; waiting
- Application
- Committed result: v4
- Authoritative record
- Current: v4
- Successful response arrived
The Browser learns the new version and only now displays a server-save state.
- Browser / Duru
- Known: v4; saved on server
- Application
- Response: 200, ETag v4
- Authoritative record
- Current: v4
Stale precondition
- Browser uses an old snapshot
The new draft is based on v2; the trusted record is v3.
- Browser / Duru
- Base: v2; draft unsaved
- Application
- No request
- Authoritative record
- Current: v3
- Old validator arrived
The Application receives If-Match v2; this field is not authorization proof.
- Browser / Duru
- Base: v2; waiting
- Application
- Received: If-Match v2; checks pending
- Authoritative record
- Current: v3
- Precondition false
Even with valid trusted checks, v2 and v3 do not match. The fixture does not mutate the record.
- Browser / Duru
- Base: v2; waiting
- Application
- Rejected: precondition false
- Authoritative record
- Current: v3; unchanged
- Conflict response arrived
412 reaches the Browser. The draft is preserved; the server record did not change.
- Browser / Duru
- Conflict; draft preserved
- Application
- Response: 412
- Authoritative record
- Current: v3; unchanged
- Reconciliation is needed
The UI offers to read the current record and compare the draft; it does not force an overwrite.
- Browser / Duru
- Conflict; current record needed
- Application
- Response: 412
- Authoritative record
- Current: v3; unchanged
Commit · response lost
- Snapshot and draft
The Browser holds a v3 snapshot and an unsaved draft. No request has been sent; the server’s authoritative record is still v3.
- Browser / Duru
- Base: v3; draft unsaved
- Application
- No request
- Authoritative record
- Current: v3
- Request reached the Application
The update request arrived; the Browser waits for the result.
- Browser / Duru
- Base: v3; waiting
- Application
- Received: If-Match v3
- Authoritative record
- Current: v3
- Trusted conditional commit
Supplied trusted checks and the v3 precondition pass; the record becomes v4. This is a local operation.
- Browser / Duru
- Base: v3; waiting
- Application
- Committed: v4; response prepared
- Authoritative record
- Current: v4
- Response did not arrive
In this authored branch, the connection fails after commit. There is no completed arrival, so no transfer to the Browser is drawn.
- Browser / Duru
- Known: v3; no response
- Application
- Response delivery failed after commit
- Authoritative record
- Current: v4
- Client outcome unknown
The Browser preserves the draft and needs to reobserve state. Server v4 is known to the fixture but not yet to the Browser.
- Browser / Duru
- Outcome unknown; draft preserved
- Application
- Response delivery failed after commit
- Authoritative record
- Current: v4
ACTORS · STATE · DATA FLOW
A note can have several valid states
Duru writes a note for a book in Atlas. Seeing “new note” in the Browser input does not mean the server record changed. The UI owns the first copy; the trusted server side of the application owns the canonical business record.
Client state does not have to be temporary; it can remain in IndexedDB. Server state does not have to mean a database; it can reside in process memory or another store. The distinction comes from who accepts a change and which decision the state authoritatively controls, not the storage product name.
In this example, UI preferences and an unsent draft belong to the Browser. The accepted note text, resource ownership, and Atlas membership are determined on the server. Collapsing them into one “saved” label creates false confidence.
Separate a snapshot from authority
The Browser can keep text and a version received from the server. This is a snapshot observed at a particular time. If another tab or worker updates the note, the Browser’s older copy does not become authoritative by itself.
The server should also treat tenant, owner, or role fields in the request body as user claims. The request’s authenticated Duru context and the target note’s ownership within Atlas are resolved through trusted records. The client can choose a resource ID; it cannot choose the right to act on that resource.
Application and Authoritative record are drawn as separate actors, but this lesson models them as logical components within the same server process. Their commit is not an HTTP request; the following session-store lesson depicts a remote store differently.
Normal save: make an accepted update visible
Initially, the server has note-v3 and the Browser knows a snapshot of the same version. Duru’s new text is based on that snapshot. The request contains the new text and an If-Match precondition for the strong ETag “note-v3”.
The Application first applies authenticated context, resource authorization, and input rules. It then evaluates the precondition and update within one consistent commit boundary. If the record is still v3 in this fixture, the new text is accepted as v4. The response reports the new representation version.
Only after processing the successful response can the Browser say “saved on the server”. Sending a request expresses intent, server commit is a decision, and response arrival lets the Browser learn that decision. Keeping these states separate makes connection failures understandable.
Stale snapshots: do not silently overwrite newer work
In the second branch, the Browser writes from v2 while the server’s current note is v3. An unconditional overwrite could erase a change already accepted elsewhere. If-Match uses strong comparison for the current representation to help prevent this lost update.
The fixture does not apply the mutation when the precondition is false and returns 412 Precondition Failed. This is not a login failure; the representation the Browser relied on does not match current state. The server remains v3, the draft is preserved, and the UI shows a conflict.
The client should not merely obtain a newer ETag and automatically force the old draft over it. Fetch the current note, compare the user’s intent with the current text, and, when appropriate, build a newly approved update on the new base version. Separately racing check and write operations defeat the intended protection.
A lost response can leave the outcome unknown
After the server commits v4, the response might not reach the Browser. With only v3 known, the Browser has no basis to say either “the server did not save it” or, merely because a request was sent, “saved”. In this branch, server state changed while client knowledge is unknown.
Recovery means rereading the current representation and reconciling it with the draft, rather than treating a lost connection as proof of failed mutation. The PUT method in this example is idempotent under HTTP semantics: repeating the same request has the same intended effect, although the response can differ. With the fixture’s strict If-Match policy, v3 no longer matches after the first commit. Other operations need their own semantics and idempotency design for safe retries.
Keep the same distinction for an offline queue. “Queued locally” is useful success for a user, but it is not a promise of server acceptance. A queued update is validated again when it reaches the server because both permission and state may have changed.
What does an ETag establish?
An ETag is an opaque representation validator, not a credential, role, or ownership proof. Here, note-v3 is a readable instructional alias. The application chooses its validator format; any field that resembles an increasing number is not automatically a strong ETag.
If-Match carries a concurrency condition. Authorization answers whether Duru may perform this action on this note. When both are needed, both are enforced. Knowing the same version does not grant permission, and having permission does not require unconditional overwrites of stale state.
2. Red · Trust & Impact
Read every scenario without motion
Matching snapshot
- The update intent is in the browser
Duru’s draft is based on snapshot note-v3. The PUT prepares If-Match="note-v3"; new text in the browser does not show that the server record changed.
- Browser draft
- Draft; base="note-v3"
- Application
- Update not received
- Server record
- Server="note-v3"
- The conditional update arrived
The application received PUT /books/book-17/notes/mine. Duru/Atlas authorization is supplied as already allowed in this fixture; no new permission is derived from the header.
- Browser draft
- Awaiting outcome
- Application
- If-Match="note-v3" received
- Server record
- Server="note-v3"
- The version condition was separated
If-Match is a concurrency condition; an ETag is neither a credential nor ownership proof. Application identity, input and resource controls are separate from this condition.
- Browser draft
- Awaiting outcome
- Application
- Base note-v3; current note-v3
- Server record
- Server="note-v3"
- The condition was applied at the commit boundary
Comparison and mutation share one consistent boundary. Matching base and current versions allow the new note to commit as note-v4. Application and record are logical components in the same process here.
- Browser draft
- Awaiting outcome
- Application
- precondition=true
- Server record
- Server="note-v4"
- The HTTP outcome was prepared
The application prepares a 200 response. The browser has not yet learned the successful commit.
- Browser draft
- Awaiting outcome
- Application
- 200 response ready
- Server record
- Server="note-v4"
- The server outcome reached the browser
After receiving the response, the browser knows the server-saved outcome and note-v4 snapshot.
- Browser draft
- Saved; note-v4
- Application
- 200 response ready
- Server record
- Server="note-v4"
Stale snapshot
- The update intent is in the browser
Duru’s draft is based on snapshot note-v2. The PUT prepares If-Match="note-v2"; new text in the browser does not show that the server record changed.
- Browser draft
- Draft; base="note-v2"
- Application
- Update not received
- Server record
- Server="note-v3"
- The conditional update arrived
The application received PUT /books/book-17/notes/mine. Duru/Atlas authorization is supplied as already allowed in this fixture; no new permission is derived from the header.
- Browser draft
- Awaiting outcome
- Application
- If-Match="note-v2" received
- Server record
- Server="note-v3"
- The version condition was separated
If-Match is a concurrency condition; an ETag is neither a credential nor ownership proof. Application identity, input and resource controls are separate from this condition.
- Browser draft
- Awaiting outcome
- Application
- Base note-v2; current note-v3
- Server record
- Server="note-v3"
- The condition was applied at the commit boundary
Comparison and mutation share one consistent boundary. Base v2 does not match current v3, so no mutation occurs and note-v3 remains. Application and record are logical components in the same process here.
- Browser draft
- Awaiting outcome
- Application
- precondition=false
- Server record
- Server="note-v3"
- The HTTP outcome was prepared
The application prepares a 412 response. 412 is a concurrency conflict, not failed login, and does not change the preserved draft.
- Browser draft
- Awaiting outcome
- Application
- 412 response ready
- Server record
- Server="note-v3"
- The server outcome reached the browser
After receiving the response, the browser knows the conflict; the old snapshot and draft remain. Old text is not blindly forced through with a new ETag.
- Browser draft
- Conflict; draft preserved
- Application
- 412 response ready
- Server record
- Server="note-v3"
ASSUMPTION · BOUNDARY · CONTROL
Which state feeds the decision?
In a defensive review, distinguish a visible UI button from the server accepting its action. A hidden/disabled control is interaction state only. Seeing role=editor in the Browser is not a trusted authorization result.
When a workflow receives a tenant or owner selection from the client, the server must resolve the relationship between authenticated subject and resource through its own trusted data. This fixture supplies an already validated Duru/Atlas context; it is not a simulated login flow or permission test.
A success label is also a claim
When reviewing a save flow, inspect when optimistic UI claims “saved”. If rejection or an unknown outcome does not correct that claim, the product misleads the user about state.
Three inert branches suffice for inspection: a matching base version, a stale base version, and response loss after commit. In the last branch, two copies carrying different knowledge is normal; it does not imply authentication compromise.
For retry review, preserving the same intended effect and receiving the same response are separate questions. If the first PUT committed here, another attempt with the old If-Match can receive 412 under the selected strict policy; that does not undo the earlier commit. Rereading a representation establishes state observed at that time. The missing response alone cannot reveal whether another update intervened, so the UI must preserve the corresponding pending uncertainty.
3. Blue · Secure development & defense
Read every scenario without motion
Response observed
- The update evidence scope was selected
Review uses the normal row. Server decision and browser knowledge are separate fields; they need not have the same snapshot.
- Update row
- case=normal
- Commit review
- Not compared
- Client knowledge
- Client outcome unknown
- The precondition result was read
browserBase and serverBefore are note-v3; precondition=true describes this update’s version condition. It is not authentication or resource-permission evidence.
- Update row
- case=normal
- Commit review
- precondition=true
- Client knowledge
- Client outcome unknown
- The server commit was checked separately
The row records serverDecision=committed and serverAfter=note-v4. A 200 response was prepared; that status alone does not establish response receipt.
- Update row
- case=normal
- Commit review
- Committed; note-v4; status=200
- Client knowledge
- Client outcome unknown
- Receipt information was compared
With responseObserved=true, browserKnowledge is note-v4 and the UI is saved on server.
- Update row
- case=normal
- Commit review
- Server: note-v4
- Client knowledge
- Browser: note-v4; saved
- The integrity selection was bounded
The selector counting false preconditions with true commits returns 0 for the main fixture and 1 for its separate positive fixture. Missing response receipt does not violate that predicate.
- Update row
- case=normal
- Commit review
- Main count=0; positive=1
- Client knowledge
- Receipt: true
- The client claim was tied to evidence
This row shows both commit and receipt, supporting the server-saved claim. It says nothing about later updates.
- Update row
- case=normal
- Commit review
- Commit evidence present
- Client knowledge
- Receipt evidence present
Missing response
- The update evidence scope was selected
Review uses the response-lost row. Server decision and browser knowledge are separate fields; they need not have the same snapshot.
- Update row
- case=response-lost
- Commit review
- Not compared
- Client knowledge
- Client outcome unknown
- The precondition result was read
browserBase and serverBefore are note-v3; precondition=true describes this update’s version condition. It is not authentication or resource-permission evidence.
- Update row
- case=response-lost
- Commit review
- precondition=true
- Client knowledge
- Client outcome unknown
- The server commit was checked separately
The row records serverDecision=committed and serverAfter=note-v4. A 200 response was prepared; that status alone does not establish response receipt.
- Update row
- case=response-lost
- Commit review
- Committed; note-v4; status=200
- Client knowledge
- Client outcome unknown
- Receipt information was compared
With responseObserved=false, browserKnowledge remains the old note-v3 snapshot with outcome unknown. This does not support concluding that no commit occurred.
- Update row
- case=response-lost
- Commit review
- Server: note-v4
- Client knowledge
- Browser: note-v3; UNKNOWN
- The integrity selection was bounded
The selector counting false preconditions with true commits returns 0 for the main fixture and 1 for its separate positive fixture. Missing response receipt does not violate that predicate.
- Update row
- case=response-lost
- Commit review
- Main count=0; positive=1
- Client knowledge
- Receipt: false
- The client claim was tied to evidence
This row shows a server commit while the client outcome is unknown. Preserve the draft and reread the current representation for reconciliation before asserting either saved or not saved.
- Update row
- case=response-lost
- Commit review
- Commit evidence present
- Client knowledge
- UNKNOWN: client confirmation
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Write down ownership and success boundaries
Define the Browser as owner of UI input and local drafts, and the trusted server as owner of accepted notes and access policy. Track each state’s source, version, and freshness. When using a cache or snapshot, identify where the authoritative record resides.
Use distinct UI states such as unsaved, saving, saved locally, queued, saved on server, and conflict where they matter. Do not invent success or failure for an outcome the state model does not know.
Enforce the condition together with commit
For an endpoint accepting concurrent updates, enforce request authorization and input validation, then perform the precondition comparison and mutation at a consistent commit boundary. Choosing a database conditional update, transaction, or another appropriate primitive depends on the implementation.
A stale precondition should leave the current record unchanged. Provide a conflict response and reconciliation flow that preserves the draft. Checking an ETag only in the frontend does not protect the server’s lost-update boundary.
Reobserve an unknown response outcome
After a connection failure, reread the server record and compare it with the known version and draft. If the mutation needs retrying, use the operation’s idempotency and concurrency policy. Explain that the draft is preserved and what remains pending while the server outcome is confirmed.
Nonsecret observations can use baseVersion, currentVersion, decision, and responseObserved. These do not constitute a real HTTP capture; the fixture shows its own state transitions. The boundary is understandable without logging the user’s note text or session credential.
NOW APPLY IT TO THE CASE
When does the browser know that the server changed?
If-Match is only a version condition in this note update. Server commit and browser learning the response are shown as distinct boundaries.
Which owner holds the case’s browser data? Explain why presence or deletion of a client value differs from the server-side session outcome.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Note-update ownership and version ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"resource": "atlas/book-17/note-duru",
"authenticatedContext": {
"displayName": "Duru",
"tenant": "Atlas",
"authorization": "supplied as already allowed; not verified by this fixture"
},
"requestShape": {
"method": "PUT",
"path": "/books/book-17/notes/mine",
"ifMatch": "\"note-v3\"",
"bodyClass": "harmless new reading note"
},
"cases": [
{
"case": "normal",
"browserBase": "\"note-v3\"",
"serverBefore": "\"note-v3\"",
"precondition": true,
"serverAfter": "\"note-v4\"",
"serverDecision": "committed",
"responseStatus": 200,
"responseObserved": true,
"browserKnowledge": "\"note-v4\"",
"ui": "saved on server"
},
{
"case": "stale",
"browserBase": "\"note-v2\"",
"serverBefore": "\"note-v3\"",
"precondition": false,
"serverAfter": "\"note-v3\"",
"serverDecision": "not committed",
"responseStatus": 412,
"responseObserved": true,
"browserKnowledge": "old snapshot plus conflict",
"ui": "conflict; draft preserved"
},
{
"case": "response-lost",
"browserBase": "\"note-v3\"",
"serverBefore": "\"note-v3\"",
"precondition": true,
"serverAfter": "\"note-v4\"",
"serverDecision": "committed",
"responseStatus": 200,
"responseObserved": false,
"browserKnowledge": "\"note-v3\"; outcome unknown",
"ui": "pending confirmation; draft preserved"
}
],
"selector": {
"xml": "<updates><update case=\"normal\" precondition=\"true\" committed=\"true\" responseObserved=\"true\"/><update case=\"stale\" precondition=\"false\" committed=\"false\" responseObserved=\"true\"/><update case=\"response-lost\" precondition=\"true\" committed=\"true\" responseObserved=\"false\"/></updates>",
"xpath": "count(/updates/update[@precondition=\"false\" and @committed=\"true\"])",
"expected": 0,
"positiveFixtureXml": "<updates><update precondition=\"false\" committed=\"true\"/></updates>",
"positiveExpected": 1
}
}- browserBase / serverBefore
- The Browser snapshot validator and the server current validator; equality is only a concurrency precondition.
- serverAfter / serverDecision
- The trusted server commit result, independent of whether the response arrived.
- responseObserved / browserKnowledge
- Whether the Browser learned the outcome; sending alone is not proof of a server save.
- selector
- Selects fixture records that simultaneously claim a false precondition and a commit.
ETags are instructional aliases and response codes are chosen fixture behavior. The store is a logical in-process record. Real authorization, an HTTP server, concurrency races, and network failure were not executed.
Two reality checks
A value stored or displayed in the Browser is authoritative state for the server.
A client copy can be input or a snapshot. Resource ownership, authenticated authority, and accepted records are resolved on the trusted server.
Without a response, the server update certainly failed; resending is always safe.
A commit may have happened before the response was lost. For an unknown outcome, observe current state and reconcile using the operation’s concurrency/idempotency rules.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
The browser sends PUT using note-v2 while the server has note-v3. Does the selected state change?
- No; 412, the server retains note-v3 and the draft is preserved.
- Yes; the client draft overrides current server state.
Read the explained answer
No; 412, the server retains note-v3 and the draft is preserved. — The stale precondition is false; the selected no-commit branch need not discard the user draft.
What saved claim can the browser establish in the response-lost branch?
Selected lesson artifact fields; teaching example
- cases[2].serverDecision
- committed
- cases[2].responseObserved
- false
- cases[2].browserKnowledge
- "note-v3"; outcome unknown
- cases[2].ui
- pending confirmation; draft preserved
- If the request failed, the server definitely performed no write.
- Outcome is unknown; pending confirmation and draft preservation are appropriate.
Read the explained answer
Outcome is unknown; pending confirmation and draft preservation are appropriate. — The global fixture supplies a server commit but responseObserved=false; browserKnowledge does not contain it.
Explain the three ownership boundaries of a client draft, a current server record and supplied authorization.
Read the explained answer
A client draft is content the browser has not yet had accepted by the server. The server record carries the current revision and committed value; If-Match distinguishes stale state. AuthenticatedContext permission is supplied as already allowed here, not a verified authentication test. Revision matching does not replace tenant/user permission.
□ I distinguished the draft from the current record.
□ I did not count supplied permission as native authentication proof.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- OWASP — Authorization Cheat SheetValidate the Permissions on Every Request · Verify that Authorization Checks are Performed in the Right Location
- OWASP — HTML5 Security Cheat SheetStorage APIs: Local Storage · Storage APIs: IndexedDB
- RFC 9110 — HTTP Semantics8.8.3 ETag · 13.1.1 If-Match · 13.2.1 When to Evaluate Preconditions · 15.5.13 412 Precondition Failed · 9.2.2 Idempotent Methods