The batch phase is about to hand per-file guard lists to cheap workers,
and the census currently cannot distinguish **"not yet converted"** from
**"converted, with a documented degradation."**
## The measurement that makes this a class, not a preference
A proximity scan for *"legacy literal near a role-resolved call"* — the
heuristic that produced #2670 and #2672 from the engine — returned **19
hits across the dashboard and zero defects.** Every one was:
```ts
if (flags) return flags.hold === true || flags.countsTowardWip === true;
return column === "todo" || column === "in-progress"; // reachable only without traits
```
That literal is **correct**: it answers for callers with no resolved
column metadata, which is the case `resolveLifecycleColumns` returns
`undefined`-for-the-whole-struct to preserve. A worker told to "convert"
it would delete the only answer available when traits are absent.
## And the difference is structural, so the parser can see it
In **both** engine defects the literal sat in a **separate statement
beside resolved data**, not in a fallback branch. Proximity cannot tell
those apart; an AST can.
`traitFallback` flags the ternary form and the **early-return** form
(which is how most are actually written), and deliberately does **not**
flag a fallback whose test is itself a column-*name* check — otherwise
any `if/else` over column names would launder itself.
## Reported beside the backlog, not subtracted from it
```
COLUMN guards (the backlog): 746
of the column guards, 9 are trait-fallback branches (already converted)
```
A fallback literal is still a literal and should go when the trait path
becomes unconditional. This only says which **kind** of work it is.
**Advisory, and structurally so:** `traitFallback` never changes `kind`,
and the count lives *outside* `totals`. My first attempt put it in
`totals` and broke two existing suites that correctly deep-equal that
shape — an advisory number does not belong in the structure that defines
the bar.
## Revert proof
Forcing `traitFallback: false` fails **3 of 33** (both fallback forms,
plus the kind-unchanged case). The two *negative* cases pass under the
revert — which is the point: they assert what must **not** be flagged,
and a classifier that flags nothing satisfies them trivially. Worth
stating, because a revert proof that only counts failures would look
stronger than it is.
## Baseline
Re-recorded: `executor.ts` 87 → 85 was **main's own drift** from #2568
landing, so `--strict` was red on main again. #2668 made the re-record
possible; the auto-tighten (coordinator item 2) is still open, and this
is the third time in this program that a legitimate merge has left the
gate red for everyone else.
## Verification
62/62 across both census suites, `pnpm test:gate` **71/71**, `--strict`
exits 0, `pnpm lint` clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added reporting for legacy column comparisons found in trait-fallback
branches.
* Census results now include a separate count for these fallback-related
column guards.
* Human-readable reports display the new metric alongside the existing
backlog totals.
* **Tests**
* Added coverage for fallback detection across ternary, early-return,
and conditional patterns.
* Added safeguards to prevent false positives in resolved-data and
column-name checks.
* **Maintenance**
* Updated baseline census metrics to reflect revised classifications.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>