set_page has no compare-and-swap, so concurrent agents can silently overwrite each other #1

Closed
opened 2026-08-18 16:23:21 +00:00 by coilyco-ops · 0 comments
Owner

set_page accepts no expected-revision, so read-before-write is advisory rather
than enforced. Four agents write this site during the 2026-08-19 stream, and the
guardfile's describe text is currently the only thing holding the discipline.

The gap

GET /pages/{name} returns a revision, and PUT /pages/{name} ignores it. A
writer that read a page, spent a turn composing, and put it back will silently
overwrite anything another agent landed in between. The put succeeds, reports
success, and the other agent's section is gone with no signal to either party.

This is the failure the whole read-before-write rule exists to prevent, and
nothing in the server enforces it.

The fix

Accept an optional expected revision on put and refuse the write when it does
not match the current one:

  • PUT takes expected_revision, and returns 409 with the current revision and markdown when it disagrees.
  • Omitting it keeps today's last-write-wins behaviour, so a first write to a page that does not exist yet stays a single call.
  • The guardfile's set_page describe gains the field, and its resource text changes from "check the revision yourself" to "pass the revision you read".

A 409 carrying the current state is what lets a caller merge and retry inside one
turn rather than discovering the loss afterwards.

A put with empty markdown blanks a page. The page survives, so the no-delete
bound holds in the sense that matters for the guardfile, but content deletion is
reachable through the write verb. Worth deciding whether an empty body should be
refused outright, since no legitimate batch writes a zero-byte page.

`set_page` accepts no expected-revision, so read-before-write is advisory rather than enforced. Four agents write this site during the 2026-08-19 stream, and the guardfile's describe text is currently the only thing holding the discipline. ## The gap `GET /pages/{name}` returns a `revision`, and `PUT /pages/{name}` ignores it. A writer that read a page, spent a turn composing, and put it back will silently overwrite anything another agent landed in between. The put succeeds, reports success, and the other agent's section is gone with no signal to either party. This is the failure the whole read-before-write rule exists to prevent, and nothing in the server enforces it. ## The fix Accept an optional expected revision on put and refuse the write when it does not match the current one: * `PUT` takes `expected_revision`, and returns 409 with the current revision and markdown when it disagrees. * Omitting it keeps today's last-write-wins behaviour, so a first write to a page that does not exist yet stays a single call. * The guardfile's `set_page` describe gains the field, and its resource text changes from "check the revision yourself" to "pass the revision you read". A 409 carrying the current state is what lets a caller merge and retry inside one turn rather than discovering the loss afterwards. ## Related, smaller A put with empty markdown blanks a page. The page survives, so the no-delete bound holds in the sense that matters for the guardfile, but content deletion is reachable through the write verb. Worth deciding whether an empty body should be refused outright, since no legitimate batch writes a zero-byte page.
Commenting is not possible because the repository is archived.
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/quire#1
No description provided.