## A red that no CI run can see
Found by pre-flighting #2796 against current `main`. Merged, the engine
suite fails:
```
scheduler-load-lane-union.test.ts > the scheduler builds this same union
expected 'import {…' to contain '...columnsWithFlag(loadLaneIr, "intake")'
```
**Neither side is red on its own.** `main` is green (10913 passed, 0
failed) and #2796's own CI is green — this test landed on `main` via
**#2787**, *after* #2796 was cut, and #2796 does not touch the file. It
fails only in the merged state, which is exactly the shape no branch's
CI checks.
## #2796 is not at fault
The union is still built (`scheduler.ts`, the `columnsWithFlag(ir, ...)`
spread). Resolving assignment load per task renamed the local from
`loadLaneIr` to `ir`, and the guard hardcoded that name:
```ts
expect(source).toContain(`...columnsWithFlag(loadLaneIr, "${flag}")`);
```
It would fail identically on a reformat, a line wrap, or any rename —
reporting drift that did not happen. And the reflex fix is to edit the
string to match, which protects nothing and teaches nobody anything.
## The fix
Parse `scheduler.ts` and collect the string literal passed as the
**second** argument to every `columnsWithFlag(...)` call, whatever the
first argument is called. Same invariant — every legacy role is unioned
somewhere in the scheduler — now actually checked.
It also asserts the parse found **something** before checking the six
flags. A visitor that matched nothing would make every assertion below
it vacuous, which is the specific failure mode this guard family keeps
producing.
Still structural rather than behavioural, for the reason the file header
already gives: the call site sits inside a dispatch path a unit test has
no business standing up. The three sibling cases cover the resolver's
behaviour; this one covers the wiring.
## Evidence — the discrimination is the right way round
| mutation | expected | result |
|---|---|---|
| rename `ir` → `loadLaneIr` (behaviour identical) | pass | **4/4
passed** |
| drop `...columnsWithFlag(ir, "hold")` from the union | fail |
**fails**: `scheduler.ts no longer passes "hold" to columnsWithFlag` |
Engine **10936 passed / 0 failed** · gate **732 green** · lint clean ·
engine `tsc --noEmit` **0 errors**. Test-only; `scheduler.ts` restored
clean after the mutations.
**Unblocks #2796 with no change needed on its side** — commented there.
## Pre-flight results for the rest of the queue
Same method (merge with current `main`, run the suites), since
batch-engine's earlier landing put 32 failures on main that were only
caught post-merge:
| PR | result |
|---|---|
| #2785 batch-engine-tail | clean — 10903 passed |
| #2783 batch-core-2 | clean — core 4751 passed; its one api failure is
pre-existing on main |
| #2797 engine tail | clean — 10941 passed |
| #2772 batch-dashboard-app | clean — backfill total 112 → **111**, no
lane regresses |
| #2796 assignment load | **this failure only** |
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>