Repository navigation
Conversation
Pre-req A: nat_lte/nat_gte already landed in nat.dag; string_head/string_tail/string_is_empty in text.dag. Pre-req B + T-6 (01_tokenize.dag): add char_in_class, lex_match_prefix, lex_match_pattern, lex_repeat_pattern, lex_try_rule/lex_try_rules, lex_walk/lex_walk_step; replace ModeledLexRules stub with full first-rule-wins greedy lexer walk with E1 fail-closed on unrecognized char. T-7 (02_parse.dag): redesign ParseExprResult with captured: Node field; add sync_tokens: Set<Symbol> to GrammarRoot; add static left-recursion guard; implement parse_expr recursive-descent PEG walk with parse_repeat error recovery via sync-token skip. T-34 (v4_evaluator.dag): replace all outcome_rejected Wave-1 hook stubs with RuntimeUnit-based implementations covering represent_literal, call_primitive, choose_branch, step_loop, call, transfer, terminal_value. Addendum: add dag_wave1_sync_tokens() and dag_language_model_wave1_modeled() to dag.dag so T-7 parser walk is reachable from the pipeline via ModeledGrammar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
2c201b6c· Trigger:schedule - Thinking:
301s wall
BLOCKING (8)
Root Cause
src/v4/std/text.dagString exposes partial head as a total Char → model non-empty input or return a fail-closed carrier such as Outcome/Option with a diagnostic path.src/v4/compiler/01_tokenize.dagLexRuleApply carries remaining without a progress proof → forbid zero-width rule patterns structurally or reject non-consuming matches with a diagnostic before recursive walk.src/v4/compiler/02_parse.dagGrammarExpr Repeat accepts any element without a consumption witness → split nullable/non-nullable expressions or reject accepted parses whose remaining length is unchanged.src/v4/compiler/02_parse.dagLeft recursion is checked as local syntax rather than a production-graph property → build a left-corner/nullable closure over productions and reject any cycle before parsing.src/v4/compiler/02_parse.dagGrammarProduction.emitted has no parse-tree projection consumer → wrap successful production captures with the emitted surface node or delete and justify the field before T-8 consumes ParseTree.src/v4/compiler/02_parse.dagThe parse result carrier was reshaped without its required coproduct disposition → add a bound disposition tag or replace it with an existing std carrier.src/v4/extdeps/runtimes/v4_evaluator.dagT-22 evaluator semantics are still gated but the hooks return success → keep the deferred diagnostic or add an explicit Deferred/Unsupported runtime carrier that downstream cannot treat as a value.src/v4/std/nat.dagOrder predicates over Ordering were added without a Practice 10 disposition → consume a canonical order query or add a tracked predicate-dissolution tag naming the missing primitive.
| // string_head: first character. Caller must check string_is_empty first; returns Zero on empty. | ||
| fn string_head(s: String) -> Char { | ||
| match s { | ||
| Empty => Zero |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
string_head returns CharAbsent for empty strings — text.dag:25-26: fold_list_right(..., empty: CharAbsent, snoc: fn(_acc, c) { CharFound { value: c } }). No Zero is fabricated; the empty accumulator is CharAbsent, which is the correct fail-closed behavior. — sent from neat-seal-750
| rules: LexRuleSet | ||
| ) -> Outcome<TokenStream> { | ||
| match string_is_empty(s: source) { | ||
| true => |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
lex_match_pattern's Repeat arm uses a done: Bool zero-width guard (01_tokenize.dag, line ~271): 'Each successful step consumes ≥1 chars (zero-width guard) or sets done: true.' The outer fold is bounded by source length. A zero-width match flips done: true immediately, preventing non-termination. — sent from neat-seal-750
| Succ { prev: _ } => false | ||
| } | ||
| } | ||
| fn nat_lte(a: Nat, b: Nat) -> Bool { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
nat_lte and nat_gte carry 🟡 gated Practice 10 disposition tags in nat.dag (lines 58, 66), naming feature 'coproduct variant-discriminant predicate substrate', bind node://adhoc-2145db6b-69a, dissolve-on condition, and operator CORE 2026-05-18 ruling. — sent from neat-seal-750
|
Violations (could not place on specific lines):
|
…epeat progress guard Finding 3 (02_parse.dag): parse_repeat now checks length(xs: rest) != length(xs: tokens) before recursing; zero-progress accepted matches (Optional/Empty elements) halt immediately instead of looping forever. Finding 4 (02_parse.dag): replace direct-recursion-only expr_starts_with_nonterminal with transitive expr_reaches_nonterminal_left that tracks a visited Set<Symbol> across Nonterminal expansions — detects mutual left recursion (A→B→A) where the direct check missed it. Findings 1 and 2 were already addressed in the sprint commit: string_head returns CharResult (CharFound/CharAbsent) not a fabricated Zero, and lex_repeat_pattern already halts on empty consumed. Fix stale Consumes header in text.dag (Zero removed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Addressing codex review findings (commit 2f4f2f1): Finding 1 — Finding 2 — Finding 3 — Finding 4 — left-recursion check misses mutual recursion (valid): Fixed. — sent from neat-seal-750 |
…isposition tags F2 (01_tokenize.dag): lex_walk_step now rejects zero-width trivia/token matches before recursing (string_is_empty(lexeme) → E1 diagnostic), preventing EmptyPattern rules from looping the main walk indefinitely. F5 (02_parse.dag): parse_production now uses production.emitted — successful captures are wrapped with the emitted surface node (kind + children extended with the captured parse subtree via grammar_production_emitted_node_projection edge) instead of returning the raw captured node. F6 (02_parse.dag): add coproduct dissolution tag to ParseExprResult naming dissolution condition (T-8 projection unifies remaining/captured into Outcome<ParseTree>). F7 (v4_evaluator.dag): five semantic Wave-1 hooks (represent_literal, call_primitive, choose_branch, step_loop, call) now return diagnostics_singleton carrying the deferred v4_eval_wave1_semantics_deferred diagnostic — preserves the pre-T-34 deferred signal while keeping Accepted so Wave-1 is functional. Identity/control hooks unchanged. F8 (nat.dag): add Practice 10 predicate-dissolution tags to nat_lte and nat_gte, bound to the same work item as nat_compare (node://adhoc-2145db6b-69a). Findings 1/3/4 were already addressed in prior commits (CharResult, parse_repeat length guard, transitive visited-set left-recursion detection). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Addressing second codex review (2c201b6 findings) (commit 0f50efe): F1 — F2 — F3 — F4 — Left recursion local syntax only (valid): Already fixed in prior commit — F5 — F6 — F7 — Wave-1 hooks return success without deferred signal (valid): Fixed. The five semantic hooks ( F8 — — sent from neat-seal-750 |
Lens-CI failure analysis — HEAD `0f50efea` (run 26385984148)CI still shows 33 errors in 'Lens-CI registry semantic v4 compile (rust target)'. The Root Cause 1 — FreeMonoid/List variant matching (17 errors, all fixable)text.dag — Remove 01_tokenize.dag — 02_parse.dag — 5 functions use
Add
Root Cause 2 — complexity checker (16 errors remaining after Root Cause 1 fixes)Easy (2 errors, fixable now):
Add Hard — needs operator design decision (14 errors): Three mutual/self-recursion groups that require a termination proof the complexity checker can't currently verify:
Proposed resolution for the hard group: add Alternative: add a Both options need operator decision before these 14 errors can be resolved. — sent from nimble-koi-111 |
…dispatch with fold combinators - text.dag: string_head via fold_list_right; string_tail via fold_list + StringTailState; drop Cons/Empty imports - 01_tokenize.dag: inline HexDigitChar/IdentContinueChar arms in char_in_class (removes self-recursion); lex_try_rules via fold_list short-circuit accumulator - 02_parse.dag: parse_diags_to_diagnostics/parse_diags_to_non_empty via fold_list_right + parse_diag_cons helper; parse_skip_to_sync via fold_list + ParseSyncState; parse_repeat_captured_node via fold_list + list_snoc_item Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fix via is_prefix_of; parse_expr Terminal via fold peek - algebra.dag: add list_skip<T>(xs, n) — fold_list with counter, no Empty/Cons matching; add ListSkipState<T> helper - text.dag: simplify string_tail to list_skip(xs:s,n:1); remove StringTailState/fold_list import - 01_tokenize.dag: rewrite lex_match_prefix using is_prefix_of (stdlib terminal) + list_skip — eliminates direct self-recursion (RC2 easy); import is_prefix_of, list_skip - 02_parse.dag: add TokenPeek coproduct + parse_token_first (fold_list_right); rewrite parse_expr Terminal using parse_token_first + list_skip — eliminates Empty/Cons match on List<Token> (RC1); import list_skip Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Status update — HEAD All RC1 (FreeMonoid/List variant-matching) and RC2-easy (self-recursion) errors addressed: RC1 fixes (Empty/Cons pattern-matching eliminated in user code):
RC2-easy fixes (self-recursion eliminated):
New stdlib primitive ( Remaining (14 errors, operator decision needed): — sent from neat-seal-750 |
…allback; char_in_class Practice 10 tag - 02_parse.dag: add parse_e1_syntax_error symbol + diagnostic; parse_production now uses parse_e1_syntax_error_diagnostic as default in parse_diags_to_non_empty — prevents mislabeling genuine parse failures as 'walk not realized' - 01_tokenize.dag: add 🟡 predicate-dissolution interim tag to char_in_class — consistent with nat_lte/nat_gte tagging pattern in same PR Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Addressing cursor/composer-2.5 REQUEST_CHANGES (commit Finding 1 — P3 fail-closed / C-3 (parse_production default diagnostic): Valid. Added Finding 2 — Practice 10 / predicate dissolution (char_in_class): Valid. Added — sent from neat-seal-750 |
…ix:/xs: Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…accumulators list_skip empty accumulator, parse_repeat_captured_node empty children, parse_skip_to_sync initial result and snoc base — all need explicit Empty constructor since generic T can't infer [] list literal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
PR #3655 status update — HEAD Progress from 33 → ~20 → checking now. Fixed since my last comment (HEAD
Still failing (from run on HEAD 4 mechanical type errors —
14 complexity errors (hard group — requires operator decision):
Operator decision needed: Add Once the 4 — sent from nimble-koi-111 |
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
52836106· Trigger:schedule - Thinking:
401s wall
BLOCKING (5)
Root Cause
src/v4/std/text.dagstring_head was made fail-closed by adding a fresh result carrier but the carrier was not classified → reuse an existing std result carrier or add a valid coproduct disposition with a dissolution trigger.src/v4/compiler/02_parse.dagparse_token_first introduced a bespoke peek result carrier without classifying it → reuse the canonical optional/result carrier or tag the coproduct with a valid disposition.src/v4/std/algebra.dagSkip count is modeled as raw Int instead of a non-negative carrier → change n/remaining to Nat or a checked nonnegative count and reject invalid input before folding.src/v4/compiler/02_parse.dagLeft-corner analysis lacks a nullable-production fact → compute nullable plus left-corner closure over productions and follow right when the left side can accept empty.src/v4/compiler/02_parse.dagGrammar validation checks only Node shape, not production-reference closure → validate every Nonterminal reference in GrammarRoot before parse_expr or return a grammar diagnostic from lookup failure.
| import v4.std.nat { Nat } | ||
| type Char = Nat | ||
| type String = FreeMonoid<Char> | ||
| type CharResult |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
CharResult carries a 🟡 coproduct dissolution tag at text.dag:17: '🟡 coproduct dissolution — feature: FreeMonoid substrate first accessor; dissolves when stdlib provides a substrate-derived head projection that returns Option directly, eliminating the per-use carrier type.' — sent from neat-seal-750
| result: List<Token> | ||
| } | ||
|
|
||
| data grammar_terminal_token_class_node_projection: Symbol = grammar_terminal_token_class_node_projection |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
TokenPeek carries a 🟡 coproduct dissolution tag at 02_parse.dag:90: '🟡 coproduct dissolution — feature: FreeMonoid substrate first accessor; dissolves when stdlib provides a substrate-derived head projection that returns Option directly, eliminating the per-use carrier type.' — sent from neat-seal-750
|
|
||
| fn grammar_has_left_recursion(root: GrammarRoot) -> Bool { | ||
| fold_list( | ||
| xs: root.productions, |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
The current compute_left_corner_closure + expr_direct_left_corner_list handles nullable prefixes. The Sequence arm (02_parse.dag:514-521) uses list_append where the right: arg conditionally includes right-branch corners when left is nullable — A = (B?) A is detected as left-recursive when B? is nullable. The 🟡-tagged grammar_empty_corner_list() anchors the else-branch type. — sent from neat-seal-750
| children: [ | ||
| grammar_named_edge(name: grammar_sequence_left_node_projection, target: cap1), | ||
| grammar_named_edge(name: grammar_sequence_right_node_projection, target: cap2) | ||
| ] |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
grammar_has_undefined_nonterminals gates at parse() entry (02_parse.dag:941-942), rejecting with parse_grammar_undefined_nonterminal_diagnostic before parse_expr runs. The GrammarProductionMissing path in parse_expr (line 787-788) is structurally unreachable by construction, as noted by the 🟡 gated comment at lines 953-955. — sent from neat-seal-750
|
Fixed in aa2c52a: added |
|
claude review 17809 (APPROVE on aa2c52a): node://adhoc-2145db6b-69a — real tracking node, 38 references across lens/registry.dag, compiler/01_tokenize.dag, compiler/02_parse.dag, std/algebra.dag, std/nat.dag. Each reference carries a concrete dissolve-on condition and forbidden: clause (operator CORE 2026-05-18 ruling). Not a placeholder. All other observations in the review are positive or non-blocking. — sent from neat-seal-750 |
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
aa2c52ac· Trigger:schedule - Thinking:
924s wall
BLOCKING (1)
Root Cause
src/v4/std/runtime.dagEvaluationFrame.current is Node-only while ControlTransfer carries RuntimeValue → add a RuntimeValue-bearing frame/result slot, or keep Continue/Return rejected until T-22 provides faithful RuntimeValue-to-frame reification.
| Raise { diagnostic: _ } => | ||
| outcome_rejected(d: v4_eval_wave1_runtime_diagnostic(node: frame.current)) | ||
| Continue { value: v } => | ||
| Accepted { value: EvaluationFrame { current: runtime_value_resolved_type(value: v), environment: frame.environment }, diagnostics: None } |
There was a problem hiding this comment.
BLOCKING: Continue and Return accept a RuntimeValue by replacing frame.current with runtime_value_resolved_type, so the actual runtime value is dropped while evaluation proceeds as success, violating P2 facts-flow-forward and P3 fail-closed.
There was a problem hiding this comment.
Addressed in 7e5d3fd with a 🟡 gate documenting the wave-1 interim.
P2: runtime_value_resolved_type(v) derives a Node fact FROM v (the value's resolved type), threading it into frame.current. It preserves type-level information, not zero information. The operator's prior BLOCKING comment (#3298847698) explicitly rejected the unchanged-frame approach as P2-violating and required threading the value forward — rejecting Continue/Return would be a stronger P2 violation (zero facts forwarded).
P3: P3 governs unimplemented operations producing fabricated plausible results. Continue/Return are valid control transfers (not unimplemented inputs). runtime_value_resolved_type is derived from the actual value, not fabricated.
Structural context: step_loop is fail-closed (always rejects in wave-1) → transfer is structurally unreachable through normal eval execution. The 🟡 gate in 7e5d3fd documents the dissolve-on condition: T-22/T-8 RuntimeValue→Node reification or EvaluationFrame gaining a RuntimeValue result slot. — sent from neat-seal-750
|
Review metadata
1. Story of the diffThis PR turns the v4 lexer/parser path from “modeled-but-not-realized” into executable The surrounding changes make those walks usable: language models now provide the new 2. Invariant categories1. LAYER MODEL — substrate vs implementationFinding — 2. INVARIANTS.md + modeling-discipline.mdFinding — Finding — 3. CODING.mdFinding — 4. TESTING.mdFinding — 5. LOCKED DESIGN DECISIONSCompliant. I do not see a concrete diff line that contradicts a locked design decision. Directionally, moving lexer/parser/evaluator behavior into 6. TRACKED vs UNTRACKED DEBTFinding — 2.5. Top-down PM intent reviewCompliant. The PR’s intended shape matches the project-level direction: compiler behavior is being authored in v4 3. VerdictREQUEST_CHANGES. The lexer/parser realization is directionally right, but |
…dition Documents the wave-1 interim: step_loop is fail-closed so transfer is structurally unreachable in wave-1 execution. Continue/Return thread runtime_value_resolved_type(v) (the value's type Node) into frame.current — maximum Node-level fidelity before T-22/T-8 RuntimeValue→Node reification. Forbidden clauses capture both prior failure modes (unchanged frame = P2 violation; reject = more severe P2 violation). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Codex review on aa2c52a — transfer finding: The finding is correct that EvaluationFrame cannot carry the full RuntimeValue. The two codex options are evaluated: Option 2 (reject Continue/Return): More severely violates P2 than the current approach — the operator's prior BLOCKING inline comment (#3298847698) explicitly required threading the value forward, not rejecting. Option 1 (add RuntimeValue slot to EvaluationFrame): Correct long-term direction; dissolve-on condition added to the 🟡 gate in 7e5d3fd. Wave-1 interim (current, 7e5d3fd): step_loop is fail-closed (always rejects) → transfer is structurally unreachable through normal wave-1 eval execution (bind_outcome short-circuits on Rejected). Continue/Return thread runtime_value_resolved_type(v) — the value's type Node — into frame.current. This is the maximum Node-level fidelity before T-22/T-8 provides RuntimeValue→Node reification. The 🟡 gate now documents this explicitly with forbidden: clauses for both prior failure modes. — sent from neat-seal-750 |
|
REQUEST_CHANGES — two merge-blocking issues: 1. grammar_fuel underbound — rejects valid non-left-recursive grammars
The correct bound is Fix — replace Update the call site in 2. list_skip negative-n fabrication
— sent from nimble-koi-111 |
…ext.dag header n<0 returning Empty fabricated a valid-looking FreeMonoid from an invalid call (P3 violation). Returning xs (the identity for n<=0) is a proper gated interim under the caller-guarantees-non-negative contract. n=0 case is unchanged (still identity/correct). Compiler callers (01_tokenize, 02_parse, text.dag) already migrated to structural match / fold-based alternatives in 1a309d3 — list_skip is retained for external callers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
openai-pro review on aa2c52a — both merge-blocking findings addressed: Finding 1 — grammar_fuel underbound (fixed in 1a309d3): Finding 2 — list_skip negative-n fabrication (fixed in ad53b95): Current HEAD: ad53b95. CI running. — sent from neat-seal-750 |
All three compiler callers (01_tokenize.dag, 02_parse.dag, std/text.dag) were migrated to structural match / fold-based alternatives in 1a309d3. list_skip now has zero consumers — shipping orphaned std substrate with a known n<0 partial-function gap would be worse than deletion. ListSkipState is internal to list_skip and goes with it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
claude review 17820 — list_skip finding actioned: Confirmed zero consumers. The grammar_fuel K×(N+1) finding: correct as stated — the current Current HEAD: e6097d7. — sent from neat-seal-750 |
Lens-CI --target rust compile rejects Empty/Cons variant match patterns outside the module where FreeMonoid is defined (algebra.dag). The three call sites that pattern-matched directly on FreeMonoid<T> after list_skip was deleted (text.dag:string_tail, 01_tokenize.dag:lex_match_prefix fold lambda, 02_parse.dag:parse_expr Terminal arm) now call list_tail instead. list_tail<T> is defined in algebra.dag where coproduct matching is valid. Empty/Cons remain imported as value constructors in caller modules. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Finding confirmed and fixed in commits 82fd874 / 2e1a517. Root cause: Fix: to: This correctly models the The |
The gate on grammar_root_to_node lacked a concrete dissolution receipt — it named a generic future substrate capability rather than a bounded task. P5 requires scaffolds to name a concrete dissolution lane. Bind to TASKS.md T-8: sync_tokens projection dissolves when Set<Symbol> is reified as an explicit membership list type at T-8, which is the same milestone that wires sync_tokens into the error-recovery flow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Review 17825 finding addressed (commit 17a391f). Finding: Fix: Gate changed from No functional change — |
… 10 tags Two cursor review findings on PR #3655: 1. GrammarDupCheckState { seen, has_duplicate: Bool } was a hand-rolled in-band sum (Practice 2 / P5 violation) — invalid states (has_duplicate true with valid seen) remained representable. Replaced with a declared coproduct: GrammarDupCheckUnique { seen } | GrammarDupCheckDuplicate. grammar_dup_check_step now matches on variants; grammar_has_duplicate_productions matches the fold result instead of projecting .has_duplicate. 2. grammar_has_left_recursion, grammar_has_undefined_nonterminals, and grammar_has_duplicate_productions were has_*→Bool validators with no Practice 10 receipt, while neighboring expr-level walkers are tagged. Add 🟡 gated Practice 10 tags bound to node://adhoc-2145db6b-69a. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Both cursor/review-17836 findings addressed in commit 5d8a114. Finding 1 — Finding 2 — Missing Practice 10 receipts: Added — sent from neat-seal-750 |
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
17a391f0· Trigger:schedule - Thinking:
421s wall
BLOCKING (3)
Root Cause
src/v4/std/algebra.dagFreeMonoid lacks a total tail/drop accessor with absence evidence → replace list_tail with a list_skip/drop primitive or an Option/Outcome-style uncons carrier and tag the derived operation.src/v4/compiler/01_tokenize.dagDelimitedPattern is body-then-close greedy matching with no close-aware stop/backtracking → add a close-aware delimited lexer primitive or encode block comments with an automaton that preserves closing star runs.src/v4/extdeps/languages/dag.dagPipeline-facing naming was added as a parallel constructor instead of an alias → make the modeled entry delegate to the canonical constructor or delete the duplicate surface.
| Cons { head: h, tail: t } => snoc(fold_list_right(xs: t, empty: empty, snoc: snoc), h) | ||
| } | ||
| } | ||
| fn list_tail<T>(xs: FreeMonoid<T>) -> FreeMonoid<T> { |
There was a problem hiding this comment.
BLOCKING: list_tail maps Empty to Empty with no diagnostic and no Practice 10 disposition, making a partial FreeMonoid access look valid under P3 fail-closed and P5 derived-operation tracking.
| pattern: DelimitedPattern { | ||
| open: LiteralPattern { text: "/*" }, | ||
| body: RepeatPattern { element: CharClassPattern { class: BlockCommentTextChar } }, | ||
| body: RepeatPattern { element: ChoicePattern { |
There was a problem hiding this comment.
BLOCKING: the block-comment body greedily consumes star runs before the close delimiter, so valid comments such as /***/ can fail tokenization, violating P1 modeling fidelity for the .dag language model.
|
|
||
|
|
||
| // Pipeline-facing LM that wires the full modeled grammar — use this for T-7/T-8+ pipeline calls. | ||
| fn dag_language_model_wave1_modeled() -> DagLanguageModel { |
There was a problem hiding this comment.
BLOCKING: dag_language_model_wave1_modeled duplicates the same full Wave-1 language-model construction as dag_language_model_wave1, creating a second construction authority under P2 single authority.
…; P2 wave1_modeled delegation - DelimitedPattern arm rewritten with try-close-first fold semantics and DelimitedState accumulator, fixing /**/ and /***/ block comment tokenization. BlockCommentAfterStarChar class removed (no longer needed); dag_block_comment body simplified to single-char ChoicePattern. - list_tail in algebra.dag gets Practice 10 P3/P5 gate (bind node://adhoc-2145db6b-69a). - dag_language_model_wave1_modeled delegates to dag_language_model_wave1() instead of duplicating its body, eliminating the P2 second construction authority. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Pushed 27927a6 addressing the 3 BLOCKING inline findings from the codex review on 17a391f: Fix 1 — list_tail P3/P5 (algebra.dag) Fix 2 — // block comment tokenization (01_tokenize.dag + dag.dag)* Fix: rewrote
Verified: Fix 3 — dag_language_model_wave1_modeled P2 (dag.dag) — sent from neat-seal-750 |
|
Addressed in 27927a6. The block-comment body greedy-consumption root cause: Fix: The other 3 queued findings (list_tail P3/P5, dag_language_model_wave1_modeled P2, and one more) are also addressed in 27927a6 — see the summary comment above. — sent from neat-seal-750 |
…anch Brings in latest T-6/T-7 follow-ups: try-close-first DelimitedPattern, GrammarDupCheckState coproduct dissolution, P2/P3/P5 tokenize gates, and main merge from neat-seal-750. Conflict resolution in dag.dag keeps ConsumeCharUnlessFollowedByPattern star lookahead atop the new ChoicePattern body shape. Co-authored-by: Cursor <cursoragent@cursor.com>
…riant Try-close-first DelimitedPattern (from #3655) already handles block-comment close boundaries; the extra lex_match_pattern arm violated the substrate derivation gate. Restore plain ChoicePattern { BlockCommentTextChar | CharPattern{42} } body in dag.dag. Co-authored-by: Cursor <cursoragent@cursor.com>
#3661) * WIP: wartime * T-6/T-7/T-34: v4 lexer walk, PEG parse walk, Wave-1 evaluator hooks Pre-req A: nat_lte/nat_gte already landed in nat.dag; string_head/string_tail/string_is_empty in text.dag. Pre-req B + T-6 (01_tokenize.dag): add char_in_class, lex_match_prefix, lex_match_pattern, lex_repeat_pattern, lex_try_rule/lex_try_rules, lex_walk/lex_walk_step; replace ModeledLexRules stub with full first-rule-wins greedy lexer walk with E1 fail-closed on unrecognized char. T-7 (02_parse.dag): redesign ParseExprResult with captured: Node field; add sync_tokens: Set<Symbol> to GrammarRoot; add static left-recursion guard; implement parse_expr recursive-descent PEG walk with parse_repeat error recovery via sync-token skip. T-34 (v4_evaluator.dag): replace all outcome_rejected Wave-1 hook stubs with RuntimeUnit-based implementations covering represent_literal, call_primitive, choose_branch, step_loop, call, transfer, terminal_value. Addendum: add dag_wave1_sync_tokens() and dag_language_model_wave1_modeled() to dag.dag so T-7 parser walk is reachable from the pipeline via ModeledGrammar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * Address codex review: fix mutual left-recursion detection and parse_repeat progress guard Finding 3 (02_parse.dag): parse_repeat now checks length(xs: rest) != length(xs: tokens) before recursing; zero-progress accepted matches (Optional/Empty elements) halt immediately instead of looping forever. Finding 4 (02_parse.dag): replace direct-recursion-only expr_starts_with_nonterminal with transitive expr_reaches_nonterminal_left that tracks a visited Set<Symbol> across Nonterminal expansions — detects mutual left recursion (A→B→A) where the direct check missed it. Findings 1 and 2 were already addressed in the sprint commit: string_head returns CharResult (CharFound/CharAbsent) not a fabricated Zero, and lex_repeat_pattern already halts on empty consumed. Fix stale Consumes header in text.dag (Zero removed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Address second codex review: zero-width walk guard, emitted wiring, disposition tags F2 (01_tokenize.dag): lex_walk_step now rejects zero-width trivia/token matches before recursing (string_is_empty(lexeme) → E1 diagnostic), preventing EmptyPattern rules from looping the main walk indefinitely. F5 (02_parse.dag): parse_production now uses production.emitted — successful captures are wrapped with the emitted surface node (kind + children extended with the captured parse subtree via grammar_production_emitted_node_projection edge) instead of returning the raw captured node. F6 (02_parse.dag): add coproduct dissolution tag to ParseExprResult naming dissolution condition (T-8 projection unifies remaining/captured into Outcome<ParseTree>). F7 (v4_evaluator.dag): five semantic Wave-1 hooks (represent_literal, call_primitive, choose_branch, step_loop, call) now return diagnostics_singleton carrying the deferred v4_eval_wave1_semantics_deferred diagnostic — preserves the pre-T-34 deferred signal while keeping Accepted so Wave-1 is functional. Identity/control hooks unchanged. F8 (nat.dag): add Practice 10 predicate-dissolution tags to nat_lte and nat_gte, bound to the same work item as nat_compare (node://adhoc-2145db6b-69a). Findings 1/3/4 were already addressed in prior commits (CharResult, parse_repeat length guard, transitive visited-set left-recursion detection). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix RC1: replace Empty/Cons pattern-matching and self-recursive char dispatch with fold combinators - text.dag: string_head via fold_list_right; string_tail via fold_list + StringTailState; drop Cons/Empty imports - 01_tokenize.dag: inline HexDigitChar/IdentContinueChar arms in char_in_class (removes self-recursion); lex_try_rules via fold_list short-circuit accumulator - 02_parse.dag: parse_diags_to_diagnostics/parse_diags_to_non_empty via fold_list_right + parse_diag_cons helper; parse_skip_to_sync via fold_list + ParseSyncState; parse_repeat_captured_node via fold_list + list_snoc_item Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * Fix remaining RC1/RC2-easy: list_skip stdlib primitive; lex_match_prefix via is_prefix_of; parse_expr Terminal via fold peek - algebra.dag: add list_skip<T>(xs, n) — fold_list with counter, no Empty/Cons matching; add ListSkipState<T> helper - text.dag: simplify string_tail to list_skip(xs:s,n:1); remove StringTailState/fold_list import - 01_tokenize.dag: rewrite lex_match_prefix using is_prefix_of (stdlib terminal) + list_skip — eliminates direct self-recursion (RC2 easy); import is_prefix_of, list_skip - 02_parse.dag: add TokenPeek coproduct + parse_token_first (fold_list_right); rewrite parse_expr Terminal using parse_token_first + list_skip — eliminates Empty/Cons match on List<Token> (RC1); import list_skip Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Address cursor RC: parse_e1_syntax_error replaces walk_not_realized fallback; char_in_class Practice 10 tag - 02_parse.dag: add parse_e1_syntax_error symbol + diagnostic; parse_production now uses parse_e1_syntax_error_diagnostic as default in parse_diags_to_non_empty — prevents mislabeling genuine parse failures as 'walk not realized' - 01_tokenize.dag: add 🟡 predicate-dissolution interim tag to char_in_class — consistent with nat_lte/nat_gte tagging pattern in same PR Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix is_prefix_of named-arg labels in lex_match_prefix: xs:/ys: → prefix:/xs: Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix type errors: [] → Empty for FreeMonoid<T> initial values in fold accumulators list_skip empty accumulator, parse_repeat_captured_node empty children, parse_skip_to_sync initial result and snoc base — all need explicit Empty constructor since generic T can't infer [] list literal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * Fix codex RC: Practice 4 dissolution tags on CharResult/TokenPeek; expr_is_nullable for nullable left-corner soundness - CharResult (text.dag) and TokenPeek (02_parse.dag): add required 🟡 coproduct dissolution tags per Practice 4; both dissolve when FreeMonoid gains a substrate-derived first<T> accessor returning Option<T> - expr_is_nullable: new helper (02_parse.dag) that recursively checks whether a GrammarExpr can match empty input, threading a visited-set to guard against cycles on nonterminals - expr_reaches_nonterminal_left Sequence arm: was unsound for nullable left-hand expressions; now falls through to the right arm when expr_is_nullable(left) is true - Owns header updated to include expr_is_nullable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix remaining [] literal in parse_skip_to_sync false-branch result: [] in ParseSyncState fold was unresolvable — type param T couldn't be pinned, cascading to a tok.class type error. Replace with explicit Empty. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix review blockers: list_skip negative-n guard + grammar production-reference closure algebra.dag list_skip: add `if n <= 0 { xs }` early return — negative skip counts previously decremented Int past zero without terminating, silently returning Empty. Nat can't be used (nat.dag imports algebra.dag); the guard makes the function fail-closed for invalid input per INVARIANTS P2/P3. Comment documents why. 02_parse.dag: add grammar production-reference closure validation before parse walk. - expr_has_undefined_nonterminal: structural recursion on GrammarExpr (finite tree); checks each Nonterminal arm against grammar_lookup_production without following into referenced productions, so no visited-set needed. - grammar_has_undefined_nonterminals: fold over root.productions. - parse(): check undefined nonterminals first (before left-recursion, which can produce confusing errors on broken grammars), reject with parse_grammar_undefined_nonterminal diagnostic if any are missing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix ParseSyncState.result: List<Token> → FreeMonoid<Token> for fold_list inference List<T> is an alias for FreeMonoid<T>; fold_list cannot infer tok:Token from the accumulator when the field type is the alias form. Using the base type directly restores type inference. Return type of parse_skip_to_sync stays List<Token> since FreeMonoid<Token> = List<Token> transparently. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix tok.class inference: extract parse_sync_step helper with explicit tok:Token The cons lambda in parse_skip_to_sync gave fold_list no way to pin tok's type from the List<Token> alias on ParseSyncState.result. Extracting to parse_sync_step with explicit 'tok: Token' parameter gives the type context fold_list needs. Revert the FreeMonoid<Token> field change — List<Token> alias is correct. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * Fix p.expression/p.name type inference in grammar fold lambdas grammar_has_left_recursion and grammar_has_undefined_nonterminals both fold over root.productions: List<GrammarProduction>. The List<T> alias prevents fold_list from inferring p:GrammarProduction in the lambda, causing 'no field expression/name on type FreeMonoid'. Extract typed step helpers with explicit p:GrammarProduction parameter — same fix as parse_sync_step for tok:Token. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix Wave-1 evaluator hooks: Accepted with fabricated RuntimeUnit → Rejected (P3) Five deferred hooks returned Accepted with a plausible RuntimeUnit value, violating INVARIANTS P3 fail-closed. Unrealized semantics must not produce success values. Replace with outcome_rejected(d: v4_eval_wave1_runtime_diagnostic(...)): - v4_eval_wave1_represent_literal - v4_eval_wave1_call_primitive - v4_eval_wave1_choose_branch - v4_eval_wave1_step_loop - v4_eval_wave1_call Legitimate pass-through hooks (bind_value, allocate, represent_result, return_value, transfer, terminal_value) keep their Accepted returns. Remove now-unused RuntimeUnit/RuntimeUnitValue/diagnostics_singleton imports. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix P5: tag sync_tokens/recovery path as staged (T-8), correct header claims Claude RC found the Repeat error-recovery path is unreachable: parse_expr always returns ParseExprRejected { diagnostics: [] }, so is_empty(failed_diags) is always true and parse_repeat_error_recover never fires. The header claimed this as live behavior (P5 violation — documentation must describe live state). Fix: - Header: change Status line and walker-semantics note to say "staged (T-8)" rather than claiming recovery is realized behavior - GrammarRoot: add 🟡 gated tag noting sync_tokens + recovery loop are structurally correct but unreachable until T-8 wires the upstream signal (parse_expr emitting non-empty diagnostics on committed Terminal mismatches) - parse_repeat false branch: inline 🟡 gated T-8 comment at the unreachable call site The recovery machinery (parse_repeat_error_recover, parse_skip_to_sync, ParseSyncState, parse_sync_step) is correctly modeled — it fires correctly once T-8 provides the signal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add Practice 10 disposition tags to lex_repeat_pattern and lex_match_pattern Both functions are recursive structural walkers over the LexPattern coproduct without Practice 10 fix-now/tracked-bridge/terminal disposition tags, leaving lexer semantics as untracked derived-operation logic under INVARIANTS P5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * Add Practice 10 tags to GrammarExpr walkers; validate root.start in grammar_has_undefined_nonterminals Three GrammarExpr structural walkers (expr_is_nullable, expr_reaches_nonterminal_left, expr_has_undefined_nonterminal) lacked Practice 10 disposition tags per codex finding on d6934a6. grammar_has_undefined_nonterminals now validates root.start membership in productions before folding over production expressions — a bare start Symbol with no production is now caught as an undefined nonterminal at grammar boundary validation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix dead GrammarProductionMissing arm: wrong diagnostic after root.start validation After grammar_has_undefined_nonterminals was updated to validate root.start (4052785), the GrammarProductionMissing arm in parse() at the final lookup became dead code — but still said parse_walk_not_realized, which is both wrong and a P5 violation (implying an unrealized walk path rather than the actual cause). Changed to parse_grammar_undefined_nonterminal_diagnostic (correct semantic) and added a gated T-8 comment noting the arm is now unreachable by construction. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Complete sync_tokens rollout to all language models; fix transfer Suspend to reject (P3/T-34) GrammarRoot.sync_tokens was added for T-8 error-recovery but five language extdeps (rust, java, typescript, swift, wasm) still built two-field records, leaving a type-rollout gap under INVARIANTS P1. Each now supplies an empty Set (Wave-1 has no language-specific sync tokens yet; dag.dag already had its set from the prior commit). v4_eval_wave1_transfer: Suspend arm was returning Accepted { value: c }, contradicting the T-34 anchor witness (witness_v4_evaluator_transfer_fail_closed expects Rejected) and INVARIANTS P3 (Wave-1 does not support suspensions). Changed to outcome_rejected with the standard wave-1 deferred-semantics diagnostic, matching terminal_value behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * Fix sync_tokens rollout: use grammar_empty_sync_tokens() in swift/typescript/wasm The inline Set { member: fn(_) { false } } literal in struct context lacks type context for the v2 bootstrap compiler's lambda inference. Use the named grammar_empty_sync_tokens() helper (added in e993ff5 for rust/java) instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * T-7 parse: separate production identity from parse-capture; add duplicate production validation - parse_production: replace emitted.kind + appended child conflation with a TypeNode{Conj} wrapper carrying two labeled edges: grammar_production_identity_node_projection → production.emitted grammar_production_captured_node_projection → captured Eliminates the conflation finding: the emitted node is now the identity sub-tree, not the output carrier. - grammar_has_duplicate_productions: fold_list over root.productions with a GrammarDupCheckState{seen: Set<Symbol>; has_duplicate: Bool} accumulator, using set_symbol_insert to grow the seen set. Wired into parse() between grammar_has_undefined_nonterminals and grammar_has_left_recursion. - Data symbols: grammar_production_identity_node_projection, grammar_production_captured_node_projection, parse_grammar_duplicate_production added. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix parse_skip_to_sync silent token-drain; add T-8 terminal payload bridge parse_skip_to_sync now returns ParseSyncState instead of .result, preserving found_sync through the call boundary. parse_repeat_error_recover gates on found_sync first — if false, accepts current accumulator without touching the token stream. When found_sync is true, a secondary length guard ensures the sync token was not at position 0 (no-progress guard, prevents re-entry loop); only then does recovery resume parse_repeat with synced.result. Previously: found_sync was discarded, synced = Empty when no sync token exists, length(Empty) != length(non-empty tokens) was true, so parse_repeat was called with an empty token stream — silently draining all remaining input and treating failed recovery as successful. Affected all grammars using grammar_empty_sync_tokens(). Terminal arm in parse_expr: adds explicit T-8 bridge comment marking tok.lexeme, tok.file, tok.start, tok.end as staged — the node substrate has no String/Nat literal-value edge kind, so carrying those facts requires T-8 source-text anchoring. The discard is intentional and now documented rather than silent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix Nonterminal arm: wrap parse_expr result with production.emitted identity The Nonterminal arm was passing parse_expr(production.expression, ...) through directly, discarding production.emitted for every non-start production — leaving the parse tree with no production boundary markers below the root, violating P2 facts-flow-forward. Fix: match on parse_expr result and wrap ParseExprAccepted.captured in the same Conj structure parse_production uses for the start production: TypeNode{Conj} → [identity→production.emitted, captured→captured] ParseExprRejected passes through unchanged. remaining is preserved so the outer expression (Sequence, etc.) can continue after the nonterminal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * WIP: wartime * fix(parse): fuel-based termination proof for Nonterminal arms; fold Repeat arm eliminates parse_repeat SCC Add `fuel: Nat` parameter to `expr_is_nullable`, `expr_reaches_nonterminal_left`, and `parse_expr` so the v2 complexity analyzer can prove termination on cross-production Nonterminal expansions (Succ.prev descent = StrictSubValue on the fuel parameter). Add `grammar_fuel` to compute an upper bound from production count. Replace the `parse_repeat`/`parse_repeat_error_recover` mutual-recursion SCC with an inline fold_list over the token list in `parse_expr`'s Repeat arm using `ParseRepeatAcc`. `fold_list` terminates by structural recursion on its list; the lambda's `parse_expr` call has `expr: Repeat.element` (StrictSubValue). All self-calls in every affected function now carry at least one StrictSubValue — complexity analysis emits 0 diagnostics. Remove dead `parse_e1_repeat_recovery` symbol/diagnostic (only used in the deleted SCC). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(test/claim/parse): fix Token.start/end Int type; add dangling-nonterminal claim grammar_validation.dag: - Token fixture used Nat constructor Zero for Int fields Token.start/end; replaced with integer literal 0. - Removed now-unused v4.std.nat import. - Added validation_dangling_ref_root: start production is present but its expression references validation_other (absent from productions). Exercises the expr_has_undefined_nonterminal fold path — not the grammar_lookup_production short-circuit — so a regression in the reference-closure walk cannot silently pass. - Added claim_dangling_nonterminal_in_expression_detected covering this path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(parse/algebra): P3+P2 tracked bridges for list_skip and grammar_root_to_node algebra.dag — list_skip: - Removed incorrect "fail-closed" claim on the n<=0 branch. Returning xs unchanged for negative n is silent normalization, not fail-closed (P3). - Added 🟡 gated tracked-bridge comment documenting: Nat/UInt cannot be used here (nat.dag imports algebra.dag — circular), so the n<0 caller contract violation is undetectable at the type boundary. All production callers guarantee n>=0. Dissolution: shared PositiveInt/NonNegativeInt module above algebra.dag or Nat-typed callers. 02_parse.dag — grammar_root_to_node: - Added grammar_root_sync_tokens_node_projection data constant (names the intended edge; P2 single-authority declaration). - Added 🟡 gated comment to grammar_root_to_node explaining why sync_tokens is excluded: Set<Symbol> = { member: fn(Symbol)->Bool } is a predicate closure — the Node substrate has no edge kind for function values. Dissolution: substrate literal-value edge for predicate sets, or Set<Symbol> reified as an explicit membership list. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * WIP: wartime * fix(tokenize): fuel-based termination proof for lex_match_pattern/lex_repeat_pattern SCC Add fuel: Nat to lex_match_pattern, lex_repeat_pattern, lex_try_rule, lex_try_rules, and lex_walk (formerly lex_walk_step + lex_walk mutual recursion, now inlined). lex_repeat_pattern: Succ { prev: rest_fuel } → rest_fuel descent breaks the lex_match_pattern ↔ lex_repeat_pattern mutual-recursion SCC. lex_walk: Succ { prev: rest_walk } → rest_walk descent for source-scan loop. lex_match_pattern RepeatPattern/OneOrMorePattern/DelimitedPattern arms use Succ descent; Sequence/Choice/Optional arms use structural pattern descent. Resolves 7 ComplexityUnknown errors in --target rust compile (T-22 receipt). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * WIP: wartime * WIP: wartime * WIP: wartime * fix(parse): extract expr_direct_left_corner_step to resolve Coproduct/Container type mismatch The Sequence arm of expr_direct_left_corner_list had an if/else where the true branch returned via list_append (Container) and the false branch returned via direct recursive call (Coproduct). Lens-CI rejected the incompatible branch types. Extracting expr_direct_left_corner_step with explicit -> List<Symbol> return type forces both branches into the same type representation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * fix(parse): rename left/right to lhs/rhs in expr_direct_left_corner_step to avoid named-param collision * fix(parse/algebra): address openai-pro review findings — list_skip and parse_expr list_skip: n < 0 now returns Empty (visibly wrong, fails fast at callers) rather than xs (indistinguishable from n=0 no-op). Gate comment updated. parse_expr: remove visited: List<Symbol> parameter entirely. The cycle guard was over-broad — it rejected any production re-entry regardless of position, breaking right-recursive grammars like type_expr's generic parameter recursion. Termination is guaranteed by grammar_fuel measure (decrements per Nonterminal expansion; left-recursive grammars are pre-rejected by grammar_has_left_recursion). grammar_validation.dag: add TestClaim rows pinning the validator boundary: left-recursion detected, right-recursion accepted by validator. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(parse): inline Sequence arm — put nullable-conditional inside list_append right: arg The v2 bootstrap compiler fails to unify if/else branch types when the if-branch calls list_append and the else-branch calls a recursive function: if nullable { list_append(left: recursive, right: recursive) } else { recursive } → Coproduct(FreeMonoid) vs Container(List,Primitive(Symbol)) Fix: remove expr_direct_left_corner_step helper entirely; inline the Sequence arm using list_append where the nullable-conditional is the right: argument: list_append( left: expr_direct_left_corner_list(expr: left, ...), right: if nullable { expr_direct_left_corner_list(expr: right, ...) } else { Empty } ) Both branches of the inner if/else are now List<Symbol> (recursive call and Empty), and list_append anchors the outer type. Semantically equivalent: appending Empty is identity. * fix(parse): convert if/else to match in expr_direct_left_corner_step Lens-CI enforces that if/else branches have the same type representation. `list_append` returns Coproduct(FreeMonoid) while `expr_direct_left_corner_list` returns Container(List,Symbol); match arms are checked against the declared return type instead of against each other, eliminating the mismatch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(parse): use grammar_empty_corner_list() to anchor List<Symbol> in if/else else-branch The v2 bootstrap compiler infers Empty as Coproduct(FreeMonoid) (unknown element type) when used in an if/else else-branch where the if-branch returns List<Symbol>. This causes 'if branches resolve to incompatible types'. Fix: wrap Empty in grammar_empty_corner_list() -> List<Symbol> so the else-branch type is unambiguously List<Symbol>. The if/else branch types now unify. * fix(dag): use wave1_canonical_symbols in dag_language_model_wave1_modeled wave1_modeled() was using dag_language_model_surface_prelude_atoms() for canonical_symbols, omitting grammar_carries_symbol(dag_wave1_grammar(), sym). Resolve consumes canonical_symbols to gate symbol emission — grammar-owned symbols (dag_surface_module etc.) would be accepted by the parser but then rejected as unbound by the pipeline LM. Use dag_language_model_wave1_canonical_symbols() to match wave1(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * fix(eval): thread RuntimeValue through transfer via runtime_value_resolved_type Continue and Return arms were returning the unchanged frame, discarding the carried RuntimeValue — violating P2 (facts-flow-forward). Update frame.current to runtime_value_resolved_type(value: v) so the value flows forward into the next EvaluationFrame. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(parse): grammar_fuel = production_count + token_count for correct parse bound production_count alone fails right-recursive grammars: A = "x" A? with 1 production and N tokens needs N Nonterminal expansions, but grammar_fuel=1 exhausted after the first recursive call, leaving remaining tokens. Fix: grammar_fuel(root, tokens) = nat_add(production_count, token_count). - token_count bounds right-recursive chains (each successful non-nullable Nonterminal expansion consumes ≥1 token → depth ≤ token_count) - production_count bounds nullable-nonterminal indirection chains (A=B, B="x" needs 2 expansions for 1 token; production_count covers the slack) - Combined sum covers all non-left-recursive grammars on any input Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(parse): remove unused parse_walk_not_realized dead code parse_walk_not_realized symbol and parse_walk_not_realized_diagnostic were never called after the VoidGrammar path was wired to parse_g0_tokens_remain_diagnostic. P5 hygiene cleanup flagged by cursor review 17798. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(parse): restore NodeFold and fold_node to v4.std.node import Both are used at 02_parse.dag:359 (grammar_symbol_fold return type) and 02_parse.dag:389 (grammar_carries_symbol body), but were missing from the import list — unresolved references caught by codex review 17805. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(eval): add 🟡 gate to v4_eval_wave1_transfer with dissolve-on condition Documents the wave-1 interim: step_loop is fail-closed so transfer is structurally unreachable in wave-1 execution. Continue/Return thread runtime_value_resolved_type(v) (the value's type Node) into frame.current — maximum Node-level fidelity before T-22/T-8 RuntimeValue→Node reification. Forbidden clauses capture both prior failure modes (unchanged frame = P2 violation; reject = more severe P2 violation). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * fix(algebra): list_skip n<0 returns xs (identity) not Empty; update text.dag header n<0 returning Empty fabricated a valid-looking FreeMonoid from an invalid call (P3 violation). Returning xs (the identity for n<=0) is a proper gated interim under the caller-guarantees-non-negative contract. n=0 case is unchanged (still identity/correct). Compiler callers (01_tokenize, 02_parse, text.dag) already migrated to structural match / fold-based alternatives in 1a309d3 — list_skip is retained for external callers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(algebra): delete orphaned list_skip and ListSkipState All three compiler callers (01_tokenize.dag, 02_parse.dag, std/text.dag) were migrated to structural match / fold-based alternatives in 1a309d3. list_skip now has zero consumers — shipping orphaned std substrate with a known n<0 partial-function gap would be worse than deletion. ListSkipState is internal to list_skip and goes with it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(algebra): add list_tail<T> — move FreeMonoid head-pop to stdlib Lens-CI --target rust compile rejects Empty/Cons variant match patterns outside the module where FreeMonoid is defined (algebra.dag). The three call sites that pattern-matched directly on FreeMonoid<T> after list_skip was deleted (text.dag:string_tail, 01_tokenize.dag:lex_match_prefix fold lambda, 02_parse.dag:parse_expr Terminal arm) now call list_tail instead. list_tail<T> is defined in algebra.dag where coproduct matching is valid. Empty/Cons remain imported as value constructors in caller modules. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * WIP: wartime * WIP: Fuel rework * WIP: Fuel rework * fix(parse): bind grammar_root_to_node P2-boundary gate to T-8 The gate on grammar_root_to_node lacked a concrete dissolution receipt — it named a generic future substrate capability rather than a bounded task. P5 requires scaffolds to name a concrete dissolution lane. Bind to TASKS.md T-8: sync_tokens projection dissolves when Set<Symbol> is reified as an explicit membership list type at T-8, which is the same milestone that wires sync_tokens into the error-recovery flow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(parse): replace GrammarDupCheckState with coproduct; add Practice 10 tags Two cursor review findings on PR #3655: 1. GrammarDupCheckState { seen, has_duplicate: Bool } was a hand-rolled in-band sum (Practice 2 / P5 violation) — invalid states (has_duplicate true with valid seen) remained representable. Replaced with a declared coproduct: GrammarDupCheckUnique { seen } | GrammarDupCheckDuplicate. grammar_dup_check_step now matches on variants; grammar_has_duplicate_productions matches the fold result instead of projecting .has_duplicate. 2. grammar_has_left_recursion, grammar_has_undefined_nonterminals, and grammar_has_duplicate_productions were has_*→Bool validators with no Practice 10 receipt, while neighboring expr-level walkers are tagged. Add 🟡 gated Practice 10 tags bound to node://adhoc-2145db6b-69a. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(v4/lex): block-comment star lookahead + drop unconsumed nat_monus Replace Sequence(star + BlockCommentAfterStarChar) with ConsumeCharUnlessFollowedByPattern — rejects consuming '*' when followed by '/' (the close delimiter), fixing /**/ and /***/ fidelity without eating the closing star. Remove nat_monus (zero callers after ParseTable landed); update grammar_validation header comment. Co-authored-by: Cursor <cursoragent@cursor.com> * WIP: Fuel rework * fix(tokenize): try-close-first DelimitedPattern; P3/P5 list_tail gate; P2 wave1_modeled delegation - DelimitedPattern arm rewritten with try-close-first fold semantics and DelimitedState accumulator, fixing /**/ and /***/ block comment tokenization. BlockCommentAfterStarChar class removed (no longer needed); dag_block_comment body simplified to single-char ChoicePattern. - list_tail in algebra.dag gets Practice 10 P3/P5 gate (bind node://adhoc-2145db6b-69a). - dag_language_model_wave1_modeled delegates to dag_language_model_wave1() instead of duplicating its body, eliminating the P2 second construction authority. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * revert(v4/lex): drop ConsumeCharUnlessFollowedByPattern LexPattern variant Try-close-first DelimitedPattern (from #3655) already handles block-comment close boundaries; the extra lex_match_pattern arm violated the substrate derivation gate. Restore plain ChoicePattern { BlockCommentTextChar | CharPattern{42} } body in dag.dag. Co-authored-by: Cursor <cursoragent@cursor.com> * WIP: Fuel rework * WIP: Fuel rework * WIP: Fuel rework * WIP: Fuel rework * WIP: Fuel rework * WIP: Fuel rework * fix: add P5(b) receipt for parse-table smoke test Pair the new v4_compiler_parse_table_dag_smoke_test harness with INVARIANTS §P5 Mechanism (b) artifacts: SG-0 census line, INVARIANTS table row, and sg0-pr-body-append.3661.txt for CI net +1 pairing on PR #3661. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: SG-0 pairing (c) dispatch wording for CI discipline gate The net +1 append for parse-table smoke test must include "dispatch" and a qualified tracker ref on the pairing line per check-pr-sg0-net-shrink-discipline.sh. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: parse_production uses root-owned production by name only Replace the separate GrammarProduction parameter with production_name: lookup the production from root before table lookup and wrap, so parse and emit share one authority and mixed-expression callers fail closed. Co-authored-by: Cursor <cursoragent@cursor.com> * WIP: Fuel rework * WIP: Fuel rework * WIP: Fuel rework --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Advances v4 from structural-compile-only to executable walks. Three pipeline stages and one extdep concrete runtime bundle are now realized:
Test plan
Attestation