mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 14:30:13 +00:00
d509d75308
* fix(store): a move records its origin from the item as it stands under the lock (BUG-3665) On the no-precheck path (a forced move, or a direct MoveItem call), `existing` is the pre-lock pool read; preMove is the item re-read under the workspace lock. The event decisions already used preMove, but the durable item_collection_moves row still took its from_collection_id, and its "crossed collections" test, from `existing`. A move another writer landed in between therefore recorded A->C instead of B->C, and a client that cached the item in B (and can see neither A nor C) never got the moved-out tombstone. Both now use preMove. TestBUG3665_MoveRecordsTheOriginUnderTheLock moves the item A->B through a test-only seam (beforeMoveLock) between the forced move's pre-lock read and its lock: records [A->B B->C]; on main [A->B A->C]. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * fix(items): a move refuses an item changed since its payload was built (BUG-3665, codex r1 Medium) The move handler migrates the item's fields from a read taken before the move's lock and writes them under it, so a write landing in between was overwritten, and the move's history and events described the older body. Every move, a forced one included (force skips only the open-children guard), now carries a precheck that compares the locked snapshot's collection and fields with what the payload was built from, and answers 409 conflict when they differ. docs/api.md records the new 409. The beforeMoveLock seam gains SetBeforeMoveLockForTesting. TestBUG3665_MoveRefusesAnItemChangedSinceItsPayloadWasBuilt edits the item between the handler's read and the lock on both the forced and unforced routes: 409 and the edit kept; red (200, edit lost) with the check removed. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * fix(items): a move re-reads every input of its payload under the lock (BUG-3665, codex r2 Medium) r2 found the source collection's schema also shapes the payload and was not re-checked. Rather than chase members one round at a time, the precheck now covers the population of the payload's inputs, listed in the comment: the item's collection and fields, the source and target collections' schema and settings (the migration and the close-state check read them), and the target's unique-field holders (re-queried on the lock's transaction). Any change answers 409 conflict; the unique case keeps its own message. The test is a table: one case per input, each run on the forced and unforced routes, plus an unchanged-move control. Disabling each re-check fails exactly its own two legs. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS