Skip to content

Implement output provenance inference for non-recursive functions - #405

Merged
briansrls merged 1 commit into
mainfrom
claude/non-recursive-body-inference-j51Y4
Apr 12, 2026
Merged

briansrls merged 1 commit into
mainfrom
claude/non-recursive-body-inference-j51Y4

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Summary

This PR implements full output provenance inference for non-recursive function bodies, replacing the previous stub implementation. The system now walks typed expression trees to determine how function outputs relate to their parameters, enabling more precise data flow analysis.

Key Changes

  • New BodyProvenanceCtx struct: Carries state through expression walks including parameter set, binding provenances, type environment, and function signatures.

  • Expression provenance inference (infer_expr_provenance): Recursively walks typed expressions to compute output provenance maps. Handles:

    • Variable references (parameters and local bindings)
    • Field access (composes base provenance with field relations)
    • Block expressions (extends binding context for let-statements)
    • Let-bindings (tracks local variable provenances)
    • Match expressions (merges arm provenances with pattern bindings)
    • If expressions (merges then/else branches)
    • Function calls (composes caller and callee provenances)
    • Method calls (transparent for collection operations like filter, map)
    • Casts and returns (propagate inner provenance)
  • Branch merging (merge_provenance_branches): Implements lattice meet semantics for control flow joins. Parameters appear in results only if present in all non-empty branches, with values merged via meet_sub_value.

  • Call composition (compose_call_body_provenance): Maps callee parameters to caller arguments (by name or position) and composes output relations transitively. Uses non-recursive argument classification to avoid mutual recursion.

  • Argument classification (classify_arg_body_provenance): Lightweight provenance lookup for call arguments without recursion, handling variables and falling back to empty for complex expressions.

  • Integration in populate_output_provenance: Body inference is attempted first for non-recursive functions with bodies. Falls back to hardcoded tables (child accessors, size-reducing functions) when inference produces empty results, maintaining backward compatibility.

Implementation Details

  • Uses stacker::maybe_grow in infer_expr_provenance to handle deep expression trees safely.
  • Fail-closed design: returns empty maps for unhandled expression shapes.
  • Transitive provenance works within modules via fold accumulator threading in populate_output_provenance.
  • Maintains existing hardcoded provenance knowledge as fallback for functions where body inference cannot determine relations.

Testing

Diagnostic ratchet updated from 424 to 421, reflecting improved inference reducing diagnostic count.

https://claude.ai/code/session_01SnVJJPJua86vgbS96hcvmW

@briansrls

briansrls commented Apr 12, 2026 •

Copy link
Copy Markdown
Contributor Author

ChatGPT Review

Grounded in the thesis, this PR is pushing on the right bottleneck: output provenance on function signatures is the stated M1 blocker, and the end-state architecture is to compute these facts in inference and carry them through the IR rather than reconstruct them later. So the direction is right. I would still request changes before merge, because the new path can currently attach the wrong cause to a value, which is below the repo’s “if it compiles, the intent is sound” bar. chatgpt-review-7f6f605a-0b86-44…

chatgpt-review-201af2a9-c029-46…

The biggest problem is name-based provenance collapsing. In both classify_arg_body_provenance and infer_expr_provenance, ExprVar checks param_set before binding_provs, so a shadowing local or pattern-bound name can never override a parameter with the same spelling. A function like fn f(x, y) { let x = y; x } will still infer the return as preserved-from-parameter-x, not from the local binding. That is exactly the “shadowed bindings collapsed by name” failure mode the invariants reject for new semantic boundaries. This is not just an implementation detail; it means the new fact table is lossy at construction time. The fix is to drive provenance from binding identity on the typed var reference, not raw names. A local-before-param check would help one case, but the real repair is stable binding identity. chatgpt-review-7d905c07-a872-42…

chatgpt-review-201af2a9-c029-46…

Second, positional call composition is wrong in compose_call_body_provenance. For any callee parameter not found by name, the code falls back to positional_args |> first, so every unmatched parameter maps to the first positional argument. For f(a, b) called as f(x, y), both a and b compose through x. More fundamentally, callee_param_map is a map keyed by param name, so it does not carry the ordered authority needed to reconstruct positional calls at all. This is a boundary-sufficiency issue, not a local bug: the signature boundary needs ordered params or param ordinals, otherwise positional composition should fail closed. The roadmap already notes that param identity is still string-keyed; this diff runs directly into that limitation. chatgpt-review-7d905c07-a872-42…

chatgpt-review-201af2a9-c029-46…

Third, merge_provenance_branches is mathematically wrong for a meet-based merge and semantically conflates “unsupported” with “no provenance.” It initializes the merge with PreservedValue, then folds with meet_sub_value, so even branches that all return the same strict/arithmetic descent collapse to preserved. On top of that, it drops empty maps before merging, but an empty map here means two different things: “this expression really has no provenance” and “the walker could not classify this expression.” Those are not the same state. As written, an if/match wrapper can both lose real descent and invent preserved provenance for a branch set that should not have any shared relation. This violates the invariant that a boundary fact table must be an exact derivation from upstream structure, and it is the kind of fabricated fallback the thesis is trying to eliminate. The fix is to use a structurally distinct result type for unsupported analysis vs proven-empty provenance, and to start the meet from the first branch relation or a true top element, not PreservedValue. chatgpt-review-7d905c07-a872-42…

chatgpt-review-7f6f605a-0b86-44…

Fourth, match destructuring is still lossy. In the ExprMatch arm handling, every field binding gets the whole scrut_provs map copied onto it. That means Some { value: v }, Pair { left: l }, and Pair { right: r } all inherit the scrutinee relation wholesale instead of composing through the specific bound field. So field-bound values become indistinguishable from the whole scrutinee, which loses exactly the structural information provenance is supposed to surface. The right repair is to compose each binding through its resolved field witness, using the same single authority you already use for field access, rather than copying the scrutinee map unchanged. chatgpt-review-7d905c07-a872-42…

There is also a non-blocking but important compositional concern in ExprMethodCall: this PR adds a fresh string whitelist for “transparent” methods (first, last, skip, take, filter, reverse, enumerate) and a new full ExprData walk. That cuts against two existing constraints: no case enumeration for open sets, and no new parallel walkers unless one is deleted. Several of those methods are not provenance-transparent in the same relation space anyway, especially enumerate, filter, skip, and take. This logic wants to come from structural method semantics or std-level provenance contracts, not another use-site string ladder. I would not block on this alone, but I would avoid expanding it further. chatgpt-review-7d905c07-a872-42…

chatgpt-review-a1219665-489d-48…

The testing delta is too thin for the amount of semantics being added. The only test change in the PR is the diagnostic ratchet moving from 424 to 421 with a comment attributing the drop to non-recursive output provenance inference. That matches the roadmap direction, but it will not catch the specific regressions above. I’d want direct behavioral tests for shadowing, two positional arguments, branch merges with one constant branch, and match destructuring through named field bindings. A ratchet is a good outer signal; it is not a substitute for localized correctness tests. chatgpt-review-201af2a9-c029-46…

chatgpt-review-69aefb79-6853-41…

Roadmap alignment is otherwise good. ROADMAP.md is not modified, but the implementation is clearly aimed at M1/Track 1, and the bootstrap ratchet update is consistent with the current roadmap note that body inference for non-recursive functions is the active step while string-keyed param identity, per-field consumption, and the temporary body walker remain known limitations. So I would describe this as “aligned, but not yet sound enough to bank the mechanism.” chatgpt-review-201af2a9-c029-46…

My recommendation: request changes. The architecture choice is correct, but the current implementation still loses or misattributes provenance in several concrete cases, and that is exactly the kind of causal slippage this project is trying to remove.


View conversation

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0cb3f4e722

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/v2/stage0/src/v2_compiler_infer.rs Outdated
Comment on lines +4024 to +4026
let arg_expr_opt = match v2_rt::map_get(&arg_by_name, callee_pname.clone()) {
Some(ae) => Some(ae.clone()),
None => positional_args.clone().first().cloned(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use parameter index for positional argument mapping

In compose_call_body_provenance, unnamed arguments are resolved with positional_args.first() for every callee parameter, so a call like f(a, b) will compute provenance for both parameters from a. This misattributes output provenance whenever a callee has multiple positional arguments and can propagate incorrect caller/callee composition into descent analysis.

Useful? React with 👍 / 👎.

Comment thread src/v2/stage0/src/v2_compiler_infer.rs Outdated

pub fn merge_provenance_branches(maps: Rc<Vec<Rc<HashMap<String, Rc<SubValueRelation>>>>>) -> Rc<HashMap<String, Rc<SubValueRelation>>> {
{
let non_empty = Rc::new({ let mut __result = Vec::new(); for m in maps.iter().cloned() { if ((Rc::new(v2_rt::map_keys(&m)).len() as i64) > 0) { __result.push(m); } } __result });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep empty branches in provenance merge

merge_provenance_branches filters out empty maps before intersecting branches, but empty maps are also the fallback for unhandled expressions. This means if/match expressions with one analyzable branch and one unanalyzable branch can still produce non-empty provenance (for example, if c { x } else { 0 }), which is unsound because the output is not guaranteed to derive from x on all paths.

Useful? React with 👍 / 👎.

Comment thread src/v2/stage0/src/v2_compiler_infer.rs Outdated
Comment on lines +4158 to +4160
param_set: c.param_set.clone(),
binding_provs: v2_rt::rc_map_insert(c.binding_provs.clone(), bname.clone(), scrut_provs.clone()),
type_env: c.type_env.clone(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compose variant field bindings through matched field

When building arm_ctx for MatchPattern::VariantPattern, each bound field name is inserted with scrut_provs directly, so field bindings are treated as if they preserve the whole scrutinee. Returning a bound field (e.g. tail from Cons { tail }) should carry field-composed subvalue provenance; otherwise strict subvalue/shrink information is lost and helper provenance becomes less accurate.

Useful? React with 👍 / 👎.

@briansrls
briansrls force-pushed the claude/non-recursive-body-inference-j51Y4 branch from 0cb3f4e to cff6f98 Compare April 12, 2026 02:56
@briansrls

briansrls commented Apr 12, 2026 •

Copy link
Copy Markdown
Contributor Author

ChatGPT Review

I reviewed the attached PR #405 diff.

The direction is right. The thesis says correctness dimensions should be computed at binding sites during inference and then carried through the IR, not reconstructed later; the roadmap names output provenance on function signatures as the key M1 blocker, and explicitly calls the current body walker a temporary bootstrap authority while param identity remains string-keyed. This PR is moving in that intended direction by reusing SubValueRelation instead of inventing a second provenance mechanism. chatgpt-review-e460addd-59a7-46…

chatgpt-review-201af2a9-c029-46…

I would still request changes before merge.

The first blocking issue is renamed match destructuring. The language already supports bindings like Some { value: kind } => kind. The new ExprMatch logic uses a single bname = field_binding_name_at(...) both to look up the inductive field (field_name == bname) and to insert the local binding into let_prov. Those are different facts in renamed destructuring: the field name is value, the local binder is kind. So one of the two operations must be wrong. In practice that means either field composition misses or the arm body gets the wrong local name. This is exactly the kind of boundary collapse the repo is trying to eliminate: two semantic identities being represented by one string slot. The fix is to carry both the destructured field label and the introduced local binding name separately. chatgpt-review-69aefb79-6853-41…

chatgpt-review-201af2a9-c029-46…

chatgpt-review-7d905c07-a872-42…

The second blocking issue is the new fallback for non-inductive match fields. When no inductive field matches, the code currently inherits scrut_rel unchanged, with a comment justifying it via Optional.Some { value: v }. That may be acceptable for a specifically modeled transparent wrapper, but applied generically it fabricates provenance for any unmatched field. That violates the repo’s “no fallbacks that fabricate” and “heuristics indicate lost structure” rules: the code is guessing that every unmatched field is transparently provenance-preserving, when the structural witness only exists for some of them. The fix belongs upstream: either model transparent wrapper fields explicitly, or fail closed / return unknown when no field witness exists. chatgpt-review-7d905c07-a872-42…

chatgpt-review-7d905c07-a872-42…

The third blocking issue is block-scope leakage in the new ExprBlock fold. The comment says this is for block-level let bindings with no body, but the implementation carries updated_prov forward for every ExprLet, including the branch where let_body exists. That means a let-expression whose scope should be only its body can leak its binding into later sibling statements in the block. In provenance terms, that is a lexical-scope bug: later reads can inherit a fact that is no longer in scope. Given the thesis’s “if it compiles, the intent is sound” standard, scope facts have to be exact, not “close enough.” The fix is to persist updated_prov only for statement-lets (let_body == none); for let-with-body, evaluate the body under the extended map and then restore the outer map for following siblings. chatgpt-review-e460addd-59a7-46…

chatgpt-review-7d905c07-a872-42…

On invariants: I’d classify the second issue as a direct violation of “No fallbacks that fabricate,” and the first and third as boundary-sufficiency / lost-structure problems. The invariants doc is explicit that when a downstream stage has to guess from names or partial shape, the real fix is to surface the missing fact structurally, not improve the guess. That is the core concern here: this PR improves the output-provenance walker, but two of the new branches still guess where the model should carry exact pattern facts. chatgpt-review-7d905c07-a872-42…

chatgpt-review-7d905c07-a872-42…

On compositional modeling quality: there are no std-library or extdeps modeling changes in this PR, so I do not see a .dag ontology issue of the sort you’d compare against std-library.dag. The positive note is that the PR does reuse the existing SubValueRelation vocabulary and the existing composition authority rather than creating a parallel provenance type, which is exactly the thesis’s preferred unification move. chatgpt-review-e460addd-59a7-46…

chatgpt-review-201af2a9-c029-46…

On roadmap alignment: ROADMAP.md is not modified, and the work is clearly aligned with Track 1 / M1. The roadmap explicitly says output provenance on function signatures is the current blocker and also warns that param identity is still string-keyed and the body walker is temporary. These review findings land right on those known limitations rather than contradicting roadmap direction. chatgpt-review-201af2a9-c029-46…

The test delta is too thin for semantics this sensitive. In the diff, the only test change is the diagnostic ratchet going from 421 to 416. That is useful as an outer signal, but it does not protect the new behavior. This PR needs direct regression tests for renamed field destructuring, non-inductive-field fallback, and let-with-body scoping inside blocks. The thesis and modeling docs both push toward verification falling out from structure; this is exactly the class of change that should have focused semantic tests, not only a ratchet update. chatgpt-review-e460addd-59a7-46…

chatgpt-review-a1219665-489d-48…

So my recommendation is: request changes. The architecture is aligned with the thesis, but the new walker still introduces provenance facts in places where the structure does not fully justify them, and it conflates two identities in renamed destructuring. In this codebase, those are merge blockers.


View conversation

@briansrls
briansrls force-pushed the claude/non-recursive-body-inference-j51Y4 branch from cff6f98 to 38f611b Compare April 12, 2026 03:07
@briansrls

briansrls commented Apr 12, 2026 •

Copy link
Copy Markdown
Contributor Author

ChatGPT Review

I read the thesis first. Verdict: request changes.

I reviewed the attached PR diff and a numbered copy.

Directionally, the PR is pointed the right way. The thesis says correctness dimensions should be computed at binding sites during inference and then carried through the IR, and the roadmap calls output provenance on function signatures the M1 blocker. Reusing SubValueRelation and pushing more inference into classify_body_provenance matches that architecture. chatgpt-review-5843fdf0-06a9-46…

chatgpt-review-6206c230-b328-43…

The blocking problem is the new ExprMatch arm handling. In the numbered diff at lines 47–80 and 212–246, one value, bname, is doing two different jobs: selecting the structural field and naming the local binding. That cannot be sound. Existing tests already use renamed destructuring like Some { value: kind } and Value { inner: v }, so the field label and the bound local are not the same thing. With the current code, either the field lookup is wrong or the inserted local binding key is wrong. This is exactly the kind of boundary-insufficiency the invariants warn about: a downstream step is forced to use a name proxy because the real structural fact is not being carried. chatgpt-review-582f79ec-c1cd-4a…

chatgpt-review-5d0dc841-2293-4c…

There is a second bug inside that same block: the lookup only filters inductive fields by field_name, even though the existing induction authority in std-library.dag models an InductiveField with type_name, variant_name, field_name, shape, and element_type. The PR is dropping variant_name entirely. So even after fixing renamed bindings, arm-field composition is still under-specified for sum types.

The fallback at numbered diff lines 70–75 and 235–237 is also not mergeable. When no inductive field matches, it copies scrut_rel unchanged onto the bound field, justified by the Optional.Some { value: v } case. That is a fabricated fact. It makes every unmatched field look provenance-equivalent to the whole scrutinee, even when the field is non-recursive. Per the invariants, that is “No fallbacks that fabricate,” and per the modeling docs the fix is to surface the missing structural fact, not guess locally. If Some.value needs special treatment, that relation needs to be modeled explicitly, not smuggled through a generic fallback. chatgpt-review-582f79ec-c1cd-4a…

chatgpt-review-cb8189ff-88ef-40…

I also think the new ExprBlock fold is wrong on scope. In the numbered diff at lines 99–113 and 290–303, the fold carries prov: updated_prov forward even when let_body exists. But the standalone ExprLet path still treats the updated map as body-local only. So the new block path changes scope rules: a let-with-body can leak its binding into later sibling statements. That cuts against the repo’s lexical-scope / facts-flow-forward rules. At minimum, only no-body lets should persist in the block accumulator; let-with-body should evaluate under the extended map and then restore the outer map. chatgpt-review-582f79ec-c1cd-4a…

On tests, the diff only changes the bootstrap ratchet from 421 to 416, with no focused regression coverage for the new semantics. For a provenance change this central, a ratchet alone is too weak. The thesis’s whole promise is “sound by construction,” not “ratchet got smaller.” I would want direct tests for renamed destructuring, field-vs-binder identity, non-inductive-field fallback, and block scoping before merge. chatgpt-review-5843fdf0-06a9-46…

chatgpt-review-5d0dc841-2293-4c…

On compositional modeling quality: there are no std-library/extdeps/ROADMAP model changes in this PR, so there is no library .dag delta to review for ontology drift. The positive is that the PR keeps using the existing provenance vocabulary instead of inventing a second one. The negative is that the new match logic stops short of clean composition and reintroduces local guessing exactly where the existing modeling pattern wants an explicit structural witness. chatgpt-review-5843fdf0-06a9-46…

chatgpt-review-cb8189ff-88ef-40…

On roadmap alignment: ROADMAP.md is not modified. The work is aligned with M1 / Track 1, and the roadmap already documents that this body walker is a bootstrap authority while param identity is still string-keyed. So I would not call this roadmap drift; I would call it an aligned step that is still unsound in a few important places. chatgpt-review-6206c230-b328-43…

My recommendation is: request changes. The fix direction is right, but this diff still violates the project’s standard in three concrete ways:

  1. pattern-field provenance conflates field identity and local binding identity,
  2. unmatched fields get fabricated provenance,
  3. block folding appears to leak let-body scope.

The shape of the fix is straightforward: carry both field label and binder name, resolve arm fields by full structural witness rather than field-name-only lookup, delete the generic inherit-scrutinee fallback, and make block accumulation preserve only statement-level lets.


View conversation

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

codex · gpt-5.4 · 38f611b3

Review (INVARIANTS: 4, MODELING: 1+/1-, ROADMAP: 1✓/1!)

INVARIANTS — Violations (4)

ROOT CAUSE ANALYSIS

  • src/v2/04_infer.dag The new body walker re-derives pattern scope from string proxies instead of reusing the existing structural authority in extend_scope_with_pattern_node and derive_field_provenance (M8/M9): field label, binder name, and variant identity are available upstream in MatchPattern and InductiveField, but the new map builder collapses them into one name string, which then propagates wrong output provenance into call-site CX reasoning; the upstream fix is to derive arm let_prov from the typed pattern-scope pipeline, or call those helpers directly, so provenance is keyed by structural bindings rather than strings.
  • src/v2/04_infer.dag Transparent wrapper or unwrapping behavior is not surfaced as a first-class field witness, so the walker guesses after lookup failure; that missing upstream fact propagates into false helper contracts and then into descent analysis. The upstream fix is to model transparent fields explicitly in the existing InductiveField/SubValueRelation vocabulary (M1/M9) and otherwise fail closed instead of inheriting scrutinee provenance.
  • src/v2/04_infer.dag Lexical scope is already computed upstream at binding sites, but classify_body_provenance rebuilds a second name-keyed scope machine for blocks; that parallel authority drops the let-statement versus let-expression distinction and leaks provenance downstream. The upstream fix is to derive body provenance from InferScope and TypeBinding.provenance, or at minimum restore state.prov after let_body-scoped lets so block threading matches the compiler's single scope authority (M1/M8/M9).

MODELING — Strengths

  • src/v2/04_infer.dag BlockProvFoldState is a flat product accumulator rather than a new recursive semantic type or bespoke result or error shape, so it stays within the bounded-kernel rule and composes with fold.

MODELING — Improvements

  • src/v2/04_infer.dag BlockProvFoldState hardcodes binding and parameter identity as Map<String,...>; per M4/M9 and ROADMAP Track 3, this accumulator should use structural binding references or dissolve into the existing InferScope and TypeBinding.provenance pipeline instead of becoming a second string-keyed authority.

ROADMAP — Verified

  • M1 body inference active for non-recursive functions: The diff materially extends classify_body_provenance to cover match arm context, block threading, casts, and returns, which is real Track 1 progress.

ROADMAP — Incomplete

  • M1 output provenance on function signatures: The new body walker still collapses pattern identities, fabricates unmatched-field provenance, and leaks let-body scope, so the blocker is only partially addressed and not yet sound enough for the thesis's zero-bugs standard.

ROADMAP — Unclaimed

  • M1 ratchet baseline: src/v2/tests/src/bootstrap.rs lowers DIAG_RATCHET to 416, but ROADMAP.md still says currently 421, ratchet 421 and does not record the new baseline or the remaining unsoundness.

Track 1 direction is correct and algebraically aligned, but I would not merge this version because the new provenance walker still reconstructs pattern and scope facts unsoundly.

Comment thread src/v2/04_infer.dag
classify_body_provenance(expr: arm_body(n: arm), param_names: param_names, param_types: param_types, type_env: type_env, func_sigs: func_sigs, let_prov: let_prov)
// Extend let_prov with arm bindings composed through their fields.
let arm_let_prov = match arm_pattern(n: arm) {
VariantPattern { name: vname, parent_enum: _, field_bindings: bindings } =>

This comment was marked as resolved.

Comment thread src/v2/04_infer.dag
_ => ""
}
}
if lookup_type != "" {

This comment was marked as resolved.

Comment thread src/v2/04_infer.dag Outdated
Some { value: ind_field } => map_insert(fp_acc, pname, compose_sub_value(base: scrut_rel, field: ind_field))
None =>
// Field is not inductive — inherit scrutinee relation as-is.
// This handles Optional.Some { value: v } where "value" is the

This comment was marked as resolved.

Comment thread src/v2/04_infer.dag
let value = let_value(texpr: stmt)
let value_prov = classify_body_provenance(expr: value, param_names: param_names, param_types: param_types, type_env: type_env, func_sigs: func_sigs, let_prov: state.prov)
let updated_prov = map_insert(state.prov, bname, value_prov)
match let_body(texpr: stmt) {

This comment was marked as resolved.

@briansrls

Copy link
Copy Markdown
Contributor Author

codex · gpt-5.4 · 38f611b3

Review (INVARIANTS: 4, MODELING: 1+/1-, ROADMAP: 1✓/1!)

INVARIANTS — Violations (4)

ROOT CAUSE ANALYSIS

  • src/v2/04_infer.dag The new body walker re-derives pattern scope from string proxies instead of reusing the existing structural authority in extend_scope_with_pattern_node and derive_field_provenance (M8/M9): field label, binder name, and variant identity are available upstream in MatchPattern and InductiveField, but the new map builder collapses them into one name string, which then propagates wrong output provenance into call-site CX reasoning; the upstream fix is to derive arm let_prov from the typed pattern-scope pipeline, or call those helpers directly, so provenance is keyed by structural bindings rather than strings.
  • src/v2/04_infer.dag Transparent wrapper or unwrapping behavior is not surfaced as a first-class field witness, so the walker guesses after lookup failure; that missing upstream fact propagates into false helper contracts and then into descent analysis. The upstream fix is to model transparent fields explicitly in the existing InductiveField/SubValueRelation vocabulary (M1/M9) and otherwise fail closed instead of inheriting scrutinee provenance.
  • src/v2/04_infer.dag Lexical scope is already computed upstream at binding sites, but classify_body_provenance rebuilds a second name-keyed scope machine for blocks; that parallel authority drops the let-statement versus let-expression distinction and leaks provenance downstream. The upstream fix is to derive body provenance from InferScope and TypeBinding.provenance, or at minimum restore state.prov after let_body-scoped lets so block threading matches the compiler's single scope authority (M1/M8/M9).

MODELING — Strengths

  • src/v2/04_infer.dag BlockProvFoldState is a flat product accumulator rather than a new recursive semantic type or bespoke result or error shape, so it stays within the bounded-kernel rule and composes with fold.

MODELING — Improvements

  • src/v2/04_infer.dag BlockProvFoldState hardcodes binding and parameter identity as Map<String,...>; per M4/M9 and ROADMAP Track 3, this accumulator should use structural binding references or dissolve into the existing InferScope and TypeBinding.provenance pipeline instead of becoming a second string-keyed authority.

ROADMAP — Verified

  • M1 body inference active for non-recursive functions: The diff materially extends classify_body_provenance to cover match arm context, block threading, casts, and returns, which is real Track 1 progress.

ROADMAP — Incomplete

  • M1 output provenance on function signatures: The new body walker still collapses pattern identities, fabricates unmatched-field provenance, and leaks let-body scope, so the blocker is only partially addressed and not yet sound enough for the thesis's zero-bugs standard.

ROADMAP — Unclaimed

  • M1 ratchet baseline: src/v2/tests/src/bootstrap.rs lowers DIAG_RATCHET to 416, but ROADMAP.md still says currently 421, ratchet 421 and does not record the new baseline or the remaining unsoundness.

Track 1 direction is correct and algebraically aligned, but I would not merge this version because the new provenance walker still reconstructs pattern and scope facts unsoundly.

@briansrls
briansrls force-pushed the claude/non-recursive-body-inference-j51Y4 branch from 38f611b to d529802 Compare April 12, 2026 04:01
@briansrls

Copy link
Copy Markdown
Contributor Author

ChatGPT review in progress... (view conversation)

Four soundness fixes to classify_body_provenance:

1. Shadowing: check let_prov before param_names in ExprVar. A local
   `let x = y` in scope of parameter `x` now uses the local's
   provenance, not the parameter's.

2. Match destructuring: compose field bindings through inductive fields
   with variant_name in the filter. Uses field_binding_pattern (Bind
   { name }) for the introduced binding name, field_binding_name_at
   for the field label. Fails closed on unmatched fields (no fabricated
   fallback).

3. ExprBlock: fold with BlockProvFoldState tracks block-level let
   bindings. Let-with-body restores outer scope so bindings don't
   leak into subsequent statements.

4. ExprCast/ExprReturn: transparent wrappers delegate to inner
   expression provenance.

https://claude.ai/code/session_01SnVJJPJua86vgbS96hcvmW
@briansrls
briansrls force-pushed the claude/non-recursive-body-inference-j51Y4 branch from d529802 to 378dcb2 Compare April 12, 2026 04:22

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

codex · gpt-5.4 · d5298027

Review (INVARIANTS: 1, MODELING: 1+/1-, ROADMAP: 1✓/1!)

INVARIANTS — Violations (1)

ROOT CAUSE ANALYSIS

  • src/v2/04_infer.dag The upstream gap is that body provenance still rebuilds match scope from a lossy Map<String,...> instead of consuming the existing structural authority in extend_scope_with_pattern_node and derive_field_provenance; that lossy reconstruction turns any non-Bind field pattern into "", drops the nested binder from the arm scope, and downstream output_provenance underreports true source-to-drain links; the upstream fix is to derive arm scope from PatternScopeResult/InferScope and reuse the std/induction.dag SubValueRelation composition path per M4/M8/M9 rather than re-walking bindings ad hoc.

MODELING — Strengths

  • src/v2/04_infer.dag BlockProvFoldState is a local non-recursive product helper in src/v2 and the new walker stays inside the existing SubValueRelation/meet algebra, so the layering and algebraic attachment point are right.

MODELING — Improvements

  • src/v2/04_infer.dag BlockProvFoldState and arm_let_prov use Map<String,...> as identity proxies; per M4/M9 this should collapse into InferScope/PatternScopeResult or structural binding references so body inference reuses the compiler's single scope/provenance authority.

ROADMAP — Verified

  • M1 body inference for non-recursive functions: The diff materially extends non-recursive body inference to cover match-arm scope extension, sequential blocks, casts, and returns.

ROADMAP — Incomplete

  • M1 key blocker: OUTPUT PROVENANCE on function signatures: Nested match destructuring is still dropped during body inference, so the output provenance boundary is still lossy and not yet zero-bug safe.

ROADMAP — Unclaimed

  • M1 body inference coverage update: ROADMAP.md was not updated to record this additional body-form coverage or the remaining nested-pattern limitation.

Directionally correct and materially closer to M1, but the new body walker still drops nested match binders and therefore does not yet meet the thesis's zero-bugs-by-construction bar.

Comment thread src/v2/04_infer.dag
expr: Node
let_prov: Map<String, Map<String, SubValueRelation>>
}
ExprCast =>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Invariant violation: ExprMatch only records arm bindings when field_binding_pattern is a direct Bind, so nested destructuring binders vanish from arm_let_prov and body inference loses real sources, violating Boundary sufficiency.

@briansrls

Copy link
Copy Markdown
Contributor Author

codex · gpt-5.4 · d5298027

Review (INVARIANTS: 1, MODELING: 1+/1-, ROADMAP: 1✓/1!)

INVARIANTS — Violations (1)

ROOT CAUSE ANALYSIS

  • src/v2/04_infer.dag The upstream gap is that body provenance still rebuilds match scope from a lossy Map<String,...> instead of consuming the existing structural authority in extend_scope_with_pattern_node and derive_field_provenance; that lossy reconstruction turns any non-Bind field pattern into "", drops the nested binder from the arm scope, and downstream output_provenance underreports true source-to-drain links; the upstream fix is to derive arm scope from PatternScopeResult/InferScope and reuse the std/induction.dag SubValueRelation composition path per M4/M8/M9 rather than re-walking bindings ad hoc.

MODELING — Strengths

  • src/v2/04_infer.dag BlockProvFoldState is a local non-recursive product helper in src/v2 and the new walker stays inside the existing SubValueRelation/meet algebra, so the layering and algebraic attachment point are right.

MODELING — Improvements

  • src/v2/04_infer.dag BlockProvFoldState and arm_let_prov use Map<String,...> as identity proxies; per M4/M9 this should collapse into InferScope/PatternScopeResult or structural binding references so body inference reuses the compiler's single scope/provenance authority.

ROADMAP — Verified

  • M1 body inference for non-recursive functions: The diff materially extends non-recursive body inference to cover match-arm scope extension, sequential blocks, casts, and returns.

ROADMAP — Incomplete

  • M1 key blocker: OUTPUT PROVENANCE on function signatures: Nested match destructuring is still dropped during body inference, so the output provenance boundary is still lossy and not yet zero-bug safe.

ROADMAP — Unclaimed

  • M1 body inference coverage update: ROADMAP.md was not updated to record this additional body-form coverage or the remaining nested-pattern limitation.

Directionally correct and materially closer to M1, but the new body walker still drops nested match binders and therefore does not yet meet the thesis's zero-bugs-by-construction bar.

@briansrls
briansrls merged commit 1560c14 into main Apr 12, 2026
1 check passed

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

codex · gpt-5.4 · 378dcb2f

Review (INVARIANTS: 2, MODELING: 2+/1-, ROADMAP: 1✓/2!)

INVARIANTS — Violations (2)

ROOT CAUSE ANALYSIS

  • src/v2/04_infer.dag Match and block scope facts already originate upstream in extend_scope_with_pattern_node, derive_field_provenance, scope_after_stmt_node, and TypeBinding.provenance, but the new body walker rebuilds a second String-keyed scope machine; that re-derivation is what drops nested binders now and will keep creating shadow/merge bugs downstream in output_provenance. Upstream fix: make body provenance consume the existing pattern-scope/infer-scope authority end-to-end, then migrate that single authority to structural binding references for Track 3, per M4/M8/M9.

MODELING — Strengths

  • src/v2/00_core.dag Removing ident from Node deletes a duplicate identity channel and makes the kernel more faithful to single-authority structural identity.
  • src/v2/04_infer.dag BlockProvFoldState is a non-recursive product accumulator, so the new block walk stays algebraically grounded as fold state rather than introducing a second recursive semantic type.

MODELING — Improvements

  • src/v2/04_infer.dag BlockProvFoldState should dissolve into the existing scope pipeline or carry structural binding references, not Map<String,...> identity proxies, so the fold state matches M4/M9 instead of reifying a second scope model.

ROADMAP — Verified

  • M1 non-recursive body-walker coverage: The diff really does add ExprMatch arm context, sequential ExprBlock threading, ExprCast passthrough, and ExprReturn passthrough to classify_body_provenance.

ROADMAP — Incomplete

  • M1 body inference for non-recursive functions: The same diff still rebuilds scope ad hoc and leaves nested match destructuring outside the authoritative pattern-scope pipeline, so this blocker is only partially complete.
  • M1 status line currently 420, ratchet 420: The same head commit still has src/v2/tests/src/bootstrap.rs at DIAG_RATCHET = 423, so the roadmap numbers are stale.

The PR resolves four concrete soundness bugs and materially advances M1, but the new body walker still duplicates scope authority instead of consuming the existing one, so it remains short of the thesis's zero-bugs-by-construction bar.

Comment thread src/v2/04_infer.dag
// Uses field_binding_pattern (Bind { name }) for the introduced binding name,
// field_binding_name_at for the field label in inductive field lookup.
// Includes variant_name in the filter. Fails closed on unmatched fields.
let arm_let_prov = match arm_pattern(n: arm) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Invariant violation: ExprMatch rebuilds arm scope into let_prov by binder names instead of consuming the authoritative pattern-scope machinery, violating Boundary sufficiency and No parallel implementations.

Comment thread src/v2/04_infer.dag
// Fold over block statements: extend let_prov for block-level let-bindings,
// return last statement's provenance. Let-with-body restores outer scope
// so the binding does not leak into subsequent statements.
let block_result = fold(expr.children, init: BlockProvFoldState { prov: let_prov, last: empty_prov_map() }, f: (state, stmt) =>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Invariant violation: ExprBlock threads a second ad-hoc provenance scope through BlockProvFoldState instead of the existing inference scope, violating No duplicate representations.

@briansrls

Copy link
Copy Markdown
Contributor Author

codex · gpt-5.4 · 378dcb2f

Review (INVARIANTS: 2, MODELING: 2+/1-, ROADMAP: 1✓/2!)

INVARIANTS — Violations (2)

ROOT CAUSE ANALYSIS

  • src/v2/04_infer.dag Match and block scope facts already originate upstream in extend_scope_with_pattern_node, derive_field_provenance, scope_after_stmt_node, and TypeBinding.provenance, but the new body walker rebuilds a second String-keyed scope machine; that re-derivation is what drops nested binders now and will keep creating shadow/merge bugs downstream in output_provenance. Upstream fix: make body provenance consume the existing pattern-scope/infer-scope authority end-to-end, then migrate that single authority to structural binding references for Track 3, per M4/M8/M9.

MODELING — Strengths

  • src/v2/00_core.dag Removing ident from Node deletes a duplicate identity channel and makes the kernel more faithful to single-authority structural identity.
  • src/v2/04_infer.dag BlockProvFoldState is a non-recursive product accumulator, so the new block walk stays algebraically grounded as fold state rather than introducing a second recursive semantic type.

MODELING — Improvements

  • src/v2/04_infer.dag BlockProvFoldState should dissolve into the existing scope pipeline or carry structural binding references, not Map<String,...> identity proxies, so the fold state matches M4/M9 instead of reifying a second scope model.

ROADMAP — Verified

  • M1 non-recursive body-walker coverage: The diff really does add ExprMatch arm context, sequential ExprBlock threading, ExprCast passthrough, and ExprReturn passthrough to classify_body_provenance.

ROADMAP — Incomplete

  • M1 body inference for non-recursive functions: The same diff still rebuilds scope ad hoc and leaves nested match destructuring outside the authoritative pattern-scope pipeline, so this blocker is only partially complete.
  • M1 status line currently 420, ratchet 420: The same head commit still has src/v2/tests/src/bootstrap.rs at DIAG_RATCHET = 423, so the roadmap numbers are stale.

The PR resolves four concrete soundness bugs and materially advances M1, but the new body walker still duplicates scope authority instead of consuming the existing one, so it remains short of the thesis's zero-bugs-by-construction bar.

@briansrls
briansrls deleted the claude/non-recursive-body-inference-j51Y4 branch June 1, 2026 18:41
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.

2 participants