# Reject an edit based on an old revision

Carry the version a user actually edited into the save request. Compare it atomically with the current record before replacing data.

By Cobnex editorial. Published 2026-09-10. Updated 2026-09-11.

## Return a revision with the editable record

When loading an editable project, return its current revision alongside the fields. Keep that revision associated with the user's draft.

If the application refreshes data in the background, do not silently replace the draft's revision while leaving its older field values unchanged. That would make an old edit appear current to the server.

Decide which changes advance the revision. Every writer that can affect the protected representation must participate, including import jobs and support tools.

## Compare and update in one operation

Send the observed revision with the mutation and include it in the database write condition. A separate read followed by an unconditional update leaves another race between those statements.

```sql
UPDATE projects
SET name = :name,
    revision = revision + 1
WHERE tenant_id = :tenant_id
  AND id = :project_id
  AND revision = :expected_revision
RETURNING revision;
```

This illustrative statement assumes access checks and input validation occur in the appropriate request boundary. The tenant condition scopes the record but does not prove the caller is authorised to edit it.

Check whether a row was returned. No row means the expected update did not occur. Resolve missing, inaccessible and conflicting records according to the API's disclosure policy rather than leaking existence through detailed errors.

## Preserve the user's work on conflict

Return a defined conflict response and keep the draft available. Offer the current version for comparison when the caller is authorised to read it.

Do not automatically fetch the latest revision and resend the same replacement values. That bypasses the protection and overwrites changes the user has never reviewed.

For independent fields, a deliberate merge may be possible. For a long text field or a business decision, ask the user to resolve the difference. The merge policy should follow the data's meaning.

## Test more than two browser tabs

Exercise the normal form against a background import that updates the same record. Confirm that both paths advance and compare revisions as intended.

Test a timeout after the update commits. A subsequent retry may need an operation identity to recognise the already completed mutation rather than reporting a misleading edit conflict.

At the HTTP boundary, conditional requests can express version preconditions using validators. Whichever interface is chosen, keep the database enforcement atomic. A correctly named header does not itself prevent an unconditional write in the application.

## Sources

- [HTTP semantics: conditional requests](https://www.rfc-editor.org/rfc/rfc9110.html)
