aedee4b8231bf050c3240a00ab6645ede5d87ee9
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
634d487c3e |
fix(gate): a lane id hoisted into a const evaded the SQL column-literal gate (#2980)
## 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> |
||
|
|
ef50244234 |
feat(gate): freeze the SQL column-literal surface — 30 sites, none may be added (#2841)
Instruments a surface no existing check can see. Follows #2839, and **corrects the count I reported there** (12 → 14). ## Why it was invisible The lifecycle census parses TypeScript **comparisons**; a legacy id inside a SQL string is string data. The inert-seam gate reasons about parameters and call sites. Neither has ever looked here. **What it cost:** `cleanupStaleMergeQueueRowsImpl` filtered on `t.column != 'in-review'`, so on a renamed board every queued card looked stale, its `merge_queue` row was deleted, and the card became **unleaseable**. The operator found it reviewing #2819 — in SQL I had already read past during that same work. The quieter half is analytics: five sites count `"column" = 'done'`, so throughput, cycle time, and team dashboards report **zero completed work** on a renamed board. Nothing errors, which is why nobody files it. ## What this does, and does not do It does **not** fix the sites. `resolveProjectColumnsForRoles` is the mechanism and its migration has an owner (#2839). This freezes the population so the surface cannot grow underneath that migration: a new file or a higher count fails, **and a lower count fails too** — so the baseline ratchets down as sites migrate rather than leaving slots to silently regrow into. That is the same rot as an allow-list entry for a deleted function, which this repo already hit once. AST-based, deliberately: a line grep for the same pattern reports **37** hits, **25 of them prose** quoting `column === "done"` in explanatory notes. A guard that is 68% false positives trains its readers to skip it — a lesson this program has already paid for. ## Two corrections found by mutation-testing my own gate **1. Clause fragments were missed.** Requiring a SQL keyword *in the same literal* skipped `team-analytics.ts`, which builds `["assignedAgentId IS NOT NULL", `"column" = 'done'`, ...]` and joins them into a `WHERE` later. That fragment is as vocabulary-bound as any full query but contains no keyword. Fixing it took the population **12 → 14**, so the number I put on #2839 was low. **2. My first mutation test proved a direction it had not.** I replaced the first textual occurrence in a file — which was inside a **comment** — and read the unchanged count as the scanner being broken. The scanner was right; my test was wrong. All three directions are now driven against real SQL: | mutation | result | |---|---| | add a full query with a legacy comparison | `3 SQL column literal(s), baseline allows 2` | | add a bare clause **fragment** (no keyword) | caught — same failure | | migrate one away (count drops) | `1 site(s) now, baseline still allows 2 — re-record it` | | restore | exit 0 | I am flagging that second one because it is the exact failure mode this program keeps finding: a green result read as evidence when the experiment was invalid. ## Verification `pnpm test:gate` green with the new check in it · lint 0 · single AST pass. Wired into `test:gate` and both `pretest` hooks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Added automated checks to detect increases in legacy SQL column literals. * Added baseline tracking to ensure known SQL literal counts do not regress. * **Tests** * Expanded pre-test and gated verification steps with SQL literal and mock completeness checks. * Updated test validation workflows to enforce the new safeguards. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |