Files
fusion/scripts
gsxdsm 10f9df1600 fix(overseer): the whole oversight loop was inert on a renamed board (#2898)
`resolveWatchedStage` keyed on the literals `in-progress`/`in-review`,
so on a board that renames either it returned `null` for **every** card.

That is three literals with an outsized blast radius. `observeTask`
returns early on a null stage, so:

- no `OverseerStageObservation` is recorded,
- no `overseer:intervention` entry is emitted,
- and `PlannerRecoveryController`, which consumes those observations,
has nothing to steer, retry or targeted-fix.

**The entire oversight loop was inert and silent about it** — the same
shape as the self-healing sweeps whose queries returned empty arrays.

## I deferred this myself, on a cost argument that was wrong

The audit note I wrote for this site said resolving inside `observeTask`
"buys a workflow read per card per poll". Then I read the caller: the
poll **already awaits `resolveEffectiveSettings` per task**. It is a
per-task async loop regardless, so with an IR cache keyed by workflow
the addition is *(distinct workflows)* resolutions, not *(cards)*.

Pricing the fix before checking the caller cost a deferral. Worth
recording, because "this needs a cost judgement" is the most comfortable
place in this program to leave something.

## The review test is the three-trait union, deliberately

`isReviewColumnRole` checks only `mergeBlocker || humanReview`. A board
whose review lane carries `merge` (**mergeOrchestration**) — the
built-in default's own shape — would classify as *not in review* and be
skipped.

Reaching for the obvious helper would have reintroduced the bug this
change removes, through the helper meant to fix it. There is a case
asserting exactly that.

## Wiring

Both call sites, because either alone leaves a hole:

| site | why it matters |
|---|---|
| the poll (`project-engine.ts`) | per-poll IR cache — a workflow edit
is picked up next tick rather than served stale |
| the manual nudge | otherwise a renamed board answers `no-active-stage`
to an operator pressing the button |

`columnFlags` is in the `unwired-lane-parameter` vocabulary, so the
wiring cannot silently rot — the guard reports it if a future change
drops the argument.

Fail-soft throughout: an unresolvable workflow yields `undefined` and
the callee falls back to the legacy ids, which is exactly today's
behaviour. A v1 IR declares no columns, so it takes the same path.

## Revert proof (measured)

Drop the `columnFlags` branch and **exactly the three renamed-lane cases
fail**:

```
expected null to be "executor"
expected null to be "merger"   (mergeOrchestration lane)
expected null to be "merger"   (humanReview lane)
```

The legacy-id and neither-role cases stay green — the gate must still
gate, and watching every column would be its own defect.

## Verification

- `pnpm test:gate` — 161 / 487 / 13 / 71 passed
- `pnpm lint` — clean
- `tsc --noEmit` (`@fusion/engine`) — clean
- `planner-overseer.test.ts` +
`planner-recovery-controller-human-control.test.ts` — 64 passed
- unwired-lane guard — 9/9, no new entries

Carries the one-line SQL-baseline re-record (`team-analytics.ts: 6 → 3`)
that #2864 left behind, same as my other open branches — main is red on
it, and identical changes to that line merge without conflict.

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

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