Files
fusion/scripts/lib
gsxdsm 301bd8ed1e fix(census): detect membership and switch column guards, which could land silently (#3247)
## What

The census prints **"a new guard cannot land silently"** next to a zero.
That claim was true only for the guard form it happened to parse. This
closes the two it could not see. No product change.

The comparison walk visits `BinaryExpression` only, so neither of these
was visible:

```ts
["done", "archived"].includes(task.column)
switch (task.column) { case "todo": ... }
```

Both are lifecycle-column guards by any reading.

## How I found it

By applying this program's own rule — **break the guard on purpose** —
to the guard itself. I staged a probe file with five guard forms and
measured which moved the count:

| form | counted before |
|---|---|
| `t.column === "todo"` | ✅ |
| `t.column !== "in-review"` | ✅ |
| `["done","archived"].includes(t.column)` | ❌ |
| `switch (t.column) { case "triage": }` | ❌ |
| SQL string `"column" = 'done'` | ❌ (separate gate owns this) |

A worker converting a `===` chain into an array membership would have
scored the conversion **and kept the guard**.

*(The first probe run was itself invalid — the file was untracked and
the census enumerates git-tracked files, so the scanned count stayed at
1961 and nothing was measured. Staging it moved the scan to 1962.
Checking the scanned count is what caught that.)*

## The near-miss worth reading

My first implementation counted **unless** the receiver looked like a
role or status — mirroring the `===` walk. On the real tree it reported
**7 column guards**, and I nearly published that as a hidden backlog.

Six were false: `switch (eventName)`, `switch (state)`, `switch (event)`
— event and state enums routinely carry `case "done"` / `case
"archived"`. Landing it would have injected six phantom guards into a
backlog the ratchet treats as zero, and `--strict` would then have
**failed every other worker's PR**.

So the new walks require a **positive** column signal instead. That
regression is pinned by a test asserting all three receivers stay
uncounted.

## Measured

```
real repo, before and after:  COLUMN guards 0, STATUS 185   (no false positives)
staged probe:                 2 detected before -> 4 after
new tests:                    6/6 pass; 3 FAIL with the extension reverted
existing lifecycle-census test: 9/9 still green
lint clean; census --strict passes; fnxc-future-dates: none added
```

## Known limit, stated rather than left to be discovered

The positive signal is the receiver **name**, so `switch (column.id)` —
a `Column` object rather than a task's column — is **not** counted. That
is a real guard shape and it is deliberately out of scope: widening to
reach it is exactly what produced the six false positives, so it needs
its own discrimination rather than a looser regex. Flagged here so the
next person extends it deliberately instead of assuming coverage.

## Why this and not another conversion PR

The conversion queue has been genuinely empty for several cycles —
census 0, 116 resolver sites unchanged across four commits, every site
blinded and pinned. The remaining risk in this program was never another
literal; it was that **the instrument defining "done" could not see two
of the shapes it claims to protect against**. A zero from a detector
with blind spots is the exact failure this phase has spent its time
documenting.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added lifecycle-column guard detection for array membership checks and
`switch` cases.
* Recognizes supported column receiver names and classifies findings
consistently with existing guards.
* Ignores status, event, and state receivers, and avoids duplicate trait
fallback findings.

* **Tests**
* Added coverage for membership checks, `indexOf`, `switch` guards, and
deliberate-literal suppression.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 14:48:20 -07:00
..