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:
gsxdsm
2026-07-31 05:49:59 -07:00
committed by GitHub
parent 4db4052451
commit d09a856941

View File

@@ -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);