Skip to content
AX Principles
AX-SYS-34Advanced

Reject writes based on a stale read

Rechaza las escrituras basadas en una lectura obsoleta

#saas#fintech

Give every mutable resource a version tag, and refuse a write whose tag no longer matches, so an agent acting on an old read cannot silently overwrite a change it never saw.

Why

Agents read, reason and then write, and the reasoning can take seconds or minutes. In that window a person, a webhook handler or a second agent can change the same record. If the server applies whichever write arrives last, the earlier change disappears and nobody is told. HTTP calls this the lost update problem. RFC 9110 says If-Match is most often used with state-changing methods to prevent accidental overwrites when several clients act in parallel on the same resource: the server sends an ETag with the resource, the client returns it in If-Match, and a mismatch gets 412 Precondition Failed, a status that exists so a method is not applied when the resource is in an unexpected state. Google's API design guidance (AIP-154) states the same rule for any API: two processes updating one resource in parallel can "stomp over" each other's work, an etag lets the server confirm the client's copy is current, and a mismatched etag must be rejected with ABORTED. For an agent, that rejection is useful information: re-read, check the decision against the new state, then try again. Idempotency (AX-SYS-15) makes the same request safe to repeat; a conditional write stops a different request built on outdated data.

Do

Return an ETag on every read of a mutable resource, require If-Match on state-changing writes, and answer a mismatch with 412 and the current version.

Don't

Apply whichever write arrives last, or report a version conflict as a generic error the agent cannot act on.

Artifact

GET /v1/refund_cases/rc_88
-> 200 OK
ETag: "v7"

PATCH /v1/refund_cases/rc_88
If-Match: "v7"
{ "status": "approved", "amount": 4900 }

-> 412 Precondition Failed
ETag: "v8"
Content-Type: application/problem+json
{ "title": "Case changed since you read it", "current_etag": "v8", "changed_by": "human:jane", "re_read": "/v1/refund_cases/rc_88" }

The agent re-reads version v8, sees that a person already approved a partial refund, and decides again from there.

Citations

  1. [01]
    RFC 9110: HTTP Semantics (13.1.1, If-Match) ↗

    If-Match is most often used with state-changing methods (e.g., POST, PUT, DELETE) to prevent accidental overwrites when multiple user agents might be acting in parallel on the same resource, that is, to prevent the lost update problem

  2. [02]
    RFC 9110: HTTP Semantics (15.5.13, 412 Precondition Failed) ↗

    412 indicates that one or more conditions in the request header fields evaluated to false on the server; it lets the client place preconditions on the current resource state and so prevent the method from being applied if the resource is in an unexpected state

  3. [03]
    Google AIP-154: Resource freshness validation ↗

    Two processes updating the same resource in parallel could create a race condition where the latter 'stomps over' the effort of the former; an etag lets the server check that the checksums match before acting, and if a user sends back an etag that does not match the current value, the service must send an ABORTED error