test(self-healing): three suites seeded a column id the product stopped declaring (7 red → 0) (#2695)
## What was red
`self-healing-advanced-triage`, `self-healing-agent-link-drift`, and
`self-healing-starved-refinement` — **7 failed / 19 passed** on clean
`origin/main`. All three fail the same way: the sweep returns `0`
recoveries where the test expects `1`.
## Cause
Those three sweeps were converted from `listTasks({ column: "triage" })`
to **role** filters (`filterByPreWipRole(..., ["intake"])`). Each store
fake has no workflow-selection readers, so the sweep resolves the
**default IR** — in which `triage` is not a declared column at all. The
seeded cards carried no intake role, the filters returned no candidates,
and the sweeps did nothing.
The product change was correct; the fixtures were asserting against a
column id the product had stopped declaring. `self-healing.ts` even
warns about this exact shape in its own comment: *"Converting a
predicate while leaving its source query on a literal produces a sweep
that LOOKS converted and does nothing."* The fixtures are the mirror
image — a seed on the old literal makes a correctly-converted sweep look
broken.
**Verified, not assumed.** I resolved the default IR and printed the
flags rather than reasoning from column names:
```
todo {"intake":true,"hold":true,"resetOnEntry":true}
in-progress {"countsTowardWip":true,"abortOnExit":true,"timing":true}
in-review {"mergeBlocker":true,"humanReview":true,...}
done {"complete":true}
archived {"archived":true,"hiddenFromBoard":true}
```
`todo` is the intake column post-U11 (the merged Planning column).
`triage` does not appear.
## Measured
| Check | Before | After |
|---|---|---|
| the three files | 7 failed / 19 passed | **26 passed** |
| all 40 `self-healing-*` files | 3 failed files / 7 failed tests | **40
passed / 695 tests** |
| `pnpm test:gate` | — | **726 passed** |
| `pnpm lint` | — | clean |
Restoring the deleted column id reintroduces the failures, so the seeds
are load-bearing rather than cosmetic.
**Pre-existing, untouched:** the same 9 vitest *"unhandled errors"* and
the identical non-zero exit appear on clean main. Verified by reverting
only these three test files and re-running — unrelated to the fixtures,
so flagged rather than folded in.
## One deliberately surgical spot
In `starved-refinement` only the starved refinement's own seed moved. My
first pass renamed the file's default seed and **broke a test that was
passing** — the auto-approve-all case calls `recoverApprovedTask`
directly (no role filter) and asserts a move **into** `todo`, so seeding
it in `todo` makes that move degenerate. I reverted and moved one seed
instead.
That leaves a real question I did **not** answer: post-U11 intake and
hold are one column, so a `triage → todo` move may no longer encode
anything. Deciding that means changing what the test is *about*, which
is a behaviour judgement on someone else's assertion. **Flagged in-file,
not guessed.**
Also worth noting for whoever converts the remaining backlog: line 43 of
that file pairs a `triage` seed against an explicit `todo` seed as a
contrast. U11 collapsed those two columns, so the contrast the fixture
was drawing no longer exists in the product — not a defect, but any
fixture built on intake-vs-hold being distinct is now suspect.
Census unchanged (722) — test files are not scanned by the census.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Updated self-healing workflow test scenarios to reflect the current
intake column.
* Improved test coverage for triage, agent-link drift, and starved
refinement handling.
* Added documentation clarifying workflow column and role-based
filtering assumptions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -4,12 +4,26 @@ import type { Settings, Task, TaskStore } from "@fusion/core";
|
||||
import { activeSessionRegistry } from "../active-session-registry.js";
|
||||
import { SelfHealingManager } from "../self-healing.js";
|
||||
|
||||
/*
|
||||
FNXC:WorkflowResolvedColumns 2026-07-30-17:40:
|
||||
The INTAKE column of the default lineage, which post-U11 is `todo` — the merged Planning column
|
||||
carrying intake+hold+resetOnEntry. `triage` is not a declared column on any current workflow.
|
||||
|
||||
Why this fixture went stale rather than merely renamed: `recoverAdvancedTriageTasks` was converted
|
||||
from `listTasks({ column: "triage" })` to a ROLE filter (`filterByPreWipRole(..., ["intake"])`).
|
||||
This store fake has no workflow-selection readers, so the sweep resolves the DEFAULT IR, in which
|
||||
`triage` does not appear — the seeded card therefore carried no intake role, the filter returned no
|
||||
candidates, and the sweep reported 0 recoveries. The test was asserting against a column id the
|
||||
product had stopped declaring.
|
||||
*/
|
||||
const INTAKE_COLUMN = "todo";
|
||||
|
||||
function task(id: string, overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
id,
|
||||
title: id,
|
||||
description: id,
|
||||
column: "triage",
|
||||
column: INTAKE_COLUMN,
|
||||
status: null,
|
||||
paused: false,
|
||||
worktree: `/tmp/${id}`,
|
||||
|
||||
@@ -83,7 +83,8 @@ describe("FN-4296: self-healing agent link drift", () => {
|
||||
const agents = [makeAgent("agent-backend", "FN-7001", "running")];
|
||||
const queuedTask = {
|
||||
id: "FN-7001",
|
||||
column: "triage",
|
||||
// FNXC:WorkflowResolvedColumns 2026-07-30-17:40: the intake column post-U11 is `todo`.
|
||||
column: "todo",
|
||||
status: "queued",
|
||||
overlapBlockedBy: "FN-6827",
|
||||
} as Task;
|
||||
|
||||
@@ -30,8 +30,22 @@ describe("SelfHealingManager.recoverStarvedRefinementTriageTasks", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-05-15T11:00:00.000Z"));
|
||||
|
||||
/*
|
||||
FNXC:WorkflowResolvedColumns 2026-07-30-17:55:
|
||||
The starved refinement rests in the INTAKE column, which post-U11 is `todo` — the merged
|
||||
Planning column carrying intake+hold. `recoverStarvedRefinementTriageTasks` filters by ROLE, and
|
||||
this store fake has no workflow-selection readers, so it resolves the DEFAULT IR in which
|
||||
`triage` is not a declared column: the old fixture therefore carried no intake role, the filter
|
||||
returned no candidates, and the sweep reported 0 escalations.
|
||||
|
||||
Only THIS test's seed moved. The file's default stays `triage` on purpose: the case at the bottom
|
||||
("auto-approve-all overrides stored workflow approval") calls `recoverApprovedTask` directly, with
|
||||
no role filter, and asserts a move INTO `todo` — seeding it in `todo` makes that move degenerate.
|
||||
Whether asserting a triage -> todo move still encodes anything post-U11 is a question about that
|
||||
test's subject, not this fix: FLAGGED, not guessed.
|
||||
*/
|
||||
const tasks: Task[] = [
|
||||
task({ id: "FN-R1", sourceType: "task_refine", createdAt: "2026-05-15T10:00:00.000Z", updatedAt: "2026-05-15T10:00:00.000Z", priority: "low" }),
|
||||
task({ id: "FN-R1", column: "todo", sourceType: "task_refine", createdAt: "2026-05-15T10:00:00.000Z", updatedAt: "2026-05-15T10:00:00.000Z", priority: "low" }),
|
||||
task({ id: "FN-P1", column: "todo", sourceType: "dashboard_ui", updatedAt: "2026-05-15T10:15:00.000Z" }),
|
||||
task({ id: "FN-P2", column: "todo", sourceType: "dashboard_ui", updatedAt: "2026-05-15T10:16:00.000Z" }),
|
||||
task({ id: "FN-P3", column: "todo", sourceType: "dashboard_ui", updatedAt: "2026-05-15T10:17:00.000Z" }),
|
||||
|
||||
Reference in New Issue
Block a user