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:
gsxdsm
2026-07-30 03:25:06 -07:00
committed by GitHub
parent c6c947c1c2
commit 3d28e264c1
3 changed files with 32 additions and 3 deletions

View File

@@ -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}`,

View File

@@ -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;

View File

@@ -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" }),