## What
In #2979 I argued a ratchet should be mutation-probed with shapes its
author did **not** have in mind, on the day it ships. Applying that to
my own gate: two of three probes walked straight through.
```ts
const LANE = "done";
sql`... WHERE "column" = ${LANE}` // MISSED
const LANES = ["in-progress", "in-review"];
sql`... WHERE "column" IN (${sql.join(LANES)})` // MISSED
```
Both bind the query to the legacy vocabulary exactly as an inline
`'done'` does. An interpolation that wasn't a column reference collapsed
to the NUL sentinel, so the predicate dissolved before the matcher ever
ran.
**This is the shape a cleanup produces.** Hoisting a repeated string to
a named const reads as tidying, which makes it the likeliest way one of
these gets rewritten — and the gate would have gone quiet on a file that
changed only in punctuation. Third time this scanner has had that
failure (static-span join, element-access column ref, now this). The
array form isn't hypothetical: `IN ('in-progress','in-review')` was the
live workflow-analytics defect.
## The first version of this fix was wrong, and that's the useful part
Resolving *any* string-valued const double-counted the analytics files,
which build queries as:
```ts
const completedClauses = [`t."column" = 'done'`, "t.columnMovedAt IS NOT NULL"];
```
Those elements are SQL fragments **already counted where they're
written**. Resolving the const re-injected each into the outer template.
The three analytics files went `3/3/1` → `6/5/2` — and it read exactly
like a genuine find. Only **bare lane ids** are resolved now; the
fragment-array case is pinned as a test.
I also nearly shipped that version on a bad probe: `node gate | tail`
then `echo $?` reads *tail's* exit status, not the gate's. Every probe
reported "caught" while the gate was actually failing on main for an
unrelated reason. Worth repeating because the harness looked fine and
agreed with what I expected.
## Measured
| check | result |
|---|---|
| clean `main` | **22 sites, exit 0, unchanged** — no false positives
introduced |
| now caught | const string · const array via `sql.join` · as-const via
`inArray` |
| correctly **not** flagged | resolver-produced lanes · non-legacy ids ·
SQL-fragment array |
| gate's own suite | **26 → 32 tests**, all green |
| blinding the resolution | fails **exactly** the 3 new positive tests;
the 3 negatives still pass |
The negatives outnumber what feels necessary on purpose: eager
resolution is how this went wrong the first time, and the fragment-array
test is the one that would have caught it.
## Scope
Same-file `const` declarations only. Cross-file imports need a type
checker and a program-wide pass — a constant imported from another
module is **still invisible**, and `--list` output is where that gets
audited. Stating the boundary rather than half-resolving it and calling
the gate complete.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>