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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user