From 44a67df65d57d596e9ef7ce67b1469e89e40e90a Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 06:58:27 -0700 Subject: [PATCH] test(engine): pin both dependency-lease resolvers on a renamed board (one case covered only half the conversion) (#3138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Next two entries from the verified coverage map on #3115. `reconcileDependencyBlockingLeases` had **both** of its resolvers uncovered — blinding either `leaseWipColumns` or `leaseHoldColumns` back to its legacy id left all 825 self-healing tests green, because every fixture in that block uses `in-progress` / `todo`, where the literals happen to be correct. ## What the literals cost The holder scan matches no card **and** the dependency scan matches no card. A stale file-scope lease blocking a real dependency is never rebounded, so the dependent stays `overlapBlockedBy` behind a holder that is not coming back. That is the deadlock this sweep exists to break — silently not broken, no error, no log. ## Two cases, because one did not cover both — measured, not assumed My first case (holder in a renamed wip lane, dependency marked `overlapBlockedBy`) pinned `leaseWipColumns`. I then blinded `leaseHoldColumns` against it and **it stayed green**. The reason is in the control flow: the `overlapBlockedBy === holder.id` branch short-circuits and `break`s **before** the hold membership is consulted. So that fixture can never reach the guard `leaseHoldColumns` feeds. The second case drops the marker, leaving an unmarked dependency resting in a renamed hold lane, which falls through to the overlapping-hold-dependency branch. | blinded | result | |---|---| | `leaseWipColumns` | **1 failed** | | `leaseHoldColumns` | **1 failed** | Before the second case, that table read `1 failed` / `still green`. Checking each resolver separately is the only reason I noticed — a single "the suite fails when reverted" would have looked like proof and covered half the conversion. ## Remaining 23 uncovered resolvers on the map. Next by risk: `reclaimStaleActiveBranches` (deletes branches) and `reconcileInReviewBranchRebind` (rebinds branches of live cards), both needing a git-shelling harness. ## Verification `self-healing.test.ts` **415 passed** · `pnpm test:gate` 13 + 161 + 487 + 71 · lint — green. ## Summary by CodeRabbit * **Bug Fixes** * Improved recovery of stalled workflow tasks when dependency-blocking leases become stale. * Added support for workflows using customized task status lanes, including marked and unmarked overlap blockers. --- .../engine/src/__tests__/self-healing.test.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/packages/engine/src/__tests__/self-healing.test.ts b/packages/engine/src/__tests__/self-healing.test.ts index 9af7e23fb9..e787bd75d8 100644 --- a/packages/engine/src/__tests__/self-healing.test.ts +++ b/packages/engine/src/__tests__/self-healing.test.ts @@ -11796,6 +11796,79 @@ describe("FN-5335 triple-proof no-action unit coverage", () => { manager.stop(); }); + /* + FNXC:WorkflowResolvedColumns 2026-07-31-22:15: + Both of this sweep's resolvers were UNCOVERED on the #3115 coverage map: blinding either + `leaseWipColumns` or `leaseHoldColumns` back to its legacy id leaves all 825 self-healing tests + green, because every fixture above uses `in-progress` / `todo`, where the literals are correct. + + What that costs on a renamed board: the holder scan matches no card and the dependency scan + matches no card, so a stale file-scope lease blocking a real dependency is never rebounded. The + dependent stays `overlapBlockedBy` forever behind a holder that is not coming back — a deadlock + the sweep exists to break, silently not broken. + */ + it("rebounds a stale lease when holder and dependency rest in RENAMED wip and hold lanes", async () => { + const { store, manager } = setup([ + makeTask({ id: "FN-H", column: "building", dependencies: ["FN-D"], worktree: "/tmp/wt-h" }), + makeTask({ id: "FN-D", column: "drafting", status: "queued", overlapBlockedBy: "FN-H" }), + ], { + "FN-H": ["packages/engine/src/scheduler.ts"], + "FN-D": ["packages/engine/src/scheduler.ts"], + }); + (store as unknown as { listWorkflowDefinitions: unknown }).listWorkflowDefinitions = vi.fn(async () => [{ + ir: { + version: "v2", + id: "custom:renamed", + nodes: [], + edges: [], + columns: [ + { id: "drafting", name: "drafting", traits: [{ trait: "hold", config: { release: "capacity" } }] }, + { id: "building", name: "building", traits: [{ trait: "wip", config: { limitSetting: "maxConcurrent" } }] }, + ], + }, + }]); + vi.spyOn(manager as any, "evaluateBackwardMoveTripleProof").mockResolvedValue({ ok: true, stalenessMs: 10_000, reason: "test" }); + + await expect(manager.reconcileDependencyBlockingLeases()).resolves.toBe(1); + /* The dependent is released rather than left blocked behind a holder that is not coming back. */ + expect(store.updateTask).toHaveBeenCalledWith("FN-D", { overlapBlockedBy: null, status: null }); + manager.stop(); + }); + + it("rebounds an overlapping RENAMED-hold dependency with no stale-blocker marker", async () => { + /* + The hold half. The case above sets `overlapBlockedBy`, which short-circuits at the + stale-overlap-blocker branch BEFORE the hold membership is consulted — measured: blinding + `leaseHoldColumns` leaves it green. Without the marker the sweep must fall through to + "is this dependency waiting in a hold lane and overlapping my scope?", which is the guard + `leaseHoldColumns` actually feeds. + */ + const { store, manager } = setup([ + makeTask({ id: "FN-H2", column: "building", dependencies: ["FN-D2"], worktree: "/tmp/wt-h2" }), + makeTask({ id: "FN-D2", column: "drafting", status: "queued" }), + ], { + "FN-H2": ["packages/engine/src/scheduler.ts"], + "FN-D2": ["packages/engine/src/scheduler.ts"], + }); + (store as unknown as { listWorkflowDefinitions: unknown }).listWorkflowDefinitions = vi.fn(async () => [{ + ir: { + version: "v2", + id: "custom:renamed", + nodes: [], + edges: [], + columns: [ + { id: "drafting", name: "drafting", traits: [{ trait: "hold", config: { release: "capacity" } }] }, + { id: "building", name: "building", traits: [{ trait: "wip", config: { limitSetting: "maxConcurrent" } }] }, + ], + }, + }]); + vi.spyOn(manager as any, "evaluateBackwardMoveTripleProof").mockResolvedValue({ ok: true, stalenessMs: 10_000, reason: "test" }); + + await expect(manager.reconcileDependencyBlockingLeases()).resolves.toBe(1); + expect(store.moveTask).toHaveBeenCalledWith("FN-H2", expect.anything(), expect.objectContaining({ recoveryRehome: true })); + manager.stop(); + }); + it("rebounds an overlapping todo dependency even before a stale blocker marker is stamped", async () => { const { store, manager } = setup([ makeTask({ id: "FN-H", column: "in-progress", dependencies: ["FN-D"] }),