From 895faad68e64c5373d3f1566ae7018cd86fd830d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Tue, 21 Jul 2026 19:59:46 -0300 Subject: [PATCH] fix(gate): require whitespace after `select` in the raw-query shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The untested-data-access gate flagged apps/app/src/routeTree.gen.ts on a route-tree regeneration PR (Site#3614). Cause: the raw `select … from` shape matched the `/membership/select-plan` route path — `\bselect\b`'s right boundary is the hyphen — with an import `from` two lines down completing the shape. A route path read as a SQL query. Require whitespace after `select`, as real `SELECT … FROM` has, so a hyphenated (or dotted, slashed) identifier no longer reads as a query. Camel/underscore identifiers (`selectPlan`, `select_plan`) never matched `\bselect\b` and are unaffected. Refs Query-Doctor/Site#3615 Co-Authored-By: Claude Opus 4.8 (1M context) --- src/gate/test-presence.test.ts | 44 ++++++++++++++++++++++++++++++++++ src/gate/test-presence.ts | 6 ++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/gate/test-presence.test.ts b/src/gate/test-presence.test.ts index 2dfefd6..d917427 100644 --- a/src/gate/test-presence.test.ts +++ b/src/gate/test-presence.test.ts @@ -201,6 +201,33 @@ describe("classifyChangedFiles", () => { ]); expect(surface.dataAccessChanged).toEqual(["apps/api/src/schema.ts"]); }); + + test("does not read a `select-plan` route path as a SELECT ... FROM query", () => { + // Site#3615 root cause: `\bselect\b` matched the hyphenated route segment, + // and an import `from` on the next line completed the `SELECT ... FROM` + // shape. Requiring whitespace after `select`, as real SQL has, fixes it. + const surface = classifyChangedFiles([ + { + path: "apps/app/src/routeTree.gen.ts", + status: "modified", + patch: + "@@ -18,2 +18,2 @@\n" + + "+} from './routes/membership/select-plan'\n" + + "+import { Route as MembershipRegisterRouteImport } from './routes/membership/register'", + }, + ]); + expect(surface.dataAccessChanged).toEqual([]); + }); + + test("still flags a raw SELECT ... FROM query in application code", () => { + // The tightened `select`-shape must keep its recall: real SQL has whitespace + // after the keyword. This trips only the raw select shape (no `db.`, no + // `.query(`), so it locks that pattern in. + const surface = classifyChangedFiles([ + changed("apps/api/src/reports.ts", "const sql = `SELECT id, name FROM users`;"), + ]); + expect(surface.dataAccessChanged).toEqual(["apps/api/src/reports.ts"]); + }); }); describe("evaluateTestPresence", () => { @@ -256,6 +283,23 @@ describe("evaluateTestPresence", () => { expect(verdict).toBeNull(); }); + test("does not fire on the route-tree regeneration from Site#3614", () => { + // The reported false positive: a routeTree.gen.ts regeneration (import + // re-ordering) whose `select-plan` route path read as `SELECT ... FROM`. + // No data access, nothing to flag. + const verdict = evaluateTestPresence([ + { + path: "apps/app/src/routeTree.gen.ts", + status: "modified", + patch: + "@@ -18,2 +18,2 @@\n" + + "+} from './routes/membership/select-plan'\n" + + "+import { Route as MembershipRegisterRouteImport } from './routes/membership/register'", + }, + ]); + expect(verdict).toBeNull(); + }); + test("does not fire on a migration alone, even with no test", () => { // A migration's coverage can't be linked by the diff heuristic, so it is // never flagged — under-firing is the safe side for a warn-only gate. diff --git a/src/gate/test-presence.ts b/src/gate/test-presence.ts index fdfd237..8544c2c 100644 --- a/src/gate/test-presence.ts +++ b/src/gate/test-presence.ts @@ -76,7 +76,11 @@ export const DEFAULT_TEST_PRESENCE_CONFIG: TestPresenceConfig = { /\binsert\s+into\b/i, /\bdelete\s+from\b/i, /\bupdate\b[^\n]{0,80}\bset\b/i, - /\bselect\b[\s\S]{0,300}?\bfrom\b/i, + // `select` must be followed by whitespace, as real `SELECT … FROM` is — so a + // hyphenated route segment like `select-plan` (whose `\bselect\b` boundary is + // the hyphen) doesn't read as a query when an import `from` follows it + // (Site#3615). + /\bselect\s[\s\S]{0,300}?\bfrom\b/i, // DDL is matched by statement shape (ON clause, column list, target // identifier), not bare keyword adjacency: prose in a string literal — // "its suggested CREATE INDEX fix" in an MCP tool description (Site#3539)