Skip to content

Recover a CREATE VIEW/TABLE swallowed by a preceding broken routine - #2724

Open
ayushcodes10 wants to merge 15 commits into
Graphify-Labs:v8from
ayushcodes10:fix-2719-create-function-swallows-next
Open

Recover a CREATE VIEW/TABLE swallowed by a preceding broken routine#2724
ayushcodes10 wants to merge 15 commits into
Graphify-Labs:v8from
ayushcodes10:fix-2719-create-function-swallows-next

Conversation

@ayushcodes10

@ayushcodes10 ayushcodes10 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

This grammar has no rule for T-SQL's AS BEGIN...END routine body, so a
CREATE FUNCTION/PROCEDURE with such a body lands in an ERROR node —
and that ERROR span can absorb the NEXT statement too (e.g. a CREATE
VIEW right after it), with no recovery path for anything but a
routine. Extend the ERROR-node regex recovery to also catch a
swallowed CREATE VIEW/TABLE name, registering it in table_nids so a
later reference in the same file resolves onto it. Reproduces with
bare identifiers, independent of the T-SQL bracket-quoting defects.

Fixes #2719.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

This PR adds T-SQL bracket-quoted identifier support to the SQL extractor and broadens name-part handling across its regex fallback paths. Specifically, it introduces a _debracket_tsql pre-parse pass that rewrites [bracket]-quoted identifiers to backtick-quoted ones (with guards for strings/comments and array-type syntax), plus helpers (_strip_backtick_parts, _clean_name, _ident) to strip that synthetic quoting from display labels. It also factors identifier-part matching into a shared _SQL_NAME_PART regex used by the ERROR-node fallbacks for CREATE FUNCTION/PROCEDURE and swallowed CREATE VIEW/TABLE statements. The change touches the SQL extractor module and a large set of multilang extractor tests (Go, Rust, TypeScript, SQL) that appear to have been updated or added alongside it.

No blocking issues surfaced. 3 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 279 functions depend on the 125 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: extract_sql() — 17 callers, 10 callees
  • worse: walk() — 1 callers, 10 callees

Verification — 279 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 136 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extractors/sql.py
@ayushcodes10 ayushcodes10 changed the title Fix 2719 create function swallows next Recover a CREATE VIEW/TABLE swallowed by a preceding broken routine Aug 13, 2026
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

@safishamsi this PR's CI run is waiting on approval (first-time contributor gate) — could you approve the workflow run when you get a chance? Happy to address any review feedback in the meantime.

tree_sitter_sql has no grammar rule for a bracket delimited identifier,
so [dbo].[Orders] lands in ERROR nodes and comes out with a mangled
label like dbo].[Orders instead of dbo.Orders. Backtick quoting
already parses cleanly, so the source is rewritten before parsing and
the synthetic backticks are stripped again for display.

Upstream has since landed its own ERROR recovery for routine names
(CREATE FUNCTION/PROCEDURE), matched by a shared regex plus a masked
whole file scan. Since this rewrite runs before that scan, a
bracketed routine name never reaches it as a bracket anymore, only as
a backtick, so the shared regex and its masking scan are extended to
also recognize backtick delimited names. Without that a bracketed
routine would go from a recovered (if ugly) node to no node at all,
a regression this change must not introduce.

Guards against misfiring on array type and array literal syntax that
also uses square brackets (int[], numeric(10)[3], ARRAY[1,2,3]), and
against a bracket span that swallows a comment marker.

Fixes Graphify-Labs#2712.
@ayushcodes10
ayushcodes10 force-pushed the fix-2719-create-function-swallows-next branch from b369295 to f8b6def Compare September 5, 2026 23:33
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Rebased onto current v8 following the #2712 rework above (upstream's #3164 landed its own overlapping TSQL routine recovery in the meantime). Re verified against the original repro and the full test suite, all green.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Adds T-SQL bracket-quoted identifier support to the SQL extractor by rewriting [dbo].[Orders] spans to backtick-quoted form via _debracket_tsql before parsing, so statements the grammar would otherwise dump into ERROR nodes take the normal AST path; recovered names and display labels are un-rewritten by _strip_backtick_parts, applied only when the file was actually debracketed. Extends the error-recovery scan and _scan_sql masking to recognize backtick delimiters, and adds _VIEW_TABLE_RECOVERY_RX to recover CREATE TABLE/VIEW statements swallowed into an unrelated ERROR node's span, deduped against already-parsed tables. Array markers (int[], ARRAY[1,2,3]) and comment-bearing or numeric bracket content are left alone so they aren't misread as identifiers.

Worth a look

  • CREATE TABLE IF NOT EXISTS recovery captures IF as the table namegraphify/extractors/sql.py:52 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • _debracket_tsql treats closing ) as subscript-like, breaking bracketed identifier after a parenthesized expressiongraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • _strip_backtick_parts strips backticks whenever any bracket was debracketed, corrupting genuine MySQL backtick identifiers in same filegraphify/extractors/sql.py:410 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 349 functions depend on the 169 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 29 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 349 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 181 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

parts.append(p)
return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 29 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@ayushcodes10

Copy link
Copy Markdown
Contributor Author

@safishamsi rebased onto the latest v8 and all checks are green now — ready for review whenever you get a chance.

_clean_name gated on a single file wide debracketed flag, so once ANY
bracket span in a file got rewritten to backtick form, every
backtick-quoted name in that same file had its backticks stripped for
display, including a genuinely backtick-quoted MySQL name completely
unrelated to the T-SQL bracket span that triggered the rewrite.

_debracket_tsql now returns the byte range spans it actually rewrote,
in the rewritten source's own coordinates, and _clean_name only
un-rewrites a name whose own byte range overlaps one of those spans.
A name read off a tree-sitter node carries its own start/end byte
naturally; the whole file regex fallback has no node, so its
character offsets into the decoded source are converted to byte
offsets first, since the decoded string and the byte-level spans can
diverge once the file contains any multi byte character.

Fixes Graphify-Labs#2721.
@ayushcodes10
ayushcodes10 force-pushed the fix-2719-create-function-swallows-next branch from f8b6def to 56d4805 Compare September 7, 2026 12:07
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Same span-tracking fix as #2723 (see that PR for full details), plus a real bug the bot correctly flagged as high severity: _VIEW_TABLE_RECOVERY_RX was missing the IF NOT EXISTS gap _ROUTINE_RECOVERY_RX already had, so CREATE TABLE/VIEW IF NOT EXISTS in an error-bearing file fabricated a phantom node literally named "IF" (the capture group greedily grabbed the next bare word after CREATE TABLE/VIEW). Fixed and added a regression test.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Rewrites T-SQL bracket-quoted identifiers ([dbo].[Orders]) to backtick-quoted form before parsing via _debracket_tsql, so statements the grammar has no rule for take the normal AST path instead of falling into error recovery that mangled labels and dropped tables behind broken foreign keys; array-type markers like int[] and bracket spans containing comment openers are left alone. Un-rewrites names for display through _clean_name/_strip_backtick_parts, but only where a node's bytes overlap a span _debracket_tsql actually inserted, so a genuine backtick-quoted MySQL name elsewhere in the same file keeps its backticks. Extends the error-recovery scan to accept backtick names and adds _VIEW_TABLE_RECOVERY_RX to recover CREATE TABLE/VIEW statements swallowed into an unrelated ERROR node's span, deduping against already-parsed tables.

Worth a look

  • Debracketing can change byte offsets used by extract_sql locationsgraphify/extractors/sql.py:459 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 355 functions depend on the 175 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 31 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 355 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 187 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

parts.append(p)
return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 31 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Exclude two cases the grammar or the scanner cannot handle safely: a
bracket span containing a literal dot, which tree_sitter_sql still
splits on even inside a backtick pair, and single/double quoted
string content past its first line, which the scanner used to treat
as ordinary source once it hit a newline before finding the real
closing quote.
A dollar quoted span ($$ ... $$ or $tag$ ... $tag$) is opaque body
text, not SQL, so bracket like content inside it was being mistaken
for a real bracket identifier and rewritten. Skip the whole span
instead, the same way a string or comment already is.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.


Graphify review — findings

Rewrites T-SQL bracket-quoted identifiers ([dbo].[Orders]) into backtick-quoted ones via _debracket_tsql before parsing, so the grammar handles them on the normal AST path instead of dropping them into ERROR nodes that mangled labels and lost tables behind broken foreign keys; the inserted backtick spans are tracked so _strip_backtick_parts un-rewrites only bracket-derived names and leaves genuine MySQL backtick identifiers alone. Excludes array-marker brackets (int[], ARRAY[1,2,3]), comment-bearing, and dotted contents from rewriting, falling back to the pre-existing plain-bracket behavior for those. Adds _VIEW_TABLE_RECOVERY_RX to recover CREATE TABLE/VIEW statements swallowed into an unrelated ERROR span, deduped against already-parsed tables, and teaches _scan_sql plus the routine-recovery regex to treat backtick-delimited identifiers as recoverable names.

Worth a look

  • _VIEW_TABLE_RECOVERY dedup only checks table_nids, not view/other node idsgraphify/extractors/sql.py:928 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 359 functions depend on the 179 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 32 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 359 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 191 function(s) in the blast radius were not formally verified this run

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

parts.append(p)
return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 32 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@ayushcodes10
ayushcodes10 force-pushed the fix-2719-create-function-swallows-next branch from fca75f8 to 9dfff03 Compare September 7, 2026 12:41
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Investigated the latest bot findings on this PR.

Confirmed real, fixed (propagated from the base branch, now at 9dfff03): the dot-inside-brackets grammar limitation, the multi-line quoted-string corruption bug, and the missing dollar-quoted-body handling in _debracket_tsql — same three fixes described on #2723/#2722/#2721, all with regression tests. Also included in this branch's own history: the IF NOT EXISTS phantom node fix already noted in the earlier comment on this PR.

Investigated and found to be a false positive: "Debracketing can change byte offsets used by extract_sql locations" (sql.py:459). Same conclusion as posted on #2721 — source_location here is line-only, never byte-based, and _debracket_tsql never inserts or removes a newline, so a within-line byte-length shift from a rewrite cannot change which line any later content is reported on. Verified empirically.

Already covered on the base PR (#2723): the reproduced _scan_sql('') behavior-change claim (tooling artifact) and the Firebird regex-recovery-skip finding (pre-existing, out of scope).

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Adds T-SQL bracket-identifier recovery to the SQL extractor so [dbo].[Orders]-style names parse cleanly. _debracket_tsql rewrites bracket-quoted identifiers to backtick-quoted ones before parsing — skipping strings, dollar-quoted bodies, comments, and array-subscript brackets, and leaving dotted names like [My.Table] as plain brackets since the grammar can't round-trip them — while _strip_backtick_parts restores the original labels only for spans it inserted. Also adds _VIEW_TABLE_RECOVERY_RX to recover a CREATE TABLE/VIEW statement swallowed into another statement's ERROR span, deduped against already-parsed tables, and teaches _scan_sql to preserve backtick-delimited identifiers so the routine-recovery scan matches them too.

Worth a look

  • Debracketing rewrites brackets inside existing backtick identifiersgraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Table/view recovery can match DDL inside dollar-quoted function bodiesgraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 361 functions depend on the 181 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 32 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 361 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 193 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

parts.append(p)
return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 32 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

A MySQL backtick quoted identifier had no dedicated scan branch, so
its content was walked like ordinary source. A bracket like
substring inside one read as a real TSQL bracket identifier and got
rewritten in place, producing doubled and orphaned backticks. Skip a
backtick span whole, honoring a doubled backtick as an escaped
literal one the same way this function writes it.
@ayushcodes10
ayushcodes10 force-pushed the fix-2719-create-function-swallows-next branch from 9dfff03 to df3773f Compare September 7, 2026 13:09
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Follow up on this round's bot findings (matching df3773f):

Confirmed real, fixed (propagated from the base branch): the same backtick-corruption bug described on #2723/#2722/#2721 — a genuine backtick-quoted identifier containing bracket-like text got corrupted because _debracket_tsql had no dedicated scan branch for a backtick span. Fixed the same way, with a regression test.

Investigated and found to be pre-existing, out of scope for this PR: "Table/view recovery can match DDL inside dollar-quoted function bodies". _VIEW_TABLE_RECOVERY_RX runs against _scan_sql's masked output, which does not model PostgreSQL dollar-quoting at all (documented in _scan_sql's own comment block) -- gated by root.has_error, so it only ever runs on a file that already failed to parse. Confirmed this is systemic, not something this PR's new regex introduced: building an equivalent repro against the pre-existing _ROUTINE_RECOVERY_RX (which predates this PR by several releases) reproduces the identical fabricated-node behavior for a CREATE PROCEDURE-shaped string sitting in the same spot. _VIEW_TABLE_RECOVERY_RX inherits this by design -- its own comment says it shares _ROUTINE_RECOVERY_RX's masked scan and gating -- so it is not a new regression, just the same known, accepted error-recovery-only trade-off applying to one more regex built on the same masked_src. No code change made for this one.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Adds T-SQL bracket-identifier handling to the SQL extractor: _debracket_tsql rewrites [dbo].[Orders]-style bracket-quoted names into backtick-quoted ones before parsing so the grammar's normal AST path handles them instead of falling into error recovery, distinguishing genuine identifiers from array markers, comments, dotted names, and quoted/dollar-quoted spans (with plain brackets left un-rewritten as the fallback). Extends _scan_sql and _ROUTINE_RECOVERY_RX to accept backtick-delimited identifiers so bracket-named routines still recover after a rewrite, and adds _VIEW_TABLE_RECOVERY_RX to recover CREATE TABLE/VIEW statements swallowed into an ERROR node's span, deduped against already-parsed tables.

Worth a look

  • _debracket_tsql return type changed from bytes to tuplegraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Backtick-escape doubling not applied when rewriting bracket identifier containing a backtickgraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 363 functions depend on the 183 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 32 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 363 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 195 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

parts.append(p)
return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 32 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

A bracket span containing a literal backtick cannot be represented
by rewriting to backtick form: the grammar treats the first
backtick of the doubled escape this function would write as the
closing delimiter rather than an escape, truncating the identifier
and leaving the rest as a stray ERROR node.

Also stop rewriting a PostgreSQL ARRAY constructor when whitespace
separates the keyword from its bracket (ARRAY [1, 2, 3]). The
preceding character check only looked one character back, which is
a space in that shape rather than the keyword, so the array literal
read as an identifier and got rewritten.
ayushcodes10 added a commit to ayushcodes10/graphify that referenced this pull request Sep 7, 2026
_VIEW_TABLE_RECOVERY_RX lacked the IF NOT EXISTS gap
_ROUTINE_RECOVERY_RX already carries. CREATE TABLE/VIEW IF NOT
EXISTS in an error bearing file matched, but the capture group
greedily grabbed the next bare word after CREATE TABLE/VIEW, which
was IF, not the real name one word later, fabricating a phantom node
literally named IF.

Fixes Graphify-Labs#2724.
@ayushcodes10
ayushcodes10 force-pushed the fix-2719-create-function-swallows-next branch from df3773f to 6058080 Compare September 7, 2026 13:22
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Two more fixes from the base branch propagated here (now at 6058080): the ARRAY-constructor-with-space and bracket-content-with-backtick fixes described on #2723 and #2722. Same root causes, same fixes, with regression tests.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Rewrites T-SQL bracket-quoted identifiers ([dbo].[Orders]) into backtick-quoted ones before parsing via _debracket_tsql, so statements the grammar previously dropped into ERROR nodes now take the normal AST path, while carefully distinguishing quoted names from array markers (int[], ARRAY[...]) and skipping string/comment/dollar-quoted/backtick spans; the inserted-span coordinates are returned so a downstream reader can un-rewrite only the brackets it introduced and leave genuine MySQL backtick names alone. Extends the routine-recovery scan to accept backtick names and adds _VIEW_TABLE_RECOVERY_RX so a CREATE TABLE/VIEW swallowed into an ERROR span is recovered and deduped against already-parsed tables. Falls back to leaving a bracket un-rewritten for shapes the grammar still can't round-trip (embedded ., literal backtick, comment openers).

Worth a look

  • Block comment scan can loop forever when unterminatedgraphify/extractors/sql.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • _debracket_tsql return type changed from bytes to tuplegraphify/extractors/sql.py:543 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 367 functions depend on the 187 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 32 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 367 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 199 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 32 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

src_text decoded with errors=replace, which collapses every invalid
byte to one U+FFFD that re encodes to 3 bytes, permanently inflating
every offset _clean_regex_name computes past an invalid byte
anywhere earlier in the file. A routine recovered only through the
regex fallback then had its overlap check miss every real debracket
span, so a name debracketing did touch kept its backticks in the
final label. Decode with surrogateescape instead, which round trips
losslessly, and sanitize a name on the way out in case its own
content carried one of those escaped bytes, since a label is not
meant to hold a lone surrogate.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Rewrites T-SQL bracket-quoted identifiers ([dbo].[Orders]) to backtick-quoted form before parsing via _debracket_tsql, so statements the grammar has no rule for take the normal AST path instead of collapsing into ERROR nodes that mangle labels or drop whole tables behind broken foreign keys. Only true identifiers are rewritten — array markers (int[], ARRAY[...]), empty/numeric content, comment-openers, and names containing . or a literal backtick are left as plain brackets since the grammar can't round-trip them, and quoted/dollar-quoted/backtick spans are skipped so their contents aren't corrupted. Extends the ERROR-recovery scans to accept backtick-quoted names and adds _VIEW_TABLE_RECOVERY_RX to recover a CREATE TABLE/VIEW swallowed into an unrelated ERROR span, deduped against already-parsed tables.

Worth a look

  • Block comment scan can loop forever when unterminated in _debracket_tsqlgraphify/extractors/sql.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • ARRAY constructor after newline is rewritten as an identifiergraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • View/table recovery scans string literals and can fabricate declarationsgraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Table/view recovery misses T-SQL CREATE OR ALTER VIEWgraphify/extractors/sql.py:60 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 369 functions depend on the 189 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 33 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 369 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 201 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 33 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

ayushcodes10 added a commit to ayushcodes10/graphify that referenced this pull request Sep 8, 2026
_VIEW_TABLE_RECOVERY_RX lacked the IF NOT EXISTS gap
_ROUTINE_RECOVERY_RX already carries. CREATE TABLE/VIEW IF NOT
EXISTS in an error bearing file matched, but the capture group
greedily grabbed the next bare word after CREATE TABLE/VIEW, which
was IF, not the real name one word later, fabricating a phantom node
literally named IF.

Fixes Graphify-Labs#2724.
@ayushcodes10
ayushcodes10 force-pushed the fix-2719-create-function-swallows-next branch from a561ff8 to c18bf47 Compare September 8, 2026 04:24
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Investigated the latest bot findings on this PR.

Confirmed real, fixed (now at c18bf47, on the base branch and propagated through the whole stack -- _clean_regex_name and src_text originate on the base, not here): "_debracket_tsql return type changed from bytes to tuple" describes an accurate but non-actionable observation -- confirmed the one production call site (extract_sql itself) and every test call site already correctly unpack the new 2-tuple signature. Not a bug; the function is private with a single, already-updated caller.

Also confirmed real and fixed: src_text (used by the whole-file regex recovery fallback) decoded with errors="replace", which collapses each invalid UTF-8 byte to one U+FFFD that re-encodes to 3 bytes -- permanently inflating every byte offset _clean_regex_name computes past an invalid byte anywhere earlier in the file. Reproduced concretely: a procedure recovered only through the regex fallback (its AS BEGIN...END body has no grammar rule at all, so it can never reach the tree-sitter-node path) had its overlap check miss every real debracket span once 200 invalid bytes preceded it in the file, so a name debracketing did touch kept its backticks in the final label (dbo.GetCustomer() instead of dbo.GetCustomer()). Fixed by decoding with errors="surrogateescape" instead, which round-trips losslessly, and sanitizing a name on the way out in case its own content carried one of those escaped bytes (a label should never hold a lone surrogate). Regression test added.

Investigated and found to be a false positive: "Block comment scan can loop forever when unterminated" (high severity). Checked all three block-comment loops in this file (the two in _scan_sql plus the one in _debracket_tsql) -- each advances its scan index by 1 or 2 every iteration with no other way out, so each is mathematically bounded by the source length and cannot loop forever. Verified empirically under a timeout with several adversarial unterminated/nested-comment inputs, including a 100k-byte unterminated comment: all completed instantly. No code change made for this one.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Adds a T-SQL debracketing pass (_debracket_tsql) that rewrites [dbo].[Orders]-style bracket-quoted identifiers into backtick-quoted ones before parsing, so statements the grammar can't handle in brackets take the normal AST path instead of falling into error recovery; it skips string/dollar-quoted/backtick spans and leaves spans containing dots, embedded backticks, or comment openers un-rewritten as a fallback, and returns the inserted backtick spans so a downstream reader can un-rewrite only bracket-derived names. Extends the routine-recovery regex and _scan_sql to recognize backtick-quoted names (what debracketing produces), and adds _VIEW_TABLE_RECOVERY_RX to recover a CREATE TABLE/VIEW swallowed into an unrelated ERROR node's span, deduped against already-parsed table nodes. Adds multilang tests covering the new SQL recovery/bracket behavior alongside Go, Rust, and TS extraction cases.

Worth a look

  • _debracket_tsql return type changed from bytes to tuplegraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • src_text decoded with surrogateescape then re.finditer over CREATE TABLE uses group(1) directly without sanitizing surrogatesgraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • CREATE OR ALTER VIEW is not recoveredgraphify/extractors/sql.py:59 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • ARRAY constructor after newline is rewritten as a bracketed identifiergraphify/extractors/sql.py:278 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 369 functions depend on the 189 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 33 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 369 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 201 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_normalize\_ts\_import\_types (not a proof).

The verifier ran both versions of \_normalize\_ts\_import\_types on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_resolve\_rescued\_specifier.

The verifier did not have enough to check \_resolve\_rescued\_specifier, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_dart.

The verifier did not have enough to check extract\_dart, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_js\_module\_path.

The verifier did not have enough to check \_resolve\_js\_module\_path, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `start_dir` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_rust.

The verifier did not have enough to check extract\_rust, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_atomic\_replace.

The verifier did not have enough to check \_atomic\_replace, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `'str | Path'` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_query\_graph\_text (not a proof).

The verifier ran both versions of \_query\_graph\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 33 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

The ARRAY keyword lookback only skipped spaces and tabs, so ARRAY
followed by a newline then its bracket (SQL treats all whitespace
between tokens the same way) still read as a bracket quoted
identifier and got rewritten. Skip any whitespace kind, not just
spaces and tabs.
ayushcodes10 added a commit to ayushcodes10/graphify that referenced this pull request Sep 8, 2026
_VIEW_TABLE_RECOVERY_RX lacked the IF NOT EXISTS gap
_ROUTINE_RECOVERY_RX already carries. CREATE TABLE/VIEW IF NOT
EXISTS in an error bearing file matched, but the capture group
greedily grabbed the next bare word after CREATE TABLE/VIEW, which
was IF, not the real name one word later, fabricating a phantom node
literally named IF.

Fixes Graphify-Labs#2724.
@ayushcodes10
ayushcodes10 force-pushed the fix-2719-create-function-swallows-next branch from c18bf47 to f722b21 Compare September 8, 2026 04:43
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Investigated this round's bot findings on this PR.

Confirmed real, fixed: "CREATE OR ALTER VIEW is not recovered". _VIEW_TABLE_RECOVERY_RX only accepted OR REPLACE, matching Postgres, but not OR ALTER -- T-SQL's own re-creation syntax, since T-SQL has no OR REPLACE at all (_ROUTINE_RECOVERY_RX already accepts both, for the same reason, and its own comment documents why). Confirmed directly against the regex: CREATE OR ALTER VIEW ... never matched before this fix. Now accepts both forms, additive only (OR REPLACE still matches). Regression test added at f722b21.

Also fixed here (propagated from the base branch, now at f722b21): the ARRAY-with-newline gap described on #2723.

Investigated and found to be non-issues, no code change made:

  • "_debracket_tsql return type changed from bytes to tuple" -- already addressed in an earlier comment on this PR: the one production call site and every test call site already correctly unpack the new 2-tuple signature.
  • "src_text decoded with surrogateescape then re.finditer over CREATE TABLE uses group(1) directly without sanitizing surrogates" -- checked directly: the [\w$]+ character class this specific regex uses cannot match a lone surrogate codepoint at all (confirmed empirically -- re.match(r"[\w$]+", ...) stops right before one), so a name captured there can never carry one through, regardless of what's elsewhere in the file. This is a different, narrower regex than the delimited-name one _clean_regex_name's sanitization already covers.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Rewrites T-SQL bracket-quoted identifiers to backtick-quoted ones before parsing via _debracket_tsql, so [dbo].[Orders]-style names take the normal AST path instead of landing in ERROR nodes and mangling labels or dropping tables behind broken foreign keys; the rewrite is skipped inside string, dollar-quoted, comment, and existing backtick spans, and bails to a plain bracket for spans that can't round-trip (dotted names, embedded backticks or comment openers, empty/numeric content). Extends the error-recovery scans to accept backtick names and adds _VIEW_TABLE_RECOVERY_RX to recover a CREATE TABLE/VIEW (including OR ALTER) swallowed into an unrelated ERROR span, deduped against already-registered tables. Returns the inserted backtick spans so a downstream reader un-rewrites only the bracket-derived identifiers and leaves genuine MySQL names alone.

No blocking issues surfaced. 12 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 373 functions depend on the 193 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 33 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 373 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 205 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_normalize\_ts\_import\_types (not a proof).

The verifier ran both versions of \_normalize\_ts\_import\_types on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_resolve\_rescued\_specifier.

The verifier did not have enough to check \_resolve\_rescued\_specifier, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_dart.

The verifier did not have enough to check extract\_dart, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_js\_module\_path.

The verifier did not have enough to check \_resolve\_js\_module\_path, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `start_dir` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_rust.

The verifier did not have enough to check extract\_rust, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_atomic\_replace.

The verifier did not have enough to check \_atomic\_replace, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `'str | Path'` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_query\_graph\_text (not a proof).

The verifier ran both versions of \_query\_graph\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 33 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Unlike a string or dollar quoted span, a backtick span must not
cross a newline: a name does not span lines in practice, so an
unclosed backtick is far more likely a stray character than a
genuine multi line identifier. Scanning past a newline for one let
one accidental, unmatched backtick anywhere in the file silently
swallow everything after it, disabling debracketing for the rest of
the file. A backtick that does not close on its own line is now
left as an ordinary character, matching how _scan_sql treats an
unterminated delimited identifier.
A regex recovered name whose own content carried one of src_text's
surrogate escaped invalid bytes was sanitized via
encode(errors=replace).decode(), but that handler turns a lone
surrogate into a bare question mark, not the U+FFFD every other
invalid byte path in this module produces. Replace it explicitly
instead so the placeholder character stays consistent.
A bracket quoted FOREIGN KEY ... REFERENCES clause could confuse the
parser badly enough that the whole child table (with its FK
constraint) landed as bogus nested content inside the parent table's
own subtree, dropping the child table from the graph and fabricating
a self referencing EXTRACTED edge on the parent. Already fixed as a
side effect of the Graphify-Labs#2712 debracketing change; this adds coverage for
the specific Graphify-Labs#2713 repro.

Fixes Graphify-Labs#2713.
This grammar has no rule at all for TSQL's AS BEGIN...END routine
body, bracketed name or not, so every CREATE PROCEDURE/FUNCTION only
ever gets extracted through the ERROR node regex fallback. That
fallback matched a bare or double quoted name but not a bracketed
one, so a bracketed routine name produced no node at all rather than
a mangled one (0 of 845 procedures on a real SSMS scripted dump).

Already fixed as a required part of Graphify-Labs#2712's rewrite: rewriting a
bracketed name to a backtick quoted one before parsing means the
fallback has to recognize that form too, or it would regress a
bracketed routine from a recovered (if ugly) node to no node at all.
This adds dedicated coverage for the Graphify-Labs#2718 repro, schema qualified
and bare.

Fixes Graphify-Labs#2718.
This grammar has no rule for TSQL's AS BEGIN...END routine body, so a
CREATE FUNCTION/PROCEDURE with such a body lands in an ERROR node,
and that ERROR span can absorb the NEXT statement too (e.g. a CREATE
VIEW right after it), with no recovery path for anything but a
routine.

Adds a second recovery regex sharing the same delimited name
alternatives and the same whole file masked scan already used for
routines, gated the same way and deduped against table_nids so a
statement that DID parse cleanly is never registered twice.
Reproduces with bare identifiers, independent of the Graphify-Labs#2712/Graphify-Labs#2713/
Graphify-Labs#2718 bracket defects.

Fixes Graphify-Labs#2719.
_VIEW_TABLE_RECOVERY_RX lacked the IF NOT EXISTS gap
_ROUTINE_RECOVERY_RX already carries. CREATE TABLE/VIEW IF NOT
EXISTS in an error bearing file matched, but the capture group
greedily grabbed the next bare word after CREATE TABLE/VIEW, which
was IF, not the real name one word later, fabricating a phantom node
literally named IF.

Fixes Graphify-Labs#2724.
_VIEW_TABLE_RECOVERY_RX only accepted OR REPLACE, matching Postgres,
but not OR ALTER, TSQL's own re creation syntax since it has no OR
REPLACE at all. _ROUTINE_RECOVERY_RX already accepts both for the
same reason. A swallowed CREATE OR ALTER VIEW or TABLE could never
be recovered by this regex before.
@ayushcodes10
ayushcodes10 force-pushed the fix-2719-create-function-swallows-next branch from f722b21 to d51c559 Compare September 8, 2026 13:23
@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Two more fixes from the base branch propagated here (now at d51c559): the backtick-scan line-scoping fix and the U+FFFD-vs-"?" placeholder consistency fix, both described on #2721.

@graphify-labs graphify-labs 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.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.


Graphify review — findings

Rewrites T-SQL bracket-quoted identifiers ([dbo].[Orders]) to backtick-quoted form before parsing via _debracket_tsql, so statements the SQL grammar previously dropped into ERROR nodes now take the normal AST path; array markers (int[], ARRAY[…]), comment-bearing or dotted content, and spans inside strings, dollar-quotes, or genuine backtick identifiers are left untouched, and the inserted spans are tracked so only bracket-derived names get un-rewritten downstream. Adds _VIEW_TABLE_RECOVERY_RX to recover a CREATE TABLE/VIEW swallowed into an unrelated ERROR node's span, deduped against already-parsed tables, and accepts OR ALTER alongside OR REPLACE. Extends _scan_sql and _ROUTINE_RECOVERY_RX to treat backtick-delimited identifiers as recoverable names and normalizes lone surrogates to U+FFFD for placeholder consistency.

Worth a look

  • _debracket_tsql return type changed from bytes to tuplegraphify/extractors/sql.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • ARRAY constructor after a SQL comment is debracketed as an identifiergraphify/extractors/sql.py:535 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • _clean_regex_name reconstructs byte offsets from src_text but src_text decode error mode change may break span mappinggraphify/extractors/sql.py:947 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Table/view ERROR recovery scans string literals as DDLgraphify/extractors/sql.py:1045 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 377 functions depend on the 197 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract_sql() — 34 callers, 11 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: walk() — 1 callers, 9 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 377 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 209 function(s) in the blast radius were not formally verified this run

Formal verification

Behavior changes: \_scan\_sql changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_scan\_sql behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"text":"''"\}, the old code produced \('', \[\]\) but the new code produces \(' ', \[\]\). Paste that input straight into a regression test.

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_normalize\_ts\_import\_types (not a proof).

The verifier ran both versions of \_normalize\_ts\_import\_types on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_resolve\_rescued\_specifier.

The verifier did not have enough to check \_resolve\_rescued\_specifier, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_dart.

The verifier did not have enough to check extract\_dart, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_js\_module\_path.

The verifier did not have enough to check \_resolve\_js\_module\_path, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `start_dir` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_rust.

The verifier did not have enough to check extract\_rust, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_atomic\_replace.

The verifier did not have enough to check \_atomic\_replace, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `'str | Path'` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_query\_graph\_text (not a proof).

The verifier ran both versions of \_query\_graph\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 1 grounded finding(s) anchored inline below; 3 more finding(s) on lines outside this diff (see the check run).

return ".".join(parts)


def extract_sql(path: Path, content: str | bytes | None = None) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionextract_sql()

fans out to 11 callees (efferent coupling); 34 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

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.

T-SQL: a CREATE FUNCTION swallows the declaration that follows it — the next CREATE VIEW is silently absent

1 participant