Fourth and last convertible site in `triage.ts`. This one needed a
different fix from the other three, and the codebase already said so.
## The defect
```ts
const sweepLanes = resolvePlannerLanes(this.store, "");
const sweepColumns = [...new Set(["triage", "todo", sweepLanes.intake, sweepLanes.hold])];
```
There is no task `""`. No selection can be read for it, no board
resolved — the lanes come back as the **default** board's and the union
collapses to the legacy pair `{triage, todo}`. On a renamed board the
sweep queries columns the card is not in, so its stale `planning` status
survives and it **holds a planning admission slot indefinitely**.
## A characterization test already pinned this, and called the fix
correctly
`workflow-sweep-sentinel-task-id-live-e2e.pg.test.ts` documents it as a
third inert-conversion mechanism — *"inert by construction rather than
by environment"* — and its header says:
> making `resolvePlannerLanes` async would **NOT** repair this site,
because the defect is the argument, not the resolver
That is right, and it is why this fix differs from #3191 / #3193 /
#3195, which all used the async twin. Here the sweep has **no task to
resolve against** and wants every column playing these roles **anywhere
in the project** — so the correct resolver is
`resolveProjectColumnsForRoles(store, ["intake", "hold"])`, the same
helper `self-healing.ts` already uses for the same purpose.
The legacy pair stays in the union deliberately: the note at the site
explains that `triage` and `todo` must both be swept for pre-U11 and
Coding (Ideas) rows, and extra columns are free because the sweep only
**reads** and filters on `status === "planning"` first.
## The test is inverted, not deleted
It asserted `"planning"` survives — the bug. It now asserts the status
is cleared. Keeping the case with its original reasoning intact
preserves the file's record of what the defect *was*.
## Measured
| | result |
|---|---|
| broad suite (triage / planning / self-healing) | **76 files, 1302
tests passed** |
| differential | restoring the sentinel call → **1 failed \| 2 passed**
|
| `census --strict`, `check-fnxc-future-dates` | exit 0 |
**Inert count unchanged at 4 for `triage.ts`** — these lanes fed an
*array literal*, not a comparison, so the ratchet never counted them.
Third fix this session in that blind-spot class, stated so the number is
not read as the whole picture.
## What remains in this file
Two sites: the `task:moved` wake handler and the evacuation handler —
both **synchronous arrow callbacks** whose answers are consumed in-tick.
Genuinely blocked on the emitter-side work in #3082, with corroborating
evidence attached there.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>