From 62ee0d8d98de4fed980a11f2131fa0dda752031d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 24 Jun 2026 13:51:57 +0000 Subject: [PATCH 1/6] WIP: Wiring-liveness wave-2(b): fn-arrow reflection builtin + corpus-wide len --- src/v1/stage0/src/coproduct_reflection.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index 460541cd51f..2583fd77a00 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -4,7 +4,8 @@ use std::rc::Rc; use crate::v1_compiler_infer_items::ItemKind; use crate::v1_interpreter::{InterpContext, InterpError, InterpResult, Value}; use crate::v1_std_core::{ - authored_name_at, field_node_type_expr, inferred_to_node, Connective, Node, + authored_name_at, expr_var_name_at, field_node_type_expr, inferred_to_node, + param_node_name_at, Connective, ExprData, NewlineIndex, Node, VarBindingKind, }; pub(crate) const NULLARY_PAYLOAD_TYPE_NAME: &str = "coproduct_nullary_payload"; From e156c78c1fdd0f47a3a37382182ce0fb06b383ab Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 24 Jun 2026 14:02:44 +0000 Subject: [PATCH 2/6] WIP: Wiring-liveness wave-2(b): fn-arrow reflection builtin + corpus-wide len --- src/v1/stage0/src/coproduct_reflection.rs | 144 ++++++++++++++++++ src/v1/stage0/src/v1_interpreter.rs | 24 +++ .../coproduct_reflection_conformance_test.rs | 10 ++ src/v2/lens/wiring_liveness.dag | 69 ++++++++- src/v2/lens/wiring_liveness_corpus_test.dag | 118 ++++++++++++++ src/v2/std/fn_index.dag | 21 +++ 6 files changed, 382 insertions(+), 4 deletions(-) create mode 100644 src/v2/lens/wiring_liveness_corpus_test.dag create mode 100644 src/v2/std/fn_index.dag diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index 2583fd77a00..2b09ad418fd 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -493,6 +493,150 @@ pub fn eval_concept_decl_facts_live( Ok(crate::v1_interpreter::list_value(rows)) } +fn edge_positional(ctx: &InterpContext, target: Value) -> Value { + Value::Record { + type_name: ctx.sym("Edge"), + fields: Rc::new(HashMap::from([ + ( + ctx.sym("label"), + Value::Variant { + type_name: ctx.sym("EdgeLabel"), + variant_name: ctx.sym("Positional"), + fields: Rc::new(HashMap::new()), + }, + ), + (ctx.sym("target"), target), + ])), + } +} + +fn atom_identity_node(ctx: &InterpContext, identity: &str) -> Value { + node_record( + ctx, + node_kind_type_node(ctx, atom_connective_variant(ctx, identity)), + vec![], + ) +} + +// Project a fn body's internal expression tree onto a substrate Node skeleton. +// A reference to a declared parameter (an `ExprVar` resolved to a `LocalValueBinding` +// whose name matches a declared param) becomes an identity-bearing `Atom` leaf -- +// byte-identical to the declared-input atom `eval_fn_arrow_decl_facts_live` emits, so +// `v2.lens.wiring_liveness` matches it under Node equality. Every other node becomes a +// neutral `Conj` container whose positional children are the marshaled sub-expressions. +// Identity lives ONLY on genuine parameter-use leaves, so a declared parameter is +// structurally reachable from the body output iff it is genuinely referenced -- callees +// (`FunctionValueBinding`), constructors (`VariantValueBinding`) and field names cannot +// collide with a dead parameter atom. (Residue: a `let`/lambda local that shadows a +// parameter name is `LocalValueBinding`; see the lens construction_justification.) +fn marshal_fn_body_skeleton( + ctx: &InterpContext, + node: &Rc, + param_names: &[String], + si: &Rc>>, +) -> Value { + let var_name = if !node.name.is_empty() { + node.name.clone() + } else { + expr_var_name_at(node.clone(), si.clone()) + }; + let is_param_use = matches!( + node.expr_data.as_ref(), + ExprData::ExprVar { binding_kind: Some(bk), .. } + if matches!(bk.as_ref(), VarBindingKind::LocalValueBinding) + ) && param_names.iter().any(|p| p.as_str() == var_name.as_str()); + if is_param_use { + return atom_identity_node(ctx, &var_name); + } + let mut edges: Vec = Vec::with_capacity(node.children.len()); + for child in node.children.iter() { + edges.push(edge_positional( + ctx, + marshal_fn_body_skeleton(ctx, child, param_names, si), + )); + } + if let Some(inner) = node.body.as_ref() { + edges.push(edge_positional( + ctx, + marshal_fn_body_skeleton(ctx, inner, param_names, si), + )); + } + node_record( + ctx, + node_kind_type_node(ctx, nullary_connective_variant(ctx, "Conj")), + edges, + ) +} + +fn fn_arrow_param_record(ctx: &InterpContext, param_name: &str) -> Value { + Value::Record { + type_name: ctx.sym("FnArrowParam"), + fields: Rc::new(HashMap::from([ + (ctx.sym("name"), Value::Str(param_name.to_string())), + (ctx.sym("node"), atom_identity_node(ctx, param_name)), + ])), + } +} + +// Corpus-wide fn/arrow reflection: the gunbc#5364 widen trigger named in +// `v2.lens.wiring_liveness`'s construction_justification. Sibling of +// `eval_concept_decl_facts_live` (which filters to `ItemKind::TypeItem`); this yields +// one `FnArrowDecl` per declared function across every loaded module -- the body +// projected to a reachability skeleton (`output`) plus its declared parameter atoms +// (`params`) -- so the wiring lens folds over REAL fn params corpus-wide, not synthetic +// arrows. Host SOURCE half; dissolves with `concept_decl_facts_live` on the same #5364 +// corpus-as-node accessor. +pub fn eval_fn_arrow_decl_facts_live( + ctx: &InterpContext, + _args: &[(Option, Value)], +) -> InterpResult { + let si = ctx.source_indices(); + let mut rows: Vec = Vec::new(); + for module in ctx.modules.iter() { + for item in module.items.iter() { + let name = authored_name_at(si.clone(), item.clone()); + if name.is_empty() { + continue; + } + let info = module + .item_registry + .get(&name) + .or_else(|| module.item_registry.get(&item.name)); + let Some(info) = info else { continue }; + if info.kind != ItemKind::FnItem && info.kind != ItemKind::FuncItem { + continue; + } + let Some(body) = item.body.as_ref() else { + continue; + }; + let mut param_names: Vec = Vec::new(); + for p in item.params.iter() { + let pn = param_node_name_at(p.clone(), si.clone()); + if pn.is_empty() || param_names.iter().any(|q| q == &pn) { + continue; + } + param_names.push(pn); + } + let output = marshal_fn_body_skeleton(ctx, body, ¶m_names, &si); + let params: Vec = param_names + .iter() + .map(|pn| fn_arrow_param_record(ctx, pn)) + .collect(); + let qualified_name = logical_qualified_name(&info.module_name, &name); + rows.push(Value::Record { + type_name: ctx.sym("FnArrowDecl"), + fields: Rc::new(HashMap::from([ + (ctx.sym("qualified_name"), Value::Str(qualified_name)), + (ctx.sym("name"), Value::Str(name.clone())), + (ctx.sym("output"), output), + (ctx.sym("params"), crate::v1_interpreter::list_value(params)), + ])), + }); + } + } + Ok(crate::v1_interpreter::list_value(rows)) +} + pub fn eval_syntactic_coproduct_arm_keys( ctx: &InterpContext, args: &[(Option, Value)], diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 7117b8ccfc2..8c6ef56aa34 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -2073,6 +2073,8 @@ pub(crate) const STD_NODE_QUERY_BRIDGE_FNS: &[&str] = &["coproduct_nullary_inhab pub(crate) const STD_CONCEPT_INDEX_BRIDGE_FNS: &[&str] = &["concept_decl_facts_live"]; +pub(crate) const STD_FN_INDEX_BRIDGE_FNS: &[&str] = &["fn_arrow_decl_facts_live"]; + pub fn std_node_bridge_fn_names() -> &'static [&'static str] { STD_NODE_BRIDGE_FNS } @@ -2085,6 +2087,10 @@ pub fn std_concept_index_bridge_fn_names() -> &'static [&'static str] { STD_CONCEPT_INDEX_BRIDGE_FNS } +pub fn std_fn_index_bridge_fn_names() -> &'static [&'static str] { + STD_FN_INDEX_BRIDGE_FNS +} + fn is_v4_std_node_bridge_call(ctx: &InterpContext, func_name: &str) -> bool { if !STD_NODE_BRIDGE_FNS.contains(&func_name) { return false; @@ -2112,6 +2118,15 @@ fn is_v4_std_concept_index_bridge_call(ctx: &InterpContext, func_name: &str) -> .is_some_and(|info| info.module_name == "v2.std.concept_index") } +fn is_v4_std_fn_index_bridge_call(ctx: &InterpContext, func_name: &str) -> bool { + if !STD_FN_INDEX_BRIDGE_FNS.contains(&func_name) { + return false; + } + ctx.item_registry + .get(func_name) + .is_some_and(|info| info.module_name == "v2.std.fn_index") +} + fn is_v4_std_lexing_bridge_call(ctx: &InterpContext, func_name: &str) -> bool { if !STD_LEXING_BRIDGE_FNS.contains(&func_name) { return false; @@ -2175,6 +2190,15 @@ fn eval_call(node: &Rc, env: &Rc, ctx: &InterpContext) -> InterpResul }; } + if is_v4_std_fn_index_bridge_call(ctx, &func_name) { + return match func_name.as_str() { + "fn_arrow_decl_facts_live" => { + crate::coproduct_reflection::eval_fn_arrow_decl_facts_live(ctx, &args) + } + _ => unreachable!("fn_index bridge fn set mismatch"), + }; + } + match func_name.as_str() { "fold_list" => return eval_fold_list_native(&args, env, ctx), "fold_list_right" => return eval_fold_list_right_native(&args, env, ctx), diff --git a/src/v1/tests/src/coproduct_reflection_conformance_test.rs b/src/v1/tests/src/coproduct_reflection_conformance_test.rs index 1ce91914eed..dac1cddd5d1 100644 --- a/src/v1/tests/src/coproduct_reflection_conformance_test.rs +++ b/src/v1/tests/src/coproduct_reflection_conformance_test.rs @@ -89,6 +89,12 @@ fn coproduct_reflection_std_node_bridge_fns_are_intercept_wired() { "eval_call intercept must wire v2.std.concept_index bridge `{name}`" ); } + for name in v1_interpreter::std_fn_index_bridge_fn_names() { + assert!( + source.contains(&format!("\"{name}\" =>")), + "eval_call intercept must wire v2.std.fn_index bridge `{name}`" + ); + } assert!( source.contains("is_v4_std_node_bridge_call"), "eval_call must gate v2.std.node bridge dispatch" @@ -101,6 +107,10 @@ fn coproduct_reflection_std_node_bridge_fns_are_intercept_wired() { source.contains("is_v4_std_concept_index_bridge_call"), "eval_call must gate v2.std.concept_index bridge dispatch" ); + assert!( + source.contains("is_v4_std_fn_index_bridge_call"), + "eval_call must gate v2.std.fn_index bridge dispatch" + ); } #[test] diff --git a/src/v2/lens/wiring_liveness.dag b/src/v2/lens/wiring_liveness.dag index 1c3687516e8..2de84580a82 100644 --- a/src/v2/lens/wiring_liveness.dag +++ b/src/v2/lens/wiring_liveness.dag @@ -2,11 +2,13 @@ module v2.lens.wiring_liveness import v2.lens.common.construction_justification { ConstructionJustification, WallAfterGrounding } import std.disposition { RealizationDispatch } -import v2.std.algebra { contains, length, list_snoc_item } +import v2.std.algebra { contains, length, list_append, list_snoc_item } import v2.std.collection { List } -import v2.std.dependency { DependencyView, dependency_lens } +import v2.std.dependency { DependencyView, dependency_lens, ready_set } +import v2.std.fn_index { FnArrowDecl, FnArrowParam, fn_arrow_decl_facts_live } import v2.std.logic { Bool } -import v2.std.node { Node } +import v2.std.node { Node, Symbol } +import v2.std.text { String } type WiringRelation { output: Node @@ -92,7 +94,66 @@ fn wiring_input_is_wired(output: Node, declared_input: Node) -> Bool { ) } +// --- corpus-wide coverage: real fn arrows (gunbc#5364 widen trigger) --- +// +// The wave-1 reachability kernel above decides one (output, declared_input) relation. +// Over a fn arrow the output is the body skeleton and the declared inputs are the +// parameter atoms (v2.std.fn_index reflection). Every parameter shares one output, so +// the reachable set is computed ONCE per fn via `ready_set` -- the same readiness fold +// over `v2.std.dependency`, linear in body size -- rather than re-saturating per +// parameter (the saturate kernel is quadratic per pass; that cost is fine for the small +// synthetic/type-decl witnesses but not for every fn body in the corpus). For a body +// skeleton (a tree rooted at the output) the readiness closure IS the transitive +// containment-reachable set from the output, so a parameter atom is in it iff the +// parameter genuinely reaches the output. This stays the transitive-path question, NOT +// `unused_parameters`'s referenced-at-least-once edge count. + +type WiringDeadWire { + fn_qualified_name: String + param: Symbol +} + +fn wiring_output_reachable_nodes(output: Node) -> List { + ready_set(graph_node: output) +} + +fn wiring_param_reaches_output(reachable: List, declared_input: Node) -> Bool { + wiring_reach_contains(reached: reachable, n: declared_input) +} + +fn wiring_fn_dead_wires(decl: FnArrowDecl) -> List { + let reachable = wiring_output_reachable_nodes(output: decl.output) + fold(decl.params, init: [], f: fn(acc, p) { + if wiring_param_reaches_output(reachable: reachable, declared_input: p.node) { + acc + } else { + list_snoc_item( + xs: acc, + item: WiringDeadWire { fn_qualified_name: decl.qualified_name, param: p.name } + ) + } + }) +} + +fn wiring_dead_wires_over(decls: List) -> List { + fold(decls, init: [], f: fn(acc, decl) { + list_append(left: acc, right: wiring_fn_dead_wires(decl: decl)) + }) +} + +fn wiring_liveness_corpus_dead_wires() -> List { + wiring_dead_wires_over(decls: fn_arrow_decl_facts_live()) +} + +fn wiring_liveness_corpus_dead_wire_count() -> Int { + length(xs: wiring_liveness_corpus_dead_wires()) +} + +fn wiring_liveness_corpus_is_clean() -> Bool { + wiring_liveness_corpus_dead_wire_count() == 0 +} + data construction_justification: ConstructionJustification = ConstructionJustification { class: WallAfterGrounding { dissolves_to: RealizationDispatch }, - rationale: "Wiring-liveness is the cache-purity perturbation oracle read backwards: purity asks same-input-same-output (a declared input absent from the cache key is impure); liveness asks different-input-different-output (a declared input with no structural path to the output it feeds is a dead wire). One kernel, two readings -- this lens reuses std.dependency DependencyView/dependency_lens as the dependence carrier (no new dependence type minted) and decides reachability of a declared input from its output over that graph. It is NOT the unused_parameters reference-count: that asks referenced-at-least-once; this asks transitive-path-to-output, which goes RED when an input is referenced only inside structure that itself never reaches the output. HONEST BOUNDARY (DESIGN 5/6): this is the COMPILE-TIME wall and covers only .dag-modeled / reflectable structure (type declarations via resolve_type_node and synthetic-or-real Node trees via dependency_lens). The motivating GCP IAM auth_input bug lives in the Rust-seed resolve_auth realization, which is OPAQUE to compile-time reflection -- that is the wave-2 opaque-realization perturbation witness, not this slice. The corpus also has NO fn/arrow reflection (resolve_type_node + concept_decl_facts_live yield only ItemKind::TypeItem), so a corpus-wide scan of real fn params is also opaque today. Grounding authority: realization self-host (DESIGN 5/7) bringing resolve_auth into .dag, plus gunbc#5364 (.dag compile-graph / fn reflection) letting the wall scan real fn bodies. Dissolves on: every input->output realization being .dag-reflectable, at which point a declared input with no path to its output is unwritable by construction and this lens's residue is empty." + rationale: "Wiring-liveness is the cache-purity perturbation oracle read backwards: purity asks same-input-same-output (a declared input absent from the cache key is impure); liveness asks different-input-different-output (a declared input with no structural path to the output it feeds is a dead wire). One kernel, two readings -- this lens reuses std.dependency DependencyView/dependency_lens (and ready_set) as the dependence carrier (no new dependence type minted) and decides reachability of a declared input from its output over that graph. It is NOT the unused_parameters reference-count: that asks referenced-at-least-once; this asks transitive-path-to-output, which goes RED when an input is referenced only inside structure that itself never reaches the output. WAVE-2(b) CORPUS COVERAGE: the gunbc#5364 widen trigger named below is now consumed -- v2.std.fn_index.fn_arrow_decl_facts_live (host SOURCE, sibling of concept_decl_facts_live but ItemKind::FnItem/FuncItem) yields one FnArrowDecl per declared fn across every loaded module: the body projected to a reachability skeleton (parameter references become identity Atom leaves, all else neutral Conj containers) plus its declared parameter atoms. wiring_liveness_corpus_is_clean folds the wave-1 reachability over every REAL fn param corpus-wide (not synthetic arrows) and fails the floor on any declared input with no path to its output. HONEST BOUNDARY (DESIGN 5/6): (1) the motivating GCP IAM auth_input bug lives in the Rust-seed resolve_auth realization, OPAQUE to compile-time reflection -- the wave-2 opaque-realization perturbation witness, not this slice. (2) Over a pure expression body the containment-reachable set equals the referenced set, so for fns this wall coincides in result with unused-parameter detection while remaining the transitive-path mechanism (the shared dependence reachability, not a per-edge BindsTo count); the genuine transitive residue (a param referenced only inside a dead let RHS) needs typed data-edges directed at the return, a named follow-on. (3) Skeleton identity lives only on LocalValueBinding parameter-use leaves, so callees/constructors/field-names cannot mask a dead wire -- the lone fail-open is a let/lambda local that shadows a parameter name. Grounding authority: realization self-host (DESIGN 5/7) bringing resolve_auth into .dag; gunbc#5364 (.dag compile-graph corpus-as-node accessor) folds fn_arrow_decl_facts_live's host SOURCE half into a pure .dag walk. Dissolves on: every input->output realization being .dag-reflectable AND the dead-let typed-data-edge residue grounded, at which point a declared input with no path to its output is unwritable by construction and this lens's residue is empty." } diff --git a/src/v2/lens/wiring_liveness_corpus_test.dag b/src/v2/lens/wiring_liveness_corpus_test.dag new file mode 100644 index 00000000000..89f81af3781 --- /dev/null +++ b/src/v2/lens/wiring_liveness_corpus_test.dag @@ -0,0 +1,118 @@ +module v2.test.lens_wiring_liveness.wiring_liveness_corpus_test + +import v2.std.algebra { length, list_snoc_item } +import v2.std.collection { List } +import v2.std.logic { Bool } +import v2.std.node { + Atom, + Conj, + Edge, + Node, + Positional, + SyntheticOccurrence, + Symbol, + TypeNode +} +import v2.std.text { String } +import v2.std.fn_index { FnArrowDecl, FnArrowParam, fn_arrow_decl_facts_live } +import v2.lens.wiring_liveness { + WiringDeadWire, + wiring_dead_wires_over, + wiring_fn_dead_wires +} + +// A local probe fn. Its presence in `fn_arrow_decl_facts_live()` -- a fn authored in +// THIS file -- proves the reflection reads the real loaded corpus (no-host-enumeration +// control per the reflection-evidence failure mode): a hardcoded host list could not +// contain it. Both params are referenced, so it is a known-good (wired) anchor. +fn wiring_corpus_probe(alpha: Bool, beta: Bool) -> Bool { + alpha && beta +} + +fn decls_named(decls: List, target: String) -> List { + fold(decls, init: [], f: fn(acc, d) { + if d.name == target { + list_snoc_item(xs: acc, item: d) + } else { + acc + } + }) +} + +fn params_has_name(params: List, name: Symbol) -> Bool { + fold(params, init: false, f: fn(acc, p) { acc || (p.name == name) }) +} + +// CONTROL 1 -- the probe fn is really enumerated, with exactly its two declared params. +fn probe_present_with_params(decls: List) -> Bool { + fold(decls_named(decls: decls, target: "wiring_corpus_probe"), init: false, f: fn(acc, d) { + acc + || ((length(xs: d.params) == 2) + && params_has_name(params: d.params, name: ^alpha) + && params_has_name(params: d.params, name: ^beta)) + }) +} + +// CONTROL 2 -- the known-good probe runs GREEN through the real wiring check (both +// params reach the output): the corpus path is not failing on well-wired fns. +fn probe_is_wired(decls: List) -> Bool { + let matches = decls_named(decls: decls, target: "wiring_corpus_probe") + (length(xs: matches) > 0) + && fold(matches, init: true, f: fn(acc, d) { + acc && (length(xs: wiring_fn_dead_wires(decl: d)) == 0) + }) +} + +// CONTROL 3 (discrimination / RED-capable) -- a synthetic FnArrowDecl whose output +// references only `present_input`; `dead_input` has no path to the output. The real +// `wiring_fn_dead_wires` must report exactly `dead_input`, proving the corpus gate is +// not vacuously green. +fn probe_atom(s: Symbol) -> Node { + Node { + kind: TypeNode { connective: Atom { identity: s } }, + children: [], + occurrence_id: SyntheticOccurrence + } +} + +fn dead_wire_decl() -> FnArrowDecl { + FnArrowDecl { + qualified_name: "synthetic.wiring_corpus.dead_wire", + name: "wiring_corpus_synthetic_dead_wire", + output: Node { + kind: TypeNode { connective: Conj }, + children: [ + Edge { label: Positional, target: probe_atom(s: ^wiring_corpus_present_input) } + ], + occurrence_id: SyntheticOccurrence + }, + params: [ + FnArrowParam { + name: ^wiring_corpus_present_input, + node: probe_atom(s: ^wiring_corpus_present_input) + }, + FnArrowParam { + name: ^wiring_corpus_dead_input, + node: probe_atom(s: ^wiring_corpus_dead_input) + } + ] + } +} + +fn witness_dead_wire_detected() -> Bool { + let dead = wiring_fn_dead_wires(decl: dead_wire_decl()) + (length(xs: dead) == 1) + && fold(dead, init: false, f: fn(acc, w) { acc || (w.param == ^wiring_corpus_dead_input) }) +} + +// FLOOR GATE -- every declared input of every real fn in the corpus reaches its output, +// computed over a SINGLE enumeration. Goes RED if any real .dag fn declares a parameter +// with no path to its output. +test fn wiring_liveness_corpus_witnesses() -> Bool { + let decls = fn_arrow_decl_facts_live() + return witness_dead_wire_detected() + && (length(xs: decls) > 0) + && probe_present_with_params(decls: decls) + && probe_is_wired(decls: decls) + && (length(xs: wiring_dead_wires_over(decls: decls)) == 0) +} diff --git a/src/v2/std/fn_index.dag b/src/v2/std/fn_index.dag new file mode 100644 index 00000000000..234534f36a3 --- /dev/null +++ b/src/v2/std/fn_index.dag @@ -0,0 +1,21 @@ +module v2.std.fn_index + +import v2.std.collection { List } +import v2.std.node { Node, Symbol } +import v2.std.text { String } + +type FnArrowParam { + name: Symbol + node: Node +} + +type FnArrowDecl { + qualified_name: String + name: String + output: Node + params: List +} + +fn fn_arrow_decl_facts_live() -> List { + fn_arrow_decl_facts_live() +} From c0877257313850cbd7e812fb2ab953f7692e96b4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 24 Jun 2026 14:13:34 +0000 Subject: [PATCH 3/6] WIP: Wiring-liveness wave-2(b): fn-arrow reflection builtin + corpus-wide len --- src/v2/lens/wiring_liveness.dag | 13 -------- src/v2/lens/wiring_liveness_corpus_test.dag | 37 ++++++++++++--------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/v2/lens/wiring_liveness.dag b/src/v2/lens/wiring_liveness.dag index 2de84580a82..2c594bbb55f 100644 --- a/src/v2/lens/wiring_liveness.dag +++ b/src/v2/lens/wiring_liveness.dag @@ -94,19 +94,6 @@ fn wiring_input_is_wired(output: Node, declared_input: Node) -> Bool { ) } -// --- corpus-wide coverage: real fn arrows (gunbc#5364 widen trigger) --- -// -// The wave-1 reachability kernel above decides one (output, declared_input) relation. -// Over a fn arrow the output is the body skeleton and the declared inputs are the -// parameter atoms (v2.std.fn_index reflection). Every parameter shares one output, so -// the reachable set is computed ONCE per fn via `ready_set` -- the same readiness fold -// over `v2.std.dependency`, linear in body size -- rather than re-saturating per -// parameter (the saturate kernel is quadratic per pass; that cost is fine for the small -// synthetic/type-decl witnesses but not for every fn body in the corpus). For a body -// skeleton (a tree rooted at the output) the readiness closure IS the transitive -// containment-reachable set from the output, so a parameter atom is in it iff the -// parameter genuinely reaches the output. This stays the transitive-path question, NOT -// `unused_parameters`'s referenced-at-least-once edge count. type WiringDeadWire { fn_qualified_name: String diff --git a/src/v2/lens/wiring_liveness_corpus_test.dag b/src/v2/lens/wiring_liveness_corpus_test.dag index 89f81af3781..bcdb8ed0550 100644 --- a/src/v2/lens/wiring_liveness_corpus_test.dag +++ b/src/v2/lens/wiring_liveness_corpus_test.dag @@ -18,13 +18,30 @@ import v2.std.fn_index { FnArrowDecl, FnArrowParam, fn_arrow_decl_facts_live } import v2.lens.wiring_liveness { WiringDeadWire, wiring_dead_wires_over, - wiring_fn_dead_wires + wiring_fn_dead_wires, + wiring_liveness_corpus_dead_wire_count, + wiring_liveness_corpus_is_clean } -// A local probe fn. Its presence in `fn_arrow_decl_facts_live()` -- a fn authored in -// THIS file -- proves the reflection reads the real loaded corpus (no-host-enumeration -// control per the reflection-evidence failure mode): a hardcoded host list could not -// contain it. Both params are referenced, so it is a known-good (wired) anchor. +fn probe_lambda_use(flag: Bool, items: List) -> Bool { + fold(items, init: true, f: fn(acc, it) { acc && flag }) +} + +fn dbg_probe_lambda_wired() -> Bool { + fold(decls_named(decls: fn_arrow_decl_facts_live(), target: "probe_lambda_use"), init: false, f: fn(acc, d) { + acc || (length(xs: wiring_fn_dead_wires(decl: d)) == 0) + }) +} + +fn dbg_nonempty() -> Bool { length(xs: fn_arrow_decl_facts_live()) > 0 } +fn dbg_probe_present() -> Bool { probe_present_with_params(decls: fn_arrow_decl_facts_live()) } +fn dbg_probe_wired() -> Bool { probe_is_wired(decls: fn_arrow_decl_facts_live()) } +fn dbg_corpus_clean() -> Bool { wiring_liveness_corpus_is_clean() } +fn dbg_count_le_0() -> Bool { wiring_liveness_corpus_dead_wire_count() == 0 } +fn dbg_count_le_3() -> Bool { wiring_liveness_corpus_dead_wire_count() < 4 } +fn dbg_count_le_12() -> Bool { wiring_liveness_corpus_dead_wire_count() < 13 } +fn dbg_count_le_40() -> Bool { wiring_liveness_corpus_dead_wire_count() < 41 } + fn wiring_corpus_probe(alpha: Bool, beta: Bool) -> Bool { alpha && beta } @@ -43,7 +60,6 @@ fn params_has_name(params: List, name: Symbol) -> Bool { fold(params, init: false, f: fn(acc, p) { acc || (p.name == name) }) } -// CONTROL 1 -- the probe fn is really enumerated, with exactly its two declared params. fn probe_present_with_params(decls: List) -> Bool { fold(decls_named(decls: decls, target: "wiring_corpus_probe"), init: false, f: fn(acc, d) { acc @@ -53,8 +69,6 @@ fn probe_present_with_params(decls: List) -> Bool { }) } -// CONTROL 2 -- the known-good probe runs GREEN through the real wiring check (both -// params reach the output): the corpus path is not failing on well-wired fns. fn probe_is_wired(decls: List) -> Bool { let matches = decls_named(decls: decls, target: "wiring_corpus_probe") (length(xs: matches) > 0) @@ -63,10 +77,6 @@ fn probe_is_wired(decls: List) -> Bool { }) } -// CONTROL 3 (discrimination / RED-capable) -- a synthetic FnArrowDecl whose output -// references only `present_input`; `dead_input` has no path to the output. The real -// `wiring_fn_dead_wires` must report exactly `dead_input`, proving the corpus gate is -// not vacuously green. fn probe_atom(s: Symbol) -> Node { Node { kind: TypeNode { connective: Atom { identity: s } }, @@ -105,9 +115,6 @@ fn witness_dead_wire_detected() -> Bool { && fold(dead, init: false, f: fn(acc, w) { acc || (w.param == ^wiring_corpus_dead_input) }) } -// FLOOR GATE -- every declared input of every real fn in the corpus reaches its output, -// computed over a SINGLE enumeration. Goes RED if any real .dag fn declares a parameter -// with no path to its output. test fn wiring_liveness_corpus_witnesses() -> Bool { let decls = fn_arrow_decl_facts_live() return witness_dead_wire_detected() From 8399d1f6f70063891cbf63b0a0db4401657b7c0a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 24 Jun 2026 14:23:50 +0000 Subject: [PATCH 4/6] WIP: Wiring-liveness wave-2(b): fn-arrow reflection builtin + corpus-wide len --- src/v1/stage0/src/coproduct_reflection.rs | 91 ++++++++++++++++------- src/v2/lens/wiring_liveness.dag | 2 +- src/v2/std/dependency.dag | 4 +- 3 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index 2b09ad418fd..badd336a4b5 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -518,37 +518,53 @@ fn atom_identity_node(ctx: &InterpContext, identity: &str) -> Value { ) } -// Project a fn body's internal expression tree onto a substrate Node skeleton. -// A reference to a declared parameter (an `ExprVar` resolved to a `LocalValueBinding` -// whose name matches a declared param) becomes an identity-bearing `Atom` leaf -- -// byte-identical to the declared-input atom `eval_fn_arrow_decl_facts_live` emits, so -// `v2.lens.wiring_liveness` matches it under Node equality. Every other node becomes a -// neutral `Conj` container whose positional children are the marshaled sub-expressions. -// Identity lives ONLY on genuine parameter-use leaves, so a declared parameter is -// structurally reachable from the body output iff it is genuinely referenced -- callees -// (`FunctionValueBinding`), constructors (`VariantValueBinding`) and field names cannot -// collide with a dead parameter atom. (Residue: a `let`/lambda local that shadows a -// parameter name is `LocalValueBinding`; see the lens construction_justification.) +fn node_authored_name(node: &Rc, si: &Rc>>) -> String { + if !node.name.is_empty() { + node.name.clone() + } else { + expr_var_name_at(node.clone(), si.clone()) + } +} + +// Does this node REFERENCE a declared parameter `name`? Two body forms reference a value +// parameter: an `ExprVar` value read (`x`) -- resolved to a `LocalValueBinding`, so a +// `FunctionValueBinding` global or `VariantValueBinding` constructor sharing the name is +// excluded; and an `ExprCall` whose callee IS the parameter (a fn-valued param applied: +// `predicate(x)`) -- the callee is the call node's own name, not a child, so it is invisible +// to a children-only walk. Both are genuine uses of a value parameter. +fn node_references_param(node: &Rc, name: &str, param_names: &[String]) -> bool { + if name.is_empty() || !param_names.iter().any(|p| p.as_str() == name) { + return false; + } + match node.expr_data.as_ref() { + ExprData::ExprVar { binding_kind: Some(bk) } => { + matches!(bk.as_ref(), VarBindingKind::LocalValueBinding) + } + ExprData::ExprCall { .. } => true, + _ => false, + } +} + +// Project a fn body's internal expression tree onto a substrate Node skeleton: each node +// becomes a neutral `Conj` container whose positional children are the marshaled +// sub-expressions, and a node that references a declared parameter additionally carries an +// identity-bearing `Atom` leaf -- byte-identical to the declared-input atom +// `eval_fn_arrow_decl_facts_live` emits, so `v2.lens.wiring_liveness` matches it under Node +// equality. Identity lives ONLY on genuine parameter-reference sites, so a declared +// parameter is structurally reachable from the body output iff it is genuinely used. +// (Residue: a `let`/lambda local, or a global fn called as `name(..)`, that shadows a +// parameter name; see the lens construction_justification.) fn marshal_fn_body_skeleton( ctx: &InterpContext, node: &Rc, param_names: &[String], si: &Rc>>, ) -> Value { - let var_name = if !node.name.is_empty() { - node.name.clone() - } else { - expr_var_name_at(node.clone(), si.clone()) - }; - let is_param_use = matches!( - node.expr_data.as_ref(), - ExprData::ExprVar { binding_kind: Some(bk), .. } - if matches!(bk.as_ref(), VarBindingKind::LocalValueBinding) - ) && param_names.iter().any(|p| p.as_str() == var_name.as_str()); - if is_param_use { - return atom_identity_node(ctx, &var_name); + let name = node_authored_name(node, si); + let mut edges: Vec = Vec::with_capacity(node.children.len() + 1); + if node_references_param(node, &name, param_names) { + edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); } - let mut edges: Vec = Vec::with_capacity(node.children.len()); for child in node.children.iter() { edges.push(edge_positional( ctx, @@ -568,6 +584,25 @@ fn marshal_fn_body_skeleton( ) } +// A generic type parameter (``) and a value parameter (`(xs: List)`) both land in the +// runtime item's `params` (parser: `all_params = concat(type_params, value_params)`). They +// are NOT value inputs and never appear as body value-expressions, so they must be excluded +// from the wiring check. A type parameter is built as `make_param_node(name, +// leaf_type_node(name), ..)` -- its sole type-expr child is a leaf named after the parameter +// itself (`T : T`) -- whereas a value parameter's type-expr names a different type +// (`xs : List`). So: a parameter is a type parameter iff its first child's authored name +// equals its own. +fn param_is_type_param(p: &Rc, si: &Rc>>) -> bool { + let pname = authored_name_at(si.clone(), p.clone()); + if pname.is_empty() { + return false; + } + match p.children.first() { + Some(child0) => authored_name_at(si.clone(), child0.clone()) == pname, + None => false, + } +} + fn fn_arrow_param_record(ctx: &InterpContext, param_name: &str) -> Value { Value::Record { type_name: ctx.sym("FnArrowParam"), @@ -611,8 +646,14 @@ pub fn eval_fn_arrow_decl_facts_live( }; let mut param_names: Vec = Vec::new(); for p in item.params.iter() { + if param_is_type_param(p, &si) { + continue; + } let pn = param_node_name_at(p.clone(), si.clone()); - if pn.is_empty() || param_names.iter().any(|q| q == &pn) { + // A `_`-prefixed name is the established declared-inert convention (e.g. + // node.dag `step: fn(acc, _edge, sub)`): the author has declared the input + // genuinely irrelevant, so it is not a dead wire (plan section 4). Skip it. + if pn.is_empty() || pn.starts_with('_') || param_names.iter().any(|q| q == &pn) { continue; } param_names.push(pn); diff --git a/src/v2/lens/wiring_liveness.dag b/src/v2/lens/wiring_liveness.dag index 2c594bbb55f..e5f9dc44f4b 100644 --- a/src/v2/lens/wiring_liveness.dag +++ b/src/v2/lens/wiring_liveness.dag @@ -142,5 +142,5 @@ fn wiring_liveness_corpus_is_clean() -> Bool { data construction_justification: ConstructionJustification = ConstructionJustification { class: WallAfterGrounding { dissolves_to: RealizationDispatch }, - rationale: "Wiring-liveness is the cache-purity perturbation oracle read backwards: purity asks same-input-same-output (a declared input absent from the cache key is impure); liveness asks different-input-different-output (a declared input with no structural path to the output it feeds is a dead wire). One kernel, two readings -- this lens reuses std.dependency DependencyView/dependency_lens (and ready_set) as the dependence carrier (no new dependence type minted) and decides reachability of a declared input from its output over that graph. It is NOT the unused_parameters reference-count: that asks referenced-at-least-once; this asks transitive-path-to-output, which goes RED when an input is referenced only inside structure that itself never reaches the output. WAVE-2(b) CORPUS COVERAGE: the gunbc#5364 widen trigger named below is now consumed -- v2.std.fn_index.fn_arrow_decl_facts_live (host SOURCE, sibling of concept_decl_facts_live but ItemKind::FnItem/FuncItem) yields one FnArrowDecl per declared fn across every loaded module: the body projected to a reachability skeleton (parameter references become identity Atom leaves, all else neutral Conj containers) plus its declared parameter atoms. wiring_liveness_corpus_is_clean folds the wave-1 reachability over every REAL fn param corpus-wide (not synthetic arrows) and fails the floor on any declared input with no path to its output. HONEST BOUNDARY (DESIGN 5/6): (1) the motivating GCP IAM auth_input bug lives in the Rust-seed resolve_auth realization, OPAQUE to compile-time reflection -- the wave-2 opaque-realization perturbation witness, not this slice. (2) Over a pure expression body the containment-reachable set equals the referenced set, so for fns this wall coincides in result with unused-parameter detection while remaining the transitive-path mechanism (the shared dependence reachability, not a per-edge BindsTo count); the genuine transitive residue (a param referenced only inside a dead let RHS) needs typed data-edges directed at the return, a named follow-on. (3) Skeleton identity lives only on LocalValueBinding parameter-use leaves, so callees/constructors/field-names cannot mask a dead wire -- the lone fail-open is a let/lambda local that shadows a parameter name. Grounding authority: realization self-host (DESIGN 5/7) bringing resolve_auth into .dag; gunbc#5364 (.dag compile-graph corpus-as-node accessor) folds fn_arrow_decl_facts_live's host SOURCE half into a pure .dag walk. Dissolves on: every input->output realization being .dag-reflectable AND the dead-let typed-data-edge residue grounded, at which point a declared input with no path to its output is unwritable by construction and this lens's residue is empty." + rationale: "Wiring-liveness is the cache-purity perturbation oracle read backwards: purity asks same-input-same-output (a declared input absent from the cache key is impure); liveness asks different-input-different-output (a declared input with no structural path to the output it feeds is a dead wire). One kernel, two readings -- this lens reuses std.dependency DependencyView/dependency_lens (and ready_set) as the dependence carrier (no new dependence type minted) and decides reachability of a declared input from its output over that graph. It is NOT the unused_parameters reference-count: that asks referenced-at-least-once; this asks transitive-path-to-output, which goes RED when an input is referenced only inside structure that itself never reaches the output. WAVE-2(b) CORPUS COVERAGE: the gunbc#5364 widen trigger named below is now consumed -- v2.std.fn_index.fn_arrow_decl_facts_live (host SOURCE, sibling of concept_decl_facts_live but ItemKind::FnItem/FuncItem) yields one FnArrowDecl per declared fn across every loaded module: the body projected to a reachability skeleton (parameter references become identity Atom leaves, all else neutral Conj containers) plus its declared parameter atoms. wiring_liveness_corpus_is_clean folds the wave-1 reachability over every REAL fn param corpus-wide (not synthetic arrows) and fails the floor on any declared input with no path to its output. HONEST BOUNDARY (DESIGN 5/6): (1) the motivating GCP IAM auth_input bug lives in the Rust-seed resolve_auth realization, OPAQUE to compile-time reflection -- the wave-2 opaque-realization perturbation witness, not this slice. (2) Over a pure expression body the containment-reachable set equals the referenced set, so for fns this wall coincides in result with unused-parameter detection while remaining the transitive-path mechanism (the shared dependence reachability, not a per-edge BindsTo count); the genuine transitive residue (a param referenced only inside a dead let RHS) needs typed data-edges directed at the return, a named follow-on. (3) Skeleton identity lives only on genuine parameter-reference sites (ExprVar LocalValueBinding reads and ExprCall param-callee applications), so type-parameters, global callees, constructors and field-names cannot mask a dead wire -- the lone fail-open is a let/lambda local, or a same-named global fn call, that shadows a parameter name. (4) A legitimately-irrelevant input (e.g. a NodeFold step's ignored edge) is DECLARED inert by the established `_`-prefix convention (node.dag `step: fn(acc, _edge, sub)`); fn_arrow_decl_facts_live skips `_`-prefixed params, so declaring-inert resolves the diagnostic without a false RED (plan section 4) while a normally-named dead param still fails the floor. Grounding authority: realization self-host (DESIGN 5/7) bringing resolve_auth into .dag; gunbc#5364 (.dag compile-graph corpus-as-node accessor) folds fn_arrow_decl_facts_live's host SOURCE half into a pure .dag walk. Dissolves on: every input->output realization being .dag-reflectable AND the dead-let typed-data-edge residue grounded, at which point a declared input with no path to its output is unwritable by construction and this lens's residue is empty." } diff --git a/src/v2/std/dependency.dag b/src/v2/std/dependency.dag index 59f1e19e992..b873da5fd43 100644 --- a/src/v2/std/dependency.dag +++ b/src/v2/std/dependency.dag @@ -209,7 +209,7 @@ fn readiness_fold_init(n: Node) -> ReadinessFoldState { } } -fn ready_set_fold_step(acc: ReadinessFoldState, edge: Edge, child_state: ReadinessFoldState) -> ReadinessFoldState { +fn ready_set_fold_step(acc: ReadinessFoldState, _edge: Edge, child_state: ReadinessFoldState) -> ReadinessFoldState { ReadinessFoldState { current: acc.current, ready: append_nodes(left: acc.ready, right: child_state.ready), @@ -253,7 +253,7 @@ fn topological_layers_fold_init(n: Node) -> ReadinessFoldState { } } -fn topological_layers_fold_step(acc: ReadinessFoldState, edge: Edge, child_state: ReadinessFoldState) -> ReadinessFoldState { +fn topological_layers_fold_step(acc: ReadinessFoldState, _edge: Edge, child_state: ReadinessFoldState) -> ReadinessFoldState { ReadinessFoldState { current: acc.current, ready: append_nodes(left: acc.ready, right: child_state.ready), From 58abc79d55d58e56050726cfba64f83511ebfe75 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 24 Jun 2026 14:34:56 +0000 Subject: [PATCH 5/6] WIP: Wiring-liveness wave-2(b): fn-arrow reflection builtin + corpus-wide len --- src/v2/lens/wiring_liveness_corpus_test.dag | 34 ++++++--------------- src/v2/std/dependency.dag | 2 +- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/src/v2/lens/wiring_liveness_corpus_test.dag b/src/v2/lens/wiring_liveness_corpus_test.dag index bcdb8ed0550..4fd13d1b43d 100644 --- a/src/v2/lens/wiring_liveness_corpus_test.dag +++ b/src/v2/lens/wiring_liveness_corpus_test.dag @@ -18,34 +18,17 @@ import v2.std.fn_index { FnArrowDecl, FnArrowParam, fn_arrow_decl_facts_live } import v2.lens.wiring_liveness { WiringDeadWire, wiring_dead_wires_over, - wiring_fn_dead_wires, - wiring_liveness_corpus_dead_wire_count, - wiring_liveness_corpus_is_clean + wiring_fn_dead_wires } -fn probe_lambda_use(flag: Bool, items: List) -> Bool { - fold(items, init: true, f: fn(acc, it) { acc && flag }) -} - -fn dbg_probe_lambda_wired() -> Bool { - fold(decls_named(decls: fn_arrow_decl_facts_live(), target: "probe_lambda_use"), init: false, f: fn(acc, d) { - acc || (length(xs: wiring_fn_dead_wires(decl: d)) == 0) - }) -} - -fn dbg_nonempty() -> Bool { length(xs: fn_arrow_decl_facts_live()) > 0 } -fn dbg_probe_present() -> Bool { probe_present_with_params(decls: fn_arrow_decl_facts_live()) } -fn dbg_probe_wired() -> Bool { probe_is_wired(decls: fn_arrow_decl_facts_live()) } -fn dbg_corpus_clean() -> Bool { wiring_liveness_corpus_is_clean() } -fn dbg_count_le_0() -> Bool { wiring_liveness_corpus_dead_wire_count() == 0 } -fn dbg_count_le_3() -> Bool { wiring_liveness_corpus_dead_wire_count() < 4 } -fn dbg_count_le_12() -> Bool { wiring_liveness_corpus_dead_wire_count() < 13 } -fn dbg_count_le_40() -> Bool { wiring_liveness_corpus_dead_wire_count() < 41 } - fn wiring_corpus_probe(alpha: Bool, beta: Bool) -> Bool { alpha && beta } +fn wiring_corpus_probe_lambda(flag: Bool, items: List) -> Bool { + fold(items, init: true, f: fn(acc, it) { acc && flag }) +} + fn decls_named(decls: List, target: String) -> List { fold(decls, init: [], f: fn(acc, d) { if d.name == target { @@ -69,8 +52,8 @@ fn probe_present_with_params(decls: List) -> Bool { }) } -fn probe_is_wired(decls: List) -> Bool { - let matches = decls_named(decls: decls, target: "wiring_corpus_probe") +fn probe_named_is_wired(decls: List, target: String) -> Bool { + let matches = decls_named(decls: decls, target: target) (length(xs: matches) > 0) && fold(matches, init: true, f: fn(acc, d) { acc && (length(xs: wiring_fn_dead_wires(decl: d)) == 0) @@ -120,6 +103,7 @@ test fn wiring_liveness_corpus_witnesses() -> Bool { return witness_dead_wire_detected() && (length(xs: decls) > 0) && probe_present_with_params(decls: decls) - && probe_is_wired(decls: decls) + && probe_named_is_wired(decls: decls, target: "wiring_corpus_probe") + && probe_named_is_wired(decls: decls, target: "wiring_corpus_probe_lambda") && (length(xs: wiring_dead_wires_over(decls: decls)) == 0) } diff --git a/src/v2/std/dependency.dag b/src/v2/std/dependency.dag index b873da5fd43..c132f3dcfb3 100644 --- a/src/v2/std/dependency.dag +++ b/src/v2/std/dependency.dag @@ -209,7 +209,7 @@ fn readiness_fold_init(n: Node) -> ReadinessFoldState { } } -fn ready_set_fold_step(acc: ReadinessFoldState, _edge: Edge, child_state: ReadinessFoldState) -> ReadinessFoldState { +fn ready_set_fold_step(acc: ReadinessFoldState, edge: Edge, child_state: ReadinessFoldState) -> ReadinessFoldState { ReadinessFoldState { current: acc.current, ready: append_nodes(left: acc.ready, right: child_state.ready), From 61d688d9e9fd0c068991b8e4beaf64a284ea642f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 24 Jun 2026 14:39:00 +0000 Subject: [PATCH 6/6] Wiring-liveness wave-2(b): fn-arrow reflection builtin + corpus-wide lens coverage Adds fn/arrow corpus reflection (the gunbc#5364 widen trigger) and wires it into the wave-1 wiring-liveness lens so a declared fn parameter with no path to its output fails the floor -- proven green-by-execution over the live closure and red-on-perturbation. - v2.std.fn_index.fn_arrow_decl_facts_live: host SOURCE reflection (sibling of concept_decl_facts_live, ItemKind::FnItem/FuncItem) yielding one FnArrowDecl per fn -- body projected to a reachability skeleton (param references become identity Atom leaves, all else neutral Conj containers) + declared param atoms. Excludes generic type-parameters (self-named type-expr) and _-prefixed declared-inert params; resolves fn-valued params used as call callees (ExprCall node name, not a child). - v2.lens.wiring_liveness: corpus fold reusing std.dependency DependencyView/dependency_lens/ready_set (no new dependence type) -- a param is wired iff reachable from the body output. - Floor witness wiring_liveness_corpus_test with no-host-enumeration controls and synthetic dead-wire discrimination. - Declared the two NodeFold-step ignored edge params inert (_edge) per the existing node.dag convention. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/v1/stage0/src/coproduct_reflection.rs | 8 +++++--- src/v2/std/dependency.dag | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index badd336a4b5..c4107a020cb 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -4,8 +4,8 @@ use std::rc::Rc; use crate::v1_compiler_infer_items::ItemKind; use crate::v1_interpreter::{InterpContext, InterpError, InterpResult, Value}; use crate::v1_std_core::{ - authored_name_at, expr_var_name_at, field_node_type_expr, inferred_to_node, - param_node_name_at, Connective, ExprData, NewlineIndex, Node, VarBindingKind, + authored_name_at, expr_var_name_at, field_node_type_expr, inferred_to_node, param_node_name_at, + Connective, ExprData, NewlineIndex, Node, VarBindingKind, }; pub(crate) const NULLARY_PAYLOAD_TYPE_NAME: &str = "coproduct_nullary_payload"; @@ -537,7 +537,9 @@ fn node_references_param(node: &Rc, name: &str, param_names: &[String]) -> return false; } match node.expr_data.as_ref() { - ExprData::ExprVar { binding_kind: Some(bk) } => { + ExprData::ExprVar { + binding_kind: Some(bk), + } => { matches!(bk.as_ref(), VarBindingKind::LocalValueBinding) } ExprData::ExprCall { .. } => true, diff --git a/src/v2/std/dependency.dag b/src/v2/std/dependency.dag index c132f3dcfb3..b873da5fd43 100644 --- a/src/v2/std/dependency.dag +++ b/src/v2/std/dependency.dag @@ -209,7 +209,7 @@ fn readiness_fold_init(n: Node) -> ReadinessFoldState { } } -fn ready_set_fold_step(acc: ReadinessFoldState, edge: Edge, child_state: ReadinessFoldState) -> ReadinessFoldState { +fn ready_set_fold_step(acc: ReadinessFoldState, _edge: Edge, child_state: ReadinessFoldState) -> ReadinessFoldState { ReadinessFoldState { current: acc.current, ready: append_nodes(left: acc.ready, right: child_state.ready),