From 5eec7dc73b461d1fe0fcf7b1f11f311978722dfb Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 08:19:52 -0700 Subject: [PATCH] test(engine): pin the completed-blocked park release on a renamed board (12th resolver) (#3180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twelfth resolver from the verified coverage map on #3115. `completedBlockedHoldColumns` was uncovered: every case in this file seeds the park in `todo`, where the literal is correct, so blinding the resolver left all 21 tests green. ## What the literal costs A completed-blocked park rests in the board's **hold** lane, which is only called `todo` on the built-in workflow. Keyed on the id, the sweep selects nothing on a renamed board — so **finished work stays parked behind a blocker that has already cleared**, stranded exactly as FN-7926 describes. Silently: a sweep that selects no rows reports success. ## Two fixture facts, found by the test failing first - **The completion-blocker gate resolves the *blocker's* own workflow**, so the per-task selection readers are required too. `listWorkflowDefinitions` alone leaves the renamed complete lane unrecognised and the park is rejected for the wrong reason — a green-for-the-wrong-reason test, which is the exact thing this effort removes. - **The blocker must rest in the renamed complete lane**, not the legacy one, or the case proves nothing about the board it claims to test. I only learned both because the first version failed. Had it passed, I would have shipped a test that exercised none of this. ## Measured 21 pass; blinding `completedBlockedHoldColumns` fails exactly this case. ## Map status **12 of 26 pinned.** Also re-measured six entries this turn: `starvedWaitingColumns` is **now covered by another worker's test** (#3128-era, peer-progress vocabulary), so the map is drifting green underneath me as the fleet adds coverage too — worth re-running before anyone picks the next entry. ## Verification `execute-requeue-loop-guard` **21 passed** · `pnpm test:gate` full pass · lint — green. --- .../execute-requeue-loop-guard.test.ts | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/packages/engine/src/__tests__/reliability-interactions/execute-requeue-loop-guard.test.ts b/packages/engine/src/__tests__/reliability-interactions/execute-requeue-loop-guard.test.ts index bbf9f856b5..1d8f35f09f 100644 --- a/packages/engine/src/__tests__/reliability-interactions/execute-requeue-loop-guard.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/execute-requeue-loop-guard.test.ts @@ -584,6 +584,63 @@ describe("execute requeue loop guard", () => { ); }); + /* + FNXC:WorkflowResolvedColumns 2026-07-31-17:10: + `completedBlockedHoldColumns` was UNCOVERED on the #3115 map: every case here seeds the park in + `todo`, where the literal is correct, so blinding the resolver leaves the file green. + + A completed-blocked park rests in the board's HOLD lane, which is only called `todo` on the built-in + workflow. Keyed on the id the sweep selects nothing on a renamed board, so finished work stays + parked behind a blocker that has already cleared — stranded exactly as FN-7926 describes, and + silently, because a sweep that selects no rows reports success. + */ + it("auto-advances a completed-blocked park resting in a RENAMED hold lane", async () => { + const h = harness( + task({ + id: "FN-RENAMED-PARK", + column: "drafting", + blockedBy: "FN-BLOCKER", + paused: true, + pausedReason: COMPLETED_BLOCKED_PAUSE_REASON, + status: "queued", + steps: [{ name: "Implement", status: "done" }], + }), + [task({ id: "FN-BLOCKER", column: "shipped" })], + ); + const RENAMED_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" } }] }, + { id: "shipped", name: "shipped", traits: [{ trait: "complete" }] }, + ], + }; + /* The completion-blocker gate resolves the blocker's OWN workflow, so the per-task selection + readers are needed too — `listWorkflowDefinitions` alone leaves `shipped` unrecognised and the + park is rejected for the wrong reason. */ + Object.assign(h.store as unknown as Record, { + getTaskWorkflowSelection: vi.fn(() => ({ workflowId: "custom:renamed", stepIds: [] })), + getTaskWorkflowSelectionAsync: vi.fn(async () => ({ workflowId: "custom:renamed", stepIds: [] })), + getWorkflowDefinition: vi.fn(async () => ({ ir: RENAMED_IR })), + listWorkflowDefinitions: vi.fn(async () => [{ id: "custom:renamed", ir: RENAMED_IR }]), + }); + const recoverCompletedTask = vi.fn(async () => true); + const healer = new SelfHealingManager(h.store, { + rootDir: "/tmp/test", + recoverCompletedTask: recoverCompletedTask as any, + getExecutingTaskIds: () => new Set(), + isTaskActive: () => false, + }); + + await (healer as any).reconcileCompletedBlockedTasks(); + + /* Selected by the board's own hold lane, so the finished work is released. */ + expect(recoverCompletedTask).toHaveBeenCalledWith(expect.objectContaining({ id: "FN-RENAMED-PARK" })); + }); + it("auto-advances a zero-step taskDone completed-blocked park once the blocker clears (invariant: park and advance must agree on workComplete)", async () => { // Regression for the FN-7926 park/advance asymmetry: parkCompletedBlockedTask() // accepts workComplete=taskDone for a task with zero planned steps (see the