From 1136474a63f6f9074d4168d2ecacdc1e57c4411e Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 06:55:15 -0700 Subject: [PATCH] =?UTF-8?q?test(engine):=20pin=20the=20archive=20skip=20in?= =?UTF-8?q?=20branch=20reclaim=20=E2=80=94=20the=20first=20uncovered=20res?= =?UTF-8?q?olver=20whose=20failure=20deletes=20a=20branch=20(#3144)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Next entry from the verified coverage map on #3115 — and the first one whose failure mode is **irreversible**. ## The gap `reclaimArchivedColumns` was uncovered: blinding it back to the id `archived` leaves all 825 self-healing tests green, because no fixture in this suite puts a card in a renamed archive lane. ## Why it matters more than the other 22 That guard **skips** archived cards — their branches belong to archive cleanup, not to branch reclaim. Keyed on the id, a card filed in a renamed archive lane fails the skip, and this sweep reaches: ``` git branch -D "fusion/" ``` Every other uncovered resolver I have pinned so far causes a wrong lifecycle decision — a card not requeued, a lease not released, a diagnostic not surfaced. All of those are recoverable from the task row. **A deleted branch is not.** ## Measured 415 pass. Blinding `reclaimArchivedColumns` fails exactly this case, and the assertion that fails is the one checking `git branch -D` was never called — so the failure *is* the branch being deleted, not a proxy for it. ## Progress on the map Closed so far: `archiveStaleDoneTasks` ×2 (#3115), `reconcileOrphanedPendingStepResults` (#3090), `recoverDriftedAgentTaskLinks` (#3102), `reconcileTaskWorktreeMetadata` wip (#3132), `reconcileDependencyBlockingLeases` ×2 (#3138), and this one. **22 uncovered resolvers remain.** Every sweep probed so far has been uncovered, and one (`reconcileDependencyBlockingLeases`) was only half-covered by its own first test — the branch short-circuited before the second resolver was ever consulted. That is why I now blind each resolver separately rather than trusting a single revert. Next: `reconcileInReviewBranchRebind` (rebinds branches of live cards) and the two remaining `reconcileTaskWorktreeMetadata` resolvers (terminal, review). ## Verification `self-healing.test.ts` **415 passed** · `pnpm test:gate` 13 + 161 + 487 + 71 · lint — green. --- .../engine/src/__tests__/self-healing.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/engine/src/__tests__/self-healing.test.ts b/packages/engine/src/__tests__/self-healing.test.ts index 0cadf74bcf..724e8da394 100644 --- a/packages/engine/src/__tests__/self-healing.test.ts +++ b/packages/engine/src/__tests__/self-healing.test.ts @@ -11003,6 +11003,45 @@ describe("SelfHealingManager reclaimStaleActiveBranches (FN-4546)", () => { })); }); + /* + FNXC:WorkflowResolvedColumns 2026-07-31-23:10: + `reclaimArchivedColumns` was UNCOVERED on the #3115 map: blinding it back to the id `archived` leaves + all 825 self-healing tests green, because no fixture here puts a card in a renamed archive lane. + + This guard SKIPS archived cards — their branches belong to archive cleanup, not to branch reclaim. + Keyed on the id, a card filed in a renamed archive lane fails the skip and this sweep DELETES its + branch (`git branch -D`), which is not recoverable from the task row. + */ + it("does not reclaim the branch of a card filed in a RENAMED archive lane", async () => { + (store.listTasks as any).mockResolvedValueOnce([ + { id: "FN-1001", column: "vault", checkedOutBy: null, userPaused: false, worktree: null, branch: null }, + ]); + (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: "vault", name: "vault", traits: [{ trait: "archived" }] }, + ], + }, + }]); + mockedExecSync.mockImplementation((command: string) => { + if (command.includes("git branch --list 'fusion/*'")) return Buffer.from(" fusion/fn-1001\n"); + if (command.includes("git rev-parse --verify") && command.includes("fusion/fn-1001")) return Buffer.from("abc123\n"); + if (command.includes("git rev-list --count") && command.includes("fusion/fn-1001")) return Buffer.from("0\n"); + return Buffer.from(""); + }); + + const recovered = await manager.reclaimStaleActiveBranches(); + + expect(recovered).toBe(0); + /* The branch survives: deleting it is not recoverable from the task row. */ + expect(mockedExecSync).not.toHaveBeenCalledWith(expect.stringContaining("git branch -D"), expect.anything()); + }); + it("does not delete branch with unique commits", async () => { (store.listTasks as any).mockResolvedValueOnce([ { id: "FN-1001", column: "todo", checkedOutBy: null, userPaused: false, worktree: null, branch: null },