A slow model call can outlive its source draft
An assistant reads a release-note draft, spends time proposing edits, then sends the result. During that gap, a person may save a newer version. If the tool replaces the whole document without checking what it read, it can erase that work. This is a lost update: the write is based on an older representation.
A fictional draft with two editors
Imagine an assistant reading a made-up release note. The API returns ETag: "a91c" with the draft. The assistant prepares a shorter summary. Before it saves, an editor changes the launch date and saves; the current representation now has ETag: "c30d". These tag strings are opaque validators, not numbers to sort. The assistant should tell the API which representation it used as its starting point.
Two safeguards answer different questions
- A request identity such as an idempotency key asks whether this same command has already been applied, when the receiving endpoint records and honors that key. It helps resolve a repeated request after a lost reply.
- If-Match with the exact strong ETag asks: is the representation I read still current? It prevents a write based on an older draft from silently replacing a newer one.
- One does not replace the other. Reusing a request key does not detect a human edit made after the assistant read; an ETag does not establish whether a timed-out request already committed.
- A later 412 can mean another editor saved first, or that the assistant’s first request already advanced the draft before its reply was lost. Resolve request outcome separately; the precondition alone does not identify the winner.
Make each save conditional
The assistant could send PATCH /drafts/release-note-42 with If-Match: "a91c". RFC 9110 requires the origin server to compare If-Match using strong comparison and evaluate the condition before performing the method. When no supplied tag matches, the server must not perform that update. It may return 412 Precondition Failed; the RFC also permits a success response when the server can determine that this same state-changing request already succeeded. Define that response contract clearly. For an ordinary stale edit, return a conflict and let the caller read the current representation before preparing another write. If-Match: * only checks that a representation exists; it does not confirm that the draft is still the one the assistant read.
A conflict needs a recovery path
When the service identifies a stale-base conflict, fetch the current text and show the proposed AI change beside it. If this response arrived after a lost reply, first resolve whether the earlier attempt committed; a stale validator alone cannot distinguish the assistant’s saved edit from a human edit. If the conflict is genuine and the application has a well-defined field-level merge rule, create a new proposal from the current representation and submit it with the new ETag. Otherwise ask a person to review the difference. Even a safe merge must use another conditional write; blindly retrying the old whole-document replacement with a fresh tag would turn stale content into an accepted update.
Keep the version check with the write
A handler that reads the ETag, checks it in application code, then saves in a separate unguarded operation leaves room for a second editor to write between the check and the save. Make the expected-version check part of the database update itself, or use an equivalent transaction with locking or serialization that prevents a competing write between the check and save. Opening an ordinary transaction around an unguarded read followed by a write is not enough. AWS’s DynamoDB guide shows one vendor-specific pattern: store a version attribute and include an equality condition in the update. Check the actual database and replication mode in your system; an HTTP header by itself does not make storage updates atomic.
Acceptance cases for a fictional edit tool
- With no intervening edit, submit the current strong ETag and a proposed change. The update succeeds once and returns the validator for the new representation.
- Save a human edit after the assistant reads the draft, then submit the assistant’s old ETag. The stale update is rejected and the saved human version remains intact.
- Send two different text changes simultaneously, both based on the same ETag, with each successful write advancing the validator. At the persistence boundary, only one conditional write can win; the other receives the documented conflict. Check this against the real store semantics.
- After a known conflict, refresh and show a diff. Merge only under an explicit safe policy; otherwise ask the person. Do not loop by refreshing the tag and resubmitting the old complete document.
- Commit an update, then drop its response. On resubmission, verify that request-identity or status lookup can distinguish the already-completed action. An old ETag and a 412 alone do not reveal whether the first attempt committed.
A matching ETag is not an approval
A matching strong ETag establishes that the representation has not changed in a way covered by that validator; it does not establish that the AI’s proposed text is correct, authorized, or acceptable. Keep application validation and any required human approval in place. The release-note example and endpoint are fictional, not claims about an implemented EasyKOT or FinancialCents endpoint. These acceptance cases are proposed design checks, not executed tests.