## The check I shipped in #3062 has a hole, and there is an open PR
standing in it
#3062 counts `to === parked.complete`. **#3065 rewrites exactly that
into `parked.terminal.has(to)`.**
#3065's change is **correct and should merge** — a board may declare
more than one complete-trait column, so a single `parked.complete` was
genuinely wrong, and the PR fixes a red main. But the answer still comes
from `resolveTaskParkedColumnsSync`, which resolves through
`store.resolveTaskWorkflowIrSync` and therefore always describes the
default board. The guard is exactly as inert; it simply stopped being a
comparison node, and my check only counted comparisons.
## Measured, both ways
Applying #3065's rewrite shape to `scheduler.ts`:
| | result |
|---|---|
| check as shipped in #3062 | **20 → 15**, exit 0, prints *"total fell —
re-record with `--update-baseline`"* |
| this PR | **20 → 18**, the three `.has()` guards stay counted |
The shipped behaviour is the bad half: it does not merely miss them, it
invites re-recording a smaller baseline, permanently retiring sites that
are still inert.
And on the case the ratchet actually exists for — a **fresh literal**
converted via the Set spelling:
| | result |
|---|---|
| check as shipped | **exit 0** — missed |
| this PR | **exit 1** — caught, `20 → 21` |
## What changed
- `terminal` (plus `lanes`, `columns`) added to the role-field
vocabulary — Set-valued roles, not single ids.
- Membership calls counted alongside comparisons: `X.<role>.has(...)` /
`.includes(...)` where `X` is sync-resolved, inline or via a local.
Neither exit code changes on current main: still 20, still exit 0.
## The pattern, stated plainly
This is the **second** evasion of this check, found the same way as the
first. #3062's own body records the first: the initial draft matched
only the local-variable spelling and a mutation run proved the inline
`resolveX(...).review` walked straight past it.
Both times the guard was correct about the shape in front of it and
blind to a trivially different spelling of the same defect. Worth
generalising rather than patching a third time — the durable fix is to
key on **the source** (any value derived from
`resolveTaskWorkflowIrSync`) rather than enumerate the syntax that
consumes it. That is a dataflow question and a larger change than this;
recording it here as the known limit rather than claiming this version
is complete.
Related and unresolved: the same "a rewrite makes the counter drop
without the guard changing" shape is live in the census itself — see my
comment on #3057 about `LEGACY_*` named sets, and #3058, where ten
guards left the census for zero behaviour change.
## Census before / after
```
before: COLUMN guards (the backlog): 88
after: COLUMN guards (the backlog): 88
```
Unchanged — this converts nothing. It stops a counter from falling for
the wrong reason.
## Verification
`pnpm check:inert-sync-lanes` exit 0 · `test:gate` exit 0 ·
lifecycle-column census exit 0 · `pnpm lint` clean. Gate-script only; no
production file touched (`scheduler.ts` restored clean after every
mutation run).