You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
packages/loopover-miner/lib/worktree-allocator.ts's worktree_slots table has a repo_full_name column, populated whenever a slot is actively allocated to an attempt against a repo:
CREATETABLEIF NOT EXISTS worktree_slots (
slot_index INTEGERPRIMARY KEY,
worktree_path TEXTNOT NULL UNIQUE,
attempt_id TEXT UNIQUE,
repo_full_name TEXT,
status TEXTNOT NULLCHECK (status IN ('free', 'active')),
...
)
packages/loopover-miner/lib/purge-cli.ts's right-to-be-forgotten sweep (#5564) is meant to cover every repo-scoped local store — its REAL_PURGE_TARGETS list currently has 12 entries (claim-ledger, event-ledger, governor-ledger, prediction-ledger, portfolio-queue, run-state, contribution-profile-cache, governor-state, policy-verdict-cache, ranked-candidates, replay-snapshot, deny-hook-synthesis), and store-maintenance.ts defines a matching *_PURGE_SPEC for each. worktree-allocator.ts is in neither — there is no WORKTREE_ALLOCATOR_PURGE_SPEC and worktree-allocator.ts is absent from REAL_PURGE_TARGETS, despite worktree_slots carrying the exact same repo-identifying column every other covered store does. This is the same recurring gap class already fixed three times (#7091, #6599, #8009).
worktree_slots is NOT a plain append-only ledger, though — it's a fixed pool of maxConcurrency pre-allocated slot rows (slot_index is the primary key and every slot 0..maxConcurrency-1 always exists). The generic purgeStoreByRepo helper every other spec uses does a hard DELETE FROM table WHERE repoColumn = ?, which is the wrong shape here: deleting a slot row would shrink the pool below maxConcurrency and break ensureSlots/selectFreeSlot's invariant that every slot index exists. release() and reclaimOrphanedAllocations() already blank repo_full_name (and attempt_id/owner_pid/owner_host/allocated_at) to NULL on every path that frees a slot, so a free slot never carries a real repo name in practice; only a currently active slot (a live, in-flight attempt) can ever match a purge target.
Requirements
⚠️ Required pattern. Follow governor-state.ts's own precedent for a store needing custom (non-generic-purgeStoreByRepo) purge logic: add a purgeByRepo method directly on the WorktreeAllocator object returned by openWorktreeAllocator, using a hand-written UPDATE (mirroring release()'s/reclaimOrphanedAllocations()'s own SET status = 'free', attempt_id = NULL, repo_full_name = NULL, owner_pid = NULL, owner_host = NULL, allocated_at = NULL statement) rather than the generic LedgerPurgeSpec/purgeStoreByRepo helper, which would incorrectly DELETE a fixed pool slot.
Add purgeByRepo(repoFullName: string): number to the WorktreeAllocator type and openWorktreeAllocator's implementation.
Never delete a row.Never touch a row with status = 'active' — an active slot's repo_full_name reflects a live, currently-running attempt's real worktree checkout on disk; force-clearing it while status = 'active' would desync the allocator from that live checkout (the row would read "free" while the on-disk worktree and any in-flight process still exist). purgeByRepo against a repo with a currently-active attempt must leave that row untouched and must not count it.
Only clear (UPDATE ... SET repo_full_name = NULL, attempt_id = NULL, owner_pid = NULL, owner_host = NULL, allocated_at = NULL) a row where status = 'free' AND repo_full_name = ? — a defensive backstop for any row that predates this fix or was left stale by an unexpected crash path, even though every normal release/reclaim path already blanks these fields on free. State explicitly in code comments that this purge is expected to affect 0 rows in the overwhelming majority of real calls, by design.
Register the store in purge-cli.ts's REAL_PURGE_TARGETS ({ name: "worktree-allocator", optionKey: ..., opener: openWorktreeAllocator, resolveDbPath: resolveWorktreeAllocatorDbPath } — no spec/specs field, since this store's purge logic lives on the store object itself rather than a generic spec, exactly mirroring how governor-state's entry already carries specs instead of a single spec for its own non-uniform case).
--dry-run's read-only row-count path (purge-cli.ts's dry-run counting logic) must count only status = 'free' AND repo_full_name = ? rows for this store, matching the real purge's own match condition exactly — an active-slot row must never be counted as purgeable in dry-run either.
Deliverables
WorktreeAllocator.purgeByRepo implemented per the rules above.
worktree-allocator registered in purge-cli.ts's REAL_PURGE_TARGETS.
--dry-run row-count path covers worktree-allocator consistently with the real purge.
Test Coverage Requirements
packages/loopover-miner/** is not Codecov-gated, but npm run test:ci must stay green with full branch coverage in test/unit/miner-worktree-allocator.test.ts and test/unit/miner-purge-cli.test.ts: cover (1) a free slot directly seeded with a stale non-null repo_full_name (bypassing the normal acquire/release path, since normal operation never leaves one) — purged and counted; (2) an active slot for the target repo — left completely untouched, not counted; (3) no matching rows at all — returns 0; (4) --dry-run reports the same count the real purge would remove, for both the free-stale-row and active-row cases.
Expected Outcome
loopover-miner purge --repo <owner/repo> covers worktree-allocator's worktree_slots table like every other repo-scoped store, without risking corruption of a live, in-flight attempt's worktree allocation.
Context
packages/loopover-miner/lib/worktree-allocator.ts'sworktree_slotstable has arepo_full_namecolumn, populated whenever a slot is actively allocated to an attempt against a repo:packages/loopover-miner/lib/purge-cli.ts's right-to-be-forgotten sweep (#5564) is meant to cover every repo-scoped local store — itsREAL_PURGE_TARGETSlist currently has 12 entries (claim-ledger, event-ledger, governor-ledger, prediction-ledger, portfolio-queue, run-state, contribution-profile-cache, governor-state, policy-verdict-cache, ranked-candidates, replay-snapshot, deny-hook-synthesis), andstore-maintenance.tsdefines a matching*_PURGE_SPECfor each.worktree-allocator.tsis in neither — there is noWORKTREE_ALLOCATOR_PURGE_SPECandworktree-allocator.tsis absent fromREAL_PURGE_TARGETS, despiteworktree_slotscarrying the exact same repo-identifying column every other covered store does. This is the same recurring gap class already fixed three times (#7091, #6599, #8009).worktree_slotsis NOT a plain append-only ledger, though — it's a fixed pool ofmaxConcurrencypre-allocated slot rows (slot_indexis the primary key and every slot 0..maxConcurrency-1 always exists). The genericpurgeStoreByRepohelper every other spec uses does a hardDELETE FROM table WHERE repoColumn = ?, which is the wrong shape here: deleting a slot row would shrink the pool belowmaxConcurrencyand breakensureSlots/selectFreeSlot's invariant that every slot index exists.release()andreclaimOrphanedAllocations()already blankrepo_full_name(andattempt_id/owner_pid/owner_host/allocated_at) toNULLon every path that frees a slot, so afreeslot never carries a real repo name in practice; only a currentlyactiveslot (a live, in-flight attempt) can ever match a purge target.Requirements
purgeByRepo(repoFullName: string): numberto theWorktreeAllocatortype andopenWorktreeAllocator's implementation.status = 'active'— an active slot'srepo_full_namereflects a live, currently-running attempt's real worktree checkout on disk; force-clearing it whilestatus = 'active'would desync the allocator from that live checkout (the row would read "free" while the on-disk worktree and any in-flight process still exist).purgeByRepoagainst a repo with a currently-active attempt must leave that row untouched and must not count it.UPDATE ... SET repo_full_name = NULL, attempt_id = NULL, owner_pid = NULL, owner_host = NULL, allocated_at = NULL) a row wherestatus = 'free' AND repo_full_name = ?— a defensive backstop for any row that predates this fix or was left stale by an unexpected crash path, even though every normal release/reclaim path already blanks these fields on free. State explicitly in code comments that this purge is expected to affect 0 rows in the overwhelming majority of real calls, by design.purge-cli.ts'sREAL_PURGE_TARGETS({ name: "worktree-allocator", optionKey: ..., opener: openWorktreeAllocator, resolveDbPath: resolveWorktreeAllocatorDbPath }— nospec/specsfield, since this store's purge logic lives on the store object itself rather than a generic spec, exactly mirroring howgovernor-state's entry already carriesspecsinstead of a singlespecfor its own non-uniform case).--dry-run's read-only row-count path (purge-cli.ts's dry-run counting logic) must count onlystatus = 'free' AND repo_full_name = ?rows for this store, matching the real purge's own match condition exactly — an active-slot row must never be counted as purgeable in dry-run either.Deliverables
WorktreeAllocator.purgeByRepoimplemented per the rules above.worktree-allocatorregistered inpurge-cli.ts'sREAL_PURGE_TARGETS.--dry-runrow-count path coversworktree-allocatorconsistently with the real purge.Test Coverage Requirements
packages/loopover-miner/**is not Codecov-gated, butnpm run test:cimust stay green with full branch coverage intest/unit/miner-worktree-allocator.test.tsandtest/unit/miner-purge-cli.test.ts: cover (1) a free slot directly seeded with a stale non-nullrepo_full_name(bypassing the normal acquire/release path, since normal operation never leaves one) — purged and counted; (2) an active slot for the target repo — left completely untouched, not counted; (3) no matching rows at all — returns 0; (4)--dry-runreports the same count the real purge would remove, for both the free-stale-row and active-row cases.Expected Outcome
loopover-miner purge --repo <owner/repo>coversworktree-allocator'sworktree_slotstable like every other repo-scoped store, without risking corruption of a live, in-flight attempt's worktree allocation.Links & Resources
packages/loopover-miner/lib/worktree-allocator.ts(WorktreeAllocator,openWorktreeAllocator,release,reclaimOrphanedAllocations)packages/loopover-miner/lib/purge-cli.ts(REAL_PURGE_TARGETS, the governor-statespecsentry as the closest existing non-uniform precedent)packages/loopover-miner/lib/store-maintenance.ts(purgeStoreByRepo,LedgerPurgeSpec— explicitly NOT reused here, see Requirements)