test(engine): pin the completed-blocked park release on a renamed board (12th resolver) (#3180)

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.
This commit is contained in:
gsxdsm
2026-07-31 08:19:52 -07:00
committed by GitHub
parent bc37185026
commit 5eec7dc73b

View File

@@ -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<string, unknown>, {
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