Skip to content

fix(gate): match DDL by statement shape, not keyword adjacency#178

Merged
veksen merged 2 commits into
mainfrom
fix-ddl-prose-false-positive
Jul 14, 2026
Merged

fix(gate): match DDL by statement shape, not keyword adjacency#178
veksen merged 2 commits into
mainfrom
fix-ddl-prose-false-positive

Conversation

@veksen

@veksen veksen commented Jul 14, 2026

Copy link
Copy Markdown
Member

Goal

The test-presence gate should only flag PRs that actually change query code. Site#3539 — a pure MCP tool rename with zero SQL changes — got the "changes queries with no related data-layer test" warning. This PR removes that class of false positive. It is the whole fix; no follow-up step.

What

Before: any added diff line containing CREATE INDEX / ALTER TABLE / etc. as adjacent words classified the file as changed data-access code — including prose inside a string literal (Site#3539's tool description says "its suggested CREATE INDEX fix"). After: prose mentioning DDL keywords no longer trips the gate; real DDL statements still do.

How

src/gate/test-presence.ts: the single keyword-adjacency DDL pattern becomes five statement-shaped ones — CREATE [UNIQUE] INDEX … ON, CREATE TABLE … (, CREATE [OR REPLACE] [MATERIALIZED] VIEW … AS, ALTER TABLE <ident>, DROP TABLE/INDEX/VIEW <ident>. The discriminator is the statement's own grammar (ON clause, column list, target identifier), because the alternative — stripping string literals before matching — would blind the raw-SQL patterns whose whole job is catching SQL in strings.

Verified end-to-end against Site#3539's actual GitHub patch data (pulls/3539/files): the old gate classifies packages/mcp-server/src/tools/index.ts as data-access and emits the untested-data-access verdict; the patched gate returns null. Also confirmed against prod that the capture override could never have suppressed this flag (the run captured 0 new hashes vs its staging baseline — a rename adds no query surface), so the content heuristic is the only place this false positive can die.

Known limit, unchanged by this PR: a string literal containing a full SELECT … FROM sentence can still trip the select pattern. That shape is indistinguishable from a real raw-SQL query at the diff level; the capture-based rungs (#3502/#3503) are the durable answer there.

Tests

src/gate/test-presence.test.ts: prose-with-DDL-keywords string literal → not query code (the Site#3539 line, verbatim); statement-shaped CREATE INDEX/CREATE TABLE/ALTER TABLE/DROP TABLE → still detected; a replay of Site#3539's file set (description-string edit + two spec files + 0 new hashes) → verdict null. Existing migration/DDL-in-source tests unchanged and passing. Full suite: 302 tests / 29 files green; tsc --noEmit clean.

🤖 Generated with Claude Code

The test-presence gate flagged Site#3539 — a pure MCP tool rename — because
its DDL pattern matched the words "CREATE INDEX" inside a tool-description
string literal. Stripping string literals is not an option (raw SQL lives in
strings), so the DDL patterns now require statement grammar instead: an ON
clause for CREATE INDEX, a column list for CREATE TABLE, AS for views, and a
target identifier for ALTER/DROP. Prose mentioning DDL keywords no longer
reads as a query; real DDL statements still do.

Verified against Site#3539's actual GitHub patch data: the gate previously
classified packages/mcp-server/src/tools/index.ts as changed data-access and
emitted the untested-data-access verdict; with this change it returns null.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query Doctor Analysis

3 queries analyzed

0 regressed · 0 improved · 0 new · 0 removed

2 pre-existing issues

Using assumed statistics (10000000 rows/table). For better results, sync production stats.

More detail → get_ci_run({ runId: "019f6205-7d9d-7583-9da1-cf0c2ae91ee8" }) · view run · docs

@veksen

veksen commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

Regression sweep: replayed the gate (old pattern vs this PR) over the last 40 Site PRs (#3512–#3570, 261 changed files, real pulls/N/files patch data). 39/40 classify identically; the only difference is Site#3539 — the false positive — which goes from dataAccessChanged: [packages/mcp-server/src/tools/index.ts] to clean. No PR gains a flag, none loses a legitimate one.

Shape-by-shape recall table for the DDL rewrite: every variant the old
adjacency pattern caught still detects, plus two it missed (CREATE UNIQUE
INDEX CONCURRENTLY, CREATE OR REPLACE VIEW — a word between the keywords
broke adjacency). Multi-line DDL registers via the query-call shapes;
CREATE TABLE AS SELECT via the select shape. The one accepted miss — a bare
multi-line DDL string with no query call — is pinned as a deliberate
under-fire trade-off so changing it is a decision, not an accident.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@veksen veksen merged commit b1c46cd into main Jul 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant