## 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>
13 KiB
13 KiB