Skip to content

dag grammar: node let-sugar guard is its whole head (fix forward #13056) - #13133

Merged
gunbai-bot[bot] merged 3 commits into
mainfrom
session/eager-crab-610-nodefix
Oct 3, 2026
Merged

gunbai-bot[bot] merged 3 commits into
mainfrom
session/eager-crab-610-nodefix

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Fixes forward #13056, which merged with a parse regression reported by tidy-raven-393 and calm-boar-904.

Defect

dag_grammar_let_sugar_head_expr guarded the expression statement with !(binding_name "=" | "node" binding_name). The node let-sugar is node name (: | =) expr, so the guard was one token short. Statements of the following shapes start with node <name> without being sugar:

  • a bare node statement followed on the next line by a statement that starts with a name;
  • an arm => node followed by the next arm;
  • node followed by if.

The guard excluded these from the expression alternative, and the sugar also refused them. The result was parse_g0_tokens_remain.

Fix

The guard is now the sugar's whole head: "node" binding_name (":" | "="). That is three tokens, inside the lookahead cap of 3, so the choice-overlap proof still reads it and the roster stays at 0.

Controls

v2.test.parse.bare_node_statement_route. Each claim is a route claim reading the stored Bool from the warm bns_verdicts:

  • tidy-raven-393's fixtures a1 (arm => node before the next arm) and a3 (node before if), verbatim. Each must parse with zero let_expr nodes, i.e. it takes the expression route.
  • node y = 1 must still take the let route (exactly one let_expr).

These are red-first: the two fixture claims fail on main and pass here. The sugar claim passes on both. (Red/green for these claims: from this PR's CI floor run.)

Corpus

A scan of dag and src/v2 for a statement node at end of line followed by a name-led line finds 6 files. Each is parsed whole with the dag grammar, on main and on this branch:

  • src/v2/std/node.dag (N7 closure)
  • dag/gunbc/scm/object_store.dag
  • src/v2/workflow/realization_runner.dag
  • src/v2/test/claim/wrap_decision_predicate_test.dag
  • src/v2/test/claim/verilog_interlock_emission_test.dag
  • src/v2/std/model_core.dag

Measured (one remote dispatch, same binary, each file's whole text parsed as a module with the dag grammar): all 6 refuse on main, all 6 parse on this branch.

Why #13056's evidence missed it

  • The 32-file parse was a sample, not the corpus. It selected the files carrying the sites the change was meant to touch. None of the six files above was in it: a whole-file parse of any of them refuses at the guarded head. A whole-corpus parse would have caught this; the sample could not.

  • The census enumerated sites whose meaning the change was meant to alter. It never enumerated what each new guard excludes. A guard must not exclude any input the sibling alternative fails to accept: if it does, it removes inputs rather than disambiguating them. Here the guard excluded node name while the sibling only accepts node name (: | =).

  • What evidence would have caught it:

    • a guard-soundness check per not-predicate: the guard's strings are a subset of what the sibling alternative can start with and continue at the guard's depth;
    • and, as backstop, the whole-corpus parse differential (accepted on main → accepted on the head).

    I'll propose the first as a check in the prefix-table PR that follows, since it reads the same per-production prefix table.

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 3 commits October 3, 2026 18:23
… | =)

A bare node statement followed on the next line by a name-led statement
(the next match arm, an if) began 'node name', which the two-token guard
excluded from the expression alternative while the sugar, lacking its ':' or
'=', refused too: parse_g0_tokens_remain on five corpus files, among them
src/v2/std/node.dag. Fixes forward #13056. Route controls: the reported
fixtures a1/a3 take the expression route, and the sugar keeps the let route.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…al-deer-478

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ispositions it)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit c067bd1 Oct 3, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/eager-crab-610-nodefix branch October 3, 2026 20:31
gunbai-bot Bot pushed a commit that referenced this pull request Oct 3, 2026
Conflicts: main (#13133) added one hand warm row (bns_verdicts, three readers:
derived as shared) to the roster this PR deletes; and main (#12526) deleted
v2.std.collection List in favour of std.types List, so this PR's two conflicted
files take that import (and any from v2.std.algebra, as main's roster file does).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Oct 3, 2026
…_node_statement_route

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Oct 4, 2026
…rim two arms

The node-then-if control is the same fixture as #13133's enrolled
v2.test.parse.bare_node_statement_route bare_node_statement_before_an_if_takes_the_expression_route;
repeating it here was duplicated evidence (and the claim the floor priced highest,
95797 steps). The bool-literal and contextual pattern-binder controls now end their
single arm in '=> 0'. The floor's own measurement on 49d1886 put only these three over
the 72300 new-witness budget.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

0 participants