Files
fusion/scripts
gsxdsm b7c7977c09 fix(gate): catch cast move-targets, keep ?? fallbacks unflagged, and pin all of it (#3253)
Follow-up to #3250. Two changes, and **one of them is a decision not to
widen** — which is the part I would review first.

## Caught now: the cast form

```ts
moveTask(id, "done" as ColumnId)   // was invisible
```

Columns are typed `ColumnId`, so a cast is the **natural** spelling
wherever the parameter is nominally typed. The gate was weakest exactly
where this codebase is most likely to write a literal.
Cast-wrapping-a-ternary is caught too.

## Deliberately NOT caught: `??` / `||` / `&&`

I recommended these arms on #3250. **I was wrong, and the tree proved
it.** Adding them flagged:

```ts
moveTask(id, (await resolveTaskLifecycleColumns(store, id))?.complete ?? "done", ...)
```

That is the fail-soft idiom this entire programme rests on — resolve,
fall back to the legacy id when the workflow is unreadable, exactly as
the role helpers degrade. It is the **correct** pattern.

A gate that demands a `DELIBERATE-LITERAL` marker on every safe fallback
teaches people to add markers by habit, and a habitual marker is how the
next real literal walks straight through. So: a legacy id **after** `??`
is the safe shape and stays unflagged; a legacy id as the **whole**
destination is caught. Backed out, with the reasoning at the site so
nobody re-adds it.

If you disagree, the counter-argument is that a fallback could mask a
lane that should have resolved — but that wants its own report, not this
ratchet's exit code.

## Pinned — the gate had no tests at all

Fifth spelling missed across three rounds, and every earlier probe was
run by hand and thrown away, because the scanner executed on import.
Commit 1 makes it importable (behaviour-preserving, `--strict`
identical); commit 2 adds **12 tests in both directions**:

| catches | does not catch |
|---|---|
| direct, backtick, ternary, nested ternary, parenthesised, cast,
cast-over-ternary | `??` fallback, `\|\|` fallback, resolved
destination, substituted template |

The negatives are load-bearing, not padding — four of them encode false
positives that either shipped or arrived while widening.

## The root cause, recorded in the test header

**The destination is a POSITION; every fix so far has enumerated NODE
KINDS.** A kind list is something the language extends faster than we
guess — I started that pattern myself in #3246 by requiring
`arguments[1]` to *be* a literal. The durable defence is that each shape
someone finds stays found.

## Measured

| check | result |
|---|---|
| real tree | **0** targets, `--strict` exit 0 (the `??` false positive
is gone) |
| gate tests | **12 pass / 0 fail** |
| anti-vacuity | removing the cast arm **fails** the suite; restoring
passes |
| eslint / `check-fnxc-future-dates` | clean / 0 |

One test corrected itself during writing: I asserted `"drafting"`
extracts to `[]`, and it returns `["drafting"]` — legacy filtering is
the caller's job. Kept as a test of that split, since folding the
vocabulary into the extractor would force every future shape to thread
the legacy list.
2026-07-31 15:35:08 -07:00
..