test(engine): pin the FN-5256 liveness guard on a renamed board (the sweep that clears a live task's worktree) (#3132)
Top item from the verified coverage map on #3115. `reconcileTaskWorktreeMetadata` had **three** uncovered resolvers — the most of any sweep in the file — and it is the one that nulls `worktree`/`branch`/`sessionFile` on a live row. ## Why this sweep first Its own header names FN-5256: the incident where clearing worktree metadata yanked a checkout out from under a running shell. The guard that prevents it is `scopeOverrideMergeActiveSafe`, and that guard is exactly what the wip/review resolvers feed. The existing guard test uses `column: "in-progress"` — **the literal**. So blinding `worktreeReconcileWipColumns` back to `["in-progress"]` leaves all 825 self-healing tests green. The guard is converted; nothing in the suite could tell. On a renamed board the pre-conversion form matched nothing, `scopeOverrideMergeActiveSafe` became true for a card an executor was actively running, and the sweep cleared its metadata. ## The case The renamed twin of the existing FN-5256 test: a `scopeOverride` task live in a **renamed wip lane** keeps its metadata. Same shape, same assertions, different vocabulary — which is the whole point, since the original passes either way. **Measured:** 414 pass; blinding `worktreeReconcileWipColumns` to the legacy id fails **exactly this test**. ## Remaining from the map 25 uncovered resolvers left. Next by risk: `reclaimStaleActiveBranches` (deletes branches) and `reconcileInReviewBranchRebind` (rebinds branches of live cards) — both need a git-shelling harness, so they are slower to pin than this one was. Then the two `reconcileDependencyBlockingLeases` resolvers. I will keep working down that list. The map is on #3115 with verified names; anyone can pick an entry and check it the same way — blind one resolver, run `vitest run src/__tests__/self-healing`, and if it stays green that conversion has nothing behind it. ## Verification `self-healing.test.ts` **414 passed** · `pnpm test:gate` 13 + 161 + 487 + 71 · lint — green.
This commit is contained in:
@@ -4089,6 +4089,56 @@ describe("SelfHealingManager", () => {
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:WorkflowResolvedColumns 2026-07-31-21:30:
|
||||
THE FN-5256 GUARD HAD NO RENAMED-BOARD CASE, which is why blinding this sweep's wip resolver back
|
||||
to `["in-progress"]` leaves all 825 self-healing tests green: the guard test directly above uses
|
||||
that literal, so it cannot tell the conversion from the id it replaced.
|
||||
|
||||
What that costs on a renamed board: the guard matches nothing, `scopeOverrideMergeActiveSafe`
|
||||
becomes true for a card an executor is actively running, and this sweep nulls its
|
||||
`worktree`/`branch`/`sessionFile` — yanking the checkout out from under a live shell. FN-5256 is
|
||||
the incident that guard exists to prevent.
|
||||
*/
|
||||
it("does NOT clear worktree metadata for a scopeOverride task live in a RENAMED wip lane (FN-5256)", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
|
||||
mockedExistsSync.mockReturnValue(false);
|
||||
mockedGetRegisteredWorktreeBranchMap.mockResolvedValue(new Map<string, string>());
|
||||
(store as unknown as { listWorkflowDefinitions: unknown }).listWorkflowDefinitions = vi.fn(async () => [{
|
||||
ir: {
|
||||
version: "v2",
|
||||
id: "custom:renamed",
|
||||
nodes: [],
|
||||
edges: [],
|
||||
columns: [
|
||||
{ id: "building", name: "building", traits: [{ trait: "wip", config: { limitSetting: "maxConcurrent" } }] },
|
||||
{ id: "checking", name: "checking", traits: [{ trait: "merge" }] },
|
||||
],
|
||||
},
|
||||
}]);
|
||||
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([
|
||||
{
|
||||
id: "FN-RENAMED-LIVE",
|
||||
column: "building",
|
||||
paused: false,
|
||||
status: null,
|
||||
scopeOverride: true,
|
||||
worktree: "/tmp/project/.worktrees/fn-renamed-live",
|
||||
branch: "fusion/FN-RENAMED-LIVE",
|
||||
sessionFile: "/tmp/project/.fusion/sessions/fn-renamed-live.json",
|
||||
steps: [{ status: "in-progress" }],
|
||||
log: [],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await managerWithRecovery.reconcileTaskWorktreeMetadata();
|
||||
|
||||
expect(result).toBe(0);
|
||||
/* The live checkout survives: nothing nulled the worktree out from under the executor. */
|
||||
expect(store.updateTask).not.toHaveBeenCalled();
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
|
||||
it("does NOT clear worktree metadata for a scopeOverride in-review task mid-step (status: null)", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
|
||||
mockedExistsSync.mockReturnValue(false);
|
||||
|
||||
Reference in New Issue
Block a user