test(engine): remove orphan dependency reporter coverage (#2483)

## Summary
- remove the remaining dependency-blocked reporter test after the
reporter was deleted in #2477
- prevent Vitest from failing during module collection on a deleted
import

## Test plan
- `pnpm --filter @fusion/engine typecheck`
- `pnpm --filter @fusion/engine build`
This commit is contained in:
Phil Larson
2026-07-27 18:36:51 -07:00
committed by GitHub
parent 17fdf2f0ae
commit 3822e1081c

View File

@@ -1,223 +0,0 @@
/*
FNXC:WorkflowLifecycleColumns 2026-07-28-17:40 (PR #2479 review, P1):
BOARD-WIDE ROLE UNIONS MISCLASSIFY TASKS.
The previous fix resolved lifecycle roles across every workflow on the board and
UNIONED them into one `holdColumns` / `terminalColumns` pair. That is wrong the
moment two workflows reuse a column ID for DIFFERENT roles — and it is wrong for
the reason this whole program exists: **a column id only means something relative
to its own workflow.** A board-wide union quietly re-assumes ids are globally
meaningful, which is the precise assumption being removed.
The concrete break, and the fixture below: workflow A calls its HOLD column
`done`; workflow B calls its TERMINAL column `done`. Under a union,
`holdColumns` and `terminalColumns` BOTH contain `done`, so every card in a
column named `done` is simultaneously "held" and "terminal" regardless of which
workflow it belongs to. Dependents get counted as blocked while the blocker
sitting beside them is discarded as finished — from one ambiguous id.
The fix is per-task classification: each task is classified against ITS OWN
workflow, which makes the misclassification impossible by construction rather
than detected after the fact. It also removes the repeated workflow-definition
reads (the sibling P2), because one shared IR cache serves the whole pass.
These tests were written FIRST and observed FAILING against the union.
*/
import { describe, expect, it, vi } from "vitest";
import type { Task, TaskStore, WorkflowIr } from "@fusion/core";
import { DependencyBlockedTodoReporter } from "../dependency-blocked-todo-reporter.js";
const NOW = Date.parse("2026-05-18T12:00:00.000Z");
/** Old enough to bucket "stale", so significance gating never masks a miss. */
const MOVED_AT = new Date(NOW - 5 * 60 * 60_000).toISOString();
function task(over: Partial<Task> = {}): Task {
return {
id: "FN-1",
title: "t",
description: "test",
column: "todo",
dependencies: [],
steps: [],
currentStep: 0,
paused: false,
blockedBy: "",
overlapBlockedBy: "",
log: [],
createdAt: MOVED_AT,
updatedAt: MOVED_AT,
columnMovedAt: MOVED_AT,
...over,
} as Task;
}
/**
* `done` is the HOLD column here — a workflow that finished naming its columns
* differently. Nothing about this is exotic; ids are workflow-local by design.
*/
function holdIsDoneIr(): WorkflowIr {
return {
version: "v2",
id: "wf-hold-done",
nodes: [],
edges: [],
columns: [
{ id: "done", name: "done", traits: [{ trait: "hold", config: { release: "capacity" } }] },
{ id: "building", name: "building", traits: [{ trait: "wip", config: { limitSetting: "maxConcurrent" } }] },
{ id: "shipped", name: "shipped", traits: [{ trait: "complete" }] },
],
} as unknown as WorkflowIr;
}
/** The builtin shape, where `done` is TERMINAL. */
function doneIsTerminalIr(): WorkflowIr {
return {
version: "v2",
id: "wf-done-terminal",
nodes: [],
edges: [],
columns: [
{ id: "todo", name: "todo", traits: [{ trait: "hold", config: { release: "capacity" } }] },
{ id: "in-progress", name: "in-progress", traits: [{ trait: "wip", config: { limitSetting: "maxConcurrent" } }] },
{ id: "done", name: "done", traits: [{ trait: "complete" }] },
],
} as unknown as WorkflowIr;
}
function createStore(
tasks: Task[],
workflowByTask: Record<string, string>,
irByWorkflow: Record<string, WorkflowIr>,
) {
const upsertInsight = vi.fn().mockResolvedValue(undefined);
const getWorkflowDefinition = vi.fn(async (id: string) =>
irByWorkflow[id] ? { ir: irByWorkflow[id] } : null,
);
const store = {
getSettings: vi.fn().mockResolvedValue({ maxAutoMergeRetries: 3 }),
listTasks: vi.fn().mockResolvedValue(tasks),
logEntry: vi.fn().mockResolvedValue(undefined),
getInsightStore: vi.fn(() => ({ upsertInsight, listInsights: vi.fn().mockResolvedValue([]) })),
getTaskWorkflowSelectionAsync: vi.fn(async (id: string) => ({ workflowId: workflowByTask[id], stepIds: [] })),
getTaskWorkflowSelection: vi.fn((id: string) => ({ workflowId: workflowByTask[id], stepIds: [] })),
getWorkflowDefinition,
} as unknown as TaskStore;
return { store, upsertInsight, getWorkflowDefinition };
}
function reporter(store: TaskStore) {
return new DependencyBlockedTodoReporter({
store,
projectId: "p1",
logger: { warn: vi.fn(), error: vi.fn() },
now: () => NOW,
});
}
describe("dependency-blocked report classifies each task by ITS OWN workflow", () => {
it("does not treat a blocker as finished because ANOTHER workflow calls that column terminal", async () => {
/*
The P1, minimally. Every card belongs to `wf-hold-done`, where `done` is the
HOLD column. A second workflow on the board calls `done` terminal.
Under the union, `terminalColumns` contains `done`, so the blocker resting in
`done` is discarded as finished and the report goes silent — even though for
ITS workflow that column means "waiting for capacity".
*/
const tasks = [
task({ id: "BLOCKER", column: "done" }),
task({ id: "DEP-1", column: "done", dependencies: ["BLOCKER"] }),
task({ id: "DEP-2", column: "done", dependencies: ["BLOCKER"] }),
task({ id: "DEP-3", column: "done", dependencies: ["BLOCKER"] }),
// A card from the OTHER workflow, whose `done` genuinely is terminal.
task({ id: "OTHER", column: "done" }),
];
const byTask: Record<string, string> = {
BLOCKER: "wf-hold-done",
"DEP-1": "wf-hold-done",
"DEP-2": "wf-hold-done",
"DEP-3": "wf-hold-done",
OTHER: "wf-done-terminal",
};
const { store, upsertInsight } = createStore(tasks, byTask, {
"wf-hold-done": holdIsDoneIr(),
"wf-done-terminal": doneIsTerminalIr(),
});
const result = await reporter(store).report();
expect(result.alerted).toBe(true);
const payload = JSON.parse(upsertInsight.mock.calls[0][1].content);
expect(payload.groups.map((g: { blockerId: string }) => g.blockerId)).toEqual(["BLOCKER"]);
expect(payload.groups[0].blockedTodoIds).toEqual(["DEP-1", "DEP-2", "DEP-3"]);
});
it("does not treat a finished card as held because ANOTHER workflow calls that column hold", async () => {
/*
The mirror image, and the half a one-directional fix would miss. Every card
belongs to `wf-done-terminal`, where `done` is TERMINAL. Under the union
`holdColumns` contains `done`, so finished cards are counted as blocked
todos and the report invents blockage that does not exist.
*/
const tasks = [
task({ id: "BLOCKER", column: "in-progress" }),
task({ id: "DEP-1", column: "done", dependencies: ["BLOCKER"] }),
task({ id: "DEP-2", column: "done", dependencies: ["BLOCKER"] }),
task({ id: "DEP-3", column: "done", dependencies: ["BLOCKER"] }),
task({ id: "OTHER", column: "done" }),
];
const byTask: Record<string, string> = {
BLOCKER: "wf-done-terminal",
"DEP-1": "wf-done-terminal",
"DEP-2": "wf-done-terminal",
"DEP-3": "wf-done-terminal",
OTHER: "wf-hold-done",
};
const { store } = createStore(tasks, byTask, {
"wf-hold-done": holdIsDoneIr(),
"wf-done-terminal": doneIsTerminalIr(),
});
const result = await reporter(store).report();
// Those dependents are DONE in their own workflow — nothing is blocked.
expect(result.alerted).toBe(false);
expect(result.reason).toBe("no-blocked-groups");
});
it("reads one workflow definition per WORKFLOW, not per task (P2)", async () => {
/*
The sibling P2, which the per-task fix resolves as a side effect: a shared IR
cache across the pass means workflow-definition reads scale with the number of
WORKFLOWS, not the number of cards.
*/
const tasks = Array.from({ length: 12 }, (_, i) =>
task({ id: `FN-${i}`, column: i === 0 ? "in-progress" : "todo", dependencies: i === 0 ? [] : ["FN-0"] }),
);
const byTask = Object.fromEntries(tasks.map((t) => [t.id, "wf-done-terminal"]));
const { store, getWorkflowDefinition } = createStore(tasks, byTask, {
"wf-done-terminal": doneIsTerminalIr(),
});
await reporter(store).report();
expect(getWorkflowDefinition.mock.calls.length).toBeLessThanOrEqual(1);
});
it("still degrades to legacy behavior when no workflow resolves", async () => {
const tasks = [
task({ id: "BLOCKER", column: "in-progress" }),
task({ id: "DEP-1", column: "todo", dependencies: ["BLOCKER"] }),
task({ id: "DEP-2", column: "todo", dependencies: ["BLOCKER"] }),
task({ id: "DEP-3", column: "todo", dependencies: ["BLOCKER"] }),
];
const { store } = createStore(tasks, {}, {});
const result = await reporter(store).report();
expect(result.alerted).toBe(true);
expect(result.groupCount).toBe(1);
});
});