Files
fusion/packages/dashboard/app/utils/__tests__/taskProgress.test.ts
gsxdsm e84e9d7f60 fix: the caller audit — five unwired parameters, five defects in their callers (#2803)
Seven fixes that were sitting on separate handoff branches with no owner
while `main` moved. Consolidated, rebased onto current `main`, and
verified **together** rather than only per-branch. The individual
branches remain if a subset is preferred.

This is the same consolidation that got `batch-core` and #2787 adopted.
**Close it if it breaks queue policy** — the branch keeps the work safe
either way.

## Where these came from

#2787's review found an optional parameter whose production caller never
passed it. That is a class, so I ran it against everything I had landed
and found five more. **All five turned out to have their real defect in
the CALLER, not the parameter** — in four of them the parameter was
unreachable:

| unwired parameter | what was actually wrong |
|---|---|
| `blocker-fanout.escalationColumns` | the hold default made the count
zero — **no bottleneck warning was emitted at all** |
| analytics `columnFlagsByName` | routes never built a map — **0
in-progress / 0 in-review beside correct cost totals** |
| `isLegacyAutoMergeStampCandidate` | the read **queried a column a
renamed board does not have**, so the backfill iterated nothing |
| `rankAssignedTasksForWakeDelta` | `getTasksByAssignedAgent`'s
`excludeArchived` used the literal — **archived cards returned as open
work** |
| `duplicate-intake.columnFlagsByColumnId` | intake could **archive or
soft-delete a newly created task** as a duplicate of finished work |

The heuristic worth keeping: **an optional parameter no production
caller fills is a marker pointing at an unexamined caller.** The census
cannot see any of these five — every gate is a `Set`/array literal or a
query filter, i.e. a definition rather than a comparison.

## Also included

- **`executor.ts`** — the stale-spec guard did the exact thing its own
comment forbids: on a renamed board it ran on a LIVE task and pulled it
out of execution into replan. `activeMergeStatuses` protected merging
cards *by accident*, which is why the symptom looked arbitrary.
- **`register-project-routes.ts`** — project health reported **0 active
tasks**; its list also still contained `triage`, dead since U11.
- **`dashboard/app/utils/taskTiming.ts`** — a **second copy** of
`getTotalAgentActiveMs`. Core's was converted; the card chip imports
this one, so the census counted the site as done while the rendered
number stayed keyed on `"in-progress"`.

## Verification

Verified as a set: `pnpm test:gate` **161 / 13 / 487 / 71** · core
suites **15 passed** · engine **7** · dashboard **12** · four `tsc`
targets clean · lint clean · census `--strict` exits 0.

Each fix is revert-proven individually; the specific case that fails is
named in each test header.

## Two honesty notes

**Three guards here are structural, not behavioural, and say so in their
headers.** `sanitizeAgentTaskLinks` is a closure inside
`createApiRoutes`; the analytics aggregators need a live
`AsyncDataLayer`; the stale-spec guard sits deep inside `execute()`.
Each ratchet fails on revert — verified — but none is an end-to-end
proof, and the headers state which half they cover.

**One of my behavioural test sets would have lied.** The intake-dedup
cases drive `findSameAgentDuplicates` directly; I removed the wiring to
measure the revert and **they stayed green**, because they pin the
predicate and not the caller. That is the exact illusion this audit was
chasing, reproduced in my own file. The forward now has its own
structural check.

## Deliberately not included

`worktree-pool.ts:1205` — it **fails safe** (a missed match protects a
branch from cleanup rather than deleting it) and sits in the merger's
branch-reaping path where the opposite error destroys work. That
deserves its owner's judgement, not a drive-by conversion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 12:02:53 -07:00

471 lines
19 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { Task } from "@fusion/core";
import { getRunningOptionalGateBadge, getUnifiedTaskProgress, isPlanReviewRunning } from "../taskProgress";
/*
FNXC:WorkflowSteps 2026-06-25-00:00 — graph-native progress model (plan U3).
These tests pin the render-state contract that the progress bar / Workflow tab rely on:
- names resolve from result.workflowStepName (no DB-row name lookup), with a humanized node-id fallback;
- a "pending" result with a startedAt and no completedAt is the `running` state, vs bare `pending`;
- advisory_failure (non-blocking) is distinct from failed (blocking) and counts as completed;
- disabled optional steps (absent from enabledWorkflowSteps) never appear in the counter/bar.
*/
function makeTask(overrides: Partial<Pick<Task, "steps" | "enabledWorkflowSteps" | "workflowStepResults">>) {
return {
steps: [],
enabledWorkflowSteps: [],
workflowStepResults: [],
...overrides,
} as Pick<Task, "steps" | "enabledWorkflowSteps" | "workflowStepResults">;
}
describe("isPlanReviewRunning", () => {
it.each([
{ name: "undefined results", task: { enabledWorkflowSteps: ["plan-review"], workflowStepResults: undefined }, expected: false },
{ name: "pending but not started", task: { enabledWorkflowSteps: ["plan-review"], workflowStepResults: [{ workflowStepId: "plan-review", workflowStepName: "Plan Review", status: "pending" }] }, expected: false },
{ name: "started and not completed", task: { enabledWorkflowSteps: ["plan-review"], workflowStepResults: [{ workflowStepId: "plan-review", workflowStepName: "Plan Review", status: "pending", startedAt: "2026-07-11T12:00:00.000Z" }] }, expected: true },
{ name: "passed", task: { enabledWorkflowSteps: ["plan-review"], workflowStepResults: [{ workflowStepId: "plan-review", workflowStepName: "Plan Review", status: "passed", startedAt: "2026-07-11T12:00:00.000Z", completedAt: "2026-07-11T12:01:00.000Z" }] }, expected: false },
{ name: "failed", task: { enabledWorkflowSteps: ["plan-review"], workflowStepResults: [{ workflowStepId: "plan-review", workflowStepName: "Plan Review", status: "failed", startedAt: "2026-07-11T12:00:00.000Z", completedAt: "2026-07-11T12:01:00.000Z" }] }, expected: false },
{ name: "skipped", task: { enabledWorkflowSteps: ["plan-review"], workflowStepResults: [{ workflowStepId: "plan-review", workflowStepName: "Plan Review", status: "skipped", startedAt: "2026-07-11T12:00:00.000Z", completedAt: "2026-07-11T12:01:00.000Z" }] }, expected: false },
{ name: "advisory failure", task: { enabledWorkflowSteps: ["plan-review"], workflowStepResults: [{ workflowStepId: "plan-review", workflowStepName: "Plan Review", status: "advisory_failure", startedAt: "2026-07-11T12:00:00.000Z", completedAt: "2026-07-11T12:01:00.000Z" }] }, expected: false },
])("returns $expected for $name", ({ task, expected }) => {
expect(isPlanReviewRunning(makeTask(task as Partial<Pick<Task, "steps" | "enabledWorkflowSteps" | "workflowStepResults">>))).toBe(expected);
});
});
/*
FNXC:TaskCardOptionalGateBadge 2026-07-21-22:30:
Lane-owned optional gates badge only in their planning/review columns while running.
*/
describe("getRunningOptionalGateBadge", () => {
const runningPlanReview = {
workflowStepId: "plan-review",
workflowStepName: "Plan Review",
status: "pending" as const,
startedAt: "2026-07-11T12:00:00.000Z",
};
const runningCodeReview = {
workflowStepId: "code-review",
workflowStepName: "Code Review",
status: "pending" as const,
startedAt: "2026-07-11T12:00:00.000Z",
};
const runningBrowser = {
workflowStepId: "browser-verification",
workflowStepName: "Browser Verification",
status: "pending" as const,
startedAt: "2026-07-11T12:00:00.000Z",
};
/*
FNXC:TaskCardOptionalGateBadge 2026-07-27-06:10:
Plan Review badges in EVERY column it can run in. The enumeration is the point: placement is the
WORKFLOW's call — the built-in coding workflows run this node in the planning column, a custom or
future workflow may place it elsewhere — so the badge must key on the running gate, not on a lane
allowlist that silently hides every placement it did not anticipate.
*/
it("badges Plan Review in every column the gate can run in", () => {
for (const column of ["triage", "todo", "in-progress"] as const) {
const badge = getRunningOptionalGateBadge({
...makeTask({
enabledWorkflowSteps: ["plan-review"],
workflowStepResults: [runningPlanReview],
}),
column,
} as Task);
expect(badge?.label).toBe("Plan Review");
expect(badge?.testId).toBe("reviewing");
}
// The replan half of the loop badges the same way, wherever the remediation node lives.
expect(getRunningOptionalGateBadge({
...makeTask({
enabledWorkflowSteps: ["plan-replan"],
workflowStepResults: [{ ...runningPlanReview, workflowStepId: "plan-replan", workflowStepName: "Plan Replan" }],
}),
column: "triage",
} as Task)).toMatchObject({ label: "Plan Review", testId: "reviewing" });
});
it("badges Code Review and Browser Verification on in-review only", () => {
expect(getRunningOptionalGateBadge({
...makeTask({
enabledWorkflowSteps: ["code-review"],
workflowStepResults: [runningCodeReview],
}),
column: "in-review",
} as Task)).toMatchObject({ label: "Code Review", testId: "code-review" });
expect(getRunningOptionalGateBadge({
...makeTask({
enabledWorkflowSteps: ["browser-verification"],
workflowStepResults: [runningBrowser],
}),
column: "in-review",
} as Task)).toMatchObject({ label: "Browser Verification", testId: "browser-verification" });
expect(getRunningOptionalGateBadge({
...makeTask({
enabledWorkflowSteps: ["code-review"],
workflowStepResults: [runningCodeReview],
}),
column: "in-progress",
} as Task)).toBeUndefined();
});
it("returns undefined when the gate is not running", () => {
expect(getRunningOptionalGateBadge({
...makeTask({
enabledWorkflowSteps: ["code-review"],
workflowStepResults: [{
workflowStepId: "code-review",
workflowStepName: "Code Review",
status: "pending",
}],
}),
column: "in-review",
} as Task)).toBeUndefined();
});
});
describe("getUnifiedTaskProgress", () => {
it("resolves workflow step names from result.workflowStepName without a lookup", () => {
const progress = getUnifiedTaskProgress(
makeTask({
enabledWorkflowSteps: ["browser-verification"],
workflowStepResults: [
{
workflowStepId: "browser-verification",
workflowStepName: "Browser Verification",
status: "passed",
},
],
}),
);
const item = progress.items.find((i) => i.id === "workflow-browser-verification");
expect(item?.name).toBe("Browser Verification");
expect(item?.status).toBe("done");
});
/*
FNXC:TaskCardWorkflowProgress 2026-07-21-22:26:
Implementation scope is the WIP progress contract: Plan Review / Code Review and other lane-owned gates stay out of the in-progress checklist while full scope keeps the complete pipeline for detail and badges.
*/
it("implementation scope keeps only WIP steps, not Plan Review or Code Review", () => {
const task = makeTask({
steps: [
{ name: "Implement feature", status: "done" },
{ name: "Add tests", status: "in-progress" },
] as Task["steps"],
enabledWorkflowSteps: ["plan-review", "code-review", "browser-verification"],
workflowStepResults: [
{
workflowStepId: "plan-review",
workflowStepName: "Plan Review",
status: "passed",
startedAt: "2026-07-11T12:00:00.000Z",
completedAt: "2026-07-11T12:01:00.000Z",
},
{
workflowStepId: "code-review",
workflowStepName: "Code Review",
status: "pending",
},
],
});
const full = getUnifiedTaskProgress(task);
expect(full.items.map((item) => item.id)).toEqual([
"workflow-plan-review",
"step-0",
"step-1",
"workflow-code-review",
"workflow-browser-verification",
]);
expect(full.total).toBe(5);
expect(full.completed).toBe(2);
const implementation = getUnifiedTaskProgress(task, { scope: "implementation" });
expect(implementation.items.map((item) => item.id)).toEqual(["step-0", "step-1"]);
expect(implementation.total).toBe(2);
expect(implementation.completed).toBe(1);
});
it("falls back to the workflow step id when no result name is available", () => {
const progress = getUnifiedTaskProgress(
makeTask({
enabledWorkflowSteps: ["code-review"],
workflowStepResults: [],
}),
);
const item = progress.items.find((i) => i.id === "workflow-code-review");
// Enabled-but-not-run has no recorded name → humanize the node id to proper casing,
// never render the raw lowercase id.
expect(item?.name).toBe("Code Review");
// Enabled but never run → pending.
expect(item?.status).toBe("pending");
});
it("humanizes the node id to proper casing for an enabled-but-not-run step", () => {
const progress = getUnifiedTaskProgress(
makeTask({
enabledWorkflowSteps: ["browser-verification", "frontend-ux-design", "code-review"],
workflowStepResults: [],
}),
);
expect(progress.items.find((i) => i.id === "workflow-browser-verification")?.name).toBe("Browser Verification");
expect(progress.items.find((i) => i.id === "workflow-frontend-ux-design")?.name).toBe("Frontend UX Design");
expect(progress.items.find((i) => i.id === "workflow-code-review")?.name).toBe("Code Review");
});
it("distinguishes running (started, not completed) from pending (not started)", () => {
const progress = getUnifiedTaskProgress(
makeTask({
enabledWorkflowSteps: ["running-step", "not-started-step"],
workflowStepResults: [
{
workflowStepId: "running-step",
workflowStepName: "Running Step",
status: "pending",
startedAt: "2026-06-25T00:00:00.000Z",
},
],
}),
);
expect(progress.items.find((i) => i.id === "workflow-running-step")?.status).toBe("running");
expect(progress.items.find((i) => i.id === "workflow-not-started-step")?.status).toBe("pending");
});
it("treats a completed pending entry (startedAt + completedAt) as pending, not running", () => {
// Defensive: a terminal entry should carry a non-pending status, but if a pending entry has
// both timestamps it is not actively running.
const progress = getUnifiedTaskProgress(
makeTask({
enabledWorkflowSteps: ["edge"],
workflowStepResults: [
{
workflowStepId: "edge",
workflowStepName: "Edge",
status: "pending",
startedAt: "2026-06-25T00:00:00.000Z",
completedAt: "2026-06-25T00:01:00.000Z",
},
],
}),
);
expect(progress.items.find((i) => i.id === "workflow-edge")?.status).toBe("pending");
});
it("maps advisory_failure (non-blocking) distinctly from failed (blocking)", () => {
const progress = getUnifiedTaskProgress(
makeTask({
enabledWorkflowSteps: ["advisory", "gate"],
workflowStepResults: [
{ workflowStepId: "advisory", workflowStepName: "Advisory", status: "advisory_failure" },
{ workflowStepId: "gate", workflowStepName: "Gate", status: "failed" },
],
}),
);
expect(progress.items.find((i) => i.id === "workflow-advisory")?.status).toBe("advisory_failure");
expect(progress.items.find((i) => i.id === "workflow-gate")?.status).toBe("failed");
// advisory_failure counts as completed (non-blocking); failed does not.
expect(progress.completed).toBe(1);
});
it("excludes a disabled step (absent from enabledWorkflowSteps) from the count", () => {
const progress = getUnifiedTaskProgress(
makeTask({
// "disabled-step" is toggled off → not in enabledWorkflowSteps, even though a stale result exists.
enabledWorkflowSteps: ["code-review"],
workflowStepResults: [
{ workflowStepId: "code-review", workflowStepName: "Code Review", status: "passed" },
{ workflowStepId: "disabled-step", workflowStepName: "Disabled", status: "passed" },
],
}),
);
expect(progress.total).toBe(1);
expect(progress.items.some((i) => i.id === "workflow-disabled-step")).toBe(false);
});
it("includes recorded graph-node progress that is not an optional toggle", () => {
const progress = getUnifiedTaskProgress(
makeTask({
enabledWorkflowSteps: [],
workflowStepResults: [
{
workflowStepId: "plan",
workflowStepName: "Plan",
source: "node",
status: "passed",
startedAt: "2026-06-29T21:49:55.355Z",
completedAt: "2026-06-29T21:51:00.000Z",
},
{
workflowStepId: "execute",
workflowStepName: "Execute",
source: "node",
status: "pending",
startedAt: "2026-06-29T21:51:00.996Z",
},
{
workflowStepId: "code-review",
workflowStepName: "Code Review",
source: "optional-group",
status: "passed",
startedAt: "2026-06-29T21:52:00.000Z",
completedAt: "2026-06-29T21:52:20.000Z",
},
],
}),
);
expect(progress.items.map((item) => [item.id, item.name, item.status])).toEqual([
["workflow-plan", "Plan", "done"],
["workflow-execute", "Execute", "running"],
]);
expect(progress.total).toBe(2);
expect(progress.completed).toBe(1);
});
it("produces 8 items with the correct completed count for 6 impl steps + 2 workflow steps", () => {
const progress = getUnifiedTaskProgress(
makeTask({
steps: [
{ name: "Step 1", status: "done" },
{ name: "Step 2", status: "done" },
{ name: "Step 3", status: "done" },
{ name: "Step 4", status: "done" },
{ name: "Step 5", status: "done" },
{ name: "Step 6", status: "done" },
],
enabledWorkflowSteps: ["browser-verification", "code-review"],
workflowStepResults: [
{ workflowStepId: "browser-verification", workflowStepName: "Browser Verification", status: "passed" },
{ workflowStepId: "code-review", workflowStepName: "Code Review", status: "advisory_failure" },
],
}),
);
expect(progress.total).toBe(8);
expect(progress.items).toHaveLength(8);
// 6 done impl steps + 1 passed (done) + 1 advisory_failure (non-blocking → completed) = 8.
expect(progress.completed).toBe(8);
expect(progress.items.filter((i) => i.source === "step")).toHaveLength(6);
expect(progress.items.filter((i) => i.source === "workflow")).toHaveLength(2);
});
it("orders Plan Review before implementation steps and Code Review after them", () => {
const progress = getUnifiedTaskProgress(
makeTask({
steps: [
{ name: "Preflight", status: "pending" },
{ name: "Implement", status: "pending" },
],
enabledWorkflowSteps: ["plan-review", "code-review"],
workflowStepResults: [
{
workflowStepId: "plan-review",
workflowStepName: "Plan Review",
phase: "pre-merge",
status: "failed",
startedAt: "2026-06-29T07:38:49.871Z",
completedAt: "2026-06-29T07:38:57.744Z",
},
],
}),
);
expect(progress.items.map((item) => item.name)).toEqual([
"Plan Review",
"Preflight",
"Implement",
"Code Review",
]);
expect(progress.items.map((item) => item.id)).toEqual([
"workflow-plan-review",
"step-0",
"step-1",
"workflow-code-review",
]);
});
it("maps impl step statuses straight through and skipped as completed", () => {
const progress = getUnifiedTaskProgress(
makeTask({
steps: [
{ name: "Done", status: "done" },
{ name: "Skipped", status: "skipped" },
{ name: "In progress", status: "in-progress" },
{ name: "Pending", status: "pending" },
],
}),
);
expect(progress.total).toBe(4);
// done + skipped count as completed; in-progress + pending do not.
expect(progress.completed).toBe(2);
});
});
/*
FNXC:WorkflowLifecycleColumns 2026-07-31-15:50:
THE INVARIANT: the running-gate badge appears in the board's OWN review lane.
CENSUS-INVISIBLE, in a HALF-CONVERTED file. `REVIEW_LANE_COLUMNS` is a `Set` literal — a definition,
not a comparison — sitting under a Plan Review badge whose own column restriction had already been
removed. One converted badge and one unconverted one, deciding sibling questions about the same card.
Keyed on the literal, the code-review / browser-verification / post-merge badges never appeared on a
renamed board: the gate WAS running and the card showed nothing, which reads as an idle card rather
than a missing badge. Cosmetic in consequence, but it is the surface an operator watches to know a
review is in flight.
The caller is wired in the same change — `ListView` already owns `columnFlagsById`, so the resolved
answer was one lookup away. An unwired parameter is the class this program's caller audit found five
times; adding a sixth would have been careless.
REVERT PROOF, measured: restore the literal gate and the renamed-lane case returns undefined.
*/
describe("the running-gate badge resolves the review lane", () => {
const REVIEW_FLAGS = { mergeBlocker: true } as never;
/* `makeTask` is file-scoped but `runningCodeReview` is not, so the shape is restated here. My
first draft hand-rolled `status: "in-progress"` and produced no RUNNING item at all — a gate
counts as running only when it is `pending` WITH a `startedAt` — so the renamed case failed for
the wrong reason. The fixture has to match the branch under test. */
const codeReviewRunning = (column: string) => ({
...makeTask({
enabledWorkflowSteps: ["code-review"],
workflowStepResults: [{
workflowStepId: "code-review",
workflowStepName: "Code Review",
status: "pending" as const,
startedAt: "2026-07-11T12:00:00.000Z",
}],
}),
column,
}) as never;
it("badges a card in a RENAMED review lane", () => {
/* testId is `code-review`, not the plan-review badge's `reviewing` — I asserted the latter first
and the failure was mine, not the product's. */
expect(getRunningOptionalGateBadge(codeReviewRunning("signoff"), REVIEW_FLAGS)?.testId).toBe("code-review");
});
it("still does not badge a card outside the review lane", () => {
// The gate must stay a gate — badging everywhere is its own bug.
expect(getRunningOptionalGateBadge(codeReviewRunning("building"), { countsTowardWip: true } as never)).toBeUndefined();
});
it("keeps the legacy id when no flags are supplied", () => {
expect(getRunningOptionalGateBadge(codeReviewRunning("in-review"))?.testId).toBe("code-review");
expect(getRunningOptionalGateBadge(codeReviewRunning("signoff"))).toBeUndefined();
});
});