Summary
project_page_state.py can overwrite a concurrent in-place edit while rolling back a multi-page projection.
Reproduction
- Start a page-state projection that changes at least two pages.
- Let the first page publish successfully.
- Modify that published page in place from another process, preserving its inode.
- Make publication of a later page fail.
Actual behavior
Rollback recognizes the page only by the transaction's published inode, unlinks it, restores the original before-image, and removes the recovery lock. The concurrent writer's bytes are lost.
A regression reproducer observes:
exit_code=1
concurrent_edit_preserved=False
restored_to_old_bytes=True
lock_exists=False
Expected behavior
Rollback must behave as a compare-and-swap against the exact verified after-image. If the canonical page's bytes or stable identity have changed since publication, it must:
- preserve the concurrent page bytes;
- preserve the original rollback image;
- retain the writer lock and recovery journal with
rollback-required status;
- require reconciliation instead of declaring rollback complete.
Impact
This is a data-loss issue: an unrelated editor can successfully write a page and have that edit silently replaced by a later failure elsewhere in the projection batch.
Summary
project_page_state.pycan overwrite a concurrent in-place edit while rolling back a multi-page projection.Reproduction
Actual behavior
Rollback recognizes the page only by the transaction's published inode, unlinks it, restores the original before-image, and removes the recovery lock. The concurrent writer's bytes are lost.
A regression reproducer observes:
Expected behavior
Rollback must behave as a compare-and-swap against the exact verified after-image. If the canonical page's bytes or stable identity have changed since publication, it must:
rollback-requiredstatus;Impact
This is a data-loss issue: an unrelated editor can successfully write a page and have that edit silently replaced by a later failure elsewhere in the projection batch.