events: cleanup restores an orphaned lease row, and can write an old baseline over a later run's value #205

Open
opened 2026-09-24 05:16:30 +00:00 by wtclaude · 0 comments
Member

Found by the module-rust phase 12 walk. Recorded in docs/modules/rust/PLAN.md §27.6.

What happens

claimRevert (server/src/model/events/eventRunResources.db.js) moves pending | confirmed | orphaned | drifted rows to reverting. For an action's resource, reverting something that is already gone is harmless, because revert() of an absent thing succeeds. For a lease, an orphaned row means the module said "this hold is no longer in force". A later restore() on it is then a write of that run's old baseline over whatever the world holds now.

Reproduction (Oxide rig, rust.group.permission)

  1. Run 2 takes a hold on a group permission. Core is stopped, and the plugin's deadline releases the hold.
  2. Core boots. Reconcile hears held: false and marks run 2's row orphaned. Run 2 is cancelled.
  3. Run 3 takes the same target. Cleanup tries to move run 2's orphaned row back to reverting and collides with run 3's live row on uq_evres_target. It failed on every sweep (13 times).
  4. Run 3 releases, leaving the permission granted on purpose, because the site asked for it. Run 2's stale restore() then goes through and revokes it.

A lease's compare-and-set cannot catch this when the later holder applied the same value.

Suggested direction (not decided)

An orphaned row whose action is core.lease (reversible: 'override') should be terminal for cleanup, like reverted: nobody holds it, so there is nothing to put back. At minimum, it should not be claimable while another row holds the same target.

  • AI-assisted: Claude Code (Claude Opus 5.5)
Found by the module-rust phase 12 walk. Recorded in `docs/modules/rust/PLAN.md` §27.6. ## What happens `claimRevert` (`server/src/model/events/eventRunResources.db.js`) moves `pending | confirmed | orphaned | drifted` rows to `reverting`. For an **action's** resource, reverting something that is already gone is harmless, because `revert()` of an absent thing succeeds. For a **lease**, an `orphaned` row means the module said "this hold is no longer in force". A later `restore()` on it is then a write of that run's old baseline over whatever the world holds now. ## Reproduction (Oxide rig, `rust.group.permission`) 1. Run 2 takes a hold on a group permission. Core is stopped, and the plugin's deadline releases the hold. 2. Core boots. Reconcile hears `held: false` and marks run 2's row `orphaned`. Run 2 is cancelled. 3. Run 3 takes the same target. Cleanup tries to move run 2's orphaned row back to `reverting` and collides with run 3's live row on `uq_evres_target`. It failed on every sweep (13 times). 4. Run 3 releases, leaving the permission granted on purpose, because the site asked for it. Run 2's stale `restore()` then goes through and **revokes it**. A lease's compare-and-set cannot catch this when the later holder applied the same value. ## Suggested direction (not decided) An `orphaned` row whose action is `core.lease` (`reversible: 'override'`) should be terminal for cleanup, like `reverted`: nobody holds it, so there is nothing to put back. At minimum, it should not be claimable while another row holds the same target. - [x] AI-assisted: Claude Code (Claude Opus 5.5)
Sign in to join this conversation.
No description provided.