Epic: unify sandbox mutation lock decisions across sync and async callers
- Dominant language
- TypeScript
- Stars
- 22.5k
- Forks
- 3.1k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 715
Description
## Outcome
Remove duplicated synchronous and asynchronous sandbox mutation lock decisions without changing lock behavior.
The final implementation must use one reviewed decision model for ordinary acquisition, stale-owner recovery, Shields deadline fences, protected-path clearing, and deadline-owned main publication. Synchronous and asynchronous callers may retain separate I/O executors where their contracts differ.
The completed series must reduce production lines in the lifecycle-lock implementation. It must not add a generic transaction framework or a public extension point.
## Current implementation
`src/lib/state/mcp-lifecycle-lock-acquisition.ts` repeats the same security decisions across synchronous and asynchronous implementations:
- stale main-lock reaping: `tryReapStaleMainLock` and `tryReapStaleMainLockSync`
- ordinary acquisition: `acquireMcpLifecycleLock` and `acquireMcpLifecycleLockSync`
- deadline-fence acquisition: `acquireDeadlineFence` and `acquireDeadlineFenceSync`
- protected-path clearing: `clearDeadlineProtectedPath` and `clearDeadlineProtectedPathSync`
- deadline main-owner publication: `publishDeadlineMainOwner` and `publishDeadlineMainOwnerSync`
`src/lib/state/mcp-lifecycle-lock-storage.ts` also provides paired sync and async storage operations. Those filesystem primitives are outside the first refactor boundary because hard-link publication, rename behavior, inode identity, and exact-generation release are security controls.
The lock now serializes sandbox lifecycle, policy, channel, Shields, snapshot, inference, session, and MCP mutations. The historical MCP name no longer describes its full ownership boundary.
## Prior defects and related work
This epic consolidates behavior after these fixes and tests:
- #7858 and PR #7859: acquisition timeout must not cause a later mutation.
- #8356 and PR #8436: lifecycle-lock classifier properties belong in the source-local suite.
- #9750 and PRs #9724, #9749, and #9866: stale owners, expired Shields timers, deadline recovery, and timer-authority release ordering.
- PR #8130: serialize Shields deadline recovery.
These items define behavior to preserve. This epic does not reopen their product behavior.
## Capability slices
- [ ] Define synchronous and asynchronous behavior parity with shared characterization tests.
- [ ] Extract pure lifecycle-lock observations and next-action decisions.
- [ ] Reuse those decisions for ordinary synchronous and asynchronous lock acquisition.
- [ ] Reuse those decisions for Shields deadline-fence recovery and publication.
- [ ] Delete superseded implementations and duplicated tests, then confirm negative production LOC.
The linked task issues own acceptance and test evidence for each slice.
## Delivery order
1. The parity task must land first.
2. The pure-decision task depends on parity coverage.
3. Ordinary acquisition and Shields deadline-fence tasks both depend on the pure-decision task. They may proceed in parallel in separate files or stacked branches after that dependency lands.
4. Final deletion and consolidation depends on both implementation tasks.
## Acceptance criteria
- [ ] One shared decision model owns every invariant common to synchronous and asynchronous lifecycle locking.
- [ ] Synchronous process-exit and reentrant behavior remains synchronous.
- [ ] Asynchronous setup-failure and containment callbacks retain their current behavior.
- [ ] A timeout cannot publish or reclaim a lock after the caller receives the timeout.
- [ ] Stale, corrupt, active, timer-bound, deadline-owned, and committed-containment generations retain their current fail-closed behavior.
- [ ] Takeover-token changes prevent entry or recovery under stale Shields authority.
- [ ] Every release still targets the exact owned generation.
- [ ] Hard-link publication, inode checks, filesystem identity checks, and storage durability do not weaken.
- [ ] Existing public APIs remain compatible during the series.
- [ ] The completed series has negative production LOC for the affected lifecycle-lock implementation.
- [ ] Focused CLI tests, CLI type-checking, repository checks, test shuffle, and leak diagnostics pass.
## Security boundaries
This work changes a host-side security and concurrency gate. Tests must prove that malformed lock state, stale generations, process-identity ambiguity, timeout, takeover-token drift, and partial recovery cannot enter the protected mutation or release another owner's generation.
The lock stores process and filesystem authority metadata. It does not handle credential values. Tests and diagnostics must not add secrets or new credential access.
## Non-goals
- Change lock behavior or timeout defaults.
- Rename the public modules during the behavior refactor.
- Replace synchronous callers with promise blocking.
- Generalize the lock into a cross-feature transaction framework.
- Refactor filesystem publication and reclamation primitives before decision parity is proven.
- Change Shields, MCP, policy, or sandbox user-visible behavior.
Contributor guide
Research direction
Start by reading src/lib/state/mcp-lifecycle-lock-acquisition.ts and the paired operations in src/lib/state/mcp-lifecycle-lock-storage.ts, while keeping the storage primitives outside the first boundary. Begin with the shared characterization tests and the linked capability-task acceptance evidence. Done means one shared decision model covers the listed sync and async invariants without weakening security behavior, and focused CLI tests, type-checking, repository checks, shuffle, and leak diagnostics pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- security
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100