From f67d66c75b9d5ecf182a6cb1379933302fc1a870 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 16:29:21 +0000 Subject: [PATCH 01/89] Land the add-slice per-stage verdict instrument named as the floor_expected_red note's producer The add-slice roster note in v2.workflow.floor_expected_red carried a dated receipt (main 3a8344b5c: infer accepts dag_add_emitted_root; the infer-then-translate composition refuses headed by infer_grounding_not_derived) and named its own next-rung trigger: a .dag entry returning the per-stage verdicts for one root, so the paragraph can name a producer instead of a commit. v2.compiler.self_host.candidate_generation_stage_verdicts is that entry, parameterized over root and target: the receipt's verdict vocabulary (infer_accepted / infer_rejected; candidate_accepted or the rejection head reason) plus the carried-reasons lists -- the half the verdict symbols cannot say, namely that infer accepts while carrying the frontier diagnostic on its accepted path, so the enrolled witness's d == None conjunct fails even where the composition reaches acceptance. v2.test.execution.self_host_candidate_generation_stage_verdicts binds the instrument to the slice's own fixture, with add_slice_stage_verdicts_entry the runnable gunbc run --function form (ExitSuccess only when infer accepts clean and the composition accepts clean). Two witnesses: infer-accepts as a permanent positive control, and the frontier-state pin that is expected to red the day the add-slice stall's trigger lands, flipping to a permanent regression control in the same change that removes the roster row (DESIGN 4b(4)). Measured by execution on this branch: the entry exits 1 printing infer=infer_accepted, infer_carried=[infer_grounding_not_derived x10], composition=infer_grounding_not_derived, composition_carried=[x11] -- the receipt reproduced, with bind_outcome's pending-plus-gate chain counted. Both witnesses PASS; the enrolled semantic witness still fails as enrolled. Co-authored-by: Brian Searls --- .../candidate_generation_stage_verdicts.dag | 120 ++++++++++++++++++ ...ndidate_generation_stage_verdicts_test.dag | 110 ++++++++++++++++ src/v2/workflow/floor_expected_red.dag | 26 ++-- 3 files changed, 242 insertions(+), 14 deletions(-) create mode 100644 src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag create mode 100644 src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag diff --git a/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag b/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag new file mode 100644 index 00000000000..a0e23454e24 --- /dev/null +++ b/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag @@ -0,0 +1,120 @@ +module v2.compiler.self_host.candidate_generation_stage_verdicts + +import v2.compiler.infer { infer } +import v2.compiler.self_host.candidate_generation { generate_translate_self_emit_candidate } +import v2.std.algebra { Cons, Empty, fold_list, list_snoc_item } +import v2.std.collection { List } +import v2.std.compilers.target_model { TargetModel } +import v2.std.diagnostic { + Accepted, + Diagnostics, + NonEmptyDiagnostics, + None, + Rejected, + Some +} +import v2.std.node { Node, Symbol } + +// THE PER-STAGE VERDICT INSTRUMENT `v2.workflow.floor_expected_red` NAMES AS THE REPRODUCIBLE +// PRODUCER of its add-slice receipt. That receipt was measured once, at main 3a8344b5c, by a +// throwaway probe: `infer(dag_add_emitted_root)` answered `infer_accepted` while +// `generate_translate_self_emit_candidate` over the same root and `dag_add_target_model` refused +// headed by `infer_grounding_not_derived`. No `.dag` entry re-derived it, so a reader could not +// re-run it; this module is the standing entry, parameterized over the one root and target so +// the seam is measured, never transcribed (DESIGN section 6). +// +// THE VERDICT VOCABULARY IS THE RECEIPT'S. `infer_verdict` is `infer_accepted` or +// `infer_rejected`. `composition_verdict` is `candidate_accepted` when the infer-then-translate +// composition accepts, and otherwise the rejection's head reason -- today +// `infer_grounding_not_derived`, the typed frontier `v2.compiler.infer` +// `node_grounding_frontier_note` declares: infer derives grounding for four of the closed +// vocabulary's twelve kinds and records GroundingNotDerived for the rest, which +// `translate_grounding_derived_gate_subtree` then refuses. +// +// THE CARRIED-REASONS FIELDS ARE THE LOAD-BEARING HALF THE VERDICT SYMBOLS CANNOT SAY. Infer +// ACCEPTS this root while CARRYING the frontier diagnostic on its accepted path, so the enrolled +// witness's `d == None` conjunct fails even where the composition reaches acceptance -- the +// roster note's "both conjuncts fail for the one cause". `infer_carried_reasons` surfaces what +// infer carried; `composition_carried_reasons` is the composition's accepted-path diagnostics or, +// on refusal, the full rejection chain head-first (which bind_outcome prefixes with infer's +// pending diagnostics, so today it is the one frontier reason repeated). +// +// THIS INSTRUMENT DOES NOT RETIRE WHEN THE ADD-SLICE STALL +// (`gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall`) DISSOLVES. It is the +// standing measurement of a production seam, and its verdicts flipping to +// `candidate_accepted` with empty carried lists is the signal the stall's trigger fired. It +// retires only if the `generate_translate_self_emit_candidate` seam itself dissolves. + +type CandidateGenerationStageVerdicts { + infer_verdict: Symbol + infer_carried_reasons: List + composition_verdict: Symbol + composition_carried_reasons: List +} + +fn non_empty_diagnostic_reasons(d: NonEmptyDiagnostics) -> List { + Cons { + head: d.head.reason, + tail: fold_list( + xs: d.tail, + empty: Empty, + cons: fn(acc, diag) { list_snoc_item(xs: acc, item: diag.reason) } + ) + } +} + +fn diagnostics_carried_reasons(d: Diagnostics) -> List { + match d { + None => Empty + Some { diagnostics: ne } => non_empty_diagnostic_reasons(d: ne) + } +} + +fn candidate_generation_composition_verdicts( + resolved_module: Node, + dag_target: TargetModel, + infer_verdict: Symbol, + infer_carried_reasons: List +) -> CandidateGenerationStageVerdicts { + match generate_translate_self_emit_candidate( + resolved_module: resolved_module, + dag_target: dag_target + ) { + Accepted { value: _, diagnostics: d } => + CandidateGenerationStageVerdicts { + infer_verdict: infer_verdict, + infer_carried_reasons: infer_carried_reasons, + composition_verdict: ^candidate_accepted, + composition_carried_reasons: diagnostics_carried_reasons(d: d) + } + Rejected { diagnostics: d } => + CandidateGenerationStageVerdicts { + infer_verdict: infer_verdict, + infer_carried_reasons: infer_carried_reasons, + composition_verdict: d.head.reason, + composition_carried_reasons: non_empty_diagnostic_reasons(d: d) + } + } +} + +fn candidate_generation_stage_verdicts( + resolved_module: Node, + dag_target: TargetModel +) -> CandidateGenerationStageVerdicts { + match infer(tree: resolved_module) { + Accepted { value: _, diagnostics: d } => + candidate_generation_composition_verdicts( + resolved_module: resolved_module, + dag_target: dag_target, + infer_verdict: ^infer_accepted, + infer_carried_reasons: diagnostics_carried_reasons(d: d) + ) + Rejected { diagnostics: d } => + candidate_generation_composition_verdicts( + resolved_module: resolved_module, + dag_target: dag_target, + infer_verdict: ^infer_rejected, + infer_carried_reasons: non_empty_diagnostic_reasons(d: d) + ) + } +} diff --git a/src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag b/src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag new file mode 100644 index 00000000000..ba33e9656d8 --- /dev/null +++ b/src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag @@ -0,0 +1,110 @@ +module v2.test.execution.self_host_candidate_generation_stage_verdicts + +import std.process { ExitSuccess, ProcessExit, exit_failure } +import v2.compiler.self_host.candidate_generation_stage_verdicts { + CandidateGenerationStageVerdicts, + candidate_generation_stage_verdicts +} +import v2.test.execution.dag_add_emit_round_trip { + dag_add_emitted_root, + dag_add_target_model +} +import v2.std.algebra { Empty, fold_list, is_empty, list_snoc_item } +import v2.std.collection { List } +import v2.std.compilers.lexing { symbol_lexeme } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import v2.std.logic { Bool } +import v2.std.node { Symbol } +import v2.std.text { String, string_join } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE ADD-SLICE BINDING OF `v2.compiler.self_host.candidate_generation_stage_verdicts` -- the +// instrument the `v2.workflow.floor_expected_red` add-slice roster note names as the producer of +// its per-stage receipt. `add_slice_stage_verdicts` is the pure entry over the slice's own +// fixture (`dag_add_emitted_root`, `dag_add_target_model`); `add_slice_stage_verdicts_entry` is +// the runnable `gunbc run --function` form, exiting nonzero with the verdicts rendered while the +// composition refuses, and ExitSuccess only when infer accepts clean and the composition accepts +// clean -- the enrolled witness's green state. +// +// THE TWO WITNESSES ARE DELIBERATELY ASYMMETRIC. `add_slice_infer_accepts_holds` is a PERMANENT +// positive control: infer accepting this root is the fact the roster note's structural account +// could not establish, and it stays true after the repair lands. +// `add_slice_composition_refuses_at_the_grounding_frontier_holds` pins TODAY's frontier state -- +// composition refusal headed by `infer_grounding_not_derived`, with infer carrying that same +// reason and nothing else -- so the instrument's composition arm has a discriminating consumer +// (DESIGN section 5). It is expected to RED on the day +// `gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall`'s trigger lands; the +// repair rewrites it to assert `candidate_accepted` with empty carried lists -- the DESIGN 4b(4) +// flip from frontier guard to permanent regression control -- in the same change that removes the +// roster row. + +fn add_slice_stage_verdicts() -> CandidateGenerationStageVerdicts { + candidate_generation_stage_verdicts( + resolved_module: dag_add_emitted_root, + dag_target: dag_add_target_model + ) +} + +fn symbol_list_text(xs: List) -> String { + string_join( + fields: fold_list( + xs: xs, + empty: Empty, + cons: fn(acc, sym) { list_snoc_item(xs: acc, item: symbol_lexeme(sym: sym)) } + ), + separator: ", " + ) +} + +fn add_slice_stage_verdicts_text(v: CandidateGenerationStageVerdicts) -> String { + concat( + "add-slice per-stage verdicts: infer=", + concat( + symbol_lexeme(sym: v.infer_verdict), + concat( + " infer_carried=[", + concat( + symbol_list_text(xs: v.infer_carried_reasons), + concat( + "] composition=", + concat( + symbol_lexeme(sym: v.composition_verdict), + concat( + " composition_carried=[", + concat(symbol_list_text(xs: v.composition_carried_reasons), "]") + ) + ) + ) + ) + ) + ) + ) +} + +fn add_slice_stage_verdicts_entry() -> ProcessExit { + let verdicts = add_slice_stage_verdicts() + if (verdicts.infer_verdict == ^infer_accepted) + && is_empty(xs: verdicts.infer_carried_reasons) + && (verdicts.composition_verdict == ^candidate_accepted) + && is_empty(xs: verdicts.composition_carried_reasons) { + ExitSuccess + } else { + exit_failure(reason: add_slice_stage_verdicts_text(v: verdicts)) + } +} + +test fn add_slice_infer_accepts_holds() -> Bool { + add_slice_stage_verdicts().infer_verdict == ^infer_accepted +} + +test fn add_slice_composition_refuses_at_the_grounding_frontier_holds() -> Bool { + let verdicts = add_slice_stage_verdicts() + (verdicts.composition_verdict == ^infer_grounding_not_derived) + && !is_empty(xs: verdicts.infer_carried_reasons) + && fold_list( + xs: verdicts.infer_carried_reasons, + empty: true, + cons: fn(acc, reason) { acc && (reason == ^infer_grounding_not_derived) } + ) +} diff --git a/src/v2/workflow/floor_expected_red.dag b/src/v2/workflow/floor_expected_red.dag index 018f1a66827..5051120bab5 100644 --- a/src/v2/workflow/floor_expected_red.dag +++ b/src/v2/workflow/floor_expected_red.dag @@ -1003,20 +1003,18 @@ fn floor_expected_red_chunk_interpreter_first_optional_divergence() -> List Date: Sun, 6 Sep 2026 17:32:20 +0000 Subject: [PATCH 02/89] Derive grounding for dag declared inhabitants: the add slice greens end-to-end infer gains the declared-inhabitant membership derivation: a node declared in the dag language authority's declared-inhabitants roster derives its grounding by lookup, with the roster as evidence -- the namespacing answer to the atom authority question, at specimen scope. The add slice's ten type-spine nodes (Arrow, Conj, Atom) are all roster members, so: - candidate_generation_translate_self_emit_dag_add_slice_holds passes; its floor_expected_red roster row and per-row note delete per the roster's own stale-quarantine arm - the dag same-language ingest path compiles end-to-end: cross_language_compile accepts, byte-equal to the authority's own serialization, no carried diagnostics - the add-slice stall narrows to its four python/typescript round-trip members; the original trigger's causal clause was refuted by execution and is restated against the grammar parse-product population - the instrument's frontier guard flips to add_slice_composition_accepts_holds (DESIGN 4b(4): frontier guard to permanent regression control) - five manual witnesses flip with it: two root flips rewritten to assert the green state, three transitive conjunctions updated The kinds stay frontier: non-member Arrow/Conj/Atom specimens carry GroundingNotDerived exactly as before, and all fourteen enrolled refusal/acceptance controls pass unchanged. The door's production path still reds inside rust emission, untouched by this rule. Co-authored-by: Brian Searls --- ...t_candidate_generation_add_slice_stall.dag | 20 +++++- src/v2/compiler/04_infer.dag | 47 +++++++++++--- .../candidate_generation_stage_verdicts.dag | 43 ++++++------- ...ndidate_generation_stage_verdicts_test.dag | 41 ++++++------ .../test/claim/manual/ingest_bridge_test.dag | 63 +++++++++++++------ .../manual/inhabitant_neutralization_test.dag | 12 ++-- src/v2/workflow/floor_expected_red.dag | 60 +----------------- 7 files changed, 154 insertions(+), 132 deletions(-) diff --git a/dag/gunbc/guarantee_stall/self_host_candidate_generation_add_slice_stall.dag b/dag/gunbc/guarantee_stall/self_host_candidate_generation_add_slice_stall.dag index c04258004da..f196c68ddd0 100644 --- a/dag/gunbc/guarantee_stall/self_host_candidate_generation_add_slice_stall.dag +++ b/dag/gunbc/guarantee_stall/self_host_candidate_generation_add_slice_stall.dag @@ -4,11 +4,25 @@ import v2.std.algebra { Cons, Empty } import gunbc.guarantee_rung { OutsideTheLadder, MechanicallyPreventable } import gunbc.guarantee_stall { GuaranteeStall, ClimbableButUnbuilt, BoundedPopulation, climbs_when } +// HISTORY, recorded because the trigger below only makes sense with it. This stall was authored +// with a five-member population and a trigger whose causal clause read: derive grounding for the +// Arrow, Conj and Atom kinds of the add fn, SO cross_language_compile stops carrying +// infer_grounding_not_derived over the same-language python and typescript parse fixtures. The +// add-fn half LANDED as the declared-inhabitant membership derivation (v2.compiler.infer +// infer_node_declared_in_dag_inhabitants): the add slice's ten type-spine nodes are all declared +// in the dag language authority's declared-inhabitants roster, so they derive by lookup, and +// candidate_generation_translate_self_emit_dag_add_slice_holds passed -- the roster row deleted +// itself per its own stale-quarantine arm. The causal clause was REFUTED BY EXECUTION: the four +// round-trip members still red, and the cross_language_compile_refuses_canonical_underived control +// confirms the refusal reason is unchanged. The parse fixtures' frontier is a different node +// population -- grammar parse products, not language-authority inhabitants -- so the remaining +// trigger names that population, not the add fn's spine. + data self_host_candidate_generation_add_slice_stall: GuaranteeStall = GuaranteeStall { - subject: "self-host candidate generation, translation, and emission disagree on the add slice", + subject: "infer carries the grounding frontier over the python and typescript same-language parse fixtures, so the same-language round trips refuse", current: OutsideTheLadder, ceiling: MechanicallyPreventable, blocker: ClimbableButUnbuilt, - population: BoundedPopulation { members: Cons { head: "v2.test.execution.self_host_candidate_generation.candidate_generation_translate_self_emit_dag_add_slice_holds", tail: Cons { head: "v2.test.execution.emit_ingest_python_same_language_round_trip.emit_ingest_python_same_language_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_python_same_language_round_trip.python_same_language_source_emit_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_typescript_same_language_round_trip.emit_ingest_typescript_same_language_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_typescript_same_language_round_trip.typescript_same_language_source_emit_round_trip_holds", tail: Empty {} } } } } } }, - next_rung_trigger: climbs_when(capability: "candidate generation, translation, and self-emission agree on the add slice: infer derives grounding for the Arrow, Conj and Atom kinds of the add fn, so cross_language_compile stops carrying infer_grounding_not_derived over the same-language python and typescript parse fixtures") + population: BoundedPopulation { members: Cons { head: "v2.test.execution.emit_ingest_python_same_language_round_trip.emit_ingest_python_same_language_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_python_same_language_round_trip.python_same_language_source_emit_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_typescript_same_language_round_trip.emit_ingest_typescript_same_language_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_typescript_same_language_round_trip.typescript_same_language_source_emit_round_trip_holds", tail: Empty {} } } } } }, + next_rung_trigger: climbs_when(capability: "infer derives grounding for the grammar parse-tree node population of the python and typescript parse fixtures -- the parse-product kinds their cross_language_compile same-language round trips carry infer_grounding_not_derived over today") } diff --git a/src/v2/compiler/04_infer.dag b/src/v2/compiler/04_infer.dag index 2aec278ef71..92dc0527069 100644 --- a/src/v2/compiler/04_infer.dag +++ b/src/v2/compiler/04_infer.dag @@ -1,7 +1,7 @@ module v2.compiler.infer import std.occurrence_identity { OccurrenceSynthetic } -import v2.std.algebra { length, list_snoc_item } +import v2.std.algebra { any, length, list_snoc_item } import v2.std.bounded_lattice_completeness { infer_bounded_lattice_consumer_gate, partial_bounded_lattice_instances_in_tree @@ -22,7 +22,8 @@ import v2.compiler.inferred_tree { import v2.extdeps.languages.dag { DagCanonicalBoolLiteral, DagCanonicalIntLiteral, - dag_canonical_literal_from_node + dag_canonical_literal_from_node, + dag_declared_inhabitants_root } import v2.std.cardinality { TerminationProof, termination_proof_witness_for_node } import v2.std.collection { @@ -87,6 +88,7 @@ import v2.std.node { loop_edge_contributes_to_iteration_fold, match_arm_body, match_arm_pattern, + node_subtree_nodes, well_formed, } import v2.std.node_query { find_named_child, node_positional_child_targets } @@ -97,7 +99,7 @@ type AlgebraRef { witness: Node } -data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for exactly four of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only — 6 connectives + 6 behaviors = 12); the other eight kinds, and every other Transform shape, have no derivation rule yet. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." +data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for exactly four of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only — 6 connectives + 6 behaviors = 12); the other eight kinds, and every other Transform shape, have no derivation rule yet. Above the kind rules sits one specimen-scope derivation: a node declared in the dag language authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants) — the authority already fixes what its members denote, so the grounding is a lookup, not an invention; non-member specimens of every kind stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." fn infer_grounding_not_derived_diagnostic(at: Locus) -> Diagnostic { Diagnostic { @@ -460,6 +462,29 @@ fn concat_inferred_facts_entries( // remains the structural-solve authority named by dag/gunbc/plans/solve_higher_order_design.dag, // to be EXTENDED with real candidate generation, never forked. +// THE DECLARED-INHABITANT MEMBERSHIP DERIVATION. The dag language authority's declared-inhabitants +// roster (v2.extdeps.languages.dag dag_declared_inhabitants_root) already fixes what its members +// denote — the add fn Arrow, the int inhabitant Conj, and the inhabitant and surface-spelling +// Atoms are all declared there — so a node that IS a roster member derives its grounding by +// LOOKUP, never by invention: the roster is the evidence, which is the intersubjective-grounding +// move DESIGN section 1 requires (point at the shared framework, never an internal taxonomy). This +// is the derivation the self-host candidate-generation add-slice stall's original trigger named, +// at specimen scope: the add fn's Arrow, Conj and Atom nodes derive, while the KINDS stay frontier — +// a non-member Arrow, Conj or Atom carries GroundingNotDerived exactly as before, so the enrolled +// refusal witnesses pinning other shapes (the bodied arrow, the empty Conj, the rust grammar atom) +// are untouched. Growth is execution-driven only: the python and typescript authorities carry +// their own declared-inhabitants rosters, and their same-language round-trip witnesses are the +// consumers that will name the moment this lookup widens to them. The end-state is the resolver +// handing infer declaration-resolved identities directly — the namespace migration's completed +// state — at which point this lookup deletes in favor of consuming resolution output. + +fn infer_node_declared_in_dag_inhabitants(n: Node) -> Bool { + any( + xs: node_subtree_nodes(root: dag_declared_inhabitants_root()), + predicate: fn(member) { member == n } + ) +} + fn infer_node_facts(n: Node, partials: List) -> Outcome { match infer_bounded_lattice_consumer_gate(consumer: n, partials: partials) { Rejected { diagnostics: r } => Rejected { diagnostics: r } @@ -482,10 +507,18 @@ fn infer_node_facts(n: Node, partials: List) -> Outcome { descent: Holds { value: descent_proof } ) Rejected { diagnostics: _ } => - inferred_facts_not_derived( - node: n, - descent: Holds { value: descent_proof } - ) + if infer_node_declared_in_dag_inhabitants(n: n) { + inferred_facts_from_derived_type( + node: n, + derived_type: dag_declared_inhabitants_root(), + descent: Holds { value: descent_proof } + ) + } else { + inferred_facts_not_derived( + node: n, + descent: Holds { value: descent_proof } + ) + } } } } diff --git a/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag b/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag index a0e23454e24..32441c2df79 100644 --- a/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag +++ b/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag @@ -15,34 +15,35 @@ import v2.std.diagnostic { } import v2.std.node { Node, Symbol } -// THE PER-STAGE VERDICT INSTRUMENT `v2.workflow.floor_expected_red` NAMES AS THE REPRODUCIBLE -// PRODUCER of its add-slice receipt. That receipt was measured once, at main 3a8344b5c, by a -// throwaway probe: `infer(dag_add_emitted_root)` answered `infer_accepted` while +// THE PER-STAGE VERDICT INSTRUMENT `v2.workflow.floor_expected_red` NAMED AS THE REPRODUCIBLE +// PRODUCER of its add-slice receipt, before that row greened and the note deleted itself per its +// own dissolution statement. That receipt was measured once, at main 3a8344b5c, by a throwaway +// probe: `infer(dag_add_emitted_root)` answered `infer_accepted` while // `generate_translate_self_emit_candidate` over the same root and `dag_add_target_model` refused -// headed by `infer_grounding_not_derived`. No `.dag` entry re-derived it, so a reader could not -// re-run it; this module is the standing entry, parameterized over the one root and target so -// the seam is measured, never transcribed (DESIGN section 6). +// headed by `infer_grounding_not_derived`. This module is the standing entry, parameterized over +// the one root and target so the seam is measured, never transcribed (DESIGN section 6). // // THE VERDICT VOCABULARY IS THE RECEIPT'S. `infer_verdict` is `infer_accepted` or // `infer_rejected`. `composition_verdict` is `candidate_accepted` when the infer-then-translate -// composition accepts, and otherwise the rejection's head reason -- today +// composition accepts, and otherwise the rejection's head reason -- for the add slice that was // `infer_grounding_not_derived`, the typed frontier `v2.compiler.infer` -// `node_grounding_frontier_note` declares: infer derives grounding for four of the closed -// vocabulary's twelve kinds and records GroundingNotDerived for the rest, which -// `translate_grounding_derived_gate_subtree` then refuses. +// `node_grounding_frontier_note` declares, until the declared-inhabitant membership derivation +// (`infer_node_declared_in_dag_inhabitants`) landed: the slice's ten type-spine nodes are all +// declared in the dag language authority's declared-inhabitants roster, so they derive by lookup +// and the composition accepts clean. // -// THE CARRIED-REASONS FIELDS ARE THE LOAD-BEARING HALF THE VERDICT SYMBOLS CANNOT SAY. Infer -// ACCEPTS this root while CARRYING the frontier diagnostic on its accepted path, so the enrolled -// witness's `d == None` conjunct fails even where the composition reaches acceptance -- the -// roster note's "both conjuncts fail for the one cause". `infer_carried_reasons` surfaces what -// infer carried; `composition_carried_reasons` is the composition's accepted-path diagnostics or, -// on refusal, the full rejection chain head-first (which bind_outcome prefixes with infer's -// pending diagnostics, so today it is the one frontier reason repeated). +// THE CARRIED-REASONS FIELDS ARE THE LOAD-BEARING HALF THE VERDICT SYMBOLS CANNOT SAY. Before the +// membership derivation, infer ACCEPTED this root while CARRYING the frontier diagnostic on its +// accepted path, so the enrolled witness's `d == None` conjunct failed even where the composition +// reached acceptance. `infer_carried_reasons` surfaces what infer carried; +// `composition_carried_reasons` is the composition's accepted-path diagnostics or, on refusal, +// the full rejection chain head-first (which bind_outcome prefixes with infer's pending +// diagnostics). For the add slice both are now empty, and the flipped witness pins that. // -// THIS INSTRUMENT DOES NOT RETIRE WHEN THE ADD-SLICE STALL -// (`gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall`) DISSOLVES. It is the -// standing measurement of a production seam, and its verdicts flipping to -// `candidate_accepted` with empty carried lists is the signal the stall's trigger fired. It +// THIS INSTRUMENT DID NOT RETIRE WHEN THE ADD-SLICE STALL +// (`gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall`) NARROWED. It is the +// standing measurement of a production seam; its verdicts flipping to `candidate_accepted` with +// empty carried lists was the signal the add-slice half of the stall's trigger had fired. It // retires only if the `generate_translate_self_emit_candidate` seam itself dissolves. type CandidateGenerationStageVerdicts { diff --git a/src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag b/src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag index ba33e9656d8..1aeb047982e 100644 --- a/src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag +++ b/src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag @@ -20,24 +20,28 @@ import v2.std.text { String, string_join } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // THE ADD-SLICE BINDING OF `v2.compiler.self_host.candidate_generation_stage_verdicts` -- the -// instrument the `v2.workflow.floor_expected_red` add-slice roster note names as the producer of -// its per-stage receipt. `add_slice_stage_verdicts` is the pure entry over the slice's own +// instrument the `v2.workflow.floor_expected_red` add-slice roster note named as the producer of +// its per-stage receipt, before that row greened and the note deleted itself per its own +// dissolution statement. `add_slice_stage_verdicts` is the pure entry over the slice's own // fixture (`dag_add_emitted_root`, `dag_add_target_model`); `add_slice_stage_verdicts_entry` is // the runnable `gunbc run --function` form, exiting nonzero with the verdicts rendered while the // composition refuses, and ExitSuccess only when infer accepts clean and the composition accepts // clean -- the enrolled witness's green state. // -// THE TWO WITNESSES ARE DELIBERATELY ASYMMETRIC. `add_slice_infer_accepts_holds` is a PERMANENT -// positive control: infer accepting this root is the fact the roster note's structural account -// could not establish, and it stays true after the repair lands. -// `add_slice_composition_refuses_at_the_grounding_frontier_holds` pins TODAY's frontier state -- +// THE TWO WITNESSES ARE NOW BOTH PERMANENT REGRESSION CONTROLS. `add_slice_infer_accepts_holds` +// was authored as the permanent positive control: infer accepting this root is the fact the +// roster note's structural account could not establish, and it stays true after the repair. +// `add_slice_composition_accepts_holds` was authored as +// `add_slice_composition_refuses_at_the_grounding_frontier_holds`, pinning the frontier state -- // composition refusal headed by `infer_grounding_not_derived`, with infer carrying that same -// reason and nothing else -- so the instrument's composition arm has a discriminating consumer -// (DESIGN section 5). It is expected to RED on the day -// `gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall`'s trigger lands; the -// repair rewrites it to assert `candidate_accepted` with empty carried lists -- the DESIGN 4b(4) -// flip from frontier guard to permanent regression control -- in the same change that removes the -// roster row. +// reason and nothing else -- and flipped to assert the green state in the change that landed the +// declared-inhabitant membership derivation (`v2.compiler.infer` +// `infer_node_declared_in_dag_inhabitants`): the add slice's ten type-spine nodes are all declared +// in the dag language authority's declared-inhabitants roster, so they derive by lookup. The flip +// is the DESIGN 4b(4) move from frontier guard to permanent regression control, landed in the +// same change that removed the roster row. The stall narrowed to its four python/typescript +// round-trip members rather than dissolving: the membership derivation does not reach grammar +// parse-product nodes, which is the remaining population's frontier. fn add_slice_stage_verdicts() -> CandidateGenerationStageVerdicts { candidate_generation_stage_verdicts( @@ -98,13 +102,10 @@ test fn add_slice_infer_accepts_holds() -> Bool { add_slice_stage_verdicts().infer_verdict == ^infer_accepted } -test fn add_slice_composition_refuses_at_the_grounding_frontier_holds() -> Bool { +test fn add_slice_composition_accepts_holds() -> Bool { let verdicts = add_slice_stage_verdicts() - (verdicts.composition_verdict == ^infer_grounding_not_derived) - && !is_empty(xs: verdicts.infer_carried_reasons) - && fold_list( - xs: verdicts.infer_carried_reasons, - empty: true, - cons: fn(acc, reason) { acc && (reason == ^infer_grounding_not_derived) } - ) + (verdicts.infer_verdict == ^infer_accepted) + && is_empty(xs: verdicts.infer_carried_reasons) + && (verdicts.composition_verdict == ^candidate_accepted) + && is_empty(xs: verdicts.composition_carried_reasons) } diff --git a/src/v2/test/claim/manual/ingest_bridge_test.dag b/src/v2/test/claim/manual/ingest_bridge_test.dag index c1983f54335..975af24c368 100644 --- a/src/v2/test/claim/manual/ingest_bridge_test.dag +++ b/src/v2/test/claim/manual/ingest_bridge_test.dag @@ -10,7 +10,7 @@ import v2.compiler.ingest { parse_tree_to_target_model_bridge } import v2.compiler.infer { canonical_grounding_for_node, infer } -import v2.compiler.translate { coerce_grounded_node } +import v2.compiler.translate { coerce_grounded_node, target_serialize_source_from_model } import v2.compiler.parse { grammar_validate_for_parse, parse_production, @@ -207,23 +207,33 @@ test fn ingest_bridge_to_canonical_holds() -> Bool { } } -test fn ingest_identity_coercion_needs_roster_membership_not_derived_grounding() -> Bool { +// FLIPPED 2026-09-06, in the change that landed the declared-inhabitant membership derivation +// (v2.compiler.infer infer_node_declared_in_dag_inhabitants). Authored as +// ingest_identity_coercion_needs_roster_membership_not_derived_grounding, it pinned that identity +// coercion succeeds via the authored roster while canonical_grounding_for_node REJECTED the +// roster member with infer_grounding_not_derived -- the isolation proving coercion does not read +// derived grounding. That premise is gone: dag_fixture_emitted_add_fn is declared in the dag +// authority's inhabitants roster, so its grounding now derives and canonical_grounding_for_node +// ACCEPTS it. The coercion half is unchanged and remains this witness's claim; the +// grounding-independence isolation now lives in the companion refusal witness below (a +// roster-ABSENT source still refuses), not here. + +test fn ingest_identity_coercion_accepts_source_present_in_authored_roster() -> Bool { let source = dag_fixture_emitted_add_fn match infer(tree: source) { Rejected { diagnostics: _ } => false Accepted { value: inferred, diagnostics: _ } => match canonical_grounding_for_node(tree: inferred, node: source) { - Accepted { value: _, diagnostics: _ } => false - Rejected { diagnostics: grounding_r } => - (grounding_r.head.reason == ^infer_grounding_not_derived) - && match coerce_grounded_node( - source: source, - tree: inferred, - target: ingest_fixture_model( - emitted: source, - spine_class: ^ingest_fixture_tok - ) - ) { + Rejected { diagnostics: _ } => false + Accepted { value: _, diagnostics: _ } => + match coerce_grounded_node( + source: source, + tree: inferred, + target: ingest_fixture_model( + emitted: source, + spine_class: ^ingest_fixture_tok + ) + ) { Rejected { diagnostics: _ } => false Accepted { value: coerced, diagnostics: _ } => (coerced.quality == Identity) && (coerced.target == source) @@ -297,7 +307,17 @@ test fn ingest_fails_closed_on_unstamped_holds() -> Bool { } } -test fn ingest_cross_language_compile_refuses_underived_holds() -> Bool { +// FLIPPED 2026-09-06, in the change that landed the declared-inhabitant membership derivation +// (v2.compiler.infer infer_node_declared_in_dag_inhabitants). Authored as +// ingest_cross_language_compile_refuses_underived_holds, it pinned the frontier refusal +// (infer_grounding_not_derived) on the dag same-language ingest path. The bridge recovers +// dag_fixture_emitted_add_fn -- a declared inhabitant -- so infer now grounds the whole tree by +// roster lookup and the compile ACCEPTS with no carried diagnostics. The oracle is the target +// model's own serializer: the compiled source is byte-equal to the authority's serialization of +// the same node, so the round trip is faithful by the authority's own measure, not by a +// transcribed literal. + +test fn ingest_cross_language_compile_accepts_holds() -> Bool { match ingest_parse() { Accepted { value: tree, diagnostics: _ } => match cross_language_compile( @@ -305,9 +325,16 @@ test fn ingest_cross_language_compile_refuses_underived_holds() -> Bool { source_model: ingest_fixture_model(emitted: dag_fixture_emitted_add_fn, spine_class: ^ingest_fixture_tok), target_model: dag_target_model() ) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => false + Accepted { value: source, diagnostics: d } => + match target_serialize_source_from_model( + target: dag_target_model(), + emitted: dag_fixture_emitted_add_fn + ) { + Accepted { value: authority, diagnostics: ad } => + (source.carried == authority.carried) && (d == None) && (ad == None) + Rejected { diagnostics: _ } => false + } } Rejected { diagnostics: _ } => false } @@ -319,6 +346,6 @@ test fn ingest_bridge_realized() -> Bool { && ingest_fails_closed_off_frontier_holds() && ingest_fails_closed_ambiguous_holds() && ingest_fails_closed_on_unstamped_holds() - && ingest_cross_language_compile_refuses_underived_holds() + && ingest_cross_language_compile_accepts_holds() } diff --git a/src/v2/test/claim/manual/inhabitant_neutralization_test.dag b/src/v2/test/claim/manual/inhabitant_neutralization_test.dag index 6474712c03d..adbf3bf8bf7 100644 --- a/src/v2/test/claim/manual/inhabitant_neutralization_test.dag +++ b/src/v2/test/claim/manual/inhabitant_neutralization_test.dag @@ -20,7 +20,7 @@ import v2.extdeps.languages.python { python_emitted_add_fn_node, python_target_model } -import v2.test.manual.ingest_bridge { ingest_cross_language_compile_refuses_underived_holds } +import v2.test.manual.ingest_bridge { ingest_cross_language_compile_accepts_holds } import v2.test.manual.python_grammar_claim { python_grammar_parse_fixture } import v2.extdeps.languages.typescript { ts_source_text, ts_target_model } import v2.std.inhabitant_neutralization { @@ -204,8 +204,12 @@ fn inhabitant_neutralization_raw_emit_still_fails_closed() -> Bool { } } -fn inhabitant_neutralization_same_language_cross_language_compile_unchanged_holds() -> Bool { - ingest_cross_language_compile_refuses_underived_holds() +// The same-language ingest compile this guard watches flipped from frontier refusal +// (infer_grounding_not_derived) to clean acceptance on 2026-09-06, under the declared-inhabitant +// membership derivation (v2.compiler.infer infer_node_declared_in_dag_inhabitants) -- an infer-lane +// change, not a neutralization one, which is exactly the distinction this guard exists to draw. +fn inhabitant_neutralization_same_language_cross_language_compile_accepts_holds() -> Bool { + ingest_cross_language_compile_accepts_holds() } fn inhabitant_neutralization_same_flavor_python_emit_refuses_underived_holds() -> Bool { @@ -228,7 +232,7 @@ fn inhabitant_neutralization_same_flavor_python_emit_refuses_underived_holds() - } test fn inhabitant_neutralization_03_ingest_regression_guard_holds() -> Bool { - inhabitant_neutralization_same_language_cross_language_compile_unchanged_holds() + inhabitant_neutralization_same_language_cross_language_compile_accepts_holds() && inhabitant_neutralization_same_flavor_python_emit_refuses_underived_holds() && inhabitant_neutralization_raw_emit_still_fails_closed() } diff --git a/src/v2/workflow/floor_expected_red.dag b/src/v2/workflow/floor_expected_red.dag index 5051120bab5..7d8b9a35e65 100644 --- a/src/v2/workflow/floor_expected_red.dag +++ b/src/v2/workflow/floor_expected_red.dag @@ -994,64 +994,6 @@ fn floor_expected_red_chunk_interpreter_first_optional_divergence() -> List List { } fn floor_expected_red_chunk_live_tree_admission() -> List { - Cons { head: "test.claim.ci_budget_tree_witness.witness_live_is_fail_closed", tail: Cons { head: "test.claim.ci_budget_tree_witness.witness_srv2_symmetric_to_srv1", tail: Cons { head: "test.claim.ci_budget_tree_witness.witness_srv3_outbudgets_srv1_by_exactly_the_overhead_gap", tail: Cons { head: "test.claim.doc_reachability_witness.doc_graph_has_no_dangling_links", tail: Cons { head: "test.claim.doc_reachability_witness.doc_graph_has_no_orphan_docs", tail: Cons { head: "test.claim.doc_reachability_witness.doc_graph_registered_plan_roots_all_admitted", tail: Cons { head: "test.claim.external_model_scope_live_cover_witness.frontier_cover_of_live_extdeps_tree_holds", tail: Cons { head: "test.claim.external_model_scope_witness.carriers_declare_scope_on_disk", tail: Cons { head: "test.claim.guarantee_floor_class_probe_witness.floor_generic_instantiation_hole_does_not_satisfy_the_refusing_control", tail: Cons { head: "test.claim.guarantee_floor_class_probe_witness.floor_generic_instantiation_is_still_silently_accepted", tail: Cons { head: "test.claim.guarantee_floor_class_probe_witness.floor_hole_receipt_records_acceptance_as_the_observation", tail: Cons { head: "test.claim.guarantee_probe_corpus_witness_test.bare_none_generic_parameter_field_is_outside_the_wall_boundary", tail: Cons { head: "test.claim.guarantee_probe_corpus_witness_test.dark_suite_dispositions_cover_migrated_v1_probes", tail: Cons { head: "test.claim.legacy_test_behavior_disposition_acceptance_test.legacy_test_behavior_unclassified_frontier_is_zero", tail: Cons { head: "test.claim.operation_argv_corpus_witness.operation_argv_corpus_executable_position_wall_fires_on_the_fixture_only", tail: Cons { head: "test.claim.operation_argv_corpus_witness.operation_argv_corpus_expression_residue_is_pinned", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f10_direct_selected_open_type_is_clean", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f10_direct_selected_sealed_type_refuses", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f10_neither_direct_ab_at_least_one_violation", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f10_neither_direct_order_independent", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f13_variant_construction_refuses", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f19_record_lit_default_value_position_refuses", tail: Cons { head: "v2.lens.mandatory_tag.corpus_scan_witness_test.corpus_live_clean_tree_wall_holds", tail: Cons { head: "v2.test.claim.enforcement.cost_coverage_witness.cost_coverage_dag_logic_has_three_fn_bodies", tail: Cons { head: "v2.test.claim.enforcement.cost_coverage_witness.cost_coverage_file_row_totality_holds_on_smoke", tail: Cons { head: "v2.test.claim.enforcement.cost_coverage_witness.cost_coverage_v2_ingested_body_is_not_decl_facts_skeleton", tail: Cons { head: "v2.test.execution.emit_ingest_grammar_relation_round_trip.same_grammar_parse_ingest_bridge_holds", tail: Cons { head: "v2.test.execution.self_host_candidate_generation.candidate_generation_translate_self_emit_dag_add_slice_holds", tail: Cons { head: "v2.test.lens_doc_reachability.doc_reachability_test.doc_graph_has_no_dangling_links", tail: Cons { head: "v2.test.lens_doc_reachability.doc_reachability_test.doc_graph_has_no_orphan_docs", tail: Cons { head: "v2.test.lens_doc_reachability.doc_reachability_test.doc_graph_is_clean", tail: Cons { head: "v2.test.lens_test_migration_debt.test_migration_debt_test.retained_rust_kernel_wall_holds_against_live_tree", tail: Cons { head: "v2.test.program_assembly.real_ingest.program_assembly_real_ingest_host_manifest_receipt_holds", tail: Cons { head: "v2.test.program_assembly.real_ingest.program_assembly_real_ingest_module_roots_parse_holds", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.withdrawing_the_consumer_admissions_surfaces_exactly_three", tail: Cons { head: "v2.test.self_host.compiler_closure_emit_from_ingest.compiler_closure_ingest_overflow_refuses_never_empty_holds", tail: Cons { head: "v2.test.self_host.compiler_closure_emit_from_ingest.compiler_closure_ingest_receipt_describes_carrier_holds", tail: Cons { head: "v2.test.self_host.compiler_closure_emit_from_ingest.compiler_closure_scoped_ingest_module_count_ok_holds", tail: Cons { head: "v2.test.self_host.compiler_closure_ingest_boundary_witness.compiler_closure_ingest_boundary_exact_cap_transport_holds", tail: Empty {} } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } + Cons { head: "test.claim.ci_budget_tree_witness.witness_live_is_fail_closed", tail: Cons { head: "test.claim.ci_budget_tree_witness.witness_srv2_symmetric_to_srv1", tail: Cons { head: "test.claim.ci_budget_tree_witness.witness_srv3_outbudgets_srv1_by_exactly_the_overhead_gap", tail: Cons { head: "test.claim.doc_reachability_witness.doc_graph_has_no_dangling_links", tail: Cons { head: "test.claim.doc_reachability_witness.doc_graph_has_no_orphan_docs", tail: Cons { head: "test.claim.doc_reachability_witness.doc_graph_registered_plan_roots_all_admitted", tail: Cons { head: "test.claim.external_model_scope_live_cover_witness.frontier_cover_of_live_extdeps_tree_holds", tail: Cons { head: "test.claim.external_model_scope_witness.carriers_declare_scope_on_disk", tail: Cons { head: "test.claim.guarantee_floor_class_probe_witness.floor_generic_instantiation_hole_does_not_satisfy_the_refusing_control", tail: Cons { head: "test.claim.guarantee_floor_class_probe_witness.floor_generic_instantiation_is_still_silently_accepted", tail: Cons { head: "test.claim.guarantee_floor_class_probe_witness.floor_hole_receipt_records_acceptance_as_the_observation", tail: Cons { head: "test.claim.guarantee_probe_corpus_witness_test.bare_none_generic_parameter_field_is_outside_the_wall_boundary", tail: Cons { head: "test.claim.guarantee_probe_corpus_witness_test.dark_suite_dispositions_cover_migrated_v1_probes", tail: Cons { head: "test.claim.legacy_test_behavior_disposition_acceptance_test.legacy_test_behavior_unclassified_frontier_is_zero", tail: Cons { head: "test.claim.operation_argv_corpus_witness.operation_argv_corpus_executable_position_wall_fires_on_the_fixture_only", tail: Cons { head: "test.claim.operation_argv_corpus_witness.operation_argv_corpus_expression_residue_is_pinned", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f10_direct_selected_open_type_is_clean", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f10_direct_selected_sealed_type_refuses", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f10_neither_direct_ab_at_least_one_violation", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f10_neither_direct_order_independent", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f13_variant_construction_refuses", tail: Cons { head: "test.claim.sole_constructor_completeness_audit_probe_test.f19_record_lit_default_value_position_refuses", tail: Cons { head: "v2.lens.mandatory_tag.corpus_scan_witness_test.corpus_live_clean_tree_wall_holds", tail: Cons { head: "v2.test.claim.enforcement.cost_coverage_witness.cost_coverage_dag_logic_has_three_fn_bodies", tail: Cons { head: "v2.test.claim.enforcement.cost_coverage_witness.cost_coverage_file_row_totality_holds_on_smoke", tail: Cons { head: "v2.test.claim.enforcement.cost_coverage_witness.cost_coverage_v2_ingested_body_is_not_decl_facts_skeleton", tail: Cons { head: "v2.test.execution.emit_ingest_grammar_relation_round_trip.same_grammar_parse_ingest_bridge_holds", tail: Cons { head: "v2.test.lens_doc_reachability.doc_reachability_test.doc_graph_has_no_dangling_links", tail: Cons { head: "v2.test.lens_doc_reachability.doc_reachability_test.doc_graph_has_no_orphan_docs", tail: Cons { head: "v2.test.lens_doc_reachability.doc_reachability_test.doc_graph_is_clean", tail: Cons { head: "v2.test.lens_test_migration_debt.test_migration_debt_test.retained_rust_kernel_wall_holds_against_live_tree", tail: Cons { head: "v2.test.program_assembly.real_ingest.program_assembly_real_ingest_host_manifest_receipt_holds", tail: Cons { head: "v2.test.program_assembly.real_ingest.program_assembly_real_ingest_module_roots_parse_holds", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.withdrawing_the_consumer_admissions_surfaces_exactly_three", tail: Cons { head: "v2.test.self_host.compiler_closure_emit_from_ingest.compiler_closure_ingest_overflow_refuses_never_empty_holds", tail: Cons { head: "v2.test.self_host.compiler_closure_emit_from_ingest.compiler_closure_ingest_receipt_describes_carrier_holds", tail: Cons { head: "v2.test.self_host.compiler_closure_emit_from_ingest.compiler_closure_scoped_ingest_module_count_ok_holds", tail: Cons { head: "v2.test.self_host.compiler_closure_ingest_boundary_witness.compiler_closure_ingest_boundary_exact_cap_transport_holds", tail: Empty {} } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } fn floor_expected_red_chunks() -> List> { From ffe2a1f84d1d4bd222a000b4b606d9b4339df0be Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 19:55:40 +0000 Subject: [PATCH 03/89] Derive grounding for canonical binding atoms: dag_binding_denotation joins binding to inhabitant once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resolver already binds the surface spelling Int to the canonical binding symbol dag_binding_type_int; what that binding DENOTES is the Int inhabitant declared at dag_declared_inhabitants_core. Every hand-rolled fixture facts lookup re-authored that join (dag_add_canonical_grounding_for, record_construct_canonical_grounding_for). The language authority now declares it once as dag_binding_denotation, and infer_node_facts consumes it: an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its grounding evidence. Direct-rust-door specimen census: 14 underived -> 10 underived (the four dag_binding_type_int atoms derive; grammar-production atoms, algebra atoms, bare operand atoms, and the arrow/conj spine stay on the frontier unchanged). Specimen-scope interim in the same frame as infer_node_declared_in_dag_inhabitants: both delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state). Witness: v2.test.execution.dag_binding_denotation — all four Int binding atoms in the door specimen derive with dag_int_inhabitant_node() as structural evidence, and the two bare operand atoms stay GroundingNotDerived (boundary control). Refusal suite 14/14, ingest bridge 7/7, add-slice instruments 2/2 green; every remaining red in the at-risk population reproduces identically on the pre-change tree and is enrolled in floor_expected_red. Co-authored-by: Brian Searls --- src/v2/compiler/04_infer.dag | 28 +++- src/v2/extdeps/languages/dag.dag | 17 ++ .../execution/dag_binding_denotation_test.dag | 150 ++++++++++++++++++ 3 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 src/v2/test/claim/execution/dag_binding_denotation_test.dag diff --git a/src/v2/compiler/04_infer.dag b/src/v2/compiler/04_infer.dag index 92dc0527069..5310c3d2c73 100644 --- a/src/v2/compiler/04_infer.dag +++ b/src/v2/compiler/04_infer.dag @@ -22,6 +22,7 @@ import v2.compiler.inferred_tree { import v2.extdeps.languages.dag { DagCanonicalBoolLiteral, DagCanonicalIntLiteral, + dag_binding_denotation, dag_canonical_literal_from_node, dag_declared_inhabitants_root } @@ -99,7 +100,7 @@ type AlgebraRef { witness: Node } -data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for exactly four of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only — 6 connectives + 6 behaviors = 12); the other eight kinds, and every other Transform shape, have no derivation rule yet. Above the kind rules sits one specimen-scope derivation: a node declared in the dag language authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants) — the authority already fixes what its members denote, so the grounding is a lookup, not an invention; non-member specimens of every kind stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." +data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for exactly four of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only — 6 connectives + 6 behaviors = 12); the other eight kinds, and every other Transform shape, have no derivation rule yet. Above the kind rules sit two specimen-scope derivations, both lookups into the dag language authority rather than inventions: a node declared in the authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants), and an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes. Both delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens neither rostered nor binding-denoted stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." fn infer_grounding_not_derived_diagnostic(at: Locus) -> Diagnostic { Diagnostic { @@ -514,10 +515,27 @@ fn infer_node_facts(n: Node, partials: List) -> Outcome { descent: Holds { value: descent_proof } ) } else { - inferred_facts_not_derived( - node: n, - descent: Holds { value: descent_proof } - ) + match infer_atom_binding_sym(node: n) { + Present { value: binding } => + match dag_binding_denotation(sym: binding) { + Present { value: denotation } => + inferred_facts_from_derived_type( + node: n, + derived_type: denotation, + descent: Holds { value: descent_proof } + ) + Absent => + inferred_facts_not_derived( + node: n, + descent: Holds { value: descent_proof } + ) + } + Absent => + inferred_facts_not_derived( + node: n, + descent: Holds { value: descent_proof } + ) + } } } } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 4f4caea7044..9dca23f735b 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -4109,6 +4109,23 @@ fn dag_int_type_binding() -> Symbol { ^dag_binding_type_int } +// THE BINDING→DENOTATION JOIN, declared once at the language authority. The resolver binds the +// surface spelling "Int" to the canonical binding symbol (dag_binding_spellings); what that +// binding DENOTES is the Int inhabitant declared at dag_declared_inhabitants_core. Every +// hand-rolled fixture facts lookup re-authors this join (dag_add_canonical_grounding_for, +// record_construct_canonical_grounding_for); the authority declares it once and infer consumes it +// (v2.compiler.infer infer_node_facts). Specimen-scope interim in the same frame as +// infer_node_declared_in_dag_inhabitants: deletes in favor of consuming resolution output when +// the resolver hands infer declaration-resolved identities directly (the namespace migration's +// completed state). +fn dag_binding_denotation(sym: Symbol) -> Optional { + if sym == dag_int_type_binding() { + optional_present(value: dag_int_inhabitant_node()) + } else { + optional_absent() + } +} + fn dag_binding_branded_atom_identity(node: Node) -> Optional { match node.kind { TypeNode { connective: Atom { identity: id } } => diff --git a/src/v2/test/claim/execution/dag_binding_denotation_test.dag b/src/v2/test/claim/execution/dag_binding_denotation_test.dag new file mode 100644 index 00000000000..727d030e328 --- /dev/null +++ b/src/v2/test/claim/execution/dag_binding_denotation_test.dag @@ -0,0 +1,150 @@ +module v2.test.execution.dag_binding_denotation + +import v2.compiler.infer { + DerivedGrounding, + GroundingNotDerived, + InferredTree, + infer +} +import v2.compiler.program_assembly { assemble_program_from_ingest } +import v2.compiler.self_host.direct_rust_door_fixture { + direct_rust_door_admission, + direct_rust_door_ingest +} +import v2.extdeps.languages.dag { + dag_int_inhabitant_node, + dag_int_type_binding, + dag_language_model +} +import v2.std.algebra { fold_list } +import v2.std.diagnostic { Accepted, Outcome, Rejected } +import v2.std.integer { Int } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import v2.std.logic { Bool } +import v2.std.node { Atom, Node, TypeNode, node_subtree_nodes } +import v2.std.optional { Absent, Present } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE ENROLLED CONTROLS FOR THE BINDING-DENOTATION DERIVATION (`v2.compiler.infer` +// infer_node_facts consulting `v2.extdeps.languages.dag` dag_binding_denotation). The specimen +// is the direct-rust-door fixture's ingest -- the corpus's smallest real-ingest dag module whose +// resolved tree carries canonical binding atoms (`dag_binding_type_int`, the resolver's binding +// of the surface spelling "Int"). The positive control pins both the derivation and its evidence: +// every canonical Int binding atom carries DerivedGrounding whose structural evidence is the dag +// authority's Int inhabitant, and the census count (4) pins that the witness actually saw them. +// The boundary control pins the rule's edge in the same tree: the bare operand atoms `x`/`y`, +// which no authority declares a denotation for, remain GroundingNotDerived. Deleting the rule +// reddens the positive control; widening the rule to unbound atoms reddens the boundary control. + +type BindingDenotationCensus { + int_atoms: Int + int_atoms_derived: Int + operand_atoms: Int + operand_atoms_frontier: Int +} + +fn binding_denotation_specimen_inferred() -> Outcome { + match assemble_program_from_ingest( + ingest: direct_rust_door_ingest(), + admission: direct_rust_door_admission(), + lm: dag_language_model() + ) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: resolved, diagnostics: _ } => infer(tree: resolved) + } +} + +fn is_int_binding_atom(n: Node) -> Bool { + match n.kind { + TypeNode { connective: Atom { identity: id } } => id == dag_int_type_binding() + _ => false + } +} + +fn is_bare_operand_atom(n: Node) -> Bool { + match n.kind { + TypeNode { connective: Atom { identity: id } } => (id == ^x) || (id == ^y) + _ => false + } +} + +fn int_binding_atom_derived(inferred: InferredTree, n: Node) -> Bool { + match inferred.facts.lookup(n) { + Absent => false + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => false + DerivedGrounding { grounding: g } => + g.witness.structural.evidence == dag_int_inhabitant_node() + } + } +} + +fn bare_operand_atom_stays_frontier(inferred: InferredTree, n: Node) -> Bool { + match inferred.facts.lookup(n) { + Absent => false + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => true + DerivedGrounding { grounding: _ } => false + } + } +} + +fn binding_denotation_census_step( + inferred: InferredTree, + acc: BindingDenotationCensus, + n: Node +) -> BindingDenotationCensus { + if is_int_binding_atom(n: n) { + BindingDenotationCensus { + int_atoms: acc.int_atoms + 1, + int_atoms_derived: acc.int_atoms_derived + (if int_binding_atom_derived(inferred: inferred, n: n) { 1 } else { 0 }), + operand_atoms: acc.operand_atoms, + operand_atoms_frontier: acc.operand_atoms_frontier + } + } else { + if is_bare_operand_atom(n: n) { + BindingDenotationCensus { + int_atoms: acc.int_atoms, + int_atoms_derived: acc.int_atoms_derived, + operand_atoms: acc.operand_atoms + 1, + operand_atoms_frontier: acc.operand_atoms_frontier + (if bare_operand_atom_stays_frontier(inferred: inferred, n: n) { 1 } else { 0 }) + } + } else { + acc + } + } +} + +fn binding_denotation_census(inferred: InferredTree) -> BindingDenotationCensus { + fold_list( + xs: node_subtree_nodes(root: inferred.root), + empty: BindingDenotationCensus { + int_atoms: 0, + int_atoms_derived: 0, + operand_atoms: 0, + operand_atoms_frontier: 0 + }, + cons: fn(acc, n) { binding_denotation_census_step(inferred: inferred, acc: acc, n: n) } + ) +} + +test fn dag_binding_denotation_derives_int_binding_atoms_holds() -> Bool { + match binding_denotation_specimen_inferred() { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + let census = binding_denotation_census(inferred: inferred) + (census.int_atoms == 4) && (census.int_atoms_derived == 4) + } +} + +test fn dag_binding_denotation_leaves_unbound_operand_atoms_on_the_frontier_holds() -> Bool { + match binding_denotation_specimen_inferred() { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + let census = binding_denotation_census(inferred: inferred) + (census.operand_atoms == 2) && (census.operand_atoms_frontier == 2) + } +} From 4638e6a847fff7d666574bd162d269a9a7288eb1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 18:34:53 +0000 Subject: [PATCH 04/89] Add v2 self-host direct-path orientation: axes, sequence, autonomy contract A point-in-time orientation that defers to the existing authorities (DESIGN section 7, the four-wave self-host program, the roadmap node chain, the three frontier carriers, the guarantee-stall roster, XL-N) rather than restating them: state is re-derived by the named instruments, never transcribed here. Sequences the remaining work in roadmap order (door, parse-product grounding, first behavioral module, XL-N milestones, native bootstrap, fixed point, v1 deletion) and states which decisions stay operator-gated. Co-authored-by: Brian Searls --- .../v2-self-host-direct-path-2026-09-06.md | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/plans/v2-self-host-direct-path-2026-09-06.md diff --git a/docs/plans/v2-self-host-direct-path-2026-09-06.md b/docs/plans/v2-self-host-direct-path-2026-09-06.md new file mode 100644 index 00000000000..067cb2aab5f --- /dev/null +++ b/docs/plans/v2-self-host-direct-path-2026-09-06.md @@ -0,0 +1,67 @@ +# The direct path to a clean v2 self-host — orientation, sequencing, autonomy (2026-09-06) + +**What this file is.** An orientation and sequencing read, written at one point in time, plus the autonomy contract under which an agent executes it. It is **not** a work-item roster and not a second authority: every fact about what remains lives in the carriers named below and is re-derived by running the instruments named below, never by reading a number off this page (DESIGN §6 — name the instrument, never transcribe its output). When this file and a carrier disagree, the carrier is right and this file is stale. + +**The authorities this file defers to:** + +- DESIGN §7 — the goal itself, and the honesty boundary around it. +- [v2-self-hosting.md](v2-self-hosting.md) — the operator-signed (2026-07-11) Weak → Strong Self Host program: four waves, dependency-gated exits, `src/v1` deleted only at the end of Wave 4. That is the plan from here to there; this file does not replace it. +- `dag/gunbc/roadmap/roadmap_authority.dag` — the node chain with per-node boundary, first slice, RED control, exclusions, handback. Projected to `ROADMAP.md`; every node requires explicit manual acceptance. +- The three self-host frontier carriers: `v2.compiler.self_host.seed_retention_frontier`, `v2.compiler.self_host.emit_coverage_frontier`, `v2.compiler.self_host.native_routing_frontier`. +- `dag/gunbc/guarantee_stall/` — the typed stall roster; `self_host_candidate_generation_add_slice_stall` is the grounding-frontier row. +- gunbc#9664 ("XL-N") — the native-closure burn-down program with go/no-go gates. + +## 1. What you actually get at the end + +In daily terms: **you edit `.dag` files, and the compiler that compiles them was itself compiled from `.dag` files.** The hand-written Rust seed is gone except a pinned, content-addressed bootstrap kernel (the wave doc sizes it at roughly 8–15k LOC); everything else the compiler does — reading source, resolving names, deriving types, translating, emitting — is emitted from the graph it compiles. + +The proof shape, per the roadmap nodes' own RED controls: + +- The generator builds itself **twice**; the second build is shown never to have reached the old compiler — run with the old one taken away, or with a receipt proving it was never called. Behaving the same is not accepted as proof of independence. +- Every self-emitted module passes the same behavioral corpus as the seed it replaced, including the deliberately-broken controls that must still fail. Byte-identity with the seed is explicitly **not** the goal (operator, 2026-07-08) — the seed's warts are not reproduced. +- Required CI runs `v2.test.*` through the native emitted crate; the interpreter survives only for `src/v1` regen until that too is gone. + +The payoff is DESIGN §7's: **language design opens up.** A new guarantee is a row, not a compiler fork; and because the substrate reads one grammar in both directions over many media, that row applies on top of an existing language through ingest — no adoption problem. This is the economic point of the whole exercise: today every improvement routes through the generator we are trying to delete. + +What you do **not** get (§7's honesty boundary): the compiler does not invent unstated intent, does not prove unmodeled external reality, and does not lift arbitrary predicates to proof. Runtime mechanisms — typed refusal, totality, rollback, budgets — remain real at honest boundaries. + +## 2. Where "here" is — by instrument + +Each line names the producer that re-derives the state. Run it; do not believe this sentence. + +- **Axis A — the real path, slice by slice.** The add slice (the `add` fn of `std.integer`) is green end-to-end on the dag path as of #10673: infer derives grounding by declared-inhabitant roster membership, and the ingest→compile round trip is byte-faithful. Instrument: `gunbc run --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag --function add_slice_stage_verdicts_entry`. The **direct-rust-door** (roadmap `v2-emitter-direct-rust-door`) is the critical-path red: real ingest and infer succeed on its specimen; the real inferred tree rejects inside emission. Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/long/direct_rust_door_production_group_test.dag --functions direct_rust_door_production_group_closing_expectation_holds` (a `long/` row — it reconstructs the pipeline per evaluation; the cost defect is declared in the test module's header). +- **The grounding frontier.** Derivation rules exist for a minority of the twelve node kinds; the stall carrier `self_host_candidate_generation_add_slice_stall` holds the live population and its `next_rung_trigger` names the parse-product family (grammar parse-tree nodes of the python/typescript fixtures). The add-slice rule and its blast-radius handling (#10673) are the template. +- **Axis B — the native closure (XL-N).** The XL-0 gate (`cargo check` = 0 on the emitted compiler closure) is **not satisfied**; the issue's own receipt table records that the closure reached complete rustc type checking for the first time at XL-0A and carries the honest error population there. gunbc#10266 (fabricated runtime panics on representation-identical casts) is filed as an independent compiler slice on this path. Instrument (the issue's own): `gunbc compile --source-root dag --source-root src/v2 --entry src/v2/compiler/00_compile.dag --target rust --output-dir /tmp/v2emit && cd /tmp/v2emit && cargo check --message-format short`. +- **Axis C — the seed dissolves.** `v2.compiler.self_host.seed_retention_frontier` holds every retained seed file as a declared row; `undeclared_reason_rows()` is the prioritizable debt, and the census refuses unrostered retention in three directions. `v2.compiler.self_host.emit_coverage_frontier`'s `interpreter_retained_rows()` and `v2.compiler.self_host.native_routing_frontier`'s roster cover the witness-execution axes. +- **Wave position.** Per the wave doc, Wave 1's four exit items all carry landed receipts; Wave 2 (first real flips — emit-surface tracks self-emit, behaviorally verify, and **replace** their v1 counterparts) is the open wave. The wave doc's header records that its roster carrier was deleted and per-module dispositions must be re-derived from the emitter — which is what the door-first sequence below does. + +## 3. The sequence from here + +Ordered by the roadmap chain; each step names its green condition as the node's own contract states it. + +1. **The door** (`v2-emitter-direct-rust-door`). Loop: run the production witness → read the `EmissionRejected` diagnostics → each missing Rust `TargetModel` rule is a `.dag` row → leaves establish one by one. Green condition: the production observation reaches `ArtifactProduced`, all five leaf projections establish, the known-red expectation row deletes in the same change, and the group root promotes to permanent regression evidence (`v2_emitter_direct_rust_door_contract_dissolution_note`). +2. **The parse-product grounding family** (the stall's named trigger). Generalize the roster-membership derivation from the dag language roster to the grammar parse-tree rosters of python/typescript. Greens the stall's remaining population and the same-grammar parse ingest bridge; the stall retires by its trigger. +3. **First behavioral module** (`v2-emitter-first-behavioral-module`). One already-tested module regenerated by the new generator; its existing tests — including the deliberately-broken one — pass unchanged, and the run is shown never to have reached the old generator. +4. **The XL-N milestones, in the issue's order** — arms → 0; emitter defects C, B → 0; crate compiles; native = interpreted with every divergence typed; swap. This is Wave 4's "pipeline runs on emitted Rust" executed as a burn-down, and its rules bind: fixes land in `.dag` and reach Rust via regen, and each closed class gets a `test.claim` fixture under the required gate. Its no-go gates are real: M2 stops if a fix needs a fact the inferred tree doesn't carry (an `04_infer` modeling gap — file it, don't grind); M3 re-plans if a class needs a language capability we don't have. +5. **Native bootstrap, then fixed point** (`v2-emitter-native-bootstrap`, `v1-emitter-fixed-point`). The two-round self-build for the generator, then the same two-round test extended stage by stage to the rest of the compiler. Then `dag/gunbc/v1/v1_deletion_plan.dag` executes; the seed-retention roster drains as modules flip, and Wave 3's first obligation — a derived denominator for what the compiler consists of — is named in the wave doc. +6. **Parallel track, not a wave gate:** the import-deletion ladder (wave doc §3) — B3's migration alongside Waves 2–3, B4's grammar deletion at Wave 4. + +## 4. The autonomy contract + +An agent can execute, end to end and without supervision, the per-slice loop: run the instrument → diagnose from the typed diagnostics → land the `.dag` fix → flip each affected witness per §4b(4) with per-witness justification → narrow the roster/stall rows in the same change → commit, push, open the PR, and iterate to green CI. #10673 is the worked example of that loop. Declaring seed-retention reasons, landing derivation rules, and burning XL-N error classes are all inside it. + +The operator gates that remain, each with its repo citation: + +- **Merges.** The agent never merges; every slice pauses at a green PR for human review. +- **Roadmap node acceptance.** ROADMAP.md's header states every active row requires explicit manual acceptance; a green node is handed back, not self-accepted. +- **CI job roster growth.** Closed without operator sign-off (DESIGN *Building & checks*, 2026-09-04 ruling). XL-N's M5 swap changes what required CI runs on — it is an operator decision by construction. +- **Scaffold admission.** §5: approval is external to the diff; an author cannot self-approve a scaffold. +- **Declared rung drops.** §4b(3): the agent can author the full declaration (previous rung, temporary rung, reason, population, restoration trigger); landing it is review-gated. +- **Language-semantics rulings** that the tree cannot decide. The Atom-authority question is the template: surfaced de-jargoned, decided by the operator (2026-09-06: the namespacing rules answer it), recorded in the carrier. These are rare but real, and the agent's obligation is to stop and raise them rather than guess. + +## 5. What would make this plan wrong + +- An XL-N no-go gate firing (M2's modeling gap, M3's missing language capability) — re-plan at that gate, per the issue's own rule. +- The door's emission diagnostics turning out to be an `04_infer` modeling gap rather than `TargetModel` rows — same shape as the XL-N M2 no-go. +- A Wave-2 flip surfacing behavioral divergence that re-sequences the module order — typed as emitter bug or interpreter bug either way, but the sequence absorbs it. +- This file aging. It is a position read, not a carrier; the instruments in §2 are the standing truth. From 8184a6fda74f8e893bdcc48c4b1ae4c38f8a33c9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 21:11:42 +0000 Subject: [PATCH 05/89] Derive grounding for fully-evidenced Conj and Arrow products MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sixth and seventh kind rules: a non-roster Conj or Arrow whose every child carries DerivedGrounding derives, its evidence the same shape re-formed over the children's grounding evidence (a fresh OccurrenceSynthetic node, never the source — the self-evidence wall holds by construction). A product with any frontier or absent child stays on the frontier with its typed diagnostic; a childless product has no evidence to compose and stays frontier. Roster members keep their roster evidence. Measured on the direct-rust-door specimen (scratch probe, uncommitted): 10 underived of 15 -> 6. The parameter conj, the module-structure conjs, and the bodied add arrow derive; what remains is the algebra atoms from the + operation (AlgebraPrimitive, ring_field_add), the module atom (dag_surface_module), the parameter references (x, y), and the grammar-projection root conj that cascades once they land. Enrolled witnesses (src/v2/test/claim/execution/infer_product_introduction_test.dag): - product_introduction_derives_fully_evidenced_products_holds — census: 4 Conj (3 derived, 1 frontier-by-frontier-child) + 1 Arrow (derived). - product_introduction_composed_evidence_carries_child_groundings_holds — the params conj's evidence is a Conj whose x/y children target the dag authority's Int inhabitant. - product_introduction_leaves_childless_conj_on_the_frontier_holds — boundary control via direct infer over a hand-built childless Conj. Flip census (pre- and post-change, zero unexpected flips): translate_underived_refusal 14/14, infer_self_grounding_wall 12/12, branch_infer_if_then_else 2/2, compile_eval_thesis_proof 6/6, ingest_bridge 9/9, cross_language_add_python_to_typescript 4/4, inhabitant_neutralization 6/6 + e2e 6/6, emit_host_classical_not 14/14, dag_binding_denotation 2/2, stage-verdicts instrument 2/2, dag_add_emit_round_trip 4/6 (the 2 enrolled reds unchanged), door production group still enrolled-red (unchanged). Co-authored-by: Brian Searls --- src/v2/compiler/04_infer.dag | 194 ++++++++++++++- .../infer_product_introduction_test.dag | 232 ++++++++++++++++++ 2 files changed, 422 insertions(+), 4 deletions(-) create mode 100644 src/v2/test/claim/execution/infer_product_introduction_test.dag diff --git a/src/v2/compiler/04_infer.dag b/src/v2/compiler/04_infer.dag index 5310c3d2c73..07cb03227d1 100644 --- a/src/v2/compiler/04_infer.dag +++ b/src/v2/compiler/04_infer.dag @@ -1,7 +1,7 @@ module v2.compiler.infer import std.occurrence_identity { OccurrenceSynthetic } -import v2.std.algebra { any, length, list_snoc_item } +import v2.std.algebra { any, Empty, length, list_snoc_item } import v2.std.bounded_lattice_completeness { infer_bounded_lattice_consumer_gate, partial_bounded_lattice_instances_in_tree @@ -100,7 +100,7 @@ type AlgebraRef { witness: Node } -data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for exactly four of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only — 6 connectives + 6 behaviors = 12); the other eight kinds, and every other Transform shape, have no derivation rule yet. Above the kind rules sit two specimen-scope derivations, both lookups into the dag language authority rather than inventions: a node declared in the authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants), and an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes. Both delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens neither rostered nor binding-denoted stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." +data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for six of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only, and Conj and Arrow by product introduction — 6 connectives + 6 behaviors = 12); the other six kinds, and every other Transform shape, have no derivation rule yet. The Conj and Arrow rules are compositional: a non-roster Conj or Arrow whose every child carries DerivedGrounding derives with the same shape re-formed over the children's grounding evidence as its evidence, and one with any frontier or absent child stays frontier — the derivation discharges only on a fully-evidenced child spine. Above the kind rules sit two specimen-scope derivations, both lookups into the dag language authority rather than inventions: a node declared in the authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants), and an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes. Both delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens neither rostered nor binding-denoted stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." fn infer_grounding_not_derived_diagnostic(at: Locus) -> Diagnostic { Diagnostic { @@ -486,6 +486,94 @@ fn infer_node_declared_in_dag_inhabitants(n: Node) -> Bool { ) } +// THE PRODUCT-INTRODUCTION DERIVATION (Conj and Arrow kinds). A Conj node denotes the product of +// its conjuncts' denotations and an Arrow node the function from its domain's denotation to its +// codomain's — the closed vocabulary's own composition semantics (v2.std.node), not an invented +// meaning. So when EVERY child of a non-roster Conj or Arrow carries DerivedGrounding, the node +// derives: its evidence is the same shape re-formed over each child's grounding evidence, a fresh +// OccurrenceSynthetic node whose evidential chain bottoms out in authority nodes at the leaves +// (never the source node — canonical_grounding_from_derived_type refuses self-evidence by +// construction). When ANY child is still on the frontier or absent from the entries, the node +// stays on the frontier with its typed diagnostic — the derivation discharges only when the whole +// child spine is evidenced, so a partially-derived product can never masquerade as grounded +// (DESIGN section 5). Roster members keep their roster evidence (the authority's declaration +// outranks composition for what the language itself declares), and a childless Conj or Arrow has +// no evidence to compose and stays frontier. This is the frontier note's dissolve-on for these +// two kinds: the kind-level rule the specimen-scope roster lookups stood in for. + +fn infer_type_node_awaits_product_row(n: Node) -> Bool { + (length(xs: n.children) > 0) && !infer_node_declared_in_dag_inhabitants(n: n) +} + +fn infer_product_child_evidence_edges( + children: List, + entries: List, +) -> Optional> { + fold( + children, + init: optional_present(value: Empty), + f: fn(acc, edge) { + match acc { + Absent => acc + Present { value: evidence_edges } => + match lookup_inferred_facts_in_entries(entries: entries, key: edge.target) { + Absent => optional_absent() + Present { value: child_facts } => + match inferred_facts_resolved_type(facts: child_facts) { + Violates { diagnostic: _ } => optional_absent() + Holds { value: child_evidence } => + optional_present( + value: list_snoc_item( + xs: evidence_edges, + item: Edge { label: edge.label, target: child_evidence } + ) + ) + } + } + } + } + ) +} + +fn infer_product_facts_from_entries( + node: Node, + entries: List, + partials: List, +) -> Outcome { + match infer_bounded_lattice_consumer_gate(consumer: node, partials: partials) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: _, diagnostics: cd } => + match infer_descent_witness_for_node(n: node) { + Violates { diagnostic: d } => + Rejected { diagnostics: diagnostics_singleton(d: d) } + Holds { value: descent_proof } => + match infer_product_child_evidence_edges(children: node.children, entries: entries) { + Absent => + bind_outcome_accepted( + od: cd, + inner: inferred_facts_not_derived( + node: node, + descent: Holds { value: descent_proof } + ) + ) + Present { value: evidence_edges } => + bind_outcome_accepted( + od: cd, + inner: inferred_facts_from_derived_type( + node: node, + derived_type: Node { + kind: node.kind, + children: evidence_edges, + occurrence_id: OccurrenceSynthetic + }, + descent: Holds { value: descent_proof } + ) + ) + } + } + } +} + fn infer_node_facts(n: Node, partials: List) -> Outcome { match infer_bounded_lattice_consumer_gate(consumer: n, partials: partials) { Rejected { diagnostics: r } => Rejected { diagnostics: r } @@ -1766,9 +1854,37 @@ fn infer_gather_fold_init(n: Node, partials: List) -> InferGatherFoldAcc { } ComputationNode { behavior: Bind } => infer_gather_fold_not_derived(n: n, partials: partials) TypeNode { connective: Atom { identity: _ } } => infer_gather_fold_not_derived(n: n, partials: partials) - TypeNode { connective: Conj } => infer_gather_fold_not_derived(n: n, partials: partials) + TypeNode { connective: Conj } => + if infer_type_node_awaits_product_row(n: n) { + infer_gather_fold_acc_ok( + node: n, + entries: empty_inferred_facts_entry_list(), + pending: None, + await_branch_row: false, + await_match_row: false, + await_loop_row: false, + await_transform_row: false, + children_remaining: length(xs: n.children) + ) + } else { + infer_gather_fold_not_derived(n: n, partials: partials) + } TypeNode { connective: Disj } => infer_gather_fold_not_derived(n: n, partials: partials) - TypeNode { connective: Arrow } => infer_gather_fold_not_derived(n: n, partials: partials) + TypeNode { connective: Arrow } => + if infer_type_node_awaits_product_row(n: n) { + infer_gather_fold_acc_ok( + node: n, + entries: empty_inferred_facts_entry_list(), + pending: None, + await_branch_row: false, + await_match_row: false, + await_loop_row: false, + await_transform_row: false, + children_remaining: length(xs: n.children) + ) + } else { + infer_gather_fold_not_derived(n: n, partials: partials) + } TypeNode { connective: Cardinality } => infer_gather_fold_not_derived(n: n, partials: partials) TypeNode { connective: Instantiation } => infer_gather_fold_not_derived(n: n, partials: partials) } @@ -1802,6 +1918,69 @@ fn infer_literal_edge_diagnostics_derived(parent: Node, edge: Edge) -> Bool { } } +fn infer_gather_acc_awaits_product_row(node: Node, entries: List) -> Bool { + match node.kind { + TypeNode { connective: Conj } => + match lookup_inferred_facts_in_entries(entries: entries, key: node) { + Absent => true + Present { value: _ } => false + } + TypeNode { connective: Arrow } => + match lookup_inferred_facts_in_entries(entries: entries, key: node) { + Absent => true + Present { value: _ } => false + } + _ => false + } +} + +fn infer_gather_product_row_on_entries( + node: Node, + entries: List, + pending: Diagnostics, + partials: List, +) -> InferGatherFoldAcc { + match infer_product_facts_from_entries(node: node, entries: entries, partials: partials) { + Rejected { diagnostics: r } => + infer_gather_fold_acc_failed( + node: node, + pending: rejected_with_pending(pending: pending, rejected: r), + await_branch_row: false, + await_match_row: false, + await_loop_row: false, + await_transform_row: false, + children_remaining: 0 + ) + Accepted { value: facts, diagnostics: d } => + match admitted_inferred_facts_entry(node: node, facts: facts) { + Rejected { diagnostics: r } => + infer_gather_fold_acc_failed( + node: node, + pending: rejected_with_pending(pending: pending, rejected: r), + await_branch_row: false, + await_match_row: false, + await_loop_row: false, + await_transform_row: false, + children_remaining: 0 + ) + Accepted { value: entry, diagnostics: ed } => + infer_gather_fold_acc_ok( + node: node, + entries: list_snoc_item(xs: entries, item: entry), + pending: diagnostics_merge( + outer: diagnostics_merge(outer: pending, inner: d), + inner: ed + ), + await_branch_row: false, + await_match_row: false, + await_loop_row: false, + await_transform_row: false, + children_remaining: 0 + ) + } + } +} + fn infer_gather_fold_step( acc: InferGatherFoldAcc, edge: Edge, @@ -1862,6 +2041,13 @@ fn infer_gather_fold_step( partials: partials, tree: tree ) + } else if children_remaining == 0 && infer_gather_acc_awaits_product_row(node: acc.node, entries: merged_entries) { + infer_gather_product_row_on_entries( + node: acc.node, + entries: merged_entries, + pending: merged_pending, + partials: partials + ) } else { infer_gather_fold_acc_ok( node: acc.node, diff --git a/src/v2/test/claim/execution/infer_product_introduction_test.dag b/src/v2/test/claim/execution/infer_product_introduction_test.dag new file mode 100644 index 00000000000..22683d4d72f --- /dev/null +++ b/src/v2/test/claim/execution/infer_product_introduction_test.dag @@ -0,0 +1,232 @@ +module v2.test.execution.infer_product_introduction + +import std.occurrence_identity { OccurrenceSynthetic } +import v2.compiler.infer { + DerivedGrounding, + GroundingNotDerived, + InferredTree, + infer +} +import v2.compiler.program_assembly { assemble_program_from_ingest } +import v2.compiler.self_host.direct_rust_door_fixture { + direct_rust_door_admission, + direct_rust_door_ingest +} +import v2.extdeps.languages.dag { + dag_int_inhabitant_node, + dag_language_model +} +import v2.std.algebra { fold_list } +import v2.std.collection { List, list_at_optional } +import v2.std.diagnostic { Accepted, Outcome, Rejected } +import v2.std.integer { Int } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import v2.std.logic { Bool } +import v2.std.node { + Arrow, + Atom, + Conj, + Edge, + Named, + Node, + TypeNode, + node_subtree_nodes +} +import v2.std.optional { Absent, Present } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE ENROLLED CONTROLS FOR THE PRODUCT-INTRODUCTION DERIVATION (`v2.compiler.infer` +// infer_gather_product_row_on_entries): a non-roster Conj or Arrow whose every child carries +// DerivedGrounding derives, its evidence the same shape re-formed over the children's grounding +// evidence. The specimen is the direct-rust-door fixture's ingest -- the corpus's smallest +// real-ingest dag module. The census pins that the witness actually saw the population: four +// Conj nodes (three fully-evidenced, one -- the grammar-projection root -- with a frontier child) +// and one bodied Arrow. The evidence control pins the composed evidence's SHAPE on the parameter +// conj: a Conj whose named children target the dag authority's Int inhabitant, never the source +// node (the self-evidence wall). The two boundary controls pin the refusal edge: a conj with a +// frontier child stays frontier, and a childless conj -- no evidence to compose -- stays frontier. +// Deleting the rule reddens the census and evidence controls; widening it to partially-evidenced +// or childless products reddens the boundary controls. + +type ProductIntroductionCensus { + conjs: Int + conjs_derived: Int + arrows: Int + arrows_derived: Int +} + +fn product_introduction_specimen_inferred() -> Outcome { + match assemble_program_from_ingest( + ingest: direct_rust_door_ingest(), + admission: direct_rust_door_admission(), + lm: dag_language_model() + ) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: resolved, diagnostics: _ } => infer(tree: resolved) + } +} + +fn product_introduction_node_derived(inferred: InferredTree, n: Node) -> Bool { + match inferred.facts.lookup(n) { + Absent => false + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => false + DerivedGrounding { grounding: _ } => true + } + } +} + +fn product_introduction_census_step( + inferred: InferredTree, + acc: ProductIntroductionCensus, + n: Node +) -> ProductIntroductionCensus { + match n.kind { + TypeNode { connective: Conj } => + ProductIntroductionCensus { + conjs: acc.conjs + 1, + conjs_derived: acc.conjs_derived + (if product_introduction_node_derived(inferred: inferred, n: n) { 1 } else { 0 }), + arrows: acc.arrows, + arrows_derived: acc.arrows_derived + } + TypeNode { connective: Arrow } => + ProductIntroductionCensus { + conjs: acc.conjs, + conjs_derived: acc.conjs_derived, + arrows: acc.arrows + 1, + arrows_derived: acc.arrows_derived + (if product_introduction_node_derived(inferred: inferred, n: n) { 1 } else { 0 }) + } + _ => acc + } +} + +fn product_introduction_census(inferred: InferredTree) -> ProductIntroductionCensus { + fold_list( + xs: node_subtree_nodes(root: inferred.root), + empty: ProductIntroductionCensus { + conjs: 0, + conjs_derived: 0, + arrows: 0, + arrows_derived: 0 + }, + cons: fn(acc, n) { product_introduction_census_step(inferred: inferred, acc: acc, n: n) } + ) +} + +fn params_conj_children_match(children: List) -> Bool { + match list_at_optional(xs: children, index: 0) { + Absent => false + Present { value: first } => + match list_at_optional(xs: children, index: 1) { + Absent => false + Present { value: second } => + match list_at_optional(xs: children, index: 2) { + Absent => + match first.label { + Named { name: a } => + match second.label { + Named { name: b } => (a == ^x) && (b == ^y) + _ => false + } + _ => false + } + Present { value: _ } => false + } + } + } +} + +fn is_params_conj(n: Node) -> Bool { + match n.kind { + TypeNode { connective: Conj } => params_conj_children_match(children: n.children) + _ => false + } +} + +fn evidence_children_target_int_inhabitant(children: List) -> Bool { + match list_at_optional(xs: children, index: 0) { + Absent => false + Present { value: first } => + match list_at_optional(xs: children, index: 1) { + Absent => false + Present { value: second } => + match list_at_optional(xs: children, index: 2) { + Absent => + (first.target == dag_int_inhabitant_node()) + && (second.target == dag_int_inhabitant_node()) + Present { value: _ } => false + } + } + } +} + +fn params_conj_evidence_is_composed_over_int_inhabitant(inferred: InferredTree, n: Node) -> Bool { + match inferred.facts.lookup(n) { + Absent => false + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => false + DerivedGrounding { grounding: g } => + let evidence = g.witness.structural.evidence + match evidence.kind { + TypeNode { connective: Conj } => + evidence_children_target_int_inhabitant(children: evidence.children) + _ => false + } + } + } +} + +fn params_conj_evidence_seen(inferred: InferredTree) -> Bool { + fold_list( + xs: node_subtree_nodes(root: inferred.root), + empty: false, + cons: fn(acc, n) { + if is_params_conj(n: n) { + params_conj_evidence_is_composed_over_int_inhabitant(inferred: inferred, n: n) + } else { + acc + } + } + ) +} + +test fn product_introduction_derives_fully_evidenced_products_holds() -> Bool { + match product_introduction_specimen_inferred() { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + let census = product_introduction_census(inferred: inferred) + (census.conjs == 4) && (census.conjs_derived == 3) + && (census.arrows == 1) && (census.arrows_derived == 1) + } +} + +test fn product_introduction_composed_evidence_carries_child_groundings_holds() -> Bool { + match product_introduction_specimen_inferred() { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + params_conj_evidence_seen(inferred: inferred) + } +} + +test fn product_introduction_leaves_childless_conj_on_the_frontier_holds() -> Bool { + let childless = Node { + kind: TypeNode { connective: Conj }, + children: [], + occurrence_id: OccurrenceSynthetic + } + match infer(tree: childless) { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + match inferred.facts.lookup(childless) { + Absent => false + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => true + DerivedGrounding { grounding: _ } => false + } + } + } +} From f2978e817848a14073433d165f197adcf498c3be Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 22:04:27 +0000 Subject: [PATCH 06/89] Ground canonical-operation and grammar-production atoms by authority roster membership Two more specimen-scope derivations in infer_node_facts, both lookups into declared authorities, never inventions: - Canonical-operations roster (target_model.dag): every CanonicalOperation the target-model authority declares, rendered by target_model_canonical_operation_wire_node and gathered under one Conj root. The resolver canonicalizes surface operators (e.g. +) to those declared operations, so the wire atoms -- the operation discriminant and its field references -- derive by membership with the roster root as evidence. General over all 14 declared operations, not add-narrow. - Grammar-productions roster (dag.dag): every production in dag_grammar_root() projected to its emitted surface atom under one Conj root keyed by production name. The bridge projects a production's parse into (identity atom, captured content) pairs, so the identity atom (dag_surface_module) derives by membership with the roster root as evidence. The roster derives from the grammar root, so a production added to the grammar joins by construction. Both roster roots are Conj nodes, never structurally equal to any member atom, so the self-evidence wall holds by construction (the first attempt at the operations rule used the wire node itself as evidence and was refused by grounding_evidence_is_source -- the wall doing its work). Measured on the direct-rust-door specimen (scratch probe, uncommitted): 6 underived of 15 -> 2 (only the operand atoms x and y remain; the grammar-projection root conj cascades once the module atom grounds). Enrolled witnesses (infer_atom_grounding_rules_test.dag): each roster rule pins derivation + evidence identity + census; a boundary control pins that a bare atom with no authority membership stays frontier; the closing control pins the 2-of-15 state. Flip census: the product-introduction census witness updates 3->4 derived conjs (the top conj now cascades) and gains a hand-built partially-evidenced boundary control to replace the in-specimen one the cascade consumed. Full battery otherwise unchanged: refusal suite 14/14, grounding wall 12/12, instrument 2/2, binding-denotation 2/2, round-trips, bridge, cross-language, neutralization, emit-host all green; enrolled reds unchanged. Co-authored-by: Brian Searls --- src/v2/compiler/04_infer.dag | 66 +++++++- src/v2/extdeps/languages/dag.dag | 28 +++- src/v2/std/compilers/target_model.dag | 83 +++++++++ .../infer_atom_grounding_rules_test.dag | 158 ++++++++++++++++++ .../infer_product_introduction_test.dag | 52 +++++- 5 files changed, 371 insertions(+), 16 deletions(-) create mode 100644 src/v2/test/claim/execution/infer_atom_grounding_rules_test.dag diff --git a/src/v2/compiler/04_infer.dag b/src/v2/compiler/04_infer.dag index 07cb03227d1..9baad4a2c90 100644 --- a/src/v2/compiler/04_infer.dag +++ b/src/v2/compiler/04_infer.dag @@ -8,7 +8,8 @@ import v2.std.bounded_lattice_completeness { } import v2.std.compilers.target_model { canonical_operation_op_add, - canonical_operation_wire_matches_operation + canonical_operation_wire_matches_operation, + target_model_canonical_operations_roster_root } import v2.compiler.resolve { ResolvedTree } import v2.compiler.inferred_tree { @@ -24,7 +25,8 @@ import v2.extdeps.languages.dag { DagCanonicalIntLiteral, dag_binding_denotation, dag_canonical_literal_from_node, - dag_declared_inhabitants_root + dag_declared_inhabitants_root, + dag_grammar_productions_roster_root } import v2.std.cardinality { TerminationProof, termination_proof_witness_for_node } import v2.std.collection { @@ -100,7 +102,7 @@ type AlgebraRef { witness: Node } -data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for six of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only, and Conj and Arrow by product introduction — 6 connectives + 6 behaviors = 12); the other six kinds, and every other Transform shape, have no derivation rule yet. The Conj and Arrow rules are compositional: a non-roster Conj or Arrow whose every child carries DerivedGrounding derives with the same shape re-formed over the children's grounding evidence as its evidence, and one with any frontier or absent child stays frontier — the derivation discharges only on a fully-evidenced child spine. Above the kind rules sit two specimen-scope derivations, both lookups into the dag language authority rather than inventions: a node declared in the authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants), and an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes. Both delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens neither rostered nor binding-denoted stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." +data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for six of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only, and Conj and Arrow by product introduction — 6 connectives + 6 behaviors = 12); the other six kinds, and every other Transform shape, have no derivation rule yet. The Conj and Arrow rules are compositional: a non-roster Conj or Arrow whose every child carries DerivedGrounding derives with the same shape re-formed over the children's grounding evidence as its evidence, and one with any frontier or absent child stays frontier — the derivation discharges only on a fully-evidenced child spine. Above the kind rules sit four specimen-scope derivations, all lookups into declared authorities rather than inventions: a node declared in the dag authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants); an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes; a node that is a member of the target-model authority's canonical-operations roster derives with that roster root as its evidence (infer_node_declared_in_canonical_operations) — the resolver canonicalizes surface operators to those declared operations; a node that is a member of the dag grammar authority's productions roster derives with that roster root as its evidence (infer_node_declared_in_dag_grammar_productions) — the bridge projects a production's parse into (identity atom, captured content) pairs. All four delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens no authority declares stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." fn infer_grounding_not_derived_diagnostic(at: Locus) -> Diagnostic { Diagnostic { @@ -505,6 +507,40 @@ fn infer_type_node_awaits_product_row(n: Node) -> Bool { (length(xs: n.children) > 0) && !infer_node_declared_in_dag_inhabitants(n: n) } +// THE CANONICAL-OPERATION ROSTER MEMBERSHIP DERIVATION. The resolver canonicalizes a dag surface +// operator (e.g. `+`) to the target-model authority's declared canonical operation, and the +// transform rule already validates operators by matching against that authority's wire nodes +// (canonical_operation_wire_matches_operation). The wire atoms -- the operation discriminant and +// its field references -- are declared components of those same wire nodes, gathered under the +// authority's declared roster root (target_model_canonical_operations_roster_root). So a node +// that IS a roster member derives by lookup with the roster root as evidence, never by invention +// -- the same frame as the dag declared-inhabitants roster, and general over every declared +// operation rather than add-narrow. The end-state deletes this lookup with the other +// specimen-scope rules, when the resolver hands infer declaration-resolved identities directly. + +fn infer_node_declared_in_canonical_operations(n: Node) -> Bool { + any( + xs: node_subtree_nodes(root: target_model_canonical_operations_roster_root()), + predicate: fn(member) { member == n } + ) +} + +// THE GRAMMAR-PRODUCTION ROSTER MEMBERSHIP DERIVATION. The bridge projects a grammar production's +// parse into the tree as (identity atom, captured content) pairs; the identity atom (e.g. +// dag_surface_module) is the emitted surface atom the dag grammar authority declares for that +// production, gathered under the authority's roster root (dag_grammar_productions_roster_root). +// So a node that IS a roster member derives by lookup with the roster root as evidence, never by +// invention -- the same frame as the declared-inhabitants roster, general over every declared +// production. The end-state deletes this lookup with the other specimen-scope rules, when the +// resolver hands infer declaration-resolved identities directly. + +fn infer_node_declared_in_dag_grammar_productions(n: Node) -> Bool { + any( + xs: node_subtree_nodes(root: dag_grammar_productions_roster_root()), + predicate: fn(member) { member == n } + ) +} + fn infer_product_child_evidence_edges( children: List, entries: List, @@ -613,10 +649,26 @@ fn infer_node_facts(n: Node, partials: List) -> Outcome { descent: Holds { value: descent_proof } ) Absent => - inferred_facts_not_derived( - node: n, - descent: Holds { value: descent_proof } - ) + if infer_node_declared_in_canonical_operations(n: n) { + inferred_facts_from_derived_type( + node: n, + derived_type: target_model_canonical_operations_roster_root(), + descent: Holds { value: descent_proof } + ) + } else { + if infer_node_declared_in_dag_grammar_productions(n: n) { + inferred_facts_from_derived_type( + node: n, + derived_type: dag_grammar_productions_roster_root(), + descent: Holds { value: descent_proof } + ) + } else { + inferred_facts_not_derived( + node: n, + descent: Holds { value: descent_proof } + ) + } + } } Absent => inferred_facts_not_derived( diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 9dca23f735b..d6f922fc5c7 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -150,7 +150,7 @@ import v2.std.compilers.target_model { derive_bodied_arrow_scaffold} import v2.std.logic { Bool } import std.algebra { Cons, Empty } -import v2.std.algebra { fold_list, for_all, is_empty, length, list_append, list_snoc_item } +import v2.std.algebra { fold_list, for_all, is_empty, length, list_append, list_map, list_snoc_item } import v2.std.diagnostic { Accepted, CorrectionNotModeled, @@ -3383,6 +3383,32 @@ fn dag_grammar_root() -> GrammarRoot { } } +// THE DECLARED GRAMMAR-PRODUCTION ROSTER, AS A NODE. Every production the grammar authority +// declares, projected to its emitted surface atom and gathered under one Conj root keyed by the +// production's name. This is the single authority an inference membership rule consults to ground +// grammar-projection identity atoms (e.g. dag_surface_module) by lookup -- the same frame as the +// declared-inhabitants roster: the roster root is the evidence, and it is never structurally equal +// to any member atom, so the self-evidence wall holds by construction. The roster derives from +// dag_grammar_root(), so a production added to the grammar joins the roster by construction. + +fn dag_grammar_production_roster_edge(production: GrammarProduction) -> Edge { + Edge { + label: Named { name: production.name }, + target: production.emitted + } +} + +fn dag_grammar_productions_roster_root() -> Node { + Node { + kind: TypeNode { connective: Conj }, + children: list_map( + xs: dag_grammar_root().productions, + f: dag_grammar_production_roster_edge + ), + occurrence_id: OccurrenceSynthetic + } +} + fn dag_g0_void_grammar() -> ParseGrammar { VoidGrammar } diff --git a/src/v2/std/compilers/target_model.dag b/src/v2/std/compilers/target_model.dag index 2f9871dcf72..1247dcfab09 100644 --- a/src/v2/std/compilers/target_model.dag +++ b/src/v2/std/compilers/target_model.dag @@ -1608,6 +1608,89 @@ fn target_model_canonical_operation_wire_node(operation: CanonicalOperation) -> } } +// THE DECLARED CANONICAL-OPERATION ROSTER, AS A NODE. Every CanonicalOperation this authority +// declares, rendered by target_model_canonical_operation_wire_node and gathered under one Conj +// root. This is the single authority an inference membership rule consults to ground canonical- +// operation wire atoms by lookup (the same frame as the dag authority's declared-inhabitants +// roster): the roster root is the evidence, and it is never structurally equal to any member, so +// the self-evidence wall holds by construction. Adding an operation to this authority means adding +// its row here -- the roster is closed over exactly the operations declared above. + +fn target_model_canonical_operation_roster_edge( + name: Symbol, + operation: CanonicalOperation +) -> Edge { + Edge { + label: Named { name: name }, + target: target_model_canonical_operation_wire_node(operation: operation) + } +} + +fn target_model_canonical_operations_roster_root() -> Node { + Node { + kind: TypeNode { connective: Conj }, + children: [ + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_add, + operation: canonical_operation_op_add() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_mul, + operation: canonical_operation_op_mul() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_negate, + operation: canonical_operation_op_negate() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_and, + operation: canonical_operation_op_and() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_or, + operation: canonical_operation_op_or() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_not, + operation: canonical_operation_op_not() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_sub, + operation: canonical_operation_op_sub() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_div, + operation: canonical_operation_op_div() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_lt, + operation: canonical_operation_op_lt() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_le, + operation: canonical_operation_op_le() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_gt, + operation: canonical_operation_op_gt() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_ge, + operation: canonical_operation_op_ge() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_eq, + operation: canonical_operation_op_eq() + ), + target_model_canonical_operation_roster_edge( + name: ^canonical_operation_roster_member_ne, + operation: canonical_operation_op_ne() + ) + ], + occurrence_id: OccurrenceSynthetic + } +} + fn canonical_operation_equal( left: CanonicalOperation, right: CanonicalOperation diff --git a/src/v2/test/claim/execution/infer_atom_grounding_rules_test.dag b/src/v2/test/claim/execution/infer_atom_grounding_rules_test.dag new file mode 100644 index 00000000000..59c26c3f548 --- /dev/null +++ b/src/v2/test/claim/execution/infer_atom_grounding_rules_test.dag @@ -0,0 +1,158 @@ +module v2.test.execution.infer_atom_grounding_rules + +import std.occurrence_identity { OccurrenceSynthetic } +import v2.compiler.infer { + DerivedGrounding, + GroundingNotDerived, + InferredTree, + infer +} +import v2.compiler.program_assembly { assemble_program_from_ingest } +import v2.compiler.self_host.direct_rust_door_fixture { + direct_rust_door_admission, + direct_rust_door_ingest +} +import v2.extdeps.languages.dag { + dag_grammar_productions_roster_root, + dag_language_model +} +import v2.std.algebra { fold_list } +import v2.std.compilers.target_model { target_model_canonical_operations_roster_root } +import v2.std.diagnostic { Accepted, Outcome, Rejected } +import v2.std.integer { Int } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import v2.std.logic { Bool } +import v2.std.node { + Atom, + Conj, + Named, + Node, + TypeNode, + node_subtree_nodes +} +import v2.std.optional { Absent, optional_present, Present } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE ENROLLED CONTROLS FOR THE ATOM-GROUNDING ROSTER DERIVATIONS (`v2.compiler.infer` +// infer_node_facts): each is a lookup into a declared authority, never an invention. +// - canonical-operations roster: the resolver canonicalizes surface operators to the +// target-model authority's declared operations; the wire atoms (the AlgebraPrimitive +// discriminant and its ring_field_add field) are members of the authority's roster, and +// derive with the roster root as evidence. +// - grammar-productions roster: the bridge projects a production's parse into (identity atom, +// captured content); the identity atom (dag_surface_module) is the production's declared +// emitted atom, and derives with the grammar roster root as evidence. +// The specimen is the direct-rust-door fixture's ingest. Each control pins BOTH the derivation +// and the evidence identity, and the census pins that the witness saw the population. The +// boundary control pins that a bare atom with no authority membership stays on the frontier, and +// the closing control pins the specimen state after these two rules: exactly two frontier nodes +// of fifteen (the operand atoms, grounded by the binding-reference rule in a later change). +// Deleting a rule reddens its control; widening a rule to non-members reddens the boundary +// control. + +fn atom_grounding_specimen_inferred() -> Outcome { + match assemble_program_from_ingest( + ingest: direct_rust_door_ingest(), + admission: direct_rust_door_admission(), + lm: dag_language_model() + ) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: resolved, diagnostics: _ } => infer(tree: resolved) + } +} + +fn atom_grounding_node_evidence(inferred: InferredTree, n: Node) -> Optional { + match inferred.facts.lookup(n) { + Absent => Absent + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => Absent + DerivedGrounding { grounding: g } => optional_present(value: g.witness.structural.evidence) + } + } +} + +fn atom_grounding_count_evidence( + inferred: InferredTree, + wanted: Node, + acc: Int, + n: Node +) -> Int { + match atom_grounding_node_evidence(inferred: inferred, n: n) { + Absent => acc + Present { value: evidence } => + if evidence == wanted { acc + 1 } else { acc } + } +} + +fn atom_grounding_census_frontier(inferred: InferredTree) -> Int { + fold_list( + xs: node_subtree_nodes(root: inferred.root), + empty: 0, + cons: fn(acc, n) { + match inferred.facts.lookup(n) { + Absent => acc + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => acc + 1 + DerivedGrounding { grounding: _ } => acc + } + } + } + ) +} + +test fn canonical_operations_roster_grounds_wire_atoms_holds() -> Bool { + match atom_grounding_specimen_inferred() { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + let roster = target_model_canonical_operations_roster_root() + fold_list( + xs: node_subtree_nodes(root: inferred.root), + empty: 0, + cons: fn(acc, n) { atom_grounding_count_evidence(inferred: inferred, wanted: roster, acc: acc, n: n) } + ) == 2 + } +} + +test fn grammar_productions_roster_grounds_module_atom_holds() -> Bool { + match atom_grounding_specimen_inferred() { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + let roster = dag_grammar_productions_roster_root() + fold_list( + xs: node_subtree_nodes(root: inferred.root), + empty: 0, + cons: fn(acc, n) { atom_grounding_count_evidence(inferred: inferred, wanted: roster, acc: acc, n: n) } + ) == 1 + } +} + +test fn atom_rules_leave_unbound_atom_on_the_frontier_holds() -> Bool { + let unbound = Node { + kind: TypeNode { connective: Atom { identity: ^no_authority_declares_this_symbol } }, + children: [], + occurrence_id: OccurrenceSynthetic + } + match infer(tree: unbound) { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + match inferred.facts.lookup(unbound) { + Absent => false + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => true + DerivedGrounding { grounding: _ } => false + } + } + } +} + +test fn atom_rules_leave_only_operand_atoms_on_the_door_frontier_holds() -> Bool { + match atom_grounding_specimen_inferred() { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + atom_grounding_census_frontier(inferred: inferred) == 2 + } +} diff --git a/src/v2/test/claim/execution/infer_product_introduction_test.dag b/src/v2/test/claim/execution/infer_product_introduction_test.dag index 22683d4d72f..157e649f3ba 100644 --- a/src/v2/test/claim/execution/infer_product_introduction_test.dag +++ b/src/v2/test/claim/execution/infer_product_introduction_test.dag @@ -41,13 +41,14 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // DerivedGrounding derives, its evidence the same shape re-formed over the children's grounding // evidence. The specimen is the direct-rust-door fixture's ingest -- the corpus's smallest // real-ingest dag module. The census pins that the witness actually saw the population: four -// Conj nodes (three fully-evidenced, one -- the grammar-projection root -- with a frontier child) -// and one bodied Arrow. The evidence control pins the composed evidence's SHAPE on the parameter -// conj: a Conj whose named children target the dag authority's Int inhabitant, never the source -// node (the self-evidence wall). The two boundary controls pin the refusal edge: a conj with a -// frontier child stays frontier, and a childless conj -- no evidence to compose -- stays frontier. -// Deleting the rule reddens the census and evidence controls; widening it to partially-evidenced -// or childless products reddens the boundary controls. +// Conj nodes, all derived (the grammar-projection root included -- it cascaded once the atom +// rules grounded its children), and one bodied Arrow, derived. The evidence control pins the +// composed evidence's SHAPE on the parameter conj: a Conj whose named children target the dag +// authority's Int inhabitant, never the source node (the self-evidence wall). The two boundary +// controls pin the refusal edge on hand-built trees: a conj with a frontier child stays frontier, +// and a childless conj -- no evidence to compose -- stays frontier. Deleting the rule reddens the +// census and evidence controls; widening it to partially-evidenced or childless products reddens +// the boundary controls. type ProductIntroductionCensus { conjs: Int @@ -198,7 +199,7 @@ test fn product_introduction_derives_fully_evidenced_products_holds() -> Bool { Rejected { diagnostics: _ } => false Accepted { value: inferred, diagnostics: _ } => let census = product_introduction_census(inferred: inferred) - (census.conjs == 4) && (census.conjs_derived == 3) + (census.conjs == 4) && (census.conjs_derived == 4) && (census.arrows == 1) && (census.arrows_derived == 1) } } @@ -230,3 +231,38 @@ test fn product_introduction_leaves_childless_conj_on_the_frontier_holds() -> Bo } } } + +fn product_introduction_hand_tree_grounding(tree: Node, n: Node) -> Int { + match infer(tree: tree) { + Rejected { diagnostics: _ } => 2 + Accepted { value: inferred, diagnostics: _ } => + match inferred.facts.lookup(n) { + Absent => 2 + Present { value: facts } => + match facts.grounding { + GroundingNotDerived { node: _ } => 1 + DerivedGrounding { grounding: _ } => 0 + } + } + } +} + +test fn product_introduction_leaves_partially_evidenced_conj_on_the_frontier_holds() -> Bool { + let frontier_child = Node { + kind: TypeNode { connective: Atom { identity: ^no_authority_declares_this_symbol } }, + children: [], + occurrence_id: OccurrenceSynthetic + } + let derived_child = dag_int_inhabitant_node() + let partial = Node { + kind: TypeNode { connective: Conj }, + children: [ + Edge { label: Named { name: ^derived_field }, target: derived_child }, + Edge { label: Named { name: ^frontier_field }, target: frontier_child } + ], + occurrence_id: OccurrenceSynthetic + } + (product_introduction_hand_tree_grounding(tree: partial, n: derived_child) == 0) + && (product_introduction_hand_tree_grounding(tree: partial, n: frontier_child) == 1) + && (product_introduction_hand_tree_grounding(tree: partial, n: partial) == 1) +} From e165f2a83524a90c9f28f69730e3da383f215465 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 22:05:46 +0000 Subject: [PATCH 07/89] Ground binding-reference atoms from the enclosing arrow's domain declaration The fifth specimen-scope derivation, closing the direct-rust-door specimen's inference frontier: an Atom whose binding an enclosing arrow's domain declares derives with the declared domain type as its evidence -- the declaration-site annotation, itself derived (x: Int grounds the x reference). This is the same lookup the branch-operand path already performs (infer_find_arrow_domain_type_in_tree), now written to the operand atom's own facts; it is scope-naive (whole-tree, first match), recorded in the frontier note, and deletes with the other specimen-scope rules when the resolver hands infer declaration-resolved identities. The tree is threaded through the fold's init chain to reach infer_node_facts; the helper had exactly one caller. Measured on the door specimen (scratch probe, uncommitted): 2 underived of 15 -> 0. The specimen's inference frontier is fully closed, and the production observation advances from InferenceRejected (infer_grounding_not_derived) to EmissionRejected (target_use_site_ownership_lookup_miss) -- a new, typed, located deficit in the emitter, the next gate on the path. Flip census (all three rewrites verified by execution): - dag_binding_denotation_leaves_unbound_operand_atoms_on_the_frontier_holds -> dag_binding_denotation_declares_no_denotation_for_operand_bindings_holds: the boundary moves to the authority itself (the denotation table returns Absent for x/y), true regardless of infer's other rules. - The three emit_host classical-not refusal guards (canonical, staging, staging-swapped) flip to acceptance witnesses pinning the emitted text's shape -- the real-infer tree now fully derives, and the emission is the same one the equals-eval witness proves behaviorally correct. The translate-refuses-underived behavior stays enrolled on hand-staged fixtures in translate_underived_refusal_test.dag (14/14 green). The renames are carried into the commit_workflow and witness_deferral_freeze rosters. - New witnesses: binding_reference_derives_parameter_atoms_holds (evidence is the domain's Int binding atom, census 2) and door_specimen_fully_derives_holds (0 frontier of 15). Full battery at this state: refusal suite 14/14, grounding wall 12/12, instrument 2/2, binding-denotation 2/2, product-introduction 4/4, atom-rules 5/5, emit_host 14/14, round-trips 4/6 (2 enrolled reds unchanged), bridge 9/9, cross-language 4/4, neutralization 6/6 + e2e 6/6, branch 2/2, eval-thesis 6/6; door production group still enrolled-red (unchanged). Co-authored-by: Brian Searls --- dag/gunbc/commit_workflow.dag | 4 +- dag/gunbc/witness/witness_deferral_freeze.dag | 2 +- src/v2/compiler/04_infer.dag | 47 +++++++++------- .../execution/dag_binding_denotation_test.dag | 24 +++++---- .../infer_atom_grounding_rules_test.dag | 54 ++++++++++++++++--- ...lassical_not_ingested_equals_eval_test.dag | 41 ++++++++------ 6 files changed, 117 insertions(+), 55 deletions(-) diff --git a/dag/gunbc/commit_workflow.dag b/dag/gunbc/commit_workflow.dag index 35953767851..a5233ed93c3 100644 --- a/dag/gunbc/commit_workflow.dag +++ b/dag/gunbc/commit_workflow.dag @@ -1684,8 +1684,8 @@ data commit_gate_roster: List = [ check_fns: [ "ingested_classical_not_resolves_holds", "ingested_classical_not_arrow_has_match_holds", - "ingested_classical_not_canonical_emit_refuses_underived_holds", - "ingested_classical_not_staging_emit_refuses_underived_holds", + "ingested_classical_not_canonical_emit_accepts_holds", + "ingested_classical_not_staging_emit_accepts_holds", "ingested_classical_not_emit_accepts_holds", "ingested_classical_not_emit_octet_bridge_shape_holds", "ingested_classical_not_emit_source_arm_shape_holds" diff --git a/dag/gunbc/witness/witness_deferral_freeze.dag b/dag/gunbc/witness/witness_deferral_freeze.dag index b299f378c9e..9aef5f18a6d 100644 --- a/dag/gunbc/witness/witness_deferral_freeze.dag +++ b/dag/gunbc/witness/witness_deferral_freeze.dag @@ -549,7 +549,7 @@ data frozen_path_deferrals: List = [ entry: "src/v2/test/claim/execution/long/emit_host_classical_not_ingested_equals_eval_test.dag", functions: [ "ingested_classical_not_eval_subgraph_canonical_octet_zero_holds", - "ingested_classical_not_staging_swapped_emit_refuses_underived_holds", + "ingested_classical_not_staging_swapped_emit_accepts_holds", "emit_host_classical_not_ingested_swapped_refuses_holds", "ingested_classical_not_real_infer_holds", "ingested_classical_not_match_only_param_scrutinee_infer_refuses_holds", diff --git a/src/v2/compiler/04_infer.dag b/src/v2/compiler/04_infer.dag index 9baad4a2c90..5cd667eaa61 100644 --- a/src/v2/compiler/04_infer.dag +++ b/src/v2/compiler/04_infer.dag @@ -102,7 +102,7 @@ type AlgebraRef { witness: Node } -data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for six of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only, and Conj and Arrow by product introduction — 6 connectives + 6 behaviors = 12); the other six kinds, and every other Transform shape, have no derivation rule yet. The Conj and Arrow rules are compositional: a non-roster Conj or Arrow whose every child carries DerivedGrounding derives with the same shape re-formed over the children's grounding evidence as its evidence, and one with any frontier or absent child stays frontier — the derivation discharges only on a fully-evidenced child spine. Above the kind rules sit four specimen-scope derivations, all lookups into declared authorities rather than inventions: a node declared in the dag authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants); an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes; a node that is a member of the target-model authority's canonical-operations roster derives with that roster root as its evidence (infer_node_declared_in_canonical_operations) — the resolver canonicalizes surface operators to those declared operations; a node that is a member of the dag grammar authority's productions roster derives with that roster root as its evidence (infer_node_declared_in_dag_grammar_productions) — the bridge projects a production's parse into (identity atom, captured content) pairs. All four delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens no authority declares stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." +data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for six of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only, and Conj and Arrow by product introduction — 6 connectives + 6 behaviors = 12); the other six kinds, and every other Transform shape, have no derivation rule yet. The Conj and Arrow rules are compositional: a non-roster Conj or Arrow whose every child carries DerivedGrounding derives with the same shape re-formed over the children's grounding evidence as its evidence, and one with any frontier or absent child stays frontier — the derivation discharges only on a fully-evidenced child spine. Above the kind rules sit five specimen-scope derivations, all lookups into declared authorities rather than inventions: a node declared in the dag authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants); an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes; a node that is a member of the target-model authority's canonical-operations roster derives with that roster root as its evidence (infer_node_declared_in_canonical_operations) — the resolver canonicalizes surface operators to those declared operations; a node that is a member of the dag grammar authority's productions roster derives with that roster root as its evidence (infer_node_declared_in_dag_grammar_productions) — the bridge projects a production's parse into (identity atom, captured content) pairs; and an Atom whose binding an enclosing arrow's domain declares derives with the declared domain type as its evidence (infer_find_arrow_domain_type_in_tree) — the declaration-site annotation, itself derived. The binding-reference lookup is scope-naive (whole-tree, first match), the same interim the branch-operand path already uses. All five delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens no authority declares stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." fn infer_grounding_not_derived_diagnostic(at: Locus) -> Diagnostic { Diagnostic { @@ -610,7 +610,7 @@ fn infer_product_facts_from_entries( } } -fn infer_node_facts(n: Node, partials: List) -> Outcome { +fn infer_node_facts(n: Node, partials: List, tree: Node) -> Outcome { match infer_bounded_lattice_consumer_gate(consumer: n, partials: partials) { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: _, diagnostics: cd } => @@ -663,10 +663,19 @@ fn infer_node_facts(n: Node, partials: List) -> Outcome { descent: Holds { value: descent_proof } ) } else { - inferred_facts_not_derived( - node: n, - descent: Holds { value: descent_proof } - ) + match infer_find_arrow_domain_type_in_tree(root: tree, binding: binding) { + Present { value: domain_ty } => + inferred_facts_from_derived_type( + node: n, + derived_type: domain_ty, + descent: Holds { value: descent_proof } + ) + Absent => + inferred_facts_not_derived( + node: n, + descent: Holds { value: descent_proof } + ) + } } } } @@ -1814,8 +1823,8 @@ fn infer_gather_transform_row_on_entries( // added without deciding, at this site, whether it derives its type or joins the counted frontier // -- never defaulted into by omission (DESIGN §4 closed vocabulary, §5 unwritable-by-construction). -fn infer_gather_fold_not_derived(n: Node, partials: List) -> InferGatherFoldAcc { - match infer_node_facts(n: n, partials: partials) { +fn infer_gather_fold_not_derived(n: Node, partials: List, tree: Node) -> InferGatherFoldAcc { + match infer_node_facts(n: n, partials: partials, tree: tree) { Rejected { diagnostics: r } => infer_gather_fold_acc_failed( node: n, @@ -1853,7 +1862,7 @@ fn infer_gather_fold_not_derived(n: Node, partials: List) -> InferGatherFo } } -fn infer_gather_fold_init(n: Node, partials: List) -> InferGatherFoldAcc { +fn infer_gather_fold_init(n: Node, partials: List, tree: Node) -> InferGatherFoldAcc { match n.kind { ComputationNode { behavior: Branch } => infer_gather_fold_acc_ok( @@ -1888,7 +1897,7 @@ fn infer_gather_fold_init(n: Node, partials: List) -> InferGatherFoldAcc { await_transform_row: false, children_remaining: length(xs: n.children) ) - ComputationNode { behavior: Value } => infer_gather_fold_not_derived(n: n, partials: partials) + ComputationNode { behavior: Value } => infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) ComputationNode { behavior: Transform } => if infer_transform_is_binary_infix_int_add_shape(node: n) { infer_gather_fold_acc_ok( @@ -1902,10 +1911,10 @@ fn infer_gather_fold_init(n: Node, partials: List) -> InferGatherFoldAcc { children_remaining: length(xs: n.children) ) } else { - infer_gather_fold_not_derived(n: n, partials: partials) + infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) } - ComputationNode { behavior: Bind } => infer_gather_fold_not_derived(n: n, partials: partials) - TypeNode { connective: Atom { identity: _ } } => infer_gather_fold_not_derived(n: n, partials: partials) + ComputationNode { behavior: Bind } => infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) + TypeNode { connective: Atom { identity: _ } } => infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) TypeNode { connective: Conj } => if infer_type_node_awaits_product_row(n: n) { infer_gather_fold_acc_ok( @@ -1919,9 +1928,9 @@ fn infer_gather_fold_init(n: Node, partials: List) -> InferGatherFoldAcc { children_remaining: length(xs: n.children) ) } else { - infer_gather_fold_not_derived(n: n, partials: partials) + infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) } - TypeNode { connective: Disj } => infer_gather_fold_not_derived(n: n, partials: partials) + TypeNode { connective: Disj } => infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) TypeNode { connective: Arrow } => if infer_type_node_awaits_product_row(n: n) { infer_gather_fold_acc_ok( @@ -1935,10 +1944,10 @@ fn infer_gather_fold_init(n: Node, partials: List) -> InferGatherFoldAcc { children_remaining: length(xs: n.children) ) } else { - infer_gather_fold_not_derived(n: n, partials: partials) + infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) } - TypeNode { connective: Cardinality } => infer_gather_fold_not_derived(n: n, partials: partials) - TypeNode { connective: Instantiation } => infer_gather_fold_not_derived(n: n, partials: partials) + TypeNode { connective: Cardinality } => infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) + TypeNode { connective: Instantiation } => infer_gather_fold_not_derived(n: n, partials: partials, tree: tree) } } @@ -2118,7 +2127,7 @@ fn infer_gather_fold_step( fn infer_gather_fold_algebra(partials: List, tree: Node) -> NodeFold { NodeFold { init: fn(n0) { - infer_gather_fold_init(n: n0, partials: partials) + infer_gather_fold_init(n: n0, partials: partials, tree: tree) }, step: fn(acc, e, child) { infer_gather_fold_step(acc: acc, edge: e, child: child, partials: partials, tree: tree) diff --git a/src/v2/test/claim/execution/dag_binding_denotation_test.dag b/src/v2/test/claim/execution/dag_binding_denotation_test.dag index 727d030e328..3ce36139d53 100644 --- a/src/v2/test/claim/execution/dag_binding_denotation_test.dag +++ b/src/v2/test/claim/execution/dag_binding_denotation_test.dag @@ -12,6 +12,7 @@ import v2.compiler.self_host.direct_rust_door_fixture { direct_rust_door_ingest } import v2.extdeps.languages.dag { + dag_binding_denotation, dag_int_inhabitant_node, dag_int_type_binding, dag_language_model @@ -33,9 +34,12 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // of the surface spelling "Int"). The positive control pins both the derivation and its evidence: // every canonical Int binding atom carries DerivedGrounding whose structural evidence is the dag // authority's Int inhabitant, and the census count (4) pins that the witness actually saw them. -// The boundary control pins the rule's edge in the same tree: the bare operand atoms `x`/`y`, -// which no authority declares a denotation for, remain GroundingNotDerived. Deleting the rule -// reddens the positive control; widening the rule to unbound atoms reddens the boundary control. +// The boundary control pins the rule's edge at the authority itself: dag_binding_denotation +// declares no denotation for the operand bindings `x`/`y` -- the table is a lookup, not an +// invention. (The operand atoms derive today, but by a different rule -- the binding-reference +// derivation in infer_atom_grounding_rules_test.dag -- not by this one.) Deleting the rule +// reddens the positive control; widening the table to operand bindings reddens the boundary +// control. type BindingDenotationCensus { int_atoms: Int @@ -140,11 +144,13 @@ test fn dag_binding_denotation_derives_int_binding_atoms_holds() -> Bool { } } -test fn dag_binding_denotation_leaves_unbound_operand_atoms_on_the_frontier_holds() -> Bool { - match binding_denotation_specimen_inferred() { - Rejected { diagnostics: _ } => false - Accepted { value: inferred, diagnostics: _ } => - let census = binding_denotation_census(inferred: inferred) - (census.operand_atoms == 2) && (census.operand_atoms_frontier == 2) +test fn dag_binding_denotation_declares_no_denotation_for_operand_bindings_holds() -> Bool { + match dag_binding_denotation(sym: ^x) { + Present { value: _ } => false + Absent => + match dag_binding_denotation(sym: ^y) { + Present { value: _ } => false + Absent => true + } } } diff --git a/src/v2/test/claim/execution/infer_atom_grounding_rules_test.dag b/src/v2/test/claim/execution/infer_atom_grounding_rules_test.dag index 59c26c3f548..9a91ee36953 100644 --- a/src/v2/test/claim/execution/infer_atom_grounding_rules_test.dag +++ b/src/v2/test/claim/execution/infer_atom_grounding_rules_test.dag @@ -34,7 +34,7 @@ import v2.std.optional { Absent, optional_present, Present } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly -// THE ENROLLED CONTROLS FOR THE ATOM-GROUNDING ROSTER DERIVATIONS (`v2.compiler.infer` +// THE ENROLLED CONTROLS FOR THE THREE ATOM-GROUNDING DERIVATIONS (`v2.compiler.infer` // infer_node_facts): each is a lookup into a declared authority, never an invention. // - canonical-operations roster: the resolver canonicalizes surface operators to the // target-model authority's declared operations; the wire atoms (the AlgebraPrimitive @@ -43,13 +43,14 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // - grammar-productions roster: the bridge projects a production's parse into (identity atom, // captured content); the identity atom (dag_surface_module) is the production's declared // emitted atom, and derives with the grammar roster root as evidence. +// - binding references: an operand atom (x, y) derives with the type its enclosing arrow's +// domain declares for its binding -- the declaration-site annotation, itself derived. // The specimen is the direct-rust-door fixture's ingest. Each control pins BOTH the derivation // and the evidence identity, and the census pins that the witness saw the population. The -// boundary control pins that a bare atom with no authority membership stays on the frontier, and -// the closing control pins the specimen state after these two rules: exactly two frontier nodes -// of fifteen (the operand atoms, grounded by the binding-reference rule in a later change). -// Deleting a rule reddens its control; widening a rule to non-members reddens the boundary -// control. +// boundary control pins that a bare atom with no authority membership and no domain declaration +// stays on the frontier. The closing control pins the specimen's full closure: zero frontier +// nodes of fifteen. Deleting a rule reddens its control; widening a rule to non-members reddens +// the boundary control. fn atom_grounding_specimen_inferred() -> Outcome { match assemble_program_from_ingest( @@ -129,6 +130,43 @@ test fn grammar_productions_roster_grounds_module_atom_holds() -> Bool { } } +fn is_domain_int_binding_evidence(inferred: InferredTree, n: Node) -> Bool { + match atom_grounding_node_evidence(inferred: inferred, n: n) { + Absent => false + Present { value: evidence } => + match evidence.kind { + TypeNode { connective: Atom { identity: id } } => id == ^dag_binding_type_int + _ => false + } + } +} + +fn is_operand_atom(n: Node) -> Bool { + match n.kind { + TypeNode { connective: Atom { identity: id } } => + (id == ^x) || (id == ^y) + _ => false + } +} + +test fn binding_reference_derives_parameter_atoms_holds() -> Bool { + match atom_grounding_specimen_inferred() { + Rejected { diagnostics: _ } => false + Accepted { value: inferred, diagnostics: _ } => + fold_list( + xs: node_subtree_nodes(root: inferred.root), + empty: 0, + cons: fn(acc, n) { + if is_operand_atom(n: n) && is_domain_int_binding_evidence(inferred: inferred, n: n) { + acc + 1 + } else { + acc + } + } + ) == 2 + } +} + test fn atom_rules_leave_unbound_atom_on_the_frontier_holds() -> Bool { let unbound = Node { kind: TypeNode { connective: Atom { identity: ^no_authority_declares_this_symbol } }, @@ -149,10 +187,10 @@ test fn atom_rules_leave_unbound_atom_on_the_frontier_holds() -> Bool { } } -test fn atom_rules_leave_only_operand_atoms_on_the_door_frontier_holds() -> Bool { +test fn door_specimen_fully_derives_holds() -> Bool { match atom_grounding_specimen_inferred() { Rejected { diagnostics: _ } => false Accepted { value: inferred, diagnostics: _ } => - atom_grounding_census_frontier(inferred: inferred) == 2 + atom_grounding_census_frontier(inferred: inferred) == 0 } } diff --git a/src/v2/test/claim/execution/long/emit_host_classical_not_ingested_equals_eval_test.dag b/src/v2/test/claim/execution/long/emit_host_classical_not_ingested_equals_eval_test.dag index 307acaade0b..17418761c25 100644 --- a/src/v2/test/claim/execution/long/emit_host_classical_not_ingested_equals_eval_test.dag +++ b/src/v2/test/claim/execution/long/emit_host_classical_not_ingested_equals_eval_test.dag @@ -529,13 +529,16 @@ test fn ingested_classical_not_emit_source_arm_shape_holds() -> Bool { } } -test fn ingested_classical_not_canonical_emit_refuses_underived_holds() -> Bool { +test fn ingested_classical_not_canonical_emit_accepts_holds() -> Bool { match ingested_canonical_inferred_tree_from_source(source: ingested_classical_not_source) { Accepted { value: tree, diagnostics: _ } => match emit(tree: tree, target: rust_classical_not_ingested_target_model_staging()) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Accepted { value: Medium { carried: source, fidelity: _ }, diagnostics: _ } => + string_contains(s: source, pattern: "fn classical_not(x") + && string_contains(s: source, pattern: "!= ") + && string_contains(s: source, pattern: "true => 0") + && string_contains(s: source, pattern: "false => 1") + Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false } @@ -549,24 +552,25 @@ test fn ingested_classical_not_arrow_has_match_holds() -> Bool { ingested_arrow_has_match_body(source: ingested_classical_not_source) } -fn ingested_classical_not_staging_emit_refuses_underived(source: String) -> Bool { +fn ingested_classical_not_staging_emit_accepts(source: String) -> Bool { match ingested_staging_inferred_tree_from_source(source: source) { Accepted { value: tree, diagnostics: _ } => match emit(tree: tree, target: rust_classical_not_ingested_target_model_staging()) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Accepted { value: Medium { carried: emitted, fidelity: _ }, diagnostics: _ } => + string_contains(s: emitted, pattern: "fn classical_not(x") + && string_contains(s: emitted, pattern: "!= ") + Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false } } -test fn ingested_classical_not_staging_emit_refuses_underived_holds() -> Bool { - ingested_classical_not_staging_emit_refuses_underived(source: ingested_classical_not_source) +test fn ingested_classical_not_staging_emit_accepts_holds() -> Bool { + ingested_classical_not_staging_emit_accepts(source: ingested_classical_not_source) } -test fn ingested_classical_not_staging_swapped_emit_refuses_underived_holds() -> Bool { - ingested_classical_not_staging_emit_refuses_underived(source: ingested_classical_not_swapped_source) +test fn ingested_classical_not_staging_swapped_emit_accepts_holds() -> Bool { + ingested_classical_not_staging_emit_accepts(source: ingested_classical_not_swapped_source) } test fn emit_host_classical_not_ingested_equals_eval_holds() -> Bool { @@ -669,10 +673,15 @@ test fn ingested_classical_not_match_only_param_scrutinee_infer_refuses_holds() // KERNEL-D TESTING RE-STEER (operator direction, 2026-07-24): per-PR layer-boundary mock proof for // the classical_not cutover's emit-to-text half — ingested_emit_shape_frontier_tree_from_source // (orch-emit-compatible empty facts map) -> assert emitted Rust TEXT (string compare, no compile). -// ingested_canonical_inferred_tree_from_source is the pure infer carrier; -// ingested_classical_not_canonical_emit_refuses_underived_holds and -// ingested_classical_not_staging_emit_refuses_underived_holds witness translate refuses recorded -// GroundingNotDerived. Wet/nightly emit-vs-eval legs use +// ingested_canonical_inferred_tree_from_source is the pure infer carrier. +// FRONTIER CLOSED (2026-09-06, grounding-frontier branch): the specimen's real-infer tree fully +// derives — the grammar-production roster grounds the module identity atom, the binding-reference +// rule grounds the parameter atom from the arrow domain, and the product rules compose the rest — +// so ingested_classical_not_canonical_emit_accepts_holds and +// ingested_classical_not_staging_emit_accepts_holds now witness that emit ACCEPTS the canonical +// and staging trees and pins the emitted text's shape (the same shape the frontier-path witnesses +// pin). The translate-refuses-underived behavior stays enrolled on hand-staged frontier fixtures +// in translate_underived_refusal_test.dag. Wet/nightly emit-vs-eval legs use // ingested_wet_emit_eval_composed_tree_from_source (composed facts on every lookup — emit accepts, // eval admits); shape-frontier stays emit-only. Deliberately excludes wet legs from per-PR // CommitCheckEnrollment. From ff03246ec41e1f9db350a5bce12ee61b45d8525c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 00:25:17 +0000 Subject: [PATCH 08/89] Green the direct-rust-door: route emission through produced-decl composition and decode canonical operator wires The door specimen's inference frontier is fully closed, so its production observation now reaches the emission stage. Two defects surfaced there, both fixed here: Emission composition. generate_rust_emission_candidate served two lanes with one root shape: the door's production path (a dag module shell) and a fixture lane (a bare rust Arrow). The translate ownership gate queried the module atom's ownership at a struct-field use site and refused with target_use_site_ownership_lookup_miss, because the module's grammar-projection conj was misread as a type record. The door's real composition is the produced-decl path: collect declaration conjuncts from the inferred tree and emit via emit_produced_decl. A new generate_rust_module_emission_candidate does exactly that, enforcing an exactly-one-declaration admission policy (rust_module_emission_decl_absent / _ambiguous). The observation and production mint paths switch to it; the fixture-lane candidate is retained with a note that it is fixture-only. A pure collector, produced_decl_conjs_in_tree, finds nodes of produced-decl shape (a Conj whose first child is a Named edge to an Arrow). Its decl-head match routes through a declared FreeMonoid parameter because the v1 seed stamps pattern variables from a declared parameter type, not from a field-access scrutinee. Operator decode. With composition fixed, source fidelity still refused: the door emitted fn add(x: i32, y: i32) -> i32 { AlgebraPrimitive(x, y) } instead of { x + y }. Resolution canonicalizes a surface operator atom into a canonical-operation wire node, so a production tree's transform operator position carries the wire, while fixture trees that bypass resolution still carry the surface token atom. translate_project_transform_in_arrow_scope only knew the surface-token table, so the wire missed and fell to callable apply, rendering the discriminant identity. The projection now tries the wire decode first (canonical_operation_from_wire_node) and only on a wire miss falls to the surface-token table, then to callable apply; the arms are disjoint, so the dispatch adds no fallback widening. target_transform_operator_child extracts the operator child safely. The door's closing expectation now greens by execution, so its known_red_probe row in explicit_witness_admission is deleted per its own dissolution condition, and the roadmap authority note, the door contract note, and the direct-path plan are updated to record the green state. realized_closure_for_v2_direct_ rust_door_emit_run's module list reflects the produced-decl route. Verified by execution: the door witness greens; the fixture, containment, algebra, produced-decl, add-slice, and classical-not witnesses stay green; claim_executor required-ci lanes build and witnesses both exit 0; cargo fmt and clippy --all-targets -D warnings are clean. One pre-existing red, witness_projection_is_active_only in the floor_cost_debt containment roster, reproduces on the base revision and is unrelated to this change. Co-authored-by: Brian Searls --- dag/gunbc/explicit_witness_admission.dag | 8 -- dag/gunbc/roadmap/roadmap_authority.dag | 43 ++++++----- .../v2-self-host-direct-path-2026-09-06.md | 8 +- src/v2/compiler/06_translate.dag | 54 ++++++++++++-- src/v2/compiler/emit_produced.dag | 67 ++++++++++++++++- .../self_host/candidate_generation.dag | 73 ++++++++++++++++--- .../self_host/emitter_producer_provenance.dag | 16 ++-- .../v2_emitter_direct_rust_door_contract.dag | 2 +- src/v2/std/compilers/target_model.dag | 13 ++++ 9 files changed, 227 insertions(+), 57 deletions(-) diff --git a/dag/gunbc/explicit_witness_admission.dag b/dag/gunbc/explicit_witness_admission.dag index 1d1b3b4a5ba..53806540a9f 100644 --- a/dag/gunbc/explicit_witness_admission.dag +++ b/dag/gunbc/explicit_witness_admission.dag @@ -263,14 +263,6 @@ data explicit_witness_admissions: List = [ reason: "the discriminating closing validation for roadmap node namespace-reference-derived-closure, RED BY DESIGN while its six-clause production admission frontier carries typed unavailable rows naming #7515 and the Class-B pool-independence wall. The separately discovered fixture suite executes P1 and proves every clause rejects its planted wrong output; this witness imports no fixture admissions and is bound directly by namespace_reference_derived_closure_execution_contract, so zero implementation progress cannot verify as complete.", dissolution: unbound_dissolution(description: "the production candidate producer and dependency projection establish all six ReferenceDerivedClosureCapability rows by execution — this row deletes in the completing change, promoting the unchanged acceptance witness to ordinary DiscoverySelection as the permanent namespace-derived-closure regression wall") ), - known_red_probe( - entry: "src/v2/test/claim/long/direct_rust_door_production_group_test.dag", - f: "direct_rust_door_production_group_closing_expectation_holds", - kind: CorpusWitnessKind, - budget: SubstrateLongLaneEvalBudget, - reason: "THE canonical closing validation for roadmap node v2-emitter-direct-rust-door, RED BY EXECUTION. Say the location claim exactly: ONE production implementation authority, ONE production walk per group construction, ONE scheduled group construction (the second occurrence was the budget-by-batch-membership deficit, closed 2026-08-06 when the eval budget became its own declared axis; direct_rust_door_group_root_is_scheduled_exactly_once is the executing receipt), and the IN-MEMORY group locates EmissionRejected while the SCHEDULED executor receipt remains an unlocated Bool. Inside that group the observation stops at EmissionRejected, so the four artifact leaves report LeafNotReached carrying that stage while the missing-rule leaf is green. That one green leaf is what proves the red is located rather than uniform — for a consumer holding the group, not for anyone reading the falsifier receipt. It replaces three superseded rows deleted in the same change, whose fold answered SeedEmitterRequiredForWrittenSource for BOTH a rejected pipeline and a seed-dependent artifact. Not relaxable: the only edit that may green it is making the real inferred tree survive emission.", - dissolution: unbound_dissolution(description: "every leaf reaches LeafEstablished by execution — this row deletes in the completing change, promoting the unchanged group root to ordinary DiscoverySelection as the lane's permanent regression wall") - ), known_red_probe( entry: "dag/test/claim/self_host_parse_engine_hooks_behavioral_witness_test.dag", f: "self_host_parse_engine_hooks_behavioral_receipt_holds", diff --git a/dag/gunbc/roadmap/roadmap_authority.dag b/dag/gunbc/roadmap/roadmap_authority.dag index 1edc9bc5ee4..95106537137 100644 --- a/dag/gunbc/roadmap/roadmap_authority.dag +++ b/dag/gunbc/roadmap/roadmap_authority.dag @@ -548,28 +548,27 @@ fn v2_emitter_producer_provenance_execution_contract() -> WorkItemExecutionContr // chosen over the suite's other twenty-six claims because it is the one that fails if the // unrepresentability itself lapses, rather than one that checks a roster stayed in step. -// THE CANONICAL CLOSING VALIDATION for v2-emitter-direct-rust-door, rebound to -// direct_rust_door_production_group_closing_expectation_holds. The production path is now observed -// through ProvenancedRustEmissionObservation, which LOCATES the failure instead of erasing it: -// assembly ACCEPTS and inference ACCEPTS on the real add_probe source, and emission REJECTS the -// real inferred tree. That supersedes the earlier reading — carried in this note until 2026-08-05 -// and in the node first_slice — that the pieces were joined and the last step was simply not -// called; candidate_generation does call emit, and emit is where the path stops. The prior binding -// to witness_v2_emitter_direct_rust_door_closing_contract_holds is deleted along with its fold, -// whose single SeedEmitterRequiredForWrittenSource arm answered for BOTH a rejected pipeline and a -// seed-dependent artifact — a state-space conflation that made the red unlocatable. -// MissingTranslationRuleRefuses remains established through the rust_add missing-rules serialize -// refusal and is carried as the group's one green leaf, which is what proves the parent's red is -// located rather than uniform. Group ALGEBRA is discriminated by planted-observation controls at -// zero production cost (v2.test.self_host.direct_rust_door_group_algebra). State the walk claim -// exactly: ONE production implementation authority, ONE production walk per group construction, ONE -// scheduled group construction, measured by v2.test.claim.schedule_occurrence_multiplicity_test -// against the live falsifier schedule. The count was TWO until 2026-08-06: the root joined the -// long-lane batch solely to obtain an eval ceiling, because budget was a property of batch -// membership. Separating the execution-budget axis from the batch roster (std.witness_admission -// witness_admission_axis_separation_note) dropped it to one, which is what that witness now -// asserts. And state the location claim exactly: the in-memory group locates EmissionRejected; the -// scheduled executor receipt remains an unlocated Bool. +// THE CANONICAL CLOSING VALIDATION for v2-emitter-direct-rust-door, bound to +// direct_rust_door_production_group_closing_expectation_holds — GREEN BY EXECUTION since +// 2026-09-06, when the known-red admission row deleted per its own dissolution ("every leaf +// reaches LeafEstablished by execution"), promoting the unchanged group root to the lane's +// permanent regression wall. The production walk is ingest → assemble → infer → emission on the +// real add_probe source and reaches ArtifactProduced: inference derives grounding for the whole +// specimen (declared-inhabitants roster membership, binding denotation, Conj/Arrow product +// introduction, canonical-operations and grammar-productions roster membership, and +// binding-reference derivation from the enclosing arrow's domain), and emission is the +// single-produced-declaration module composition (generate_rust_module_emission_candidate over +// v2.compiler.emit_produced), because a resolved module is a module shell over named +// declarations, never a type record to record-project. All five leaves establish: source +// fidelity byte-equal to direct_rust_door_expected_source, producer identity, seed-emitter +// absence, SourceProduced qualification, and the missing-rule refusal. State the walk claim +// exactly: ONE production implementation authority, ONE production walk per group construction, +// ONE scheduled group construction, measured by +// v2.test.claim.schedule_occurrence_multiplicity_test against the live falsifier schedule. +// Group ALGEBRA remains discriminated by planted-observation controls at zero production cost +// (v2.test.self_host.direct_rust_door_group_algebra). The group's observation is in-memory; the +// written-to-disk artifact surface is mint_written_provenanced_rust_artifact, which rides the +// same module composition. fn v2_emitter_direct_rust_door_execution_contract() -> WorkItemExecutionContract { gunbc_claim_execution_contract( diff --git a/docs/plans/v2-self-host-direct-path-2026-09-06.md b/docs/plans/v2-self-host-direct-path-2026-09-06.md index 067cb2aab5f..21962b2ce0e 100644 --- a/docs/plans/v2-self-host-direct-path-2026-09-06.md +++ b/docs/plans/v2-self-host-direct-path-2026-09-06.md @@ -29,8 +29,8 @@ What you do **not** get (§7's honesty boundary): the compiler does not invent u Each line names the producer that re-derives the state. Run it; do not believe this sentence. -- **Axis A — the real path, slice by slice.** The add slice (the `add` fn of `std.integer`) is green end-to-end on the dag path as of #10673: infer derives grounding by declared-inhabitant roster membership, and the ingest→compile round trip is byte-faithful. Instrument: `gunbc run --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag --function add_slice_stage_verdicts_entry`. The **direct-rust-door** (roadmap `v2-emitter-direct-rust-door`) is the critical-path red: real ingest and infer succeed on its specimen; the real inferred tree rejects inside emission. Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/long/direct_rust_door_production_group_test.dag --functions direct_rust_door_production_group_closing_expectation_holds` (a `long/` row — it reconstructs the pipeline per evaluation; the cost defect is declared in the test module's header). -- **The grounding frontier.** Derivation rules exist for a minority of the twelve node kinds; the stall carrier `self_host_candidate_generation_add_slice_stall` holds the live population and its `next_rung_trigger` names the parse-product family (grammar parse-tree nodes of the python/typescript fixtures). The add-slice rule and its blast-radius handling (#10673) are the template. +- **Axis A — the real path, slice by slice.** The add slice (the `add` fn of `std.integer`) is green end-to-end on the dag path as of #10673: infer derives grounding by declared-inhabitant roster membership, and the ingest→compile round trip is byte-faithful. Instrument: `gunbc run --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag --function add_slice_stage_verdicts_entry`. The **direct-rust-door** (roadmap `v2-emitter-direct-rust-door`) is **green by execution as of 2026-09-06**: the production walk reaches `ArtifactProduced`, all five leaf projections establish (source fidelity byte-equal to `direct_rust_door_expected_source`, producer identity, seed-emitter absence, `SourceProduced` qualification, missing-rule refusal), and the known-red admission row deleted per its own dissolution, promoting the group root to the lane's permanent regression wall. Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/long/direct_rust_door_production_group_test.dag --functions direct_rust_door_production_group_closing_expectation_holds` (a `long/` row — it reconstructs the pipeline per evaluation; the cost defect is declared in the test module's header). +- **The grounding frontier.** The door specimen's frontier is closed: six derivation rules now cover declared-inhabitants roster membership, binding denotation, Conj/Arrow product introduction, canonical-operations and grammar-productions roster membership, and binding references from the enclosing arrow's domain. The stall carrier `self_host_candidate_generation_add_slice_stall` holds the live population and its `next_rung_trigger` names the parse-product family (grammar parse-tree nodes of the python/typescript fixtures). The add-slice rule and its blast-radius handling (#10673) are the template. - **Axis B — the native closure (XL-N).** The XL-0 gate (`cargo check` = 0 on the emitted compiler closure) is **not satisfied**; the issue's own receipt table records that the closure reached complete rustc type checking for the first time at XL-0A and carries the honest error population there. gunbc#10266 (fabricated runtime panics on representation-identical casts) is filed as an independent compiler slice on this path. Instrument (the issue's own): `gunbc compile --source-root dag --source-root src/v2 --entry src/v2/compiler/00_compile.dag --target rust --output-dir /tmp/v2emit && cd /tmp/v2emit && cargo check --message-format short`. - **Axis C — the seed dissolves.** `v2.compiler.self_host.seed_retention_frontier` holds every retained seed file as a declared row; `undeclared_reason_rows()` is the prioritizable debt, and the census refuses unrostered retention in three directions. `v2.compiler.self_host.emit_coverage_frontier`'s `interpreter_retained_rows()` and `v2.compiler.self_host.native_routing_frontier`'s roster cover the witness-execution axes. - **Wave position.** Per the wave doc, Wave 1's four exit items all carry landed receipts; Wave 2 (first real flips — emit-surface tracks self-emit, behaviorally verify, and **replace** their v1 counterparts) is the open wave. The wave doc's header records that its roster carrier was deleted and per-module dispositions must be re-derived from the emitter — which is what the door-first sequence below does. @@ -39,7 +39,7 @@ Each line names the producer that re-derives the state. Run it; do not believe t Ordered by the roadmap chain; each step names its green condition as the node's own contract states it. -1. **The door** (`v2-emitter-direct-rust-door`). Loop: run the production witness → read the `EmissionRejected` diagnostics → each missing Rust `TargetModel` rule is a `.dag` row → leaves establish one by one. Green condition: the production observation reaches `ArtifactProduced`, all five leaf projections establish, the known-red expectation row deletes in the same change, and the group root promotes to permanent regression evidence (`v2_emitter_direct_rust_door_contract_dissolution_note`). +1. **The door** (`v2-emitter-direct-rust-door`) — **LANDED 2026-09-06.** The loop's actual findings, for the record: not missing `TargetModel` rows. (a) The grounding frontier closed by six real derivation rules in `04_infer.dag` (see §2's frontier line). (b) Emission needed the right composition, not more rules: a resolved module is a module shell over named declarations, so the production path is `generate_rust_module_emission_candidate` — collect the produced-decl-shaped nodes, admit exactly one, emit through `v2.compiler.emit_produced` — while the raw whole-tree `generate_rust_emission_candidate` remains the fixture lane's composition with its own dissolution trigger. (c) One representation decode gap: resolution canonicalizes surface operators into canonical-operation wire nodes, so the translate value-expression projection decodes the operator position with `canonical_operation_from_wire_node` first and falls to the surface-token table only on a wire miss. Green condition met: the production observation reaches `ArtifactProduced`, all five leaf projections establish, the known-red expectation row deleted in the same change, and the group root promotes to permanent regression evidence (`v2_emitter_direct_rust_door_contract_dissolution_note`). 2. **The parse-product grounding family** (the stall's named trigger). Generalize the roster-membership derivation from the dag language roster to the grammar parse-tree rosters of python/typescript. Greens the stall's remaining population and the same-grammar parse ingest bridge; the stall retires by its trigger. 3. **First behavioral module** (`v2-emitter-first-behavioral-module`). One already-tested module regenerated by the new generator; its existing tests — including the deliberately-broken one — pass unchanged, and the run is shown never to have reached the old generator. 4. **The XL-N milestones, in the issue's order** — arms → 0; emitter defects C, B → 0; crate compiles; native = interpreted with every divergence typed; swap. This is Wave 4's "pipeline runs on emitted Rust" executed as a burn-down, and its rules bind: fixes land in `.dag` and reach Rust via regen, and each closed class gets a `test.claim` fixture under the required gate. Its no-go gates are real: M2 stops if a fix needs a fact the inferred tree doesn't carry (an `04_infer` modeling gap — file it, don't grind); M3 re-plans if a class needs a language capability we don't have. @@ -62,6 +62,6 @@ The operator gates that remain, each with its repo citation: ## 5. What would make this plan wrong - An XL-N no-go gate firing (M2's modeling gap, M3's missing language capability) — re-plan at that gate, per the issue's own rule. -- The door's emission diagnostics turning out to be an `04_infer` modeling gap rather than `TargetModel` rows — same shape as the XL-N M2 no-go. +- ~~The door's emission diagnostics turning out to be an `04_infer` modeling gap rather than `TargetModel` rows~~ — discharged 2026-09-06: the door is green; the gaps were derivation rules, one emission composition, and one operator-representation decode, none of them a modeling wall. - A Wave-2 flip surfacing behavioral divergence that re-sequences the module order — typed as emitter bug or interpreter bug either way, but the sequence absorbs it. - This file aging. It is a position read, not a carrier; the instruments in §2 are the standing truth. diff --git a/src/v2/compiler/06_translate.dag b/src/v2/compiler/06_translate.dag index 65867f35ad1..62c36ff800d 100644 --- a/src/v2/compiler/06_translate.dag +++ b/src/v2/compiler/06_translate.dag @@ -21,12 +21,14 @@ import v2.std.compilers.target_model { TargetValueExpression, TargetValueExpressionProjection, bodied_arrow_scaffold_from_grammar_relation_row, + canonical_operation_from_wire_node, target_bodied_arrow_compose_statement_tokens, target_project_arrow_body_to_value_expression, target_project_transform_primitive_apply_in_arrow_scope, target_project_transform_callable_apply_in_arrow_scope, - target_transform_surface_op_atom, + target_transform_operator_child, target_value_expr_arrow_has_value_expression_body, + target_value_expr_diagnostic, target_value_expression_projection_from_target, target_value_expression_tokens_from_target } @@ -953,14 +955,21 @@ fn serialize_concrete_syntax_tokens_to_source_string( ) } -fn translate_project_transform_in_arrow_scope( +// The transform operator position is a TWO-REPRESENTATION boundary: resolution canonicalizes a +// surface operator atom into its canonical-operation wire node (resolve_surface_operator_atom), so +// a production tree carries the wire, while fixture trees that bypass resolution still carry the +// surface token atom. The decode tries the wire first; only a wire miss falls to the surface-token +// table, and only a surface-table miss routes to callable apply. The arms are disjoint (a wire +// node's identity is a CanonicalOperation discriminant with named fields; a surface token is a +// dag_token_* atom; anything else is shape-invalid), so the dispatch adds no fallback widening. +fn translate_project_transform_surface_or_callable_in_arrow_scope( arrow: Node, body: Node, + operator_child: Node, target: TargetModel ) -> Outcome { - bind_outcome( - o: target_transform_surface_op_atom(node: body), - f: fn(op_sym) { + match operator_child.kind { + TypeNode { connective: Atom { identity: op_sym } } => match dag_surface_operator_canonicalization_member(surface_token: op_sym) { Accepted { value: canonical_op, diagnostics: _ } => target_project_transform_primitive_apply_in_arrow_scope( @@ -981,6 +990,41 @@ fn translate_project_transform_in_arrow_scope( } ) } + _ => + outcome_rejected( + d: target_value_expr_diagnostic( + reason: ^target_value_expr_reason_transform_shape_invalid, + node: operator_child + ) + ) + } +} + +fn translate_project_transform_in_arrow_scope( + arrow: Node, + body: Node, + target: TargetModel +) -> Outcome { + bind_outcome( + o: target_transform_operator_child(node: body), + f: fn(operator_child) { + match canonical_operation_from_wire_node(wire: operator_child) { + Accepted { value: canonical_op, diagnostics: _ } => + target_project_transform_primitive_apply_in_arrow_scope( + arrow: arrow, + body: body, + canonical_op: canonical_op, + target: target + ) + + Rejected { diagnostics: _ } => + translate_project_transform_surface_or_callable_in_arrow_scope( + arrow: arrow, + body: body, + operator_child: operator_child, + target: target + ) + } } ) } diff --git a/src/v2/compiler/emit_produced.dag b/src/v2/compiler/emit_produced.dag index 898f57f6533..b76f03c372e 100644 --- a/src/v2/compiler/emit_produced.dag +++ b/src/v2/compiler/emit_produced.dag @@ -13,8 +13,8 @@ import v2.std.compilers.target_model { } import extdeps.communication.medium { Medium } import v2.std.collection { Absent, List, Present, list_at_optional } -import v2.std.algebra { fold_list, list_append } -import std.algebra { Empty } +import v2.std.algebra { fold_list, FreeMonoid, list_append, list_snoc_item } +import std.algebra { Cons, Empty } import v2.std.diagnostic { Accepted, Diagnostic, @@ -28,7 +28,8 @@ import v2.std.diagnostic { node_locus, outcome_rejected } -import v2.std.node { Conj, Named, Node, Positional, Symbol, TypeNode } +import v2.std.node { Arrow, Conj, Edge, Named, Node, Positional, Symbol, TypeNode } +import v2.std.logic { Bool } import v2.std.text { String } fn emit_produced_decl_diagnostic(reason: Symbol, node: Node) -> Diagnostic { @@ -69,6 +70,66 @@ fn emit_produced_decl_inner_arrow(decl: Node) -> Outcome { } } +// WHERE PRODUCED DECLARATIONS COME FROM. A produced declaration inside a resolved tree is the +// decl-shaped node itself: a Conj whose first child is a Named edge into an Arrow — exactly the +// shape emit_produced_decl_inner_arrow and produced_decl_subject_from_decl consume, so collection +// never synthesizes a wrapper node. The fold is total and pure: it reports every decl-shaped node +// and never recurses into one (a declaration owns its subtree). How many declarations a +// composition expects, and what a zero or surplus population means, is the caller's admission +// policy — never a silent filter here. +// The children match lives behind a declared FreeMonoid parameter because the v1 seed +// stamps pattern variables from a declared parameter type, not from a field-access scrutinee: +// matching node.children directly leaves the Cons head as an unresolved T (no field 'label' on +// type 'T'), and routing the same lookup through list_at_optional fails its generic instantiation +// when the callee is typechecked lazily from inside the recursive collector cluster. +fn produced_decl_children_have_decl_head(children: FreeMonoid) -> Bool { + match children { + Empty => false + Cons { head: first, tail: _ } => + match first.label { + Named { name: _ } => + match first.target.kind { + TypeNode { connective: Arrow } => true + _ => false + } + Positional => false + } + } +} + +fn produced_decl_node_is_decl(node: Node) -> Bool { + match node.kind { + TypeNode { connective: Conj } => produced_decl_children_have_decl_head(children: node.children) + _ => false + } +} + +fn produced_decl_conjs_in_children( + children: FreeMonoid, + acc: FreeMonoid +) -> FreeMonoid { + match children { + Empty => acc + Cons { head: e, tail: rest } => + produced_decl_conjs_in_children( + children: rest, + acc: produced_decl_conjs_in_tree_into(node: e.target, acc: acc) + ) + } +} + +fn produced_decl_conjs_in_tree_into(node: Node, acc: FreeMonoid) -> FreeMonoid { + if produced_decl_node_is_decl(node: node) { + list_snoc_item(xs: acc, item: node) + } else { + produced_decl_conjs_in_children(children: node.children, acc: acc) + } +} + +fn produced_decl_conjs_in_tree(root: Node) -> FreeMonoid { + produced_decl_conjs_in_tree_into(node: root, acc: Empty) +} + fn emit_produced_decl_with_target( decl: Node, target: TargetModel diff --git a/src/v2/compiler/self_host/candidate_generation.dag b/src/v2/compiler/self_host/candidate_generation.dag index 2b837e996e4..8daf9d1d9da 100644 --- a/src/v2/compiler/self_host/candidate_generation.dag +++ b/src/v2/compiler/self_host/candidate_generation.dag @@ -3,6 +3,7 @@ module v2.compiler.self_host.candidate_generation import extdeps.communication.medium { Medium } import extdeps.filesystem.filesystem_io { Filesystem } import v2.compiler.emit { emit } +import v2.compiler.emit_produced { emit_produced_decl, produced_decl_conjs_in_tree } import v2.compiler.infer { infer, InferredTree } import v2.compiler.name_resolve { Admission } import v2.compiler.program_assembly { assemble_program_from_ingest } @@ -30,11 +31,13 @@ import v2.std.diagnostic { Rejected, Unavailable, bind_outcome, + node_locus, outcome_accepted, outcome_rejected, invariant_port_locus } import v2.std.node { Node, Symbol } +import std.algebra { Cons, Empty } import v2.std.optional { Absent, Present, optional_absent, optional_present } import v2.std.text { String } @@ -66,10 +69,17 @@ type WrittenProvenancedRustArtifact { write: RustWorkspaceWriteReceipt } -// Lane B direct v2 Rust door: generate_rust_emission_candidate wires serialize_target composed with -// translate (v2.compiler.emit); mint_written_provenanced_rust_artifact writes Cargo.toml and -// src/main.rs through Filesystem.Write so ingest-assemble-infer-translate-serialize-write is one -// candidate_generation surface. +// Lane B direct v2 Rust door: the PRODUCTION composition is +// generate_rust_module_emission_candidate — the inferred module's produced declaration emitted +// through v2.compiler.emit_produced, because a resolved module is a module shell (grammar +// projection over named declarations), not a type record, so emitting it means emitting its +// declarations and never record-projecting the shell through translate. +// mint_written_provenanced_rust_artifact writes Cargo.toml and src/lib.rs through Filesystem.Write +// so ingest-assemble-infer-emit-write is one candidate_generation surface. +// generate_rust_emission_candidate (raw v2.compiler.emit over the whole inferred tree) remains ONLY +// as the fixture lane's composition: fixture_synthetic_emission rides it over +// direct_rust_door_inferred_tree, a target-side fixture root with no module shell, and it deletes +// with that lane's own dissolution trigger. No production caller remains on it. data direct_rust_door_scaffold_build_cause: Symbol = ^direct_rust_door_scaffold_build @@ -166,6 +176,51 @@ fn generate_rust_emission_candidate( ) } +// The production module composition. Admission is exactly one produced-decl-shaped node (the +// collector in v2.compiler.emit_produced defines the shape): the door's specimen class is the +// single-declaration module, and zero or surplus populations refuse typed and located rather than +// picking a winner. The multi-declaration join convention lands with the first multi-declaration +// consumer — emit_produced_module's trailing separator is pinned by its own goldens and is not +// this composition's fact. +fn generate_rust_module_emission_candidate( + inferred: InferredTree, + target: TargetModel +) -> Outcome { + match produced_decl_conjs_in_tree(root: inferred.root) { + Empty => + outcome_rejected( + d: Diagnostic { + reason: ^rust_module_emission_decl_absent, + at: node_locus(node: inferred.root), + correction: Unavailable { reason: ExternalContractUnknown } + } + ) + Cons { head: decl, tail: rest } => + match rest { + Empty => + bind_outcome( + o: emit_produced_decl(decl: decl, target: target), + f: fn(source) { + outcome_accepted( + value: RustEmissionCandidate { + source: source, + emitted: inferred.root + } + ) + } + ) + Cons { head: surplus, tail: _ } => + outcome_rejected( + d: Diagnostic { + reason: ^rust_module_emission_decl_ambiguous, + at: node_locus(node: surplus), + correction: Unavailable { reason: ExternalContractUnknown } + } + ) + } + } +} + fn write_rust_workspace( workspace_dir: String, source: Medium @@ -221,7 +276,7 @@ fn mint_provenanced_rust_artifact( module_path: String ) -> Outcome { bind_outcome( - o: generate_rust_emission_candidate(inferred: inferred, target: target), + o: generate_rust_module_emission_candidate(inferred: inferred, target: target), f: fn(candidate) { mint_artifact_from_rust_emission_candidate( candidate: candidate, @@ -258,9 +313,9 @@ fn mint_written_provenanced_rust_artifact( } // STAGE-LOCATED production observation for the ingest-path Rust door. Replaces the Outcome-only -// surface whose Rejected arm erased WHICH stage refused: assembly, inference, emission (translate -// composed with serialize_target, observed as one production seam because v2.compiler.emit is one -// composition and splitting it here would fork it), or producer-receipt mint. +// surface whose Rejected arm erased WHICH stage refused: assembly, inference, emission (the +// single-declaration module composition of generate_rust_module_emission_candidate, observed as +// one production seam), or producer-receipt mint. // generate_provenanced_rust_candidate_from_ingest is now a PROJECTION of this observation, not a // second traversal — the pipeline is walked once per call and every existing caller keeps its // Outcome shape. @@ -285,7 +340,7 @@ fn observe_provenanced_rust_emission_from_ingest( match infer(tree: resolved) { Rejected { diagnostics: d } => InferenceRejected { diagnostics: d } Accepted { value: inferred, diagnostics: _ } => - match generate_rust_emission_candidate(inferred: inferred, target: target) { + match generate_rust_module_emission_candidate(inferred: inferred, target: target) { Rejected { diagnostics: d } => EmissionRejected { diagnostics: d } Accepted { value: candidate, diagnostics: _ } => match mint_artifact_from_rust_emission_candidate( diff --git a/src/v2/compiler/self_host/emitter_producer_provenance.dag b/src/v2/compiler/self_host/emitter_producer_provenance.dag index 73dd6dcfd0b..8c9dae6afc5 100644 --- a/src/v2/compiler/self_host/emitter_producer_provenance.dag +++ b/src/v2/compiler/self_host/emitter_producer_provenance.dag @@ -168,10 +168,12 @@ data cssl_harness_realized_closure: RealizedEmitterClosure = RealizedEmitterClos ] } -// V2EmitterInterpreted closure for Lane B direct rust door: v2.compiler.emit (serialize_target -// composed with translate) plus v2.compiler.self_host.candidate_generation +// V2EmitterInterpreted closure for the GENERIC interpreted emit route: v2.compiler.emit +// (serialize_target composed with translate) plus v2.compiler.self_host.candidate_generation // (mint_provenanced_rust_artifact entry), without v1.compiler.emit_rust. Landed as first customer -// in session bright-bear-372. +// in session bright-bear-372. The direct rust door's run closure is +// realized_closure_for_v2_direct_rust_door_emit_run below — the door's production composition +// routes through v2.compiler.emit_produced, not v2.compiler.emit. data v2_interpreted_emitter_closure: RealizedEmitterClosure = RealizedEmitterClosure { emitter_module_paths: [ @@ -202,17 +204,21 @@ fn v2_interpreted_emitter_closure_seed_free_holds() -> Bool { !seed_emit_rust_reachable(closure: v2_interpreted_emitter_closure) } +// The door run's route: candidate_generation's module composition emits the inferred module's +// produced declaration through v2.compiler.emit_produced, whose bodied-arrow serialization lives in +// v2.compiler.translate. v2.compiler.emit is NOT on this route — it remains on the generic +// emit_module / fixture-lane routes carried by v2_interpreted_emitter_closure. fn realized_closure_for_v2_direct_rust_door_emit_run() -> RealizedEmitterClosure { RealizedEmitterClosure { emitter_module_paths: [ "v2.compiler.self_host.candidate_generation", - v2_emit_interpreted_qualified_module, + "v2.compiler.emit_produced", "v2.compiler.translate" ] } } -// Post-emit-run closure authority for Lane B direct rust door: mint_provenanced_rust_artifact calls this ONLY after generate_rust_emission_candidate (the serialize_target run) succeeds — never as a caller-supplied parameter. Paths name modules on the v2 interpreted emit route excluding v1.compiler.emit_rust. +// Post-emit-run closure authority for Lane B direct rust door: mint_provenanced_rust_artifact calls this ONLY after generate_rust_module_emission_candidate (the produced-decl emission run) succeeds — never as a caller-supplied parameter. Paths name modules on the v2 interpreted emit route excluding v1.compiler.emit_rust. fn v2_interpreted_emitter_closure_authority() -> RealizedEmitterClosure { realized_closure_for_v2_direct_rust_door_emit_run() } diff --git a/src/v2/compiler/self_host/v2_emitter_direct_rust_door_contract.dag b/src/v2/compiler/self_host/v2_emitter_direct_rust_door_contract.dag index 7ca3b05c493..3604336aca5 100644 --- a/src/v2/compiler/self_host/v2_emitter_direct_rust_door_contract.dag +++ b/src/v2/compiler/self_host/v2_emitter_direct_rust_door_contract.dag @@ -63,7 +63,7 @@ import v2.std.text { String } // projected from ProvenancedRustEmissionObservation, where a stop before the artifact reports // LeafNotReached with its stage instead of a refusal. -data v2_emitter_direct_rust_door_contract_dissolution_note: String = "DISSOLVES BY GREENING, never deletion. The prior condition — candidate_generation wires serialize_target, and the seed-independent written-source check greens — is OBSOLETE and was stale prose by 2026-08-05: emit IS already called, the real inferred tree rejects inside it, and the named seed-independent admission fold has been deleted. The condition in current vocabulary: the production observation reaches ArtifactProduced, all five leaf projections establish (source fidelity, producer identity, seed-emitter absence, SourceProduced qualification, missing-rule refusal), the known-red expectation row deletes in that same change, and the unchanged group root promotes to ordinary DiscoverySelection as the lane's permanent regression evidence." +data v2_emitter_direct_rust_door_contract_dissolution_note: String = "DISSOLVES BY GREENING, never deletion — GREENED 2026-09-06 by execution. Every clause of the condition in current vocabulary is met: the production observation reaches ArtifactProduced, all five leaf projections establish (source fidelity, producer identity, seed-emitter absence, SourceProduced qualification, missing-rule refusal), the known-red expectation row deleted in the same change, and the unchanged group root stands as ordinary DiscoverySelection — the lane's permanent regression evidence, which is why this contract module remains. Superseded readings, kept so the trail stays legible: the pre-2026-08-05 belief that candidate_generation never called emit, and the 2026-08-05 reading that emission rejected the real inferred tree on the grounding frontier — the frontier closed (six derivation rules in 04_infer) and emission landed as the single-produced-declaration module composition over v2.compiler.emit_produced." // HONEST CEILING on what the SCHEDULER learns, stated so no roster prose overclaims it. The group // value retains every child outcome — which leaf refused, which stage a LeafNotReached names — but diff --git a/src/v2/std/compilers/target_model.dag b/src/v2/std/compilers/target_model.dag index 1247dcfab09..b9ab5e05f86 100644 --- a/src/v2/std/compilers/target_model.dag +++ b/src/v2/std/compilers/target_model.dag @@ -13030,6 +13030,19 @@ fn target_transform_surface_op_atom(node: Node) -> Outcome { target_transform_positional_operand_binding(node: node, index: 0) } +fn target_transform_operator_child(node: Node) -> Outcome { + match list_at_optional(xs: node_positional_child_targets(node: node), index: 0) { + Present { value: child } => outcome_accepted(value: child) + Absent => + outcome_rejected( + target_value_expr_diagnostic( + reason: ^target_value_expr_reason_transform_shape_invalid, + node: node + ) + ) + } +} + fn target_value_expr_declared_inhabitants_from_target(target: TargetModel) -> Outcome { target_model_declared_inhabitants(target: target) } From e55c179c218e98aa04852cfae892aebb053cd6ee Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 06:52:37 +0000 Subject: [PATCH 09/89] Close the parse-product grounding frontier: widen declared-inhabitant membership to the closed ingest set The declared-inhabitant roster-membership derivation in 04_infer generalized from the dag roster to the closed ingest set (dag, python, typescript): infer_node_declared_in_language_inhabitants returns the declaring authority's roster root as evidence, with deep subtree membership so a declared inhabitant's leaf fact atoms derive exactly as the inhabitant node itself. Measured: the python fixture's 19-node frontier and the typescript fixture's 28-node frontier both close to zero; all four add-slice stall population round-trip witnesses green; the python->typescript cross-language compile accepts, byte-identical to ts_source_text. Section 4b(4) flips (expecting-red probes becoming permanent regression controls for the acceptances): - cross_language_compile_refuses_canonical_underived_holds -> cross_language_compile_python_to_typescript_round_trip_holds - inhabitant_neutralization_emit_after_neutralize / same_flavor_python / go_int64_to_ts refusal helpers -> round-trip controls - inhabitant_neutralization_python_to_ts_cross_language_compile (e2e) -> round-trip control; python->go members stay refusal guards (go is outside the closed ingest set) - cross_language_emit_inhabitant_neutralization_refuses_underived_holds -> round-trip control; the python->typescript emit-matrix row reads ChainProven The add-slice stall's next-rung trigger fired, so it retired per DESIGN 4b(4): removed from all_guarantee_stalls, row file deleted, witnesses stay enrolled. Co-authored-by: Brian Searls --- dag/gunbc/guarantee_stall/roster.dag | 2 - ...t_candidate_generation_add_slice_stall.dag | 28 ----- dag/gunbc/witness/witness_deferral_freeze.dag | 2 +- .../v2-self-host-direct-path-2026-09-06.md | 4 +- src/v2/compiler/04_infer.dag | 100 +++++++++++++----- .../candidate_generation_stage_verdicts.dag | 14 ++- ...bitant_neutralization_e2e_witness_test.dag | 17 ++- ...language_add_python_to_typescript_test.dag | 38 +++++-- .../manual/inhabitant_neutralization_test.dag | 51 ++++++--- .../translate_underived_refusal_test.dag | 20 +++- src/v2/workflow/floor_cost_debt.dag | 2 +- 11 files changed, 174 insertions(+), 104 deletions(-) delete mode 100644 dag/gunbc/guarantee_stall/self_host_candidate_generation_add_slice_stall.dag diff --git a/dag/gunbc/guarantee_stall/roster.dag b/dag/gunbc/guarantee_stall/roster.dag index 09618fc59f7..a4d8c889f5a 100644 --- a/dag/gunbc/guarantee_stall/roster.dag +++ b/dag/gunbc/guarantee_stall/roster.dag @@ -26,7 +26,6 @@ import gunbc.guarantee_stall.enforcement_live_closure_gate_stall { enforcement_l import gunbc.guarantee_stall.mandatory_tag_live_corpus_stall { mandatory_tag_live_corpus_stall } import gunbc.guarantee_stall.parse_ingest_grammar_relation_stall { parse_ingest_grammar_relation_stall } import gunbc.guarantee_stall.required_regen_host_derivation_stall { required_regen_host_derivation_stall } -import gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall { self_host_candidate_generation_add_slice_stall } import gunbc.guarantee_stall.non_fold_residue_roster_stall { non_fold_residue_roster_stall } import gunbc.guarantee_stall.retained_rust_live_tree_migration_stall { retained_rust_live_tree_migration_stall } import gunbc.guarantee_stall.variant_owner_identity_stall { variant_owner_identity_stall } @@ -167,7 +166,6 @@ data all_guarantee_stalls: List = [ mandatory_tag_live_corpus_stall, parse_ingest_grammar_relation_stall, required_regen_host_derivation_stall, - self_host_candidate_generation_add_slice_stall, non_fold_residue_roster_stall, retained_rust_live_tree_migration_stall, variant_owner_identity_stall, diff --git a/dag/gunbc/guarantee_stall/self_host_candidate_generation_add_slice_stall.dag b/dag/gunbc/guarantee_stall/self_host_candidate_generation_add_slice_stall.dag deleted file mode 100644 index f196c68ddd0..00000000000 --- a/dag/gunbc/guarantee_stall/self_host_candidate_generation_add_slice_stall.dag +++ /dev/null @@ -1,28 +0,0 @@ -module gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall - -import v2.std.algebra { Cons, Empty } -import gunbc.guarantee_rung { OutsideTheLadder, MechanicallyPreventable } -import gunbc.guarantee_stall { GuaranteeStall, ClimbableButUnbuilt, BoundedPopulation, climbs_when } - -// HISTORY, recorded because the trigger below only makes sense with it. This stall was authored -// with a five-member population and a trigger whose causal clause read: derive grounding for the -// Arrow, Conj and Atom kinds of the add fn, SO cross_language_compile stops carrying -// infer_grounding_not_derived over the same-language python and typescript parse fixtures. The -// add-fn half LANDED as the declared-inhabitant membership derivation (v2.compiler.infer -// infer_node_declared_in_dag_inhabitants): the add slice's ten type-spine nodes are all declared -// in the dag language authority's declared-inhabitants roster, so they derive by lookup, and -// candidate_generation_translate_self_emit_dag_add_slice_holds passed -- the roster row deleted -// itself per its own stale-quarantine arm. The causal clause was REFUTED BY EXECUTION: the four -// round-trip members still red, and the cross_language_compile_refuses_canonical_underived control -// confirms the refusal reason is unchanged. The parse fixtures' frontier is a different node -// population -- grammar parse products, not language-authority inhabitants -- so the remaining -// trigger names that population, not the add fn's spine. - -data self_host_candidate_generation_add_slice_stall: GuaranteeStall = GuaranteeStall { - subject: "infer carries the grounding frontier over the python and typescript same-language parse fixtures, so the same-language round trips refuse", - current: OutsideTheLadder, - ceiling: MechanicallyPreventable, - blocker: ClimbableButUnbuilt, - population: BoundedPopulation { members: Cons { head: "v2.test.execution.emit_ingest_python_same_language_round_trip.emit_ingest_python_same_language_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_python_same_language_round_trip.python_same_language_source_emit_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_typescript_same_language_round_trip.emit_ingest_typescript_same_language_round_trip_holds", tail: Cons { head: "v2.test.execution.emit_ingest_typescript_same_language_round_trip.typescript_same_language_source_emit_round_trip_holds", tail: Empty {} } } } } }, - next_rung_trigger: climbs_when(capability: "infer derives grounding for the grammar parse-tree node population of the python and typescript parse fixtures -- the parse-product kinds their cross_language_compile same-language round trips carry infer_grounding_not_derived over today") -} diff --git a/dag/gunbc/witness/witness_deferral_freeze.dag b/dag/gunbc/witness/witness_deferral_freeze.dag index 9aef5f18a6d..d0475a1fde8 100644 --- a/dag/gunbc/witness/witness_deferral_freeze.dag +++ b/dag/gunbc/witness/witness_deferral_freeze.dag @@ -960,7 +960,7 @@ data frozen_path_deferrals: List = [ FrozenPathDeferral { entry: "src/v2/test/claim/long/inhabitant_neutralization_e2e_witness_test.dag", functions: [ - "inhabitant_neutralization_python_to_ts_cross_language_compile_refuses_underived_holds", + "inhabitant_neutralization_python_to_ts_cross_language_compile_round_trip_holds", "inhabitant_neutralization_python_to_go_cross_language_compile_refuses_underived_holds", "inhabitant_neutralization_python_to_go_discriminates_on_real_tree", "inhabitant_neutralization_python_to_go_holds", diff --git a/docs/plans/v2-self-host-direct-path-2026-09-06.md b/docs/plans/v2-self-host-direct-path-2026-09-06.md index 21962b2ce0e..336d9777cf2 100644 --- a/docs/plans/v2-self-host-direct-path-2026-09-06.md +++ b/docs/plans/v2-self-host-direct-path-2026-09-06.md @@ -30,7 +30,7 @@ What you do **not** get (§7's honesty boundary): the compiler does not invent u Each line names the producer that re-derives the state. Run it; do not believe this sentence. - **Axis A — the real path, slice by slice.** The add slice (the `add` fn of `std.integer`) is green end-to-end on the dag path as of #10673: infer derives grounding by declared-inhabitant roster membership, and the ingest→compile round trip is byte-faithful. Instrument: `gunbc run --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag --function add_slice_stage_verdicts_entry`. The **direct-rust-door** (roadmap `v2-emitter-direct-rust-door`) is **green by execution as of 2026-09-06**: the production walk reaches `ArtifactProduced`, all five leaf projections establish (source fidelity byte-equal to `direct_rust_door_expected_source`, producer identity, seed-emitter absence, `SourceProduced` qualification, missing-rule refusal), and the known-red admission row deleted per its own dissolution, promoting the group root to the lane's permanent regression wall. Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/long/direct_rust_door_production_group_test.dag --functions direct_rust_door_production_group_closing_expectation_holds` (a `long/` row — it reconstructs the pipeline per evaluation; the cost defect is declared in the test module's header). -- **The grounding frontier.** The door specimen's frontier is closed: six derivation rules now cover declared-inhabitants roster membership, binding denotation, Conj/Arrow product introduction, canonical-operations and grammar-productions roster membership, and binding references from the enclosing arrow's domain. The stall carrier `self_host_candidate_generation_add_slice_stall` holds the live population and its `next_rung_trigger` names the parse-product family (grammar parse-tree nodes of the python/typescript fixtures). The add-slice rule and its blast-radius handling (#10673) are the template. +- **The grounding frontier.** The door specimen's frontier is closed, and the parse-product family followed on 2026-09-07: the declared-inhabitant roster-membership derivation widened from the dag roster to the closed ingest set (dag, python, typescript), closing the python and typescript fixtures' frontiers and greening the four same-language round-trips. The add-slice stall's trigger fired and it retired per §4b(4). Six derivation rules now cover declared-inhabitants roster membership (language-general), binding denotation, Conj/Arrow product introduction, canonical-operations and grammar-productions roster membership, and binding references from the enclosing arrow's domain. The remaining frontier is the six node kinds with no derivation rule yet (the `node_grounding_frontier_note` in `04_infer.dag` is the standing carrier). - **Axis B — the native closure (XL-N).** The XL-0 gate (`cargo check` = 0 on the emitted compiler closure) is **not satisfied**; the issue's own receipt table records that the closure reached complete rustc type checking for the first time at XL-0A and carries the honest error population there. gunbc#10266 (fabricated runtime panics on representation-identical casts) is filed as an independent compiler slice on this path. Instrument (the issue's own): `gunbc compile --source-root dag --source-root src/v2 --entry src/v2/compiler/00_compile.dag --target rust --output-dir /tmp/v2emit && cd /tmp/v2emit && cargo check --message-format short`. - **Axis C — the seed dissolves.** `v2.compiler.self_host.seed_retention_frontier` holds every retained seed file as a declared row; `undeclared_reason_rows()` is the prioritizable debt, and the census refuses unrostered retention in three directions. `v2.compiler.self_host.emit_coverage_frontier`'s `interpreter_retained_rows()` and `v2.compiler.self_host.native_routing_frontier`'s roster cover the witness-execution axes. - **Wave position.** Per the wave doc, Wave 1's four exit items all carry landed receipts; Wave 2 (first real flips — emit-surface tracks self-emit, behaviorally verify, and **replace** their v1 counterparts) is the open wave. The wave doc's header records that its roster carrier was deleted and per-module dispositions must be re-derived from the emitter — which is what the door-first sequence below does. @@ -40,7 +40,7 @@ Each line names the producer that re-derives the state. Run it; do not believe t Ordered by the roadmap chain; each step names its green condition as the node's own contract states it. 1. **The door** (`v2-emitter-direct-rust-door`) — **LANDED 2026-09-06.** The loop's actual findings, for the record: not missing `TargetModel` rows. (a) The grounding frontier closed by six real derivation rules in `04_infer.dag` (see §2's frontier line). (b) Emission needed the right composition, not more rules: a resolved module is a module shell over named declarations, so the production path is `generate_rust_module_emission_candidate` — collect the produced-decl-shaped nodes, admit exactly one, emit through `v2.compiler.emit_produced` — while the raw whole-tree `generate_rust_emission_candidate` remains the fixture lane's composition with its own dissolution trigger. (c) One representation decode gap: resolution canonicalizes surface operators into canonical-operation wire nodes, so the translate value-expression projection decodes the operator position with `canonical_operation_from_wire_node` first and falls to the surface-token table only on a wire miss. Green condition met: the production observation reaches `ArtifactProduced`, all five leaf projections establish, the known-red expectation row deleted in the same change, and the group root promotes to permanent regression evidence (`v2_emitter_direct_rust_door_contract_dissolution_note`). -2. **The parse-product grounding family** (the stall's named trigger). Generalize the roster-membership derivation from the dag language roster to the grammar parse-tree rosters of python/typescript. Greens the stall's remaining population and the same-grammar parse ingest bridge; the stall retires by its trigger. +2. **The parse-product grounding family** (the stall's named trigger) — **LANDED 2026-09-07.** The roster-membership derivation generalized from the dag language roster to the closed ingest set (dag, python, typescript): `infer_node_declared_in_language_inhabitants` returns the declaring authority's roster root as evidence, with deep subtree membership so a declared inhabitant's leaf fact atoms derive exactly as the inhabitant node itself does. Measured: the python fixture's 19-node frontier and the typescript fixture's 28-node frontier both close to zero; all four stall-population round-trip witnesses green; the python→typescript cross-language compile accepts, byte-identical to `ts_source_text`. One §4b(4) flip: `cross_language_compile_refuses_canonical_underived_holds` → `cross_language_compile_python_to_typescript_round_trip_holds` (the pipeline-level frontier guard became the permanent regression control for the acceptance). The add-slice stall's trigger fired, so it retired per §4b(4) — removed from `all_guarantee_stalls`, row file deleted, witnesses stay enrolled. 3. **First behavioral module** (`v2-emitter-first-behavioral-module`). One already-tested module regenerated by the new generator; its existing tests — including the deliberately-broken one — pass unchanged, and the run is shown never to have reached the old generator. 4. **The XL-N milestones, in the issue's order** — arms → 0; emitter defects C, B → 0; crate compiles; native = interpreted with every divergence typed; swap. This is Wave 4's "pipeline runs on emitted Rust" executed as a burn-down, and its rules bind: fixes land in `.dag` and reach Rust via regen, and each closed class gets a `test.claim` fixture under the required gate. Its no-go gates are real: M2 stops if a fix needs a fact the inferred tree doesn't carry (an `04_infer` modeling gap — file it, don't grind); M3 re-plans if a class needs a language capability we don't have. 5. **Native bootstrap, then fixed point** (`v2-emitter-native-bootstrap`, `v1-emitter-fixed-point`). The two-round self-build for the generator, then the same two-round test extended stage by stage to the rest of the compiler. Then `dag/gunbc/v1/v1_deletion_plan.dag` executes; the seed-retention roster drains as modules flip, and Wave 3's first obligation — a derived denominator for what the compiler consists of — is named in the wave doc. diff --git a/src/v2/compiler/04_infer.dag b/src/v2/compiler/04_infer.dag index 5cd667eaa61..ca4fda342e8 100644 --- a/src/v2/compiler/04_infer.dag +++ b/src/v2/compiler/04_infer.dag @@ -28,6 +28,8 @@ import v2.extdeps.languages.dag { dag_declared_inhabitants_root, dag_grammar_productions_roster_root } +import v2.extdeps.languages.python { python_declared_inhabitants_root } +import v2.extdeps.languages.typescript { ts_declared_inhabitants_root } import v2.std.cardinality { TerminationProof, termination_proof_witness_for_node } import v2.std.collection { List, @@ -102,7 +104,7 @@ type AlgebraRef { witness: Node } -data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for six of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only, and Conj and Arrow by product introduction — 6 connectives + 6 behaviors = 12); the other six kinds, and every other Transform shape, have no derivation rule yet. The Conj and Arrow rules are compositional: a non-roster Conj or Arrow whose every child carries DerivedGrounding derives with the same shape re-formed over the children's grounding evidence as its evidence, and one with any frontier or absent child stays frontier — the derivation discharges only on a fully-evidenced child spine. Above the kind rules sit five specimen-scope derivations, all lookups into declared authorities rather than inventions: a node declared in the dag authority's declared-inhabitants roster derives with that roster as its grounding evidence (infer_node_declared_in_dag_inhabitants); an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes; a node that is a member of the target-model authority's canonical-operations roster derives with that roster root as its evidence (infer_node_declared_in_canonical_operations) — the resolver canonicalizes surface operators to those declared operations; a node that is a member of the dag grammar authority's productions roster derives with that roster root as its evidence (infer_node_declared_in_dag_grammar_productions) — the bridge projects a production's parse into (identity atom, captured content) pairs; and an Atom whose binding an enclosing arrow's domain declares derives with the declared domain type as its evidence (infer_find_arrow_domain_type_in_tree) — the declaration-site annotation, itself derived. The binding-reference lookup is scope-naive (whole-tree, first match), the same interim the branch-operand path already uses. All five delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens no authority declares stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." +data node_grounding_frontier_note: String = "GroundingNotDerived is v2's typed inference frontier, not a failure arm and not an escape hatch. v2 derives a node's type for six of the closed vocabulary's twelve node kinds (Branch, Match, Loop, Transform add-shape only, and Conj and Arrow by product introduction — 6 connectives + 6 behaviors = 12); the other six kinds, and every other Transform shape, have no derivation rule yet. The Conj and Arrow rules are compositional: a non-roster Conj or Arrow whose every child carries DerivedGrounding derives with the same shape re-formed over the children's grounding evidence as its evidence, and one with any frontier or absent child stays frontier — the derivation discharges only on a fully-evidenced child spine. Above the kind rules sit five specimen-scope derivations, all lookups into declared authorities rather than inventions: a node declared in any ingested language authority's declared-inhabitants roster derives with that roster root as its grounding evidence (infer_node_declared_in_language_inhabitants) — deep subtree membership over the closed ingest set (dag, python, typescript), so a declared inhabitant's leaf fact atoms derive exactly as the inhabitant node itself does; an Atom whose identity is a canonical dag binding with a declared denotation derives with that denotation as its evidence (dag_binding_denotation) — the resolver already bound the surface spelling to the canonical binding, and the authority fixes what the binding denotes; a node that is a member of the target-model authority's canonical-operations roster derives with that roster root as its evidence (infer_node_declared_in_canonical_operations) — the resolver canonicalizes surface operators to those declared operations; a node that is a member of the dag grammar authority's productions roster derives with that roster root as its evidence (infer_node_declared_in_dag_grammar_productions) — the bridge projects a production's parse into (identity atom, captured content) pairs; and an Atom whose binding an enclosing arrow's domain declares derives with the declared domain type as its evidence (infer_find_arrow_domain_type_in_tree) — the declaration-site annotation, itself derived. The binding-reference lookup is scope-naive (whole-tree, first match), the same interim the branch-operand path already uses. All five delete in favor of consuming resolution output when the resolver hands infer declaration-resolved identities directly (the namespace migration's completed state); specimens no authority declares stay on the frontier unchanged. Before this carrier existed the nine were routed through solve_constraints with the singleton candidate set [root] under a predicate that reduces to root == root, and the resulting grounding carried the SOURCE NODE as its structural evidence — i.e. as the node's resolved type (inferred_facts_resolved_type reads that field), so every literal, binding and operation in v2 was typed as itself and every downstream consumer read that as an established judgment. Each frontier node now carries a typed, located, counted diagnostic (infer_grounding_not_derived) on the Accepted path, so the deficit's frequency is observable and prioritizable rather than zeroed by construction (DESIGN §5). dissolve-on: a behavior-specific derivation rule lands for a kind, at which point that kind moves to DerivedGrounding and its frontier count drops; the carrier deletes when all twelve kinds derive." fn infer_grounding_not_derived_diagnostic(at: Locus) -> Diagnostic { Diagnostic { @@ -465,29 +467,68 @@ fn concat_inferred_facts_entries( // remains the structural-solve authority named by dag/gunbc/plans/solve_higher_order_design.dag, // to be EXTENDED with real candidate generation, never forked. -// THE DECLARED-INHABITANT MEMBERSHIP DERIVATION. The dag language authority's declared-inhabitants -// roster (v2.extdeps.languages.dag dag_declared_inhabitants_root) already fixes what its members -// denote — the add fn Arrow, the int inhabitant Conj, and the inhabitant and surface-spelling -// Atoms are all declared there — so a node that IS a roster member derives its grounding by -// LOOKUP, never by invention: the roster is the evidence, which is the intersubjective-grounding -// move DESIGN section 1 requires (point at the shared framework, never an internal taxonomy). This -// is the derivation the self-host candidate-generation add-slice stall's original trigger named, -// at specimen scope: the add fn's Arrow, Conj and Atom nodes derive, while the KINDS stay frontier — -// a non-member Arrow, Conj or Atom carries GroundingNotDerived exactly as before, so the enrolled -// refusal witnesses pinning other shapes (the bodied arrow, the empty Conj, the rust grammar atom) -// are untouched. Growth is execution-driven only: the python and typescript authorities carry -// their own declared-inhabitants rosters, and their same-language round-trip witnesses are the -// consumers that will name the moment this lookup widens to them. The end-state is the resolver -// handing infer declaration-resolved identities directly — the namespace migration's completed -// state — at which point this lookup deletes in favor of consuming resolution output. - -fn infer_node_declared_in_dag_inhabitants(n: Node) -> Bool { - any( - xs: node_subtree_nodes(root: dag_declared_inhabitants_root()), - predicate: fn(member) { member == n } +// THE DECLARED-INHABITANT MEMBERSHIP DERIVATION, GENERAL OVER THE CLOSED INGEST SET. A language +// authority's declared-inhabitants roster (v2.extdeps.languages.dag dag_declared_inhabitants_root, +// and the python/typescript analogues) already fixes what its members denote — the add fn Arrow, +// the int/number inhabitant Conj, and the inhabitant, surface-spelling, representation and +// std-projection Atoms are all declared there — so a node that IS a roster member derives its +// grounding by LOOKUP, never by invention: the roster is the evidence, which is the +// intersubjective-grounding move DESIGN section 1 requires (point at the shared framework, never +// an internal taxonomy). This is the derivation the self-host candidate-generation add-slice +// stall's original trigger named, at specimen scope: the slice's Arrow, Conj and Atom nodes derive, +// while the KINDS stay frontier — a non-member Arrow, Conj or Atom carries GroundingNotDerived +// exactly as before, so the enrolled refusal witnesses pinning other shapes (the bodied arrow, the +// empty Conj, the rust grammar atom) are untouched. The membership check is DEEP +// (node_subtree_nodes): a language's inhabitant node embeds its facts subtree, so the leaf fact +// atoms of a declared inhabitant are roster members exactly as the inhabitant node itself is. +// Growth is execution-driven: the lookup widened from the dag roster to the closed ingest set +// (dag, python, typescript) when the python/typescript same-language round-trip witnesses named +// that moment — their frontier is the grammar parse-product population, whose leaves are the +// declared inhabitants' fact atoms. The set is the compiler's own closed ingest set, not a generic +// hub enumerating upstream products: infer already fixes which languages it ingests, and each +// roster stays in its own authority module (DESIGN section 3's external-upstream decomposition). +// The end-state is the resolver handing infer declaration-resolved identities directly — the +// namespace migration's completed state — at which point this lookup deletes in favor of consuming +// resolution output. + +fn infer_language_inhabitants_roster_roots() -> List { + [ + dag_declared_inhabitants_root(), + python_declared_inhabitants_root(), + ts_declared_inhabitants_root() + ] +} + +// Returns the declaring roster ROOT as the evidence node (never the member itself, which would be +// self-evidence), or Absent when no ingested language's roster declares the node. +fn infer_node_declared_in_language_inhabitants(n: Node) -> Optional { + fold( + infer_language_inhabitants_roster_roots(), + init: optional_absent(), + f: fn(acc, roster_root) { + match acc { + Present { value: _ } => acc + Absent => + if any( + xs: node_subtree_nodes(root: roster_root), + predicate: fn(member) { member == n } + ) { + optional_present(value: roster_root) + } else { + acc + } + } + } ) } +fn infer_node_declared_in_language_inhabitants_bool(n: Node) -> Bool { + match infer_node_declared_in_language_inhabitants(n: n) { + Present { value: _ } => true + Absent => false + } +} + // THE PRODUCT-INTRODUCTION DERIVATION (Conj and Arrow kinds). A Conj node denotes the product of // its conjuncts' denotations and an Arrow node the function from its domain's denotation to its // codomain's — the closed vocabulary's own composition semantics (v2.std.node), not an invented @@ -504,7 +545,7 @@ fn infer_node_declared_in_dag_inhabitants(n: Node) -> Bool { // two kinds: the kind-level rule the specimen-scope roster lookups stood in for. fn infer_type_node_awaits_product_row(n: Node) -> Bool { - (length(xs: n.children) > 0) && !infer_node_declared_in_dag_inhabitants(n: n) + (length(xs: n.children) > 0) && !infer_node_declared_in_language_inhabitants_bool(n: n) } // THE CANONICAL-OPERATION ROSTER MEMBERSHIP DERIVATION. The resolver canonicalizes a dag surface @@ -632,13 +673,14 @@ fn infer_node_facts(n: Node, partials: List, tree: Node) -> Outcome - if infer_node_declared_in_dag_inhabitants(n: n) { - inferred_facts_from_derived_type( - node: n, - derived_type: dag_declared_inhabitants_root(), - descent: Holds { value: descent_proof } - ) - } else { + match infer_node_declared_in_language_inhabitants(n: n) { + Present { value: inhabitants_roster } => + inferred_facts_from_derived_type( + node: n, + derived_type: inhabitants_roster, + descent: Holds { value: descent_proof } + ) + Absent => match infer_atom_binding_sym(node: n) { Present { value: binding } => match dag_binding_denotation(sym: binding) { diff --git a/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag b/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag index 32441c2df79..5636133c191 100644 --- a/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag +++ b/src/v2/compiler/self_host/candidate_generation_stage_verdicts.dag @@ -40,11 +40,15 @@ import v2.std.node { Node, Symbol } // the full rejection chain head-first (which bind_outcome prefixes with infer's pending // diagnostics). For the add slice both are now empty, and the flipped witness pins that. // -// THIS INSTRUMENT DID NOT RETIRE WHEN THE ADD-SLICE STALL -// (`gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall`) NARROWED. It is the -// standing measurement of a production seam; its verdicts flipping to `candidate_accepted` with -// empty carried lists was the signal the add-slice half of the stall's trigger had fired. It -// retires only if the `generate_translate_self_emit_candidate` seam itself dissolves. +// THE ADD-SLICE STALL (`gunbc.guarantee_stall.self_host_candidate_generation_add_slice_stall`) +// RETIRED 2026-09-07 when the declared-inhabitant membership derivation widened to the closed +// ingest set (v2.compiler.infer infer_node_declared_in_language_inhabitants), grounding the python +// and typescript parse-product populations and greening the stall's four round-trip members — its +// next-rung trigger fired, so the row reached its ceiling and deleted per DESIGN 4b(4). This +// instrument did not retire with it: it is the standing measurement of a production seam, and its +// verdicts flipping to `candidate_accepted` with empty carried lists was the signal the add-slice +// half of the trigger had fired. It retires only if the +// `generate_translate_self_emit_candidate` seam itself dissolves. type CandidateGenerationStageVerdicts { infer_verdict: Symbol diff --git a/src/v2/test/claim/long/inhabitant_neutralization_e2e_witness_test.dag b/src/v2/test/claim/long/inhabitant_neutralization_e2e_witness_test.dag index 1fe02ba7dc5..8323a288e24 100644 --- a/src/v2/test/claim/long/inhabitant_neutralization_e2e_witness_test.dag +++ b/src/v2/test/claim/long/inhabitant_neutralization_e2e_witness_test.dag @@ -25,7 +25,16 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // Dissolve-on: re-enroll per-PR when measured under gunbc_ci_fast_lane_witness_eval_budget, or // delete as duplicate of a faster mechanism receipt. -test fn inhabitant_neutralization_python_to_ts_cross_language_compile_refuses_underived_holds() -> Bool { +// FLIPPED 2026-09-07 per DESIGN 4b(4): this was the e2e frontier guard +// `inhabitant_neutralization_python_to_ts_cross_language_compile_refuses_underived_holds`, asserting the +// python→typescript compile refused headed by infer_grounding_not_derived while the python inhabitants +// were on the grounding frontier. The declared-inhabitant membership derivation widening to the closed +// ingest set (v2.compiler.infer infer_node_declared_in_language_inhabitants) grounded the python and +// typescript inhabitant fact atoms, so the compile now ACCEPTS and emits the canonical typescript add +// fn byte-identical to ts_source_text. It stands as the permanent regression control for that +// acceptance; the python→go member below stays a refusal guard because go is outside the closed ingest +// set and its inhabitants remain frontier. +test fn inhabitant_neutralization_python_to_ts_cross_language_compile_round_trip_holds() -> Bool { match python_grammar_parse_fixture() { Accepted { value: python_parse_tree, diagnostics: _ } => match cross_language_compile( @@ -33,9 +42,9 @@ test fn inhabitant_neutralization_python_to_ts_cross_language_compile_refuses_un source_model: python_target_model(), target_model: ts_target_model() ) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Accepted { value: source, diagnostics: _ } => + source.carried == ts_source_text + Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false } diff --git a/src/v2/test/claim/manual/cross_language_add_python_to_typescript_test.dag b/src/v2/test/claim/manual/cross_language_add_python_to_typescript_test.dag index 93c02235156..f341933a89d 100644 --- a/src/v2/test/claim/manual/cross_language_add_python_to_typescript_test.dag +++ b/src/v2/test/claim/manual/cross_language_add_python_to_typescript_test.dag @@ -25,7 +25,7 @@ import v2.extdeps.languages.python { python_emitted_add_fn_node, python_target_model } -import v2.extdeps.languages.typescript { ts_target_model } +import v2.extdeps.languages.typescript { ts_source_text, ts_target_model } import v2.std.grammar { ParseTree } import v2.std.logic { Bool } @@ -103,7 +103,16 @@ test fn cross_language_emit_inhabitant_neutralization_fails_closed() -> Bool { } } -test fn cross_language_emit_inhabitant_neutralization_refuses_underived_holds() -> Bool { +// FLIPPED 2026-09-07 per DESIGN 4b(4): this was +// `cross_language_emit_inhabitant_neutralization_refuses_underived_holds`, asserting emit after +// python→typescript neutralization refused headed by infer_grounding_not_derived while the typescript +// inhabitants were on the grounding frontier. The declared-inhabitant membership derivation widening +// to the closed ingest set (v2.compiler.infer infer_node_declared_in_language_inhabitants) grounded +// them, so the chain now ACCEPTS and emits the canonical typescript add fn byte-identical to +// ts_source_text. It stands as the permanent regression control for that acceptance; the raw +// (un-neutralized) emit refusal stays covered by +// cross_language_emit_inhabitant_neutralization_fails_closed above. +test fn cross_language_emit_inhabitant_neutralization_round_trip_holds() -> Bool { match neutralize_core_for_target( core: python_emitted_add_fn_node(), target: ts_target_model() @@ -112,9 +121,9 @@ test fn cross_language_emit_inhabitant_neutralization_refuses_underived_holds() match infer(tree: neutralized) { Accepted { value: inferred, diagnostics: _ } => match emit(tree: inferred, target: ts_target_model()) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Accepted { value: source, diagnostics: _ } => + source.carried == ts_source_text + Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false } @@ -134,7 +143,7 @@ test fn cross_language_add_python_to_typescript_chain_status_holds() -> Bool { Accepted { value: python_core, diagnostics: _ } => python_core == python_emitted_add_fn_node() - && cross_language_compile_on_real_tree_refuses_underived_holds(parse_tree: python_parse_tree) + && cross_language_compile_on_real_tree_round_trip_holds(parse_tree: python_parse_tree) && ts_effect_io_emit_holds() Rejected { diagnostics: _ } => false } @@ -142,15 +151,19 @@ test fn cross_language_add_python_to_typescript_chain_status_holds() -> Bool { } } -fn cross_language_compile_on_real_tree_refuses_underived_holds(parse_tree: ParseTree) -> Bool { +// FLIPPED 2026-09-07 per DESIGN 4b(4): the real-tree python→typescript compile refused headed by +// infer_grounding_not_derived until the declared-inhabitant membership derivation widened to the +// closed ingest set; it now accepts byte-identical to ts_source_text, and this helper is the +// regression control for that acceptance. +fn cross_language_compile_on_real_tree_round_trip_holds(parse_tree: ParseTree) -> Bool { match cross_language_compile( parse_tree: parse_tree, source_model: python_target_model(), target_model: ts_target_model() ) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Accepted { value: source, diagnostics: _ } => + source.carried == ts_source_text + Rejected { diagnostics: _ } => false } } @@ -173,10 +186,13 @@ data cross_language_emit_matrix: List = [ status: ChainProven {} }, + // python→typescript moved ChainFailsClosedAtCheck { check: ^infer_grounding_not_derived } → + // ChainProven on 2026-09-07 when the declared-inhabitant membership derivation widened to the + // closed ingest set; the round-trip witnesses above are the executing evidence. CrossLanguageEmitMatrixRow { source_language: ^python, target_language: ^typescript, - status: ChainFailsClosedAtCheck { check: ^infer_grounding_not_derived } + status: ChainProven {} }, CrossLanguageEmitMatrixRow { diff --git a/src/v2/test/claim/manual/inhabitant_neutralization_test.dag b/src/v2/test/claim/manual/inhabitant_neutralization_test.dag index adbf3bf8bf7..435d7a29dbb 100644 --- a/src/v2/test/claim/manual/inhabitant_neutralization_test.dag +++ b/src/v2/test/claim/manual/inhabitant_neutralization_test.dag @@ -18,6 +18,7 @@ import v2.extdeps.languages.go { import v2.extdeps.languages.python { python_int_inhabitant_node, python_emitted_add_fn_node, + python_source_text, python_target_model } import v2.test.manual.ingest_bridge { ingest_cross_language_compile_accepts_holds } @@ -134,7 +135,13 @@ test fn inhabitant_neutralization_go_unqualified_std_projection_lookup_rejects_h } } -fn inhabitant_neutralization_go_int64_to_ts_emit_refuses_underived_holds() -> Bool { +// FLIPPED 2026-09-07 per DESIGN 4b(4): was `..._emit_refuses_underived_holds`, asserting emit +// refused headed by infer_grounding_not_derived while the typescript number inhabitant sat on the +// grounding frontier. The language-general declared-inhabitant membership derivation grounded it, +// so the neutralized go-int64 tree now emits the canonical typescript add fn byte-identical to +// ts_source_text. Stands as the permanent regression control for that neutralize-then-emit round +// trip; the grounding-gate refusal class stays covered by the synthetic translate-level controls. +fn inhabitant_neutralization_go_int64_to_ts_emit_round_trip_holds() -> Bool { match neutralize_core_for_target( core: go_int64_falsifier_emitted_add_fn_node(), target: ts_target_model() @@ -143,9 +150,8 @@ fn inhabitant_neutralization_go_int64_to_ts_emit_refuses_underived_holds() -> Bo match infer(tree: neutralized) { Accepted { value: inferred, diagnostics: _ } => match emit(tree: inferred, target: ts_target_model()) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Accepted { value: source, diagnostics: _ } => source.carried == ts_source_text + Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false } @@ -171,10 +177,16 @@ test fn inhabitant_neutralization_third_inhabitant_falsifier_holds() -> Bool { && inhabitant_neutralization_go_int_additive_numeric_target_lookup_holds() && inhabitant_neutralization_go_additive_numeric_lookup_tiebreak_selects_platform_int_holds() && inhabitant_neutralization_neutralize_python_to_go_holds() - && inhabitant_neutralization_go_int64_to_ts_emit_refuses_underived_holds() + && inhabitant_neutralization_go_int64_to_ts_emit_round_trip_holds() } -test fn inhabitant_neutralization_emit_after_neutralize_refuses_underived_holds() -> Bool { +// FLIPPED 2026-09-07 per DESIGN 4b(4): was `..._emit_after_neutralize_refuses_underived_holds`, +// asserting emit refused headed by infer_grounding_not_derived while the typescript number +// inhabitant sat on the grounding frontier. The language-general declared-inhabitant membership +// derivation grounded it, so the neutralized python tree now emits the canonical typescript add fn +// byte-identical to ts_source_text. Permanent regression control for the neutralize-then-emit +// round trip; the grounding-gate refusal class stays covered by the synthetic translate controls. +test fn inhabitant_neutralization_emit_after_neutralize_round_trip_holds() -> Bool { match neutralize_core_for_target( core: python_emitted_add_fn_node(), target: ts_target_model() @@ -183,9 +195,8 @@ test fn inhabitant_neutralization_emit_after_neutralize_refuses_underived_holds( match infer(tree: neutralized) { Accepted { value: inferred, diagnostics: _ } => match emit(tree: inferred, target: ts_target_model()) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Accepted { value: source, diagnostics: _ } => source.carried == ts_source_text + Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false } @@ -206,13 +217,22 @@ fn inhabitant_neutralization_raw_emit_still_fails_closed() -> Bool { // The same-language ingest compile this guard watches flipped from frontier refusal // (infer_grounding_not_derived) to clean acceptance on 2026-09-06, under the declared-inhabitant -// membership derivation (v2.compiler.infer infer_node_declared_in_dag_inhabitants) -- an infer-lane -// change, not a neutralization one, which is exactly the distinction this guard exists to draw. +// membership derivation (v2.compiler.infer infer_node_declared_in_language_inhabitants, widened to +// the closed ingest set on 2026-09-07) -- an infer-lane change, not a neutralization one, which is +// exactly the distinction this guard exists to draw. fn inhabitant_neutralization_same_language_cross_language_compile_accepts_holds() -> Bool { ingest_cross_language_compile_accepts_holds() } -fn inhabitant_neutralization_same_flavor_python_emit_refuses_underived_holds() -> Bool { +// FLIPPED 2026-09-07 per DESIGN 4b(4): was `..._same_flavor_python_emit_refuses_underived_holds`, +// asserting emit refused headed by infer_grounding_not_derived while the python int inhabitant sat +// on the grounding frontier. The language-general declared-inhabitant membership derivation +// grounded it, so the same-language (python→python) neutralize-then-emit now round-trips to the +// canonical python source byte-identical to python_source_text. Permanent regression control for +// that acceptance; the fail-closed half of this guard is carried by +// inhabitant_neutralization_raw_emit_still_fails_closed (which still refuses, now at +// NoTargetCandidate on the un-neutralized cross-target emit). +fn inhabitant_neutralization_same_flavor_python_emit_round_trip_holds() -> Bool { match neutralize_core_for_target( core: python_emitted_add_fn_node(), target: python_target_model() @@ -221,9 +241,8 @@ fn inhabitant_neutralization_same_flavor_python_emit_refuses_underived_holds() - match infer(tree: neutralized) { Accepted { value: inferred, diagnostics: _ } => match emit(tree: inferred, target: python_target_model()) { - Rejected { diagnostics: d } => - d.head.reason == ^infer_grounding_not_derived - Accepted { value: _, diagnostics: _ } => false + Accepted { value: source, diagnostics: _ } => source.carried == python_source_text + Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false } @@ -233,7 +252,7 @@ fn inhabitant_neutralization_same_flavor_python_emit_refuses_underived_holds() - test fn inhabitant_neutralization_03_ingest_regression_guard_holds() -> Bool { inhabitant_neutralization_same_language_cross_language_compile_accepts_holds() - && inhabitant_neutralization_same_flavor_python_emit_refuses_underived_holds() + && inhabitant_neutralization_same_flavor_python_emit_round_trip_holds() && inhabitant_neutralization_raw_emit_still_fails_closed() } diff --git a/src/v2/test/claim/translate_underived_refusal_test.dag b/src/v2/test/claim/translate_underived_refusal_test.dag index 61d5537434d..af9449d624a 100644 --- a/src/v2/test/claim/translate_underived_refusal_test.dag +++ b/src/v2/test/claim/translate_underived_refusal_test.dag @@ -10,7 +10,7 @@ import v2.extdeps.languages.python { python_emitted_add_fn_node, python_target_model } -import v2.extdeps.languages.typescript { ts_target_model } +import v2.extdeps.languages.typescript { ts_source_text, ts_target_model } import v2.std.inhabitant_neutralization { neutralize_core_for_target } import v2.test.manual.python_grammar_claim { python_grammar_parse_fixture } import v2.extdeps.languages.rust { @@ -393,7 +393,17 @@ fn translate_accepts_synthetic_empty_facts_tree() -> Bool { } } -test fn cross_language_compile_refuses_canonical_underived_holds() -> Bool { +// FLIPPED 2026-09-07 per DESIGN 4b(4). This was the pipeline-level frontier guard +// `cross_language_compile_refuses_canonical_underived_holds`, asserting the python→typescript +// compile refused headed by `infer_grounding_not_derived` while the python inhabitants sat on the +// grounding frontier — the control the add-slice stall cited as confirming the refusal persisted. +// The declared-inhabitant membership derivation widening to the closed ingest set +// (v2.compiler.infer infer_node_declared_in_language_inhabitants) grounded the python and +// typescript inhabitant fact atoms, so the compile now ACCEPTS and emits the canonical typescript +// add fn byte-identical to `ts_source_text` — verified by execution. It stands as the permanent +// regression control for that acceptance; the translate-level grounding-gate refusal class stays +// covered by the synthetic underived controls in this file. +test fn cross_language_compile_python_to_typescript_round_trip_holds() -> Bool { match python_grammar_parse_fixture() { Accepted { value: parse_tree, diagnostics: _ } => match cross_language_compile( @@ -401,9 +411,9 @@ test fn cross_language_compile_refuses_canonical_underived_holds() -> Bool { source_model: python_target_model(), target_model: ts_target_model() ) { - Rejected { diagnostics: d } => - translate_refused_canonical_grounding(d: d) - Accepted { value: _, diagnostics: _ } => false + Accepted { value: source, diagnostics: _ } => + source.carried == ts_source_text + Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false } diff --git a/src/v2/workflow/floor_cost_debt.dag b/src/v2/workflow/floor_cost_debt.dag index 6488f47c29b..a115029e5f0 100644 --- a/src/v2/workflow/floor_cost_debt.dag +++ b/src/v2/workflow/floor_cost_debt.dag @@ -923,7 +923,7 @@ fn floor_cost_debt_proven_chunk_12() -> List { } fn floor_cost_debt_proven_chunk_14() -> List { - Cons { head: "v2.test.claim.dag_acceptance.acceptance_accounts_for_a_stage_the_contract_never_asked_for", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_btree_set_debug_contract_requires_ord_from_im_ord_set_authority", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_btree_set_partial_eq_contract_requires_ord_from_im_ord_set_authority", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_btree_set_serialize_contract_requires_ord_and_clone_from_im_ord_set_authority", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_hash_set_unrepresentable_hash_contracts_refuse_with_typed_blockers", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_set_target_bundle_round_trip_preserves_both_production_policies", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_vec_serialize_contract_requires_clone_from_im_vector_authority", tail: Cons { head: "v2.test.claim.enforcement.lens_module_gate_witness.lens_closure_question_zero_holds_live", tail: Cons { head: "v2.test.claim.enforcement.lens_module_gate_witness.lens_closure_question_zero_live_matches_gate", tail: Cons { head: "v2.test.claim.enforcement.lens_module_gate_witness.lens_module_gate_holds_live", tail: Cons { head: "v2.test.claim.rust_crate_partition_witness.witness_partition_perturbation_member_coverage_holds", tail: Cons { head: "v2.test.claim.self_host.direct_rust_door_fixture_containment_witness_test.fixture_cargo_green_advance_is_not_behaviorally_equivalent", tail: Cons { head: "v2.test.claim.self_host.direct_rust_door_fixture_containment_witness_test.fixture_synthetic_mint_is_fixture_source_produced_only", tail: Cons { head: "v2.test.claim.translate_underived_refusal.cross_language_compile_refuses_canonical_underived_holds", tail: Cons { head: "v2.test.claim.translate_underived_refusal.translate_derived_emit_byte_identical_to_before_holds", tail: Cons { head: "v2.test.compiler.pipeline.lower_stage.pipeline_lower_add_fixture_relationship_green", tail: Cons { head: "v2.test.compiler.pipeline.tokenize_stage.pipeline_tokenize_pick_relationship_green", tail: Cons { head: "v2.test.emit.produced_decl_two_target.produced_decl_unwired_target_still_refuses", tail: Empty {} } } } } } } } } } } } } } } } } } } + Cons { head: "v2.test.claim.dag_acceptance.acceptance_accounts_for_a_stage_the_contract_never_asked_for", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_btree_set_debug_contract_requires_ord_from_im_ord_set_authority", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_btree_set_partial_eq_contract_requires_ord_from_im_ord_set_authority", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_btree_set_serialize_contract_requires_ord_and_clone_from_im_ord_set_authority", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_hash_set_unrepresentable_hash_contracts_refuse_with_typed_blockers", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_set_target_bundle_round_trip_preserves_both_production_policies", tail: Cons { head: "v2.test.claim.emit.trait_derive_supplemental_generic_bound_contract.rust_vec_serialize_contract_requires_clone_from_im_vector_authority", tail: Cons { head: "v2.test.claim.enforcement.lens_module_gate_witness.lens_closure_question_zero_holds_live", tail: Cons { head: "v2.test.claim.enforcement.lens_module_gate_witness.lens_closure_question_zero_live_matches_gate", tail: Cons { head: "v2.test.claim.enforcement.lens_module_gate_witness.lens_module_gate_holds_live", tail: Cons { head: "v2.test.claim.rust_crate_partition_witness.witness_partition_perturbation_member_coverage_holds", tail: Cons { head: "v2.test.claim.self_host.direct_rust_door_fixture_containment_witness_test.fixture_cargo_green_advance_is_not_behaviorally_equivalent", tail: Cons { head: "v2.test.claim.self_host.direct_rust_door_fixture_containment_witness_test.fixture_synthetic_mint_is_fixture_source_produced_only", tail: Cons { head: "v2.test.claim.translate_underived_refusal.cross_language_compile_python_to_typescript_round_trip_holds", tail: Cons { head: "v2.test.claim.translate_underived_refusal.translate_derived_emit_byte_identical_to_before_holds", tail: Cons { head: "v2.test.compiler.pipeline.lower_stage.pipeline_lower_add_fixture_relationship_green", tail: Cons { head: "v2.test.compiler.pipeline.tokenize_stage.pipeline_tokenize_pick_relationship_green", tail: Cons { head: "v2.test.emit.produced_decl_two_target.produced_decl_unwired_target_still_refuses", tail: Empty {} } } } } } } } } } } } } } } } } } } } fn floor_cost_debt_proven_chunk_15() -> List { From 90b725c0fc6ae27660aeba3a84225399e493ec16 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 06:52:45 +0000 Subject: [PATCH 10/89] Promote the add family to SelfEmittedNative: native-only verdict witness for the emitted add crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First InterpreterRetained -> SelfEmittedNative promotion after classical_not, executing the v2-emitter-first-behavioral-module first slice at the coverage-frontier grain: the add family (fewest dependencies — integer literals plus one canonical operation) now carries a native-only verdict witness, so its behavior is established by the emitted crate's own stdout with eval() unreachable from the verdict path. - emit_host_native_only_add_holds: real emit -> cargo build -> native run, stdout pinned to the family's expected octet, sharing the kernel family's one-build cache key exactly as the classical_not arm shares its family's key (no duplicated cold build). - emit_host_native_only_add_wrong_octet_mismatch_detected_holds: the broken control — a no-eval verdict has no oracle leg to break, so the expectation side breaks (an octet the run never produces must not match); program-side discrimination stays with the family's equals_eval primitive-five/six pair. - The add coverage row flips disposition with its backing citation enrolled by construction (the verdict entry is file-grain enrolled in falsifier_self_host_wet_template_entries). - Frontier census tests updated at identity grain: natives are exactly {classical_not, add}; split 2/13. Verified by execution: all six native-only verdict tests green locally (real wet legs — compile_skipped receipts show cold builds and native runs); all eight emit_coverage_frontier tests green, including the unbacked-claim RED control. Co-authored-by: Brian Searls --- .../v2-self-host-direct-path-2026-09-06.md | 4 +- .../self_host/emit_coverage_frontier.dag | 17 ++++-- .../emit_host_native_only_verdict_test.dag | 58 +++++++++++++++++++ .../self_host/emit_coverage_frontier_test.dag | 19 ++++-- 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/docs/plans/v2-self-host-direct-path-2026-09-06.md b/docs/plans/v2-self-host-direct-path-2026-09-06.md index 336d9777cf2..c1e1d1a175f 100644 --- a/docs/plans/v2-self-host-direct-path-2026-09-06.md +++ b/docs/plans/v2-self-host-direct-path-2026-09-06.md @@ -32,7 +32,7 @@ Each line names the producer that re-derives the state. Run it; do not believe t - **Axis A — the real path, slice by slice.** The add slice (the `add` fn of `std.integer`) is green end-to-end on the dag path as of #10673: infer derives grounding by declared-inhabitant roster membership, and the ingest→compile round trip is byte-faithful. Instrument: `gunbc run --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag --function add_slice_stage_verdicts_entry`. The **direct-rust-door** (roadmap `v2-emitter-direct-rust-door`) is **green by execution as of 2026-09-06**: the production walk reaches `ArtifactProduced`, all five leaf projections establish (source fidelity byte-equal to `direct_rust_door_expected_source`, producer identity, seed-emitter absence, `SourceProduced` qualification, missing-rule refusal), and the known-red admission row deleted per its own dissolution, promoting the group root to the lane's permanent regression wall. Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/long/direct_rust_door_production_group_test.dag --functions direct_rust_door_production_group_closing_expectation_holds` (a `long/` row — it reconstructs the pipeline per evaluation; the cost defect is declared in the test module's header). - **The grounding frontier.** The door specimen's frontier is closed, and the parse-product family followed on 2026-09-07: the declared-inhabitant roster-membership derivation widened from the dag roster to the closed ingest set (dag, python, typescript), closing the python and typescript fixtures' frontiers and greening the four same-language round-trips. The add-slice stall's trigger fired and it retired per §4b(4). Six derivation rules now cover declared-inhabitants roster membership (language-general), binding denotation, Conj/Arrow product introduction, canonical-operations and grammar-productions roster membership, and binding references from the enclosing arrow's domain. The remaining frontier is the six node kinds with no derivation rule yet (the `node_grounding_frontier_note` in `04_infer.dag` is the standing carrier). - **Axis B — the native closure (XL-N).** The XL-0 gate (`cargo check` = 0 on the emitted compiler closure) is **not satisfied**; the issue's own receipt table records that the closure reached complete rustc type checking for the first time at XL-0A and carries the honest error population there. gunbc#10266 (fabricated runtime panics on representation-identical casts) is filed as an independent compiler slice on this path. Instrument (the issue's own): `gunbc compile --source-root dag --source-root src/v2 --entry src/v2/compiler/00_compile.dag --target rust --output-dir /tmp/v2emit && cd /tmp/v2emit && cargo check --message-format short`. -- **Axis C — the seed dissolves.** `v2.compiler.self_host.seed_retention_frontier` holds every retained seed file as a declared row; `undeclared_reason_rows()` is the prioritizable debt, and the census refuses unrostered retention in three directions. `v2.compiler.self_host.emit_coverage_frontier`'s `interpreter_retained_rows()` and `v2.compiler.self_host.native_routing_frontier`'s roster cover the witness-execution axes. +- **Axis C — the seed dissolves.** `v2.compiler.self_host.seed_retention_frontier` holds every retained seed file as a declared row; `undeclared_reason_rows()` is the prioritizable debt, and the census refuses unrostered retention in three directions. `v2.compiler.self_host.emit_coverage_frontier`'s `interpreter_retained_rows()` and `v2.compiler.self_host.native_routing_frontier`'s roster cover the witness-execution axes. The first InterpreterRetained → SelfEmittedNative promotion after classical_not landed 2026-09-07: the add family's row flipped when `emit_host_native_only_add_holds` (+ its wrong-octet broken control) landed in the file-grain-enrolled native-only verdict entry — the emitted add crate's stdout pinned to its expected octet with `eval()` never called, program-side discrimination staying with the family's equals_eval primitive-five/six pair. Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag` (offline wet legs — real cargo build and native run per leg). - **Wave position.** Per the wave doc, Wave 1's four exit items all carry landed receipts; Wave 2 (first real flips — emit-surface tracks self-emit, behaviorally verify, and **replace** their v1 counterparts) is the open wave. The wave doc's header records that its roster carrier was deleted and per-module dispositions must be re-derived from the emitter — which is what the door-first sequence below does. ## 3. The sequence from here @@ -41,7 +41,7 @@ Ordered by the roadmap chain; each step names its green condition as the node's 1. **The door** (`v2-emitter-direct-rust-door`) — **LANDED 2026-09-06.** The loop's actual findings, for the record: not missing `TargetModel` rows. (a) The grounding frontier closed by six real derivation rules in `04_infer.dag` (see §2's frontier line). (b) Emission needed the right composition, not more rules: a resolved module is a module shell over named declarations, so the production path is `generate_rust_module_emission_candidate` — collect the produced-decl-shaped nodes, admit exactly one, emit through `v2.compiler.emit_produced` — while the raw whole-tree `generate_rust_emission_candidate` remains the fixture lane's composition with its own dissolution trigger. (c) One representation decode gap: resolution canonicalizes surface operators into canonical-operation wire nodes, so the translate value-expression projection decodes the operator position with `canonical_operation_from_wire_node` first and falls to the surface-token table only on a wire miss. Green condition met: the production observation reaches `ArtifactProduced`, all five leaf projections establish, the known-red expectation row deleted in the same change, and the group root promotes to permanent regression evidence (`v2_emitter_direct_rust_door_contract_dissolution_note`). 2. **The parse-product grounding family** (the stall's named trigger) — **LANDED 2026-09-07.** The roster-membership derivation generalized from the dag language roster to the closed ingest set (dag, python, typescript): `infer_node_declared_in_language_inhabitants` returns the declaring authority's roster root as evidence, with deep subtree membership so a declared inhabitant's leaf fact atoms derive exactly as the inhabitant node itself does. Measured: the python fixture's 19-node frontier and the typescript fixture's 28-node frontier both close to zero; all four stall-population round-trip witnesses green; the python→typescript cross-language compile accepts, byte-identical to `ts_source_text`. One §4b(4) flip: `cross_language_compile_refuses_canonical_underived_holds` → `cross_language_compile_python_to_typescript_round_trip_holds` (the pipeline-level frontier guard became the permanent regression control for the acceptance). The add-slice stall's trigger fired, so it retired per §4b(4) — removed from `all_guarantee_stalls`, row file deleted, witnesses stay enrolled. -3. **First behavioral module** (`v2-emitter-first-behavioral-module`). One already-tested module regenerated by the new generator; its existing tests — including the deliberately-broken one — pass unchanged, and the run is shown never to have reached the old generator. +3. **First behavioral module** (`v2-emitter-first-behavioral-module`). One already-tested module regenerated by the new generator; its existing tests — including the deliberately-broken one — pass unchanged, and the run is shown never to have reached the old generator. First-slice evidence landed 2026-09-07 at the coverage-frontier grain: the add family (fewest dependencies — integer literals plus one canonical operation) carries a native-only verdict witness, so its behavior is established by the emitted crate's own stdout with `eval()` unreachable, and its coverage row reads SelfEmittedNative. What remains for the node is the regen-grain flip — a corpus module's *production* switching generators — plus operator acceptance. 4. **The XL-N milestones, in the issue's order** — arms → 0; emitter defects C, B → 0; crate compiles; native = interpreted with every divergence typed; swap. This is Wave 4's "pipeline runs on emitted Rust" executed as a burn-down, and its rules bind: fixes land in `.dag` and reach Rust via regen, and each closed class gets a `test.claim` fixture under the required gate. Its no-go gates are real: M2 stops if a fix needs a fact the inferred tree doesn't carry (an `04_infer` modeling gap — file it, don't grind); M3 re-plans if a class needs a language capability we don't have. 5. **Native bootstrap, then fixed point** (`v2-emitter-native-bootstrap`, `v1-emitter-fixed-point`). The two-round self-build for the generator, then the same two-round test extended stage by stage to the rest of the compiler. Then `dag/gunbc/v1/v1_deletion_plan.dag` executes; the seed-retention roster drains as modules flip, and Wave 3's first obligation — a derived denominator for what the compiler consists of — is named in the wave doc. 6. **Parallel track, not a wave gate:** the import-deletion ladder (wave doc §3) — B3's migration alongside Waves 2–3, B4's grammar deletion at Wave 4. diff --git a/src/v2/compiler/self_host/emit_coverage_frontier.dag b/src/v2/compiler/self_host/emit_coverage_frontier.dag index 54e77a875ef..c1520a9548e 100644 --- a/src/v2/compiler/self_host/emit_coverage_frontier.dag +++ b/src/v2/compiler/self_host/emit_coverage_frontier.dag @@ -76,14 +76,23 @@ fn retained_via_eval_agreement( } data emit_coverage_frontier_roster: List = [ - retained_via_eval_agreement( + // add family promoted InterpreterRetained -> SelfEmittedNative 2026-09-07: the native-only + // verdict arm for add (emit_host_native_only_add_holds + its wrong-octet broken control) landed + // in the already file-grain-enrolled native-only verdict entry, so the backing citation is + // enrolled by construction (coverage_row_backing_is_enrolled). The equals_eval agreement pair + // stays enrolled as the family's program-side discrimination leg. + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.manual.rust_add_emit_translate", decl_name: "rust_add_inferred_tree" ), - entry: "src/v2/test/claim/execution/emit_host_add_equals_eval_test.dag", - f: "emit_host_add_equals_eval_holds" - ), + witness_entry: "src/v2/test/claim/execution/emit_host_add_equals_eval_test.dag", + witness_function: "emit_host_add_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_add_holds" + } + }, EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.long.emit_host_classical_not_ingested_equals_eval", diff --git a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag index 6a7bb502238..8859baee8ca 100644 --- a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag +++ b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag @@ -3,6 +3,8 @@ module v2.test.long.emit_host_native_only_verdict import v2.compiler.emit { emit } import v2.compiler.emit_host { emit_host_octets_byte_string, run_emit_host_cached } import v2.compiler.eval { inputs_root_only } +import v2.compiler.infer { InferredTree } +import v2.extdeps.languages.rust { rust_target_model_staging } import v2.extdeps.languages.rust_test { rust_classical_not_ingested_target_model_staging } import extdeps.realization.emit_on_demand_host { native_cache_workspace_root } import v2.test.long.emit_host_classical_not_ingested_equals_eval { @@ -10,10 +12,18 @@ import v2.test.long.emit_host_classical_not_ingested_equals_eval { ingested_classical_not_swapped_source, ingested_wet_emit_eval_composed_tree_from_source, } +import v2.test.execution.emit_host_add_equals_eval { + emit_eval_subject_tree, +} import v2.test.execution.emit_on_demand_classical_not_ingested_family_witness { classical_not_family_cache_root_for, classical_not_family_native_key_for, } +import v2.test.execution.emit_on_demand_kernel_witness { + kernel_add_fixture_inputs, + kernel_cache_root_for, + kernel_native_key_for, +} import v2.std.diagnostic { Accepted, Rejected, Outcome, None } import v2.std.host_run { HostLogicalRun, HostRunStdout } import v2.std.logic { Bool } @@ -104,3 +114,51 @@ test fn emit_host_native_only_verdict_stdout_mismatch_mock_holds() -> Bool { ) == false } +// ADD-FAMILY ARM (2026-09-07): the same native-only verdict shape for the emit_host_add family — +// the second construct promoted off InterpreterRetained, after classical_not. Two differences from +// the classical_not arm are family facts, not new machinery: the add family is FIXTURE-TREE +// anchored (emit_eval_subject_tree is the family's shared tree constructor — no eval leg is +// reachable from this file despite the constructor's home module's name), so the helper takes the +// tree directly with no source-compose step; and the run shares the KERNEL family's one-build +// cache key (kernel_cache_root_for + kernel_native_key_for, the same key +// emit_on_demand_kernel_native_one_build_holds colds), exactly as the classical_not arm shares its +// family's key — an uncached leg here would duplicate a full cargo build the family witness +// already performs (DESIGN S2). The expected octet is 5, the byte the kernel witness's warm leg +// pins on this same build. Program-side discrimination stays where the family already carries it: +// the equals_eval primitive-five/primitive-six pair proves the native run's actual byte +// participates in a verdict (5 agrees, 6 diverges). A no-eval verdict has no oracle leg to break, +// so this arm's broken control breaks the EXPECTATION side: octet 6 can never match this run, and +// the comparator mocks above pin the comparator itself. +fn add_native_only_matches_expected(tree: InferredTree, expected_octet: Int) -> Bool { + match emit(tree: tree, target: rust_target_model_staging()) { + Accepted { value: emitted, diagnostics: _ } => + let workspace = native_cache_workspace_root( + cache_root: kernel_cache_root_for(test_id: "native_one_build"), + key: kernel_native_key_for(tree: tree) + ) + match run_emit_host_cached( + workspace_dir: workspace, + target: rust_target_model_staging(), + source: emitted, + fixture_inputs: kernel_add_fixture_inputs() + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + native_only_stdout_matches(stdout: stdout, expected_octet: expected_octet) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_add_holds() -> Bool { + add_native_only_matches_expected(tree: emit_eval_subject_tree(), expected_octet: 5) +} + +test fn emit_host_native_only_add_wrong_octet_mismatch_detected_holds() -> Bool { + add_native_only_matches_expected(tree: emit_eval_subject_tree(), expected_octet: 6) == false +} + diff --git a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag index b72ecb12425..5767cd6d96b 100644 --- a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag +++ b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag @@ -43,17 +43,26 @@ test fn witness_unlisted_subject_has_no_row() -> Bool { } test fn witness_split_matches_expected() -> Bool { - (length(xs: self_emitted_native_rows()) == 1) - && (length(xs: interpreter_retained_rows()) == 14) + (length(xs: self_emitted_native_rows()) == 2) + && (length(xs: interpreter_retained_rows()) == 13) } -test fn witness_only_classical_not_ingested_is_native() -> Bool { +// Identity-grain census of the native rows (DESIGN section 5: completeness is an identity join, +// not a count equality — the count leg above is the budget cross-check, this leg names the +// members). 2026-09-07: the add family joined classical_not when its native-only verdict arm +// landed; the two decl names are the closed membership. +test fn witness_native_rows_are_exactly_classical_not_and_add() -> Bool { let natives = self_emitted_native_rows() - (length(xs: natives) == 1) + (length(xs: natives) == 2) && fold( natives, init: true, - f: fn(acc, row) { acc && (row.subject.decl_name == "ingested_classical_not_source") } + f: fn(acc, row) { + acc && ( + (row.subject.decl_name == "ingested_classical_not_source") + || (row.subject.decl_name == "rust_add_inferred_tree") + ) + } ) } From d5a7eed3df0952b8dd8136ed06075a4eb0a2d321 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 09:41:46 +0000 Subject: [PATCH 11/89] Record the add-slice defect's repair in the declined-live-tree classification The row classified candidate_generation_translate_self_emit_dag_add_slice_holds as RealDefect/CompilerBehaviourRefusal with measured evidence that translate refuses infer_grounding_not_derived. The owner lane (v2 self-host) repaired the subject: the declared-inhabitant roster-membership derivation grounds the slice's type spine by lookup, and the witness passes under claim_batch --hermetic on the merged tree. The dated classification is kept verbatim; the disposition flips RoutedToOwner -> RepairedInThisChange with the repair measurement appended to the evidence, so the routing carrier stops dispatching a fixed defect. Structural witnesses (count 13, no NotReproduced, exact partition) are untouched and pass. --- dag/gunbc/declined_live_tree_defect_classification.dag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/declined_live_tree_defect_classification.dag b/dag/gunbc/declined_live_tree_defect_classification.dag index 6d4d4ac44eb..ede544fcd69 100644 --- a/dag/gunbc/declined_live_tree_defect_classification.dag +++ b/dag/gunbc/declined_live_tree_defect_classification.dag @@ -205,8 +205,8 @@ data group_a_remaining_rows: List = [ ClassifiedRow { identity: identity(module_path: "v2.test.execution.self_host_candidate_generation", function: "candidate_generation_translate_self_emit_dag_add_slice_holds"), classification: RealDefect { subject: CompilerBehaviourRefusal }, - measured_evidence: "staged by execution: infer(dag_add_emitted_root) ACCEPTS, translate(inferred, dag_add_target_model) REFUSES with head reason infer_grounding_not_derived; identical under --hermetic and --wet, so the mode is not the discriminator", - disposition: RoutedToOwner { owner: "the v2 self-host lane -- infer_grounding_not_derived is the known v2-self-grounding-frontier class already registered in gunbc.guarantee_probe_corpus, not a new defect" } + measured_evidence: "staged by execution: infer(dag_add_emitted_root) ACCEPTS, translate(inferred, dag_add_target_model) REFUSES with head reason infer_grounding_not_derived; identical under --hermetic and --wet, so the mode is not the discriminator. REPAIR RECEIPT, 2026-09-07: the owner lane's declared-inhabitant roster-membership derivation (v2.compiler.infer infer_node_declared_in_language_inhabitants) grounds the slice's ten type-spine nodes by lookup against the dag language authority's declared-inhabitants roster, and the witness now PASSES under claim_batch --hermetic on the repairing tree (re-measured after the merge onto main at 5ee9cab5fa). The classification and the first sentence are kept verbatim as the dated record of the defect as routed; this sentence is the repair measurement.", + disposition: RepairedInThisChange }, ClassifiedRow { identity: identity(module_path: "v2.test.lens_test_migration_debt.test_migration_debt_test", function: "retained_rust_kernel_wall_holds_against_live_tree"), From 6e6440cf26879d5013af4a344c0b7a55b2262e9b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 09:55:15 +0000 Subject: [PATCH 12/89] Hoist two in-body annotation blocks to module-item grain Main's annotation-placement wall (source annotations admit only standalone leading blocks attached to module-scope declarations; in-body forms refuse) reached this branch through the merge and refused 8 blocking errors on the 00_compile closure: the add-family promotion note inside the emit_coverage_frontier_roster list and the python->typescript row note inside the cross_language_emit_matrix list. Both blocks move above their enclosing declarations, rephrased to name their subject row. Measured: gunbc compile of src/v2/compiler/00_compile.dag now emits 172 files with 0 blocking errors; both files' suites stay green (8/8 and 4/4). --- src/v2/compiler/self_host/emit_coverage_frontier.dag | 11 ++++++----- .../cross_language_add_python_to_typescript_test.dag | 7 ++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/v2/compiler/self_host/emit_coverage_frontier.dag b/src/v2/compiler/self_host/emit_coverage_frontier.dag index c1520a9548e..8c2a5475d1b 100644 --- a/src/v2/compiler/self_host/emit_coverage_frontier.dag +++ b/src/v2/compiler/self_host/emit_coverage_frontier.dag @@ -75,12 +75,13 @@ fn retained_via_eval_agreement( } } +// The roster's add family row (first element) was promoted InterpreterRetained -> +// SelfEmittedNative 2026-09-07: the native-only verdict arm for add +// (emit_host_native_only_add_holds + its wrong-octet broken control) landed in the already +// file-grain-enrolled native-only verdict entry, so the backing citation is enrolled by +// construction (coverage_row_backing_is_enrolled). The equals_eval agreement pair stays enrolled +// as the family's program-side discrimination leg. data emit_coverage_frontier_roster: List = [ - // add family promoted InterpreterRetained -> SelfEmittedNative 2026-09-07: the native-only - // verdict arm for add (emit_host_native_only_add_holds + its wrong-octet broken control) landed - // in the already file-grain-enrolled native-only verdict entry, so the backing citation is - // enrolled by construction (coverage_row_backing_is_enrolled). The equals_eval agreement pair - // stays enrolled as the family's program-side discrimination leg. EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.manual.rust_add_emit_translate", diff --git a/src/v2/test/claim/manual/cross_language_add_python_to_typescript_test.dag b/src/v2/test/claim/manual/cross_language_add_python_to_typescript_test.dag index f341933a89d..c6d4cdbaa2e 100644 --- a/src/v2/test/claim/manual/cross_language_add_python_to_typescript_test.dag +++ b/src/v2/test/claim/manual/cross_language_add_python_to_typescript_test.dag @@ -178,6 +178,10 @@ type CrossLanguageEmitMatrixRow { status: CrossLanguageEmitCellStatus } +// The python→typescript row of this matrix moved ChainFailsClosedAtCheck { check: +// ^infer_grounding_not_derived } → ChainProven on 2026-09-07 when the declared-inhabitant +// membership derivation widened to the closed ingest set; the round-trip witnesses above are the +// executing evidence. data cross_language_emit_matrix: List = [ CrossLanguageEmitMatrixRow { @@ -186,9 +190,6 @@ data cross_language_emit_matrix: List = [ status: ChainProven {} }, - // python→typescript moved ChainFailsClosedAtCheck { check: ^infer_grounding_not_derived } → - // ChainProven on 2026-09-07 when the declared-inhabitant membership derivation widened to the - // closed ingest set; the round-trip witnesses above are the executing evidence. CrossLanguageEmitMatrixRow { source_language: ^python, target_language: ^typescript, From 8543ad4ead5f83d73f65de26eca738dcafda1ff5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 10:49:26 +0000 Subject: [PATCH 13/89] Promote the complement family to SelfEmittedNative: native-only verdict witness for the emitted logic family crate The complement family's native execution runs family-grain per the witness_family_build_grain_ruling (one crate for meet + join + complement, argv-dispatched), so the native-only arm emits the logic family crate and runs the complement member through the family dispatcher, sharing the family witness's one-build cache key. The verdict is decided solely by the emitted native run's stdout (expected octet 0, complement(True) = False); the broken control flips the expectation side (octet 1 can never match), with the comparator pinned by the stdout mock pair. Program-side discrimination stays with the equals_eval agreement pair and the family witness's all-alt leg. The frontier row's backing citation lands in the already file-grain-enrolled native-only verdict entry, so it is enrolled by construction; the roster comment is rephrased to cover both 2026-09-07 promotions (add and complement). The frontier test's split and native membership assertions move to 3 native / 12 retained. Verified by execution: claim_batch --hermetic on emit_host_native_only_verdict_test.dag passes all 8 witnesses (the two new complement arms included), and emit_coverage_frontier_test.dag passes all 8. --- .../self_host/emit_coverage_frontier.dag | 26 +++++--- .../emit_host_native_only_verdict_test.dag | 65 ++++++++++++++++++- .../self_host/emit_coverage_frontier_test.dag | 12 ++-- 3 files changed, 86 insertions(+), 17 deletions(-) diff --git a/src/v2/compiler/self_host/emit_coverage_frontier.dag b/src/v2/compiler/self_host/emit_coverage_frontier.dag index 8c2a5475d1b..f51c144fbc6 100644 --- a/src/v2/compiler/self_host/emit_coverage_frontier.dag +++ b/src/v2/compiler/self_host/emit_coverage_frontier.dag @@ -75,12 +75,14 @@ fn retained_via_eval_agreement( } } -// The roster's add family row (first element) was promoted InterpreterRetained -> -// SelfEmittedNative 2026-09-07: the native-only verdict arm for add -// (emit_host_native_only_add_holds + its wrong-octet broken control) landed in the already -// file-grain-enrolled native-only verdict entry, so the backing citation is enrolled by -// construction (coverage_row_backing_is_enrolled). The equals_eval agreement pair stays enrolled -// as the family's program-side discrimination leg. +// Two roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07, each on a +// native-only verdict arm (positive + wrong-octet broken control) landed in the already +// file-grain-enrolled native-only verdict entry, so each backing citation is enrolled by +// construction (coverage_row_backing_is_enrolled): the add family row (first element, backing +// emit_host_native_only_add_holds) and the complement family row (backing +// emit_host_native_only_complement_holds, run through the logic family crate per the +// witness_family_build_grain_ruling). Each family's equals_eval agreement pair stays enrolled as +// its program-side discrimination leg. data emit_coverage_frontier_roster: List = [ EmitCoverageFrontierRow { subject: coverage_subject( @@ -106,14 +108,18 @@ data emit_coverage_frontier_roster: List = [ backing_function: "emit_host_native_only_classical_not_holds" } }, - retained_via_eval_agreement( + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.execution.emit_host_complement_equals_eval", decl_name: "emit_complement_eval_subject" ), - entry: "src/v2/test/claim/execution/emit_host_complement_equals_eval_test.dag", - f: "emit_host_complement_equals_eval_holds" - ), + witness_entry: "src/v2/test/claim/execution/emit_host_complement_equals_eval_test.dag", + witness_function: "emit_host_complement_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_complement_holds" + } + }, retained_via_eval_agreement( subject: coverage_subject( module_path: "v2.test.execution.emit_host_field_access_equals_eval", diff --git a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag index 8859baee8ca..fba82773768 100644 --- a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag +++ b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag @@ -1,11 +1,16 @@ module v2.test.long.emit_host_native_only_verdict import v2.compiler.emit { emit } -import v2.compiler.emit_host { emit_host_octets_byte_string, run_emit_host_cached } +import v2.compiler.emit_host { emit_host_octets_byte_string, run_emit_host_cached, run_emit_host_cached_with_run_args } +import v2.compiler.emit_module { emit_family } import v2.compiler.eval { inputs_root_only } import v2.compiler.infer { InferredTree } import v2.extdeps.languages.rust { rust_target_model_staging } -import v2.extdeps.languages.rust_test { rust_classical_not_ingested_target_model_staging } +import v2.extdeps.languages.rust_test { + rust_classical_not_ingested_target_model_staging, + rust_family_member_run_args, + rust_logic_family_target_model_staging, +} import extdeps.realization.emit_on_demand_host { native_cache_workspace_root } import v2.test.long.emit_host_classical_not_ingested_equals_eval { ingested_classical_not_source, @@ -24,6 +29,12 @@ import v2.test.execution.emit_on_demand_kernel_witness { kernel_cache_root_for, kernel_native_key_for, } +import v2.test.execution.emit_on_demand_family_crate_witness { + family_crate_cache_root_for, + family_crate_fixture_inputs, + family_crate_members, + family_crate_native_key, +} import v2.std.diagnostic { Accepted, Rejected, Outcome, None } import v2.std.host_run { HostLogicalRun, HostRunStdout } import v2.std.logic { Bool } @@ -162,3 +173,53 @@ test fn emit_host_native_only_add_wrong_octet_mismatch_detected_holds() -> Bool add_native_only_matches_expected(tree: emit_eval_subject_tree(), expected_octet: 6) == false } +// COMPLEMENT-FAMILY ARM (2026-09-07): the same native-only verdict shape for the emit_host_complement +// family — the third construct promoted off InterpreterRetained, after classical_not and add. The +// family difference is the build grain, not the verdict shape: complement's native execution is +// FAMILY-grain by the operator-directed witness_family_build_grain_ruling (per-witness crates +// measured ~17s each and lose to the interpreter; one family crate wins), so this arm emits the +// logic family crate (meet + join + complement under the argv-dispatched family main) and runs the +// complement member through the family dispatcher. It shares the family witness's one-build cache +// key (family_crate_cache_root_for + family_crate_native_key over the same member list +// family_crate_one_build_members_warm_holds colds), exactly as the add and classical_not arms share +// their families' keys — an uncached leg here would duplicate the family cargo build (DESIGN S2). +// The expected octet is 0, the byte the family witness's warm complement leg pins on this same +// build (complement(True) = False). Program-side discrimination stays where the family already +// carries it: the equals_eval pair and the family witness's all-alt leg prove the native run's +// actual byte participates in a verdict (the all-alt family flips complement 0 -> 1). A no-eval +// verdict has no oracle leg to break, so this arm's broken control breaks the EXPECTATION side: +// octet 1 can never match this run, and the comparator mocks above pin the comparator itself. +fn complement_native_only_matches_expected(expected_octet: Int) -> Bool { + let members = family_crate_members() + match emit_family(members: members, transport_target: rust_logic_family_target_model_staging()) { + Accepted { value: source, diagnostics: _ } => + match run_emit_host_cached_with_run_args( + workspace_dir: native_cache_workspace_root( + cache_root: family_crate_cache_root_for(test_id: "one_build_members_warm"), + key: family_crate_native_key(members: members) + ), + target: rust_logic_family_target_model_staging(), + source: source, + fixture_inputs: family_crate_fixture_inputs(), + run_args: rust_family_member_run_args(member_id: "complement") + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + native_only_stdout_matches(stdout: stdout, expected_octet: expected_octet) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_complement_holds() -> Bool { + complement_native_only_matches_expected(expected_octet: 0) +} + +test fn emit_host_native_only_complement_wrong_octet_mismatch_detected_holds() -> Bool { + complement_native_only_matches_expected(expected_octet: 1) == false +} + diff --git a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag index 5767cd6d96b..a04abffc815 100644 --- a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag +++ b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag @@ -43,17 +43,18 @@ test fn witness_unlisted_subject_has_no_row() -> Bool { } test fn witness_split_matches_expected() -> Bool { - (length(xs: self_emitted_native_rows()) == 2) - && (length(xs: interpreter_retained_rows()) == 13) + (length(xs: self_emitted_native_rows()) == 3) + && (length(xs: interpreter_retained_rows()) == 12) } // Identity-grain census of the native rows (DESIGN section 5: completeness is an identity join, // not a count equality — the count leg above is the budget cross-check, this leg names the // members). 2026-09-07: the add family joined classical_not when its native-only verdict arm -// landed; the two decl names are the closed membership. -test fn witness_native_rows_are_exactly_classical_not_and_add() -> Bool { +// landed, and the complement family followed on the same arm shape run through the logic family +// crate; the three decl names are the closed membership. +test fn witness_native_rows_are_exactly_classical_not_add_and_complement() -> Bool { let natives = self_emitted_native_rows() - (length(xs: natives) == 2) + (length(xs: natives) == 3) && fold( natives, init: true, @@ -61,6 +62,7 @@ test fn witness_native_rows_are_exactly_classical_not_and_add() -> Bool { acc && ( (row.subject.decl_name == "ingested_classical_not_source") || (row.subject.decl_name == "rust_add_inferred_tree") + || (row.subject.decl_name == "emit_complement_eval_subject") ) } ) From 8a95df08c649067170f6393ccb7222f31d4e6e90 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 11:19:27 +0000 Subject: [PATCH 14/89] Key the emitter's host-String arm on declaration provenance, not spelling is_host_text_carrier_type answered true for any type expression whose authored name reads "String", including references to the structural alias v2.std.text.String (type String = FreeMonoid) that the namespace lane (gunbc#9907) requalified the v2 corpus's text-carrier fields to. The emitter rendered every one of those references as the host String while value-position consumers rendered the structure -- the E0308 family dominating the self-host compile-phase frontier (41 of 64 in v2_compiler_tokenize.rs on the post-merge board). The String arm now consults the resolved declaration's provenance against v1.compiler.coercion structural_declaration_modules_for -- the same roster type_realization_decision reads -- so the legacy arm and the strict decision cannot diverge on one node (DESIGN section 3, and gunbc.recurring_failure_mode alias_resolution_collides_with_kernel_spelling). Kernel mints and unresolved references keep the host answer exactly as before. Regen: the only drifted stage0 mirror is v1_compiler_emit_rust.rs itself (no module in the stage0 closure references a structurally declared String -- verified by the whole-population candidate tree), installed from target/stage0-regen-candidate after the priced round's partitioned rebuild refused MirrorHasNoOwningPackage on the emitter (the emitter is monolith-shell, not partition-owned). Fixed point verified by execution: claim_executor --required-regen on the rebuilt seed reports first_generation_equal=true over 158 adjudicated mirrors. --- src/v1/05_emit_rust.dag | 17 +++++++++++++++-- src/v1/stage0/src/v1_compiler_emit_rust.rs | 11 +++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 1c9be27c162..1bde3a9fe70 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -131,7 +131,7 @@ import v1.compiler.coercion { coerce_primitive_type, is_copy, target_callable, rust_lookup_exact_binding, declaration_realizes_natively_on_rust, type_reference_realization, declaration_realization, type_realization_decision, realized_checkpoint, realization_host_numeric_spelling, - realization_is_host_numeric + realization_is_host_numeric, provenance_declares_structurally } import std.coercion { TypeDeclarationProvenance, CorpusDeclared, KernelMinted, DeclarationIdentityAbsent, @@ -820,11 +820,24 @@ fn rust_host_text_carrier_elem_name(n: Node, source_indices: Map String` // answered true here (its name reads as its return leaf) ahead of every Arrow arm, so the type // argument rendered `String` (E0277 x2 at v2.std.compilers.target_model). +// +// The String arm is keyed on the resolved declaration's PROVENANCE, never on the spelling alone +// (DESIGN section 3, and gunbc.recurring_failure_mode +// alias_resolution_collides_with_kernel_spelling): v2.std.text declares `type String = +// FreeMonoid`, a structural alias whose spelling collides with the kernel scalar, and the +// namespace lane (gunbc#9907) requalified the v2 corpus's text-carrier fields to +// `v2.std.text.String` on that basis. A spelling-keyed arm renders every one of those references +// as the host String while every value-position consumer renders the structure -- the E0308 +// family dominating the self-host compile-phase frontier (receipt_1_eced9d24's successor board: +// 64 E0308, 41 of them in v2_compiler_tokenize.rs). The structural roster the answer is read from +// is v1.compiler.coercion structural_declaration_modules_for, the same authority +// type_realization_decision consults, so this arm and the strict decision cannot diverge on one +// node. Kernel mints and unresolved references keep the host answer exactly as before. fn is_host_text_carrier_type(n: Node, source_indices: Map) -> Bool { if n.connective == Arrow { return false } let nm = authored_name_at(source_indices: source_indices, node: n) if nm == "String" { - true + !provenance_declares_structurally(dag_name: "String", p: type_reference_provenance(n: n)) } else if nm == "FreeMonoid" || nm == "List" { rust_host_text_carrier_elem_name(n: n, source_indices: source_indices) == "Char" } else { diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 3c41bddb3b9..304804acf93 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -116,9 +116,9 @@ pub use crate::v1_compiler_closure_stub_v2_std_integer_rust::closure_stub_v2_std pub use crate::v1_compiler_closure_stub_v2_std_text_rust::closure_stub_v2_std_text_source; pub use crate::v1_compiler_coercion::{ coerce_primitive_type, declaration_realization, declaration_realizes_natively_on_rust, is_copy, - realization_host_numeric_spelling, realization_is_host_numeric, realized_checkpoint, - rust_lookup_exact_binding, target_callable, type_realization_decision, - type_reference_realization, + provenance_declares_structurally, realization_host_numeric_spelling, + realization_is_host_numeric, realized_checkpoint, rust_lookup_exact_binding, target_callable, + type_realization_decision, type_reference_realization, }; pub use crate::v1_compiler_compiler_tests_rust::compiler_tests_source; pub use crate::v1_compiler_dag_collect_support::connective_name; @@ -1290,7 +1290,10 @@ pub fn is_host_text_carrier_type( } let nm = crate::v1_std_core::authored_name_at(source_indices.clone(), n.clone()); if (nm.clone() == "String".to_string()) { - true + !crate::v1_compiler_coercion::provenance_declares_structurally( + "String".to_string(), + crate::v1_std_core::type_reference_provenance(n.clone()), + ) } else { if ((nm.clone() == "FreeMonoid".to_string()) || (nm.clone() == "List".to_string())) { (rust_host_text_carrier_elem_name(n.clone(), source_indices.clone()) From 60cbd7b69715778cf3ea1bd2fa44fad4339e97ba Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 15:58:27 +0000 Subject: [PATCH 15/89] Peel qualified String alias leaves in field position: XL-N closure 72 -> 28 errors A field authored v2.std.text.String reached the Rust emitter as an overlay-less resolved reference leaf and rendered the bare terminal name, which binds the prelude String cross-module (#9813: kernel names are never overridden by imports, so the use-line is dropped) while every value position renders the structural carrier Rc> -- the v2_compiler_tokenize.rs E0308 family, 41 of 72 errors on the XL-N phase board. The new rust_overlayless_alias_leaf_requires_peel arm in render_rust_type_without_applied_binding detects the population (overlay-less zero-parameter alias leaf, qualified spelling, String terminal segment, closed_alias_peel_verdict agrees) and renders the alias declaration's resolved right-hand side, projecting the same realization the fn-signature positions already produce. The qualified gate is load-bearing: inside the declaring module the bare name is the correct render (the emitted module carries the alias declaration), and the local binding's resolved_type drops the RHS type argument, so an ungated peel rendered Rc there (E0107 x13, E0282 x2 on the probe). Bare String keeps denoting the kernel scalar through the host-carrier arm. Measured: probe specimen (qualified/bare/direct-FreeMonoid/container/variant/ local-alias positions) compiles clean; XL-N compiler closure cargo check 72 -> 28 errors with the residual census dominated by the declared text_boundary_identity_wall class (kernel String vs structural carrier at bare-authored boundaries, 17 of 20 E0308s); v1-corpus fixed point holds (first_generation_equal=true, 158/158 adjudicated). --- src/v1/05_emit_rust.dag | 79 +++++++++++++++++++++- src/v1/stage0/src/v1_compiler_emit_rust.rs | 60 ++++++++++++++-- 2 files changed, 133 insertions(+), 6 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 1bde3a9fe70..4707e5706a4 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -357,6 +357,45 @@ fn render_rust_arrow_type_in_scope(n: Node, generic_param_names: List, s // otherwise renders `predecessor: Nat` as `Rc` at a variant record field while the // constructor passes i64 -- a layer below the field-carrier decision // rust_field_carrier_final_type projects (E0308 x2, E0369 x1). +// PEEL, DO NOT NAME, at an overlay-less QUALIFIED String-spelled alias leaf. A field authored +// `v2.std.text.String` reaches this renderer as the RESOLVED reference leaf -- no children, no +// __applied_type_args overlay (nothing in the current tree writes that channel; the readers are +// legacy), and a provenance that answers the reference's own file (type_reference_provenance's +// documented unresolved-reference shape), so every identity-keyed arm ahead of this one answers +// about the wrong declaration. Rendering the bare name can only bind where the emitted module +// declares or imports that name, and for the String spelling neither holds: kernel names are never +// overridden by imports (#9813, v1.compiler.infer overlay_skips_kernel_name), so the use-line that +// would bind the alias is dropped by import_name_resolves_to_host_realized_kernel_scalar and the +// bare token binds the prelude String -- while every value position renders the same carrier +// Rc>. That is the v2_compiler_tokenize.rs E0308 family on the XL-N board (41 of 72), +// and post-#9907 the whole cross-module population is authored QUALIFIED, which is what the +// contains(".") conjunct keys on. The fn-signature positions peel this population by consulting +// the env-resolved alias binding; this arm asks the same question of the same authority +// (closed_alias_peel_verdict) and renders the alias declaration's resolved right-hand side, so +// both positions project one realization. +// THE QUALIFIED GATE IS LOAD-BEARING, NOT A NARROWING TASTE. Inside the declaring module itself +// (src/v2/std/text.dag authors `text: String` bare) the bare terminal name is the CORRECT render: +// the emitted module carries `pub type String = Rc>;` and the bare token binds it. +// Peeling there is not merely redundant -- the local declaration's env binding resolves to a node +// whose resolved_type drops the RHS type argument, so an ungated peel rendered `Rc` +// there (E0107 x13, E0282 x2 in emitted v2_std_text.rs, measured on the probe7 emission). A +// module-local `type String = ...` referenced bare likewise keeps its bare name and binds its own +// declaration, and the kernel scalar never reaches the peel either: a bare `String` outside the +// declaring module denotes the kernel post-#9813, which the host-carrier arm ahead of this one +// answers. SCOPE: keyed to +// the String spelling because String is the kernel-named STRUCTURAL text carrier with a measured +// error population; std.integer Int and std.float Float realize NATIVELY via +// numeric_realization_declaring_modules, so peeling them would render the algebra structure +// against i64 values -- their field-position rendering is a separate class with its own evidence. +fn rust_overlayless_alias_leaf_requires_peel(env: TypeEnv, n: Node) -> Bool { + let name = authored_name_at(source_indices: env.source_indices, node: n) + n.connective == NoConnective + && (n.children |> count) == 0 + && contains(name, ".") + && qualified_last_segment(name: name) == "String" + && closed_alias_peels_zero_param(env: env, n: n) +} + fn render_rust_type_without_applied_binding(n: Node, shared_types: Set, source_indices: Map, emit_info: EmitGraphInfo) -> String { if is_host_text_carrier_type(n: n, source_indices: source_indices) { return rust_carrier_optional_wrap(n: n, rendered: "String") } if is_host_optional_carrier_type(n: n, source_indices: source_indices) { @@ -424,6 +463,11 @@ fn render_rust_type_without_applied_binding(n: Node, shared_types: Set, "_" } else if is_host_text_carrier_type(n: n, source_indices: source_indices) { rust_carrier_optional_wrap(n: n, rendered: render_rust_text_carrier(shared_types: shared_types)) + } else if rust_overlayless_alias_leaf_requires_peel(env: emit_info.fn_type_env, n: n) { + match lookup_type_for(env: emit_info.fn_type_env, node: n) { + Present { value: binding } => render_rust_type(n: resolved_type(n: binding), shared_types: shared_types, source_indices: source_indices, emit_info: emit_info) + Absent => render_node_type(n: n, target: Rust, shared_types: shared_types, source_indices: source_indices) + } } else { match realization_host_numeric_spelling(decision: type_reference_realization_in_env(n: n, dag_name: qualified_last_segment(name: tn), env: emit_info.fn_type_env, source_indices: source_indices)) { Present { value: host } => rust_carrier_optional_wrap(n: n, rendered: host) @@ -833,13 +877,35 @@ fn rust_host_text_carrier_elem_name(n: Node, source_indices: Map` +// application whose spelling no longer mentions String at all. Keying that arm on spelling + +// Char element alone re-attributes the corpus-declared std.algebra FreeMonoid to the host text +// carrier, which is the same alias_resolution_collides_with_kernel_spelling class wearing the +// peeled spelling: the type position rendered host `String` while the value position rendered +// `Rc>` (the 41 v2_compiler_tokenize.rs E0308s on the successor board). The arm +// therefore fires only where the resolved declaration is NOT corpus-declared -- a kernel mint or +// an unresolved reference, the population the arm was authored for -- and a corpus-declared +// FreeMonoid/List over Char renders the structure, agreeing with type_realization_decision on the +// same node (measured by gunbc.instruments.carrier_realization_census as DivergesWithExactIdentity +// before this gate, Agrees after). fn is_host_text_carrier_type(n: Node, source_indices: Map) -> Bool { if n.connective == Arrow { return false } let nm = authored_name_at(source_indices: source_indices, node: n) if nm == "String" { !provenance_declares_structurally(dag_name: "String", p: type_reference_provenance(n: n)) } else if nm == "FreeMonoid" || nm == "List" { - rust_host_text_carrier_elem_name(n: n, source_indices: source_indices) == "Char" + if rust_host_text_carrier_elem_name(n: n, source_indices: source_indices) != "Char" { + false + } else { + match type_reference_provenance(n: n) { + CorpusDeclared { decl_file: _ } => false + KernelMinted { minted_name: _ } => true + DeclarationIdentityAbsent => true + } + } } else { false } @@ -1098,9 +1164,18 @@ fn closed_alias_verdict_do_not_peel() -> ClosedAliasPeelVerdict { ClosedAliasHasParams } +// The String spelling guard is provenance-gated for the same reason is_host_text_carrier_type's +// String arm is: a STRUCTURAL String alias (v2.std.text, std.string_type) is a zero-parameter +// closed alias and must be eligible for ClosedAliasPeelZeroParam, or a reference carrying the +// resolved definition's type arguments renders `String` -- name plus resolved children -- +// against a Rust alias declared with no parameters (the E0107 family the host-carrier provenance +// gate surfaces in v2_std_text.rs once the String arm stops short-circuiting those references to +// the host spelling). The kernel scalar keeps the historical do-not-peel answer exactly: its +// provenance is never structurally declared, so the guard still fires for it on every path that +// reaches this verdict without an upstream host-carrier short-circuit. fn closed_alias_peel_verdict(env: TypeEnv, n: Node) -> ClosedAliasPeelVerdict { let name = authored_name_at(source_indices: env.source_indices, node: n) - if name == "String" { + if name == "String" && !provenance_declares_structurally(dag_name: "String", p: type_reference_provenance(n: n)) { return closed_alias_verdict_do_not_peel() } let binding = match lookup_type_for(env: env, node: n) { diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 304804acf93..494aca11fa9 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -506,6 +506,17 @@ pub fn render_rust_arrow_type_in_scope( } } +pub fn rust_overlayless_alias_leaf_requires_peel(env: Rc, n: Rc) -> bool { + { + let name = crate::v1_std_core::authored_name_at(env.source_indices.clone(), n.clone()); + (((((n.connective.clone() == Connective::NoConnective) + && ((n.children.clone().len() as i64) == 0)) + && v1_rt::contains(name.clone(), ".".to_string())) + && (crate::v1_std_core::qualified_last_segment(name.clone()) == "String".to_string())) + && closed_alias_peels_zero_param(env.clone(), n.clone())) + } +} + pub fn render_rust_type_without_applied_binding( n: Rc, shared_types: Rc>, @@ -742,10 +753,37 @@ pub fn render_rust_type_without_applied_binding( render_rust_text_carrier(shared_types.clone()), ) } else { - match crate::v1_compiler_coercion::realization_host_numeric_spelling(type_reference_realization_in_env(n.clone(), crate::v1_std_core::qualified_last_segment(tn.clone()), emit_info.fn_type_env.clone(), source_indices.clone())) { + if rust_overlayless_alias_leaf_requires_peel( + emit_info.fn_type_env.clone(), + n.clone(), + ) { + match crate::v1_compiler_infer_env::lookup_type_for( + emit_info.fn_type_env.clone(), + n.clone(), + ) { + Some(binding) => render_rust_type( + crate::v1_compiler_infer_types::resolved_type( + binding.clone(), + ), + shared_types.clone(), + source_indices.clone(), + emit_info.clone(), + ), + std::option::Option::None => { + crate::v1_compiler_emit::render_node_type( + n.clone(), + RenderTarget::Rust, + shared_types.clone(), + source_indices.clone(), + ) + } + } + } else { + match crate::v1_compiler_coercion::realization_host_numeric_spelling(type_reference_realization_in_env(n.clone(), crate::v1_std_core::qualified_last_segment(tn.clone()), emit_info.fn_type_env.clone(), source_indices.clone())) { Some(host) => rust_carrier_optional_wrap(n.clone(), host.clone()), std::option::Option::None => crate::v1_compiler_emit::render_node_type(n.clone(), RenderTarget::Rust, shared_types.clone(), source_indices.clone()), } + } } } } @@ -1296,8 +1334,17 @@ pub fn is_host_text_carrier_type( ) } else { if ((nm.clone() == "FreeMonoid".to_string()) || (nm.clone() == "List".to_string())) { - (rust_host_text_carrier_elem_name(n.clone(), source_indices.clone()) - == "Char".to_string()) + if (rust_host_text_carrier_elem_name(n.clone(), source_indices.clone()) + != "Char".to_string()) + { + false + } else { + match (*crate::v1_std_core::type_reference_provenance(n.clone())).clone() { + TypeDeclarationProvenance::CorpusDeclared { decl_file: _, .. } => false, + TypeDeclarationProvenance::KernelMinted { minted_name: _, .. } => true, + TypeDeclarationProvenance::DeclarationIdentityAbsent => true, + } + } } else { false } @@ -1730,7 +1777,12 @@ pub fn closed_alias_verdict_do_not_peel() -> ClosedAliasPeelVerdict { pub fn closed_alias_peel_verdict(env: Rc, n: Rc) -> ClosedAliasPeelVerdict { { let name = crate::v1_std_core::authored_name_at(env.source_indices.clone(), n.clone()); - if (name.clone() == "String".to_string()) { + if ((name.clone() == "String".to_string()) + && !crate::v1_compiler_coercion::provenance_declares_structurally( + "String".to_string(), + crate::v1_std_core::type_reference_provenance(n.clone()), + )) + { return closed_alias_verdict_do_not_peel(); } let binding = match crate::v1_compiler_infer_env::lookup_type_for(env.clone(), n.clone()) { From 66765317ec3c33c5f948853abfe04bff57f535b5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 17:09:00 +0000 Subject: [PATCH 16/89] Resolve the 12 non-hop XL-N closure errors at source: text-wall conversions + witness_violates helper Four clusters, all measured non-hop additions between receipt_1 (155) and the post-peel census (28); the live gate now measures 15 with zero unadmitted regressions: - integer.dag: integer_string_to_decimal_digits_step takes v2.std.text.String; the public boundary converts with chars() (text_boundary_identity_wall specimen discharged at this site). - 01_tokenize.dag: Token/UnboundSourceAnnotation lexemes convert structural -> host String with chars_to_string() at construction, mirroring the v1 tokenizer's host-lexeme carrier. - target_model.dag + bash.dag: EmitSpellingEscape.from/to and apply_emit_spelling_escapes go structural (v2.std.text.String); the EmitSpellingQuote arm converts host->structural->host at its boundary; bash's escape rows wrap their kernel String literals with chars(). - witness.dag + 3 call sites (collection list_nth, provenance span_index_resolve_textual_locus_from_ids, compile outcome_with_diagnostics): new witness_violates helper puts Violates constructions in a Witness-headed position so the emitter resolves the carrier type argument; dissolves once inference records per-call substitutions. Verified: 48 targeted claim witnesses green (tokenize behavioral, shell conformance, string brace escape, string length, map-lookup violates, source text ingress, bash materialize x12, int literal smoke x6, provenance span index x2). --- src/v2/compiler/00_compile.dag | 4 ++-- src/v2/compiler/01_tokenize.dag | 4 ++-- src/v2/extdeps/languages/bash.dag | 2 +- src/v2/std/collection.dag | 4 ++-- src/v2/std/compilers/target_model.dag | 14 ++++++++------ src/v2/std/integer.dag | 4 ++-- src/v2/std/provenance.dag | 4 ++-- src/v2/std/witness.dag | 12 ++++++++++++ 8 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/v2/compiler/00_compile.dag b/src/v2/compiler/00_compile.dag index 40b3478d3f7..d0bbd203a8b 100644 --- a/src/v2/compiler/00_compile.dag +++ b/src/v2/compiler/00_compile.dag @@ -57,7 +57,7 @@ import v2.std.diagnostic { } import v2.std.node { Edge, Node, NodeFold, Symbol, fold_node } import v2.std.text { String } -import v2.std.witness { Holds, Violates, Witness } +import v2.std.witness { Holds, Violates, Witness, witness_violates } // THIS MODULE IS THE COMPILER PIPELINE ENTRY. It carries the fact rather than being recognised // by name: the emitter selected its entry-point arm by matching "v1.compiler.compile", a @@ -218,7 +218,7 @@ fn apply_compile_lens_introspect( outcome_with_diagnostics(value: w, diagnostics: od) Rejected { diagnostics: d } => outcome_with_diagnostics( - value: Violates { diagnostic: d.head }, + value: witness_violates(diagnostic: d.head), diagnostics: Some { diagnostics: d } ) } diff --git a/src/v2/compiler/01_tokenize.dag b/src/v2/compiler/01_tokenize.dag index bfd51d9f45f..84bab6dee0a 100644 --- a/src/v2/compiler/01_tokenize.dag +++ b/src/v2/compiler/01_tokenize.dag @@ -522,7 +522,7 @@ fn lex_walk_step(acc: LexWalkAcc) -> LexWalkAcc { tokens: Cons { head: Token { class: class, - lexeme: lexeme, + lexeme: chars_to_string(chars: lexeme, start: 0, end: length(xs: lexeme)), file: acc.file, start: acc.pos, end: acc.pos + length(xs: lexeme) @@ -559,7 +559,7 @@ fn lex_walk_step(acc: LexWalkAcc) -> LexWalkAcc { annotations: Cons { head: UnboundSourceAnnotation { annotation_class: class, - lexeme: lexeme, + lexeme: chars_to_string(chars: lexeme, start: 0, end: length(xs: lexeme)), placement: placement_from_line_prefix(indent_only: acc.line_prefix_indent_only), file: acc.file, start: acc.pos, diff --git a/src/v2/extdeps/languages/bash.dag b/src/v2/extdeps/languages/bash.dag index cf74e676225..6df3f4c249b 100644 --- a/src/v2/extdeps/languages/bash.dag +++ b/src/v2/extdeps/languages/bash.dag @@ -723,7 +723,7 @@ fn bash_posix_single_quote_transform(prefix: String) -> TokenClassEmitTransform open: posix_single_quote_delimiter, close: posix_single_quote_delimiter, escapes: [ - EmitSpellingEscape { from: posix_single_quote_delimiter, to: posix_single_quote_escape_encoding } + EmitSpellingEscape { from: chars(s: posix_single_quote_delimiter), to: chars(s: posix_single_quote_escape_encoding) } ] } } diff --git a/src/v2/std/collection.dag b/src/v2/std/collection.dag index 8fb9318dd84..fecf3113f6c 100644 --- a/src/v2/std/collection.dag +++ b/src/v2/std/collection.dag @@ -22,7 +22,7 @@ import v2.std.optional { optional_present, optional_prefer_first_present } -import v2.std.witness { Holds, Violates, Witness, witness_from_optional } +import v2.std.witness { Holds, Violates, Witness, witness_from_optional, witness_violates } fn optional_absent_unwrap_diagnostic() -> Diagnostic { Diagnostic { @@ -87,7 +87,7 @@ fn list_nth(xs: List, wanted: Int, absent: Diagnostic) -> Witness { xs: xs, empty: IndexedLookup { index: 0, - value: Violates { diagnostic: absent } + value: witness_violates(diagnostic: absent) }, cons: fn(acc, item) { IndexedLookup { diff --git a/src/v2/std/compilers/target_model.dag b/src/v2/std/compilers/target_model.dag index b9ab5e05f86..7dc6dc9ddcc 100644 --- a/src/v2/std/compilers/target_model.dag +++ b/src/v2/std/compilers/target_model.dag @@ -12,7 +12,7 @@ import std.algebra { FreeMonoid, FreeSemigroup } -import v2.std.text { string_replace } +import v2.std.text { String, string_replace } import v2.std.algebra { Equal, Greater, @@ -579,8 +579,8 @@ fn produced_decl_render_from_rows( // decomposes into this four-arm spelling algebra. Escapes apply in list order (a backslash rule // must precede the rules that introduce backslashes). type EmitSpellingEscape { - from: String - to: String + from: v2.std.text.String + to: v2.std.text.String } type TokenClassEmitTransform @@ -991,7 +991,7 @@ fn symbol_interned_spelling(binding: Symbol) -> Outcome { // shape declares `from` itself as FreeSemigroup so the empty row is unwritable; the climb // trigger is char-level literal sugar in the .dag realization, without which producers cannot // author the nonempty carrier from an escape row's quoted spelling. -fn apply_emit_spelling_escapes(spelling: String, escapes: List) -> String { +fn apply_emit_spelling_escapes(spelling: v2.std.text.String, escapes: List) -> v2.std.text.String { fold_list( xs: escapes, empty: spelling, @@ -1013,8 +1013,10 @@ fn apply_emit_spelling_transform( EmitSpellingIdentity => spelling EmitSpellingConstant { value: v } => v EmitSpellingWrap { prefix: p, suffix: s } => concat(p, concat(spelling, s)) - EmitSpellingQuote { prefix: p, open: o, close: c, escapes: es } => - concat(p, concat(o, concat(apply_emit_spelling_escapes(spelling: spelling, escapes: es), c))) + EmitSpellingQuote { prefix: p, open: o, close: c, escapes: es } => { + let escaped = apply_emit_spelling_escapes(spelling: chars(s: spelling), escapes: es) + concat(p, concat(o, concat(chars_to_string(chars: escaped, start: 0, end: length(xs: escaped)), c))) + } } } diff --git a/src/v2/std/integer.dag b/src/v2/std/integer.dag index a1ff477aecc..5329cb25a31 100644 --- a/src/v2/std/integer.dag +++ b/src/v2/std/integer.dag @@ -1040,7 +1040,7 @@ fn integer_decimal_digits_snoc( } fn integer_string_to_decimal_digits_step( - s: String, + s: v2.std.text.String, acc: FreeMonoid ) -> Optional> { match string_head(s: s) { @@ -1062,7 +1062,7 @@ fn integer_string_to_decimal_digits_step( } fn integer_string_to_decimal_digits_optional(s: String) -> Optional> { - integer_string_to_decimal_digits_step(s: s, acc: Empty) + integer_string_to_decimal_digits_step(s: chars(s: s), acc: Empty) } fn integer_decimal_nonzero_magnitude_to_int(m: DecimalNonZeroMagnitude) -> Int { diff --git a/src/v2/std/provenance.dag b/src/v2/std/provenance.dag index 82b8ea8af2d..784ead56b55 100644 --- a/src/v2/std/provenance.dag +++ b/src/v2/std/provenance.dag @@ -15,7 +15,7 @@ import v2.std.diagnostic { import v2.std.node { Node, Symbol } import v2.std.optional { Absent, Present } import std.occurrence_identity { OccurrenceId } -import v2.std.witness { Holds, Violates, Witness, witness_from_optional } +import v2.std.witness { Holds, Violates, Witness, witness_from_optional, witness_violates } type OriginEvent = FromSource { locus: Locus } @@ -85,7 +85,7 @@ fn span_index_resolve_textual_locus_from_ids( ) -> Witness { fold_list( xs: ids, - empty: Violates { diagnostic: span_index_origin_chain_exhausted_diagnostic() }, + empty: witness_violates(diagnostic: span_index_origin_chain_exhausted_diagnostic()), cons: fn(acc, id) { match acc { Holds { value: _ } => acc diff --git a/src/v2/std/witness.dag b/src/v2/std/witness.dag index 1ea0a6a91cc..1a22af5e31c 100644 --- a/src/v2/std/witness.dag +++ b/src/v2/std/witness.dag @@ -15,6 +15,18 @@ fn witness_from_optional(opt: Optional, absent: Diagnostic) -> Witness } } +// A bare `Violates { .. }` construction nested inside a struct literal, fold seed, or call +// argument gives the v1 emitter no Witness-headed type to read its carrier from (the enclosing +// expression's type is what reaches the constructor, never the argument position's), so those +// sites emitted a located refusal. Constructing through this fn puts the variant in a +// Witness-headed position -- the same shape witness_from_optional already emits -- and lets the +// carrier unify at the call instead. Dissolves into bare constructions once inference records +// per-call type-argument substitutions (the __applied_type_args channel named as the next-rung +// trigger in src/v1/05_emit_rust.dag's rust_witness_carrier_from_witness_headed_type_node note). +fn witness_violates(diagnostic: Diagnostic) -> Witness { + Violates { diagnostic: diagnostic } +} + type StructuralPropertyWitness { property: Symbol evidence: Node From eca49a821c41bcfed9d023e87807e3ce20a17d07 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 23:01:28 +0000 Subject: [PATCH 17/89] Record receipt_2 on the self-host compile-phase frontier: 15-error census at 66765317ec The census at the XL-N lane tip: 155 -> 15 net, credited to the qualified-alias peel (60cbd7b697, 72 -> 28) and the twelve-error source cluster (66765317ec, 28 -> 15). The epoch changes on the instrument's target pinning (found by review on gunbc#9857), admitted with receipt_1's board as the reclassified predecessor under the identity map. Nine added identities are hop relocations admitted by the hop index; four sit in python/typescript modules newly entered into the emitted closure, admitted as ExposedByNewEmittedModule. Validated: all 36 self_host_compile_phase_frontier_witness claims PASS, including current_persisted_compile_phase_frontier_holds. --- .../self_host_compile_phase_frontier.dag | 121 +++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/dag/gunbc/self_host_compile_phase_frontier.dag b/dag/gunbc/self_host_compile_phase_frontier.dag index 55ef8629beb..61d42875246 100644 --- a/dag/gunbc/self_host_compile_phase_frontier.dag +++ b/dag/gunbc/self_host_compile_phase_frontier.dag @@ -1936,7 +1936,126 @@ fn receipt_1_eced9d24_receipt() -> CompilePhaseFrontierReceipt { ) } +// Receipt 2 was produced by the same one-entry instrument (tools.emission_entry_instrument +// measure_entry_emission) in a local invocation over the branch tree at 66765317ec, the tip of the +// XL-N String-carrier lane. TWO things change at this receipt and they are recorded separately +// because the design requires it. +// +// THE EPOCH CHANGES, AND THE CAUSE IS THE INSTRUMENT, NOT THE TOOLCHAIN. The measured +// `cargo check` now PINS the host triple on the invocation (tools.emission_entry_instrument +// measure_cargo_half, found by review on gunbc#9857), where both prior receipts recorded +// TargetDefaultedToHost. comparison_inputs_same therefore answers false against receipt_1 even +// though cargo, rustc, the triple, the profile and the classification policy are all unchanged, +// and the series fold admits the transition only with a reclassified predecessor. The +// reclassification is the IDENTITY MAP on receipt_1's board: nothing about the pinning changes +// which diagnostics rustc emits for an identical closure on an identical triple, so receipt_1's +// raw population classified under this epoch is receipt_1's board unchanged. That claim is +// checkable: the live gate's comparability probe reads the same five fields. +// +// THE POPULATION MOVES 155 -> 15. The 140-identity net reduction is credited to two landed lanes, +// measured at each step: the emitter's qualified-alias peel in field position (60cbd7b697, 72 -> +// 28 on this closure) and the twelve-error source cluster on top of it (66765317ec, 28 -> 15): +// integer.dag's decimal-digits boundary converts at ingress, 01_tokenize.dag converts structural +// lexemes to the host carrier at Token/UnboundSourceAnnotation construction, target_model.dag's +// EmitSpellingEscape carrier went structural with host/structural conversion at the +// EmitSpellingQuote boundary (bash.dag's escape rows follow), and three bare Violates +// constructions route through v2.std.witness's witness_violates so the emitter reads the carrier +// from a Witness-headed position. Nine of the thirteen added identities are hop relocations -- +// the same (file, code, message) key at a new line:column after those edits -- admitted by the +// hop index, not by disposition. The remaining four sit in v2_extdeps_languages_python.rs and +// v2_extdeps_languages_typescript.rs, files with NO receipt_1 identities: both modules entered the +// emitted closure only after receipt_1's tree (the v2.compiler.compile import closure walks to +// 158 resolved modules at eced9d24c599 and 164 at 66765317ec, python and typescript absent at the +// former and present at the latter), which is exactly what ExposedByNewEmittedModule admits. +data receipt_2_66765317_parse_observation: RustfmtParseObservation = RustfmtParseObservation { + producer: "rustfmt --emit stdout --edition 2021", + files_observed: 171, + refused_files: [] +} + +data receipt_2_66765317_raw_error_diagnostic_identities: List = [ + "src/v2_std_compilers_lexing.rs:168:11 E0308 mismatched types", + "src/v2_std_qualified_name.rs:234:44 E0308 mismatched types", + "src/gunbc_v1_interpreter_primitive_surface.rs:254:75 E0599 no method named `concat` found for struct `std::string::String` in the current scope", + "src/v2_std_compilers_target_model.rs:618:12 E0308 mismatched types", + "src/v2_std_compilers_target_model.rs:622:12 E0308 mismatched types", + "src/v2_std_compilers_target_model.rs:707:37 E0308 mismatched types", + "src/v2_std_compilers_target_model.rs:2629:43 E0308 mismatched types", + "src/v2_extdeps_languages_dag.rs:536:11 E0308 mismatched types", + "src/v2_extdeps_languages_dag.rs:545:11 E0308 mismatched types", + "src/v2_extdeps_languages_python.rs:328:13 E0308 mismatched types", + "src/v2_extdeps_languages_python.rs:967:11 E0308 mismatched types", + "src/v2_extdeps_languages_typescript.rs:177:13 E0308 mismatched types", + "src/v2_extdeps_languages_typescript.rs:1597:11 E0308 mismatched types", + "src/v2_compiler_parse.rs:529:143 E0282 type annotations needed", + "src/v2_compiler_parse.rs:836:59 E0308 mismatched types" +] + +data receipt_2_66765317_phase_board_fold: PersistedPhaseBoardFold = phase_board_from_census_identities( + parse_observation: receipt_2_66765317_parse_observation, + raw_identities: receipt_2_66765317_raw_error_diagnostic_identities +) + +fn receipt_2_66765317_receipt() -> CompilePhaseFrontierReceipt { + let receipt_1 = receipt_1_eced9d24_receipt() + seal_compile_phase_receipt( + sequence: 2, + observed_at: "2026-09-07T17:16:45Z" as NonEmptyStr, + subject: CompilePhaseReceiptSubject { + source_revision: "66765317ec3c33c5f948853abfe04bff57f535b5" as CommitSha, + git_tree_object: floor_discovery_tree_id(object_id: GitSha1ObjectId { digest: Sha1Digest { hex: "739dc39ded5f4edd99d7849402b54c4fa3fa8465" } }), + emitted_artifact_identity: sha256_digest(hex: "484963a7eb74b5d96fe622cdfd1b89684314d06443547e20e1fe7374e93df763" as NonEmptyStr), + compiler_identity: sha256_digest(hex: "db954404133bb15dc2295821f9248901098c9529307004d0d0277bb3ddeeac32" as NonEmptyStr), + assembler_identity: sha256_digest(hex: "bb7f3c7684a9288c0401e4d7d63cefcd644d0f02e80330c021b6f53c4e1009a6" as NonEmptyStr), + cargo_identity: "cargo 1.93.0 (083ac5135 2025-12-15)" as NonEmptyStr, + rustc_identity: "rustc 1.93.0 (254b59607 2026-01-19); commit 254b59607d4417e9dffbc307138ae5c86280fe4c; host x86_64-unknown-linux-gnu; LLVM 21.1.8" as NonEmptyStr, + invocation: CompilePhaseInvocation { + target: "x86_64-unknown-linux-gnu", + target_selection: TargetPinnedOnInvocation, + profile: "check/dev", + features: [] + }, + classification_policy_digest: rustc_phase_classification_policy_digest, + comparison_epoch: "rustc-1.93.0-x86_64-pinned-check-dev-rustc-error-index-1.93.0" as NonEmptyStr + }, + invocation: "local-105b239b-7518-43d7-b526-a5e2ee0b610b" as NonEmptyStr, + position: PhaseBoardSuccessor { predecessor_invocation: "buildbuddy-44d90e35-ccba-41d1-9a90-928a914b26a2" as NonEmptyStr }, + previous_prefix_digest: Present { value: receipt_1.prefix_digest }, + parse_evidence: ParseReached { evidence: receipt_2_66765317_parse_observation }, + board: receipt_2_66765317_phase_board_fold.board, + raw_error_diagnostic_identities: receipt_2_66765317_raw_error_diagnostic_identities, + unplaced_identities: receipt_2_66765317_phase_board_fold.unplaced_identities, + newly_exposed: [ + NewlyExposedDiagnostic { + identity: "src/v2_extdeps_languages_python.rs:328:13 E0308 mismatched types", + cause: ExposedByNewEmittedModule { module: "src/v2_extdeps_languages_python.rs" } + }, + NewlyExposedDiagnostic { + identity: "src/v2_extdeps_languages_python.rs:967:11 E0308 mismatched types", + cause: ExposedByNewEmittedModule { module: "src/v2_extdeps_languages_python.rs" } + }, + NewlyExposedDiagnostic { + identity: "src/v2_extdeps_languages_typescript.rs:177:13 E0308 mismatched types", + cause: ExposedByNewEmittedModule { module: "src/v2_extdeps_languages_typescript.rs" } + }, + NewlyExposedDiagnostic { + identity: "src/v2_extdeps_languages_typescript.rs:1597:11 E0308 mismatched types", + cause: ExposedByNewEmittedModule { module: "src/v2_extdeps_languages_typescript.rs" } + } + ], + unadmitted_regressions: [], + regression_repairs: [], + reclassified_predecessor_board: Present { value: receipt_1.board }, + producer: PhaseBoardProducer { + module_path: "tools.emission_entry_instrument", + function: "measure_entry_emission", + entry: "src/v2/compiler/00_compile.dag" + } + ) +} + data self_host_compile_phase_frontier_receipts: List = [ genesis_5006ddc6_receipt(), - receipt_1_eced9d24_receipt() + receipt_1_eced9d24_receipt(), + receipt_2_66765317_receipt() ] From b21d3282cf5bf7ad706638b543e8fadb60c8b5c0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 23:01:33 +0000 Subject: [PATCH 18/89] Emitter: a substituted declaration node carries its own provenance Inference substitutes the resolved declaration into a data annotation's type-argument position, so BooleanAlgebra reaches the emitter with the arg BEING the type Bool = True | False declaration itself (Disj connective, ident_span in src/v2/std/logic.dag, no Resolved wrapper). type_reference_provenance_in_env's bare-leaf arm re-resolved that leaf in the REFERENCING module's scope, where post-#9813 a kernel-shadowed spelling answers the kernel declaration -- so the structural enum rendered as host bool against a value of BooleanAlgebra (the python.rs:328 / typescript.rs:177 E0308 pair on the XL-N compile-phase frontier). The connective is the discriminator: a reference node is a bare name (NoConnective); a node carrying Conj/Disj structure IS the declaration, and type_reference_provenance's own-span fallback already answers that shape correctly. The guard routes declaration-shaped nodes there directly, bypassing the scope lookup that #9813 makes answer the kernel. Mirror regenerated via the regen round; fixed-point verified (claim_executor --required-regen PASS). --- src/v1/05_emit_rust.dag | 19 ++++++++++ src/v1/stage0/src/v1_compiler_emit_rust.rs | 41 ++++++++++++++-------- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 4707e5706a4..0c0664fffc4 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -733,10 +733,28 @@ data rust_checkpoint_scalar_phantom_params_note: String = "Construction wall for // binding is still decidable from the env: the reference's leaf name resolves to one declaration // in the referencing module's scope. Accepted only when that declaration IS named by the leaf, // so an alias's right-hand side node can never stand in for the alias. +// A node that IS its own declaration carries its provenance on its own span, and the env +// bare-leaf lookup must not pre-empt it. Inference substitutes the RESOLVED declaration into a +// data annotation's type-argument position, so `BooleanAlgebra` reaches the +// emitter with the arg BEING the `type Bool = True | False` declaration itself -- Disj +// connective, True/False children, ident_span in src/v2/std/logic.dag, no Resolved wrapper +// (measured on the XL-N probe closure; the python.rs:328 / typescript.rs:177 E0308 pair on the +// self-host compile-phase frontier). The bare-leaf arm re-resolves that leaf in the REFERENCING +// module's scope, where post-#9813 a kernel-shadowed spelling answers the KERNEL declaration +// (lookup_type_by_name "Bool" binds the kernel Bool), so the structural enum rendered as host +// `bool` against a value of BooleanAlgebra. The connective is the discriminator: a +// reference node is a bare name (NoConnective), while a node carrying Conj/Disj structure IS +// the declaration, and type_reference_provenance's own-span fallback (v1.compiler.core +// declaration_provenance_of) already answers that shape correctly -- CorpusDeclared on the +// span's own file, which structural_declaration_modules_for then matches. fn type_reference_provenance_in_env(n: Node, env: TypeEnv, source_indices: Map) -> TypeDeclarationProvenance { match n.inferred { Present { value: Resolved { node: _ } } => type_reference_provenance(n: n) _ => + let node_is_own_declaration = n.connective == Conj || n.connective == Disj + if node_is_own_declaration { + type_reference_provenance(n: n) + } else { let leaf = rust_fn_sig_leaf_name(source_indices: source_indices, n: n) if leaf == "" { type_reference_provenance(n: n) } else { @@ -748,6 +766,7 @@ fn type_reference_provenance_in_env(n: Node, env: TypeEnv, source_indices: Map type_reference_provenance(n: n) } } + } } } diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 494aca11fa9..b984af97f5e 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -1110,24 +1110,35 @@ pub fn type_reference_provenance_in_env( crate::v1_std_core::type_reference_provenance(n.clone()) } _ => { - let leaf = rust_fn_sig_leaf_name(source_indices.clone(), n.clone()); - if (leaf.clone() == "".to_string()) { + let node_is_own_declaration = ((n.connective.clone() == Connective::Conj) + || (n.connective.clone() == Connective::Disj)); + if node_is_own_declaration.clone() { crate::v1_std_core::type_reference_provenance(n.clone()) } else { - match crate::v1_compiler_infer_env::lookup_type_by_name(env.clone(), leaf.clone()) { - Some(decl) => { - if (crate::v1_std_core::authored_name_at( - source_indices.clone(), - decl.clone(), - ) == leaf.clone()) - { - crate::v1_std_core::declaration_provenance_of(decl.clone()) - } else { - crate::v1_std_core::type_reference_provenance(n.clone()) - } - } - std::option::Option::None => { + { + let leaf = rust_fn_sig_leaf_name(source_indices.clone(), n.clone()); + if (leaf.clone() == "".to_string()) { crate::v1_std_core::type_reference_provenance(n.clone()) + } else { + match crate::v1_compiler_infer_env::lookup_type_by_name( + env.clone(), + leaf.clone(), + ) { + Some(decl) => { + if (crate::v1_std_core::authored_name_at( + source_indices.clone(), + decl.clone(), + ) == leaf.clone()) + { + crate::v1_std_core::declaration_provenance_of(decl.clone()) + } else { + crate::v1_std_core::type_reference_provenance(n.clone()) + } + } + std::option::Option::None => { + crate::v1_std_core::type_reference_provenance(n.clone()) + } + } } } } From 285b02eed2944331574330282fc49165a805b75e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 23:01:43 +0000 Subject: [PATCH 19/89] Clear the remaining XL-N closure errors at source: carrier conversions at the boundaries The receipt_2 census's fifteen identities, resolved at their sources: - lexing.dag, dag.dag, python.dag, typescript.dag: LexPattern.text is the structural carrier (v2.std.text.String); the construction sites held host Strings. Convert at construction with chars() -- the #9907 ingress pattern. - python.dag / typescript.dag bool groundings: qualify the annotation as BooleanAlgebra; with the emitter's substituted- declaration provenance guard the qualified arg now renders structural. - target_model.dag: target_lex_rule_literal_step returns the host carrier (chars_to_string over the structural pattern text); TargetText.source converts at the is_empty boundary; the unicode-scalar symbol intern converts its single-codepoint list to the host carrier. - qualified_name.dag: qualified_name_from_dotted_string uses the host-carrier emptiness check (string_length == 0) instead of routing through the structural string_is_empty. - 02_parse.dag: parse_looks_like_match_arm_start rewritten on host-carrier operations (string_length, char_at, code_point) rather than converting to the structural carrier for a two-character lookahead; parse_char_is_arm_pattern_lead takes the codepoint Int directly. - v1_interpreter_primitive_surface.dag row_key: the concat pipeline lowered to a .concat() method call on std::string::String (E0599); rewritten as nested concat calls. Measured: the 00_compile closure emits 172 files and cargo check reports cargo_clean=true, cargo_error_population=0 under the pinned 1.93.0 toolchain. --- .../v1/v1_interpreter_primitive_surface.dag | 6 +-- src/v2/compiler/02_parse.dag | 43 +++++++++---------- src/v2/extdeps/languages/dag.dag | 4 +- src/v2/extdeps/languages/python.dag | 4 +- src/v2/extdeps/languages/typescript.dag | 4 +- src/v2/std/compilers/lexing.dag | 2 +- src/v2/std/compilers/target_model.dag | 8 ++-- src/v2/std/qualified_name.dag | 4 +- 8 files changed, 34 insertions(+), 41 deletions(-) diff --git a/dag/gunbc/v1/v1_interpreter_primitive_surface.dag b/dag/gunbc/v1/v1_interpreter_primitive_surface.dag index b51c14ae269..207da25cab4 100644 --- a/dag/gunbc/v1/v1_interpreter_primitive_surface.dag +++ b/dag/gunbc/v1/v1_interpreter_primitive_surface.dag @@ -194,11 +194,7 @@ fn shadowed_spellings(rows: List) -> List String { - row.dispatch_symbol - |> concat(":") - |> concat(row.arm.identity) - |> concat(":") - |> concat(row.authored_spelling) + concat(concat(concat(concat(row.dispatch_symbol, ":"), row.arm.identity), ":"), row.authored_spelling) } fn duplicate_row_keys(rows: List) -> List { diff --git a/src/v2/compiler/02_parse.dag b/src/v2/compiler/02_parse.dag index 73bdc04cb71..8d20fcb0f27 100644 --- a/src/v2/compiler/02_parse.dag +++ b/src/v2/compiler/02_parse.dag @@ -100,7 +100,6 @@ import v2.std.optional { } import std.algebra { Cons, Empty } import v2.std.algebra { HeadAbsent, HeadFound, TailAbsent, TailFound, any, contains, filter, fold_list, fold_list_right, is_empty, length, list_append, list_head, list_reverse, list_snoc_item, list_tail, unique } -import v2.std.text { Char, CharAbsent, CharFound, string_head } import v2.std.witness { Holds, Violates, Witness, witness_from_optional } import std.cache_identity { ArtifactKindId, CacheInterfaceId } import std.realization { Materialization } @@ -592,7 +591,7 @@ fn parse_grammar_digest(validated: ValidatedGrammarRoot) -> Hash { fn parse_lexeme_digest(lexeme: Lexeme) -> Hash { fold_list( - xs: lexeme, + xs: chars(s: lexeme), empty: symbol_identity_digest(sym: ^parse_lexeme_digest_nil), cons: fn(acc, ch) { combine_hash(a: acc, b: byte_limb_hash_peano_digest(limb: ch)) @@ -939,7 +938,7 @@ fn parse_flatten_diag_batches(batches: List>) -> List Bool { +fn parse_char_is_arm_pattern_lead(c: Int) -> Bool { (c == 95) || ((c >= 65) && (c <= 90)) } @@ -998,27 +997,25 @@ fn parse_looks_like_match_arm_start(tokens: List) -> Bool { match parse_token_first(tokens: tokens) { Absent => false Present { value: tok0 } => - match string_head(s: tok0.lexeme) { - CharFound { value: c } => - if parse_char_is_arm_pattern_lead(c: c) { - match parse_token_second(tokens: tokens) { - Present { value: tok1 } => - if parse_token_lexeme_is(tok: tok1, lit: "=>") { - true - } else if parse_token_lexeme_is(tok: tok1, lit: "{") { - parse_scan_fat_arrow_after_lbrace( - tokens: parse_tokens_drop(tokens: tokens, count: 2), - depth: 1 - ) - } else { - false - } - Absent => false + if string_length(s: tok0.lexeme) == 0 { + false + } else if parse_char_is_arm_pattern_lead(c: code_point(c: char_at(s: tok0.lexeme, pos: 0))) { + match parse_token_second(tokens: tokens) { + Present { value: tok1 } => + if parse_token_lexeme_is(tok: tok1, lit: "=>") { + true + } else if parse_token_lexeme_is(tok: tok1, lit: "{") { + parse_scan_fat_arrow_after_lbrace( + tokens: parse_tokens_drop(tokens: tokens, count: 2), + depth: 1 + ) + } else { + false } - } else { - false - } - CharAbsent => false + Absent => false + } + } else { + false } } } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index d6f922fc5c7..7a6a37a4c0f 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -414,14 +414,14 @@ fn dag_surface_operator_canonicalization_lookup( fn dag_keyword_lex_rule(token_class: Symbol, text: String) -> LexRule { TokenRule { token_class: token_class, - pattern: KeywordPattern { text: text } + pattern: KeywordPattern { text: chars(s: text) } } } fn dag_literal_lex_rule(token_class: Symbol, text: String) -> LexRule { TokenRule { token_class: token_class, - pattern: LiteralPattern { text: text } + pattern: LiteralPattern { text: chars(s: text) } } } diff --git a/src/v2/extdeps/languages/python.dag b/src/v2/extdeps/languages/python.dag index 08555bcaa47..0e2d2207022 100644 --- a/src/v2/extdeps/languages/python.dag +++ b/src/v2/extdeps/languages/python.dag @@ -241,7 +241,7 @@ data python_singleton_facts_catalog: List = [ python_facts_ellipsis ] -data py_bool_grounding: BooleanAlgebra = bool_boolean_algebra +data py_bool_grounding: BooleanAlgebra = bool_boolean_algebra fn python_inhabitant_atom(id: Symbol) -> Node { Node { @@ -1215,7 +1215,7 @@ fn python_target_model() -> TargetModel { } fn python_lex_rule(token_class: Symbol, text: String) -> LexRule { - TokenRule { token_class: token_class, pattern: LiteralPattern { text: text } } + TokenRule { token_class: token_class, pattern: LiteralPattern { text: chars(s: text) } } } fn python_ident_lex_pattern() -> LexPattern { diff --git a/src/v2/extdeps/languages/typescript.dag b/src/v2/extdeps/languages/typescript.dag index 46fec44216c..aa9639a8532 100644 --- a/src/v2/extdeps/languages/typescript.dag +++ b/src/v2/extdeps/languages/typescript.dag @@ -250,7 +250,7 @@ data ts_facts_boolean: TsBooleanPrimitiveFacts = TsBooleanPrimitiveFacts { encoding: ^ts_repr_boolean_ecma_boolean, } -data ts_bool_grounding: BooleanAlgebra = bool_boolean_algebra +data ts_bool_grounding: BooleanAlgebra = bool_boolean_algebra data ts_numeric_facts_catalog: List = [ TsNumericFactsNumber { facts: ts_facts_number }, @@ -2054,7 +2054,7 @@ fn ts_fn_add_target_model() -> Node { fn ts_lex_rule(token_class: Symbol, text: String) -> LexRule { TokenRule { token_class: token_class, - pattern: LiteralPattern { text: text } + pattern: LiteralPattern { text: chars(s: text) } } } diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index 5ec4896a25d..63f01f67108 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -97,7 +97,7 @@ fn phrase_to_lex_rule_set(p: LexicalPhrase) -> LexRuleSet { xs: acc, item: TokenRule { token_class: s.token_class, - pattern: LiteralPattern { text: s.fragment } + pattern: LiteralPattern { text: chars(s: s.fragment) } } ) } diff --git a/src/v2/std/compilers/target_model.dag b/src/v2/std/compilers/target_model.dag index 7dc6dc9ddcc..6a623a411e7 100644 --- a/src/v2/std/compilers/target_model.dag +++ b/src/v2/std/compilers/target_model.dag @@ -862,9 +862,9 @@ fn target_lex_rule_literal_step( if c == token_class { match pattern { LiteralPattern { text: text } => - Accepted { value: text, diagnostics: None } + Accepted { value: chars_to_string(chars: text, start: 0, end: length(xs: text)), diagnostics: None } KeywordPattern { text: text } => - Accepted { value: text, diagnostics: None } + Accepted { value: chars_to_string(chars: text, start: 0, end: length(xs: text)), diagnostics: None } _ => Rejected { diagnostics: diagnostics_singleton( @@ -938,7 +938,7 @@ fn render_target_text(t: TargetText) -> String { } fn target_text_is_empty(t: TargetText) -> Bool { - is_empty(xs: t.source) + is_empty(xs: chars(s: t.source)) } fn bound_tokens_source_text( @@ -4103,7 +4103,7 @@ fn target_value_expression_char_unicode_scalar(codepoint: Char) -> TargetValueEx kind: TypeNode { connective: Atom { identity: symbol_intern_lexeme( - lexeme: list_snoc_item(xs: Empty, item: codepoint) + lexeme: chars_to_string(chars: list_snoc_item(xs: Empty, item: codepoint), start: 0, end: 1) ) } }, diff --git a/src/v2/std/qualified_name.dag b/src/v2/std/qualified_name.dag index 2c7c014014c..2d42e4c6837 100644 --- a/src/v2/std/qualified_name.dag +++ b/src/v2/std/qualified_name.dag @@ -28,7 +28,7 @@ import v2.std.optional { import v2.std.compilers.lexing { symbol_intern_lexeme, symbol_lexeme } import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, NodeFold, Symbol, TypeNode, fold_node } -import v2.std.text { String, string_is_empty } +import v2.std.text { String } type QualifiedName = FreeMonoid @@ -166,7 +166,7 @@ fn qualified_name_from_string_segments(segments: List) -> FreeMonoid FreeMonoid { - if string_is_empty(s: dotted) { + if string_length(s: dotted) == 0 { Empty } else { qualified_name_from_string_segments(segments: dotted.split(delimiter: ".")) From 5ee4892b709bd42fc9ba6a067cecf888ed2673f1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 23:01:48 +0000 Subject: [PATCH 20/89] Pin the cargo half's toolchain channel by construction The cargo half runs with cwd = a fresh mktemp directory; with no rust-toolchain.toml there, rustup resolves the host's DEFAULT toolchain, so a census under cargo 1.83 and one under cargo 1.93 would compare as equal epochs while different compilers did the measuring -- the fabricated comparability the target pin (gunbc#9857) excludes, one level up. Measured 2026-09-07: a host default of 1.83.0 met a crates.io index whose freshly published dependency manifests require edition2024, resolution failed before any diagnostic existed, and the zero-diagnostic refusal fired on an unmeasured tree. The pin is propagated by copying the repo's rust-toolchain.toml into out_dir: the file remains the sole in-repo channel authority (its header forbids a second pinned literal), and the copy makes the measured channel true by construction on any host. The gate's read_live_toolchain observes the same channel because every documented actuator invokes from the repository root, which the same file governs. --- .../instruments/emission_entry_instrument.dag | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/instruments/emission_entry_instrument.dag b/dag/gunbc/instruments/emission_entry_instrument.dag index d5fd2d4f5dd..50c1bb7df15 100644 --- a/dag/gunbc/instruments/emission_entry_instrument.dag +++ b/dag/gunbc/instruments/emission_entry_instrument.dag @@ -531,6 +531,19 @@ fn emission_scope_entry(sc: EmissionScope) -> EmissionScopeEntry { // recorded target TRUE BY CONSTRUCTION rather than checked afterwards, which is section 5's order // of preference; and where the triple cannot be read the half REFUSES, because an unpinned target // is exactly the state this pin exists to exclude. Found by review on gunbc#9857. +// +// THE TOOLCHAIN CHANNEL IS PINNED BY THE SAME ARGUMENT, ONE LEVEL UP. The cargo half runs with +// cwd = out_dir, a fresh mktemp directory: with no rust-toolchain.toml there, rustup resolves the +// host's DEFAULT toolchain, and a census under cargo 1.83 against one under cargo 1.93 would +// compare as equal epochs while different compilers did the measuring -- the fabricated +// comparability the target pin excludes, at the channel layer. Measured 2026-09-07 on this +// instrument: a host default of 1.83.0 met a crates.io index whose freshly published dependency +// manifests require edition2024, dependency resolution failed before any diagnostic existed, and +// the zero-diagnostic refusal fired (correctly) on an unmeasured tree. The pin is propagated by +// COPYING the repo's rust-toolchain.toml into out_dir: the file remains the sole in-repo channel +// authority (its header forbids a second pinned literal), and the copy makes the measured channel +// true by construction on any host. The gate's read_live_toolchain observes the same channel +// because every documented actuator invokes from the repository root, which the same file governs. fn measure_cargo_half( subject: EmissionMeasurementSubject, root: String, @@ -582,11 +595,18 @@ fn measure_cargo_half( path: concat(out_dir, "/Cargo.toml"), content: cssl_v1_compiled_probe_lib_cargo_toml(root: root) ) - if !manifest.success { + let channel = Filesystem.Read(path: concat(root, "/rust-toolchain.toml")) + let channel_pin = if channel.success { + Filesystem.Write(path: concat(out_dir, "/rust-toolchain.toml"), content: channel.content).success + } else { + false + } + let scaffold_incomplete = !manifest.success || !channel_pin + if scaffold_incomplete { EmissionCargoUnreached { subject: subject, stage: StageManifest, - cause: concat("could not write the probe crate manifest under ", out_dir), + cause: concat("could not write the probe crate manifest and toolchain channel pin under ", out_dir), emit_diagnostics: emit_diagnostics, emitted_files: emitted_files } From db8a1b58458307cdaab66b00be459ad005ae35bd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 23:18:41 +0000 Subject: [PATCH 21/89] Record receipt_3 on the self-host compile-phase frontier: the emitted closure's cargo census is empty Measured at 5ee4892b70 by the one-entry instrument: the 172-file emitted crate reports zero cargo error diagnostics, so the board attributes every phase a count of zero and furthest_phase_reached stands at Borrowck. The fifteen removals against receipt_2 need no disposition; nothing was added. The epoch does not change: the cargo half now pins the toolchain channel by copying the repo's rust-toolchain.toml into the scratch crate, and every recorded comparison field is identical to receipt_2 (whose census the fingerprint evidence shows the same 1.93.0 toolchain already compiled), so the same-epoch arm carries no reclassified predecessor. The frontier-state pin flips per DESIGN 4b(4): the_published_frontier_standing_does_not_claim_typeck_or_borrowck_passed becomes the_published_frontier_standing_claims_typeck_and_borrowck_passed, the permanent regression control over the green state. Validated: all 36 self_host_compile_phase_frontier_witness claims PASS, including current_persisted_compile_phase_frontier_holds. --- .../self_host_compile_phase_frontier.dag | 97 ++++++++++++++++++- ...st_compile_phase_frontier_witness_test.dag | 18 ++-- 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/dag/gunbc/self_host_compile_phase_frontier.dag b/dag/gunbc/self_host_compile_phase_frontier.dag index 61d42875246..47c4ce056be 100644 --- a/dag/gunbc/self_host_compile_phase_frontier.dag +++ b/dag/gunbc/self_host_compile_phase_frontier.dag @@ -2054,8 +2054,103 @@ fn receipt_2_66765317_receipt() -> CompilePhaseFrontierReceipt { ) } +// Receipt 3 was produced by the same one-entry instrument in a local invocation over the branch +// tree at 5ee4892b70, three commits past receipt_2's subject, and it records the XL-N lane's +// arrival: THE EMITTED CLOSURE'S CARGO CENSUS IS EMPTY. cargo check --lib on the 172-file emitted +// crate reports zero error diagnostics (coded and codeless), so the board attributes every phase +// a PhaseCount of zero and furthest_phase_reached stands at Borrowck. The fifteen removals against +// receipt_2 need no disposition -- the ratchet admits a shrinking population -- and nothing was +// added, so newly_exposed is empty and no epoch machinery is in play. +// +// THE EPOCH DOES NOT CHANGE, AND THE INSTRUMENT CHANGE UNDER THIS RECEIPT IS WHY THAT CLAIM IS +// CHECKABLE. The cargo half now copies the repo's rust-toolchain.toml into the scratch crate +// before invoking cargo (tools.emission_entry_instrument measure_cargo_half), so the measured +// channel is pinned by construction rather than by host default. Every recorded comparison field +// is nevertheless IDENTICAL to receipt_2 -- cargo 1.93.0, rustc 1.93.0, the pinned triple, the +// check/dev profile, the classification policy digest -- because receipt_2's census was already +// compiled by that toolchain (the run's target-dir rustc fingerprint records 1.93.0), and the pin +// removes the host-default resolution path that a later run fell through to. comparison_inputs_same +// therefore answers true against receipt_2, and the same-epoch arm forbids a reclassified +// predecessor, so none is carried. +// +// THE THREE LANDED REPAIRS, each measured before this receipt was taken. The emitter's +// substituted-declaration provenance guard (b21d3282cf) routes a Conj/Disj type-argument node -- +// inference substitutes the resolved declaration into a data annotation's type-argument position -- +// to its own-span provenance, so BooleanAlgebra renders the structural enum +// instead of the kernel bool that the referencing scope's #9813 shadowing answers for the bare +// leaf; that cleared the python.rs:328 / typescript.rs:177 pair. The source-side carrier batch +// (285b02eed2) converts at the construction boundaries the receipt_2 census named: lex-pattern +// text goes structural at construction in lexing.dag and the three language modules, the bool +// groundings qualify their annotation, target_model returns the host carrier out of +// target_lex_rule_literal_step and converts at its two other boundaries, qualified_name.dag and +// 02_parse.dag move their two-character lookahead and emptiness checks onto host-carrier +// operations, and row_key's concat pipeline becomes nested calls so the lowering no longer emits a +// method that does not exist. The channel pin (5ee4892b70) is the instrument repair above. +// +// ONE HONESTY NOTE ON THE ARTIFACT IDENTITY. This receipt's emitted_artifact_identity differs from +// the pre-commit validation run's over the SAME bytes-by-value closure: the known import-set +// ordering class (src/v1/05_emit_rust.dag's rostered annotation -- pure `pub use` reordering, +// normalized-identical after rustfmt) makes the raw emitted bytes run-dependent, and this +// instrument digests the raw bytes. The census the ratchet compares is order-independent and +// unaffected; the identity is recorded as this run's observation, not a reproducibility claim. +data receipt_3_5ee4892b_parse_observation: RustfmtParseObservation = RustfmtParseObservation { + producer: "rustfmt --emit stdout --edition 2021", + files_observed: 171, + refused_files: [] +} + +data receipt_3_5ee4892b_raw_error_diagnostic_identities: List = [ +] + +data receipt_3_5ee4892b_phase_board_fold: PersistedPhaseBoardFold = phase_board_from_census_identities( + parse_observation: receipt_3_5ee4892b_parse_observation, + raw_identities: receipt_3_5ee4892b_raw_error_diagnostic_identities +) + +fn receipt_3_5ee4892b_receipt() -> CompilePhaseFrontierReceipt { + let receipt_2 = receipt_2_66765317_receipt() + seal_compile_phase_receipt( + sequence: 3, + observed_at: "2026-09-07T23:07:19Z" as NonEmptyStr, + subject: CompilePhaseReceiptSubject { + source_revision: "5ee4892b709bd42fc9ba6a067cecf888ed2673f1" as CommitSha, + git_tree_object: floor_discovery_tree_id(object_id: GitSha1ObjectId { digest: Sha1Digest { hex: "07eee038259e268e1a543edddb9d3f105e2d11cf" } }), + emitted_artifact_identity: sha256_digest(hex: "83d95342004c0aa5954eaf46bf9c1cf5112473de5d38fce98d33ad2f889b6e11" as NonEmptyStr), + compiler_identity: sha256_digest(hex: "a31526e509283dc2235f470bc737f0906cd83baa2fd18fa603f0d3c573249a0b" as NonEmptyStr), + assembler_identity: sha256_digest(hex: "bb7f3c7684a9288c0401e4d7d63cefcd644d0f02e80330c021b6f53c4e1009a6" as NonEmptyStr), + cargo_identity: "cargo 1.93.0 (083ac5135 2025-12-15)" as NonEmptyStr, + rustc_identity: "rustc 1.93.0 (254b59607 2026-01-19); commit 254b59607d4417e9dffbc307138ae5c86280fe4c; host x86_64-unknown-linux-gnu; LLVM 21.1.8" as NonEmptyStr, + invocation: CompilePhaseInvocation { + target: "x86_64-unknown-linux-gnu", + target_selection: TargetPinnedOnInvocation, + profile: "check/dev", + features: [] + }, + classification_policy_digest: rustc_phase_classification_policy_digest, + comparison_epoch: "rustc-1.93.0-x86_64-pinned-check-dev-rustc-error-index-1.93.0" as NonEmptyStr + }, + invocation: "local-8b8875a1-eac3-47b1-a041-aa04fa22b2dd" as NonEmptyStr, + position: PhaseBoardSuccessor { predecessor_invocation: "local-105b239b-7518-43d7-b526-a5e2ee0b610b" as NonEmptyStr }, + previous_prefix_digest: Present { value: receipt_2.prefix_digest }, + parse_evidence: ParseReached { evidence: receipt_3_5ee4892b_parse_observation }, + board: receipt_3_5ee4892b_phase_board_fold.board, + raw_error_diagnostic_identities: receipt_3_5ee4892b_raw_error_diagnostic_identities, + unplaced_identities: receipt_3_5ee4892b_phase_board_fold.unplaced_identities, + newly_exposed: [], + unadmitted_regressions: [], + regression_repairs: [], + reclassified_predecessor_board: none, + producer: PhaseBoardProducer { + module_path: "tools.emission_entry_instrument", + function: "measure_entry_emission", + entry: "src/v2/compiler/00_compile.dag" + } + ) +} + data self_host_compile_phase_frontier_receipts: List = [ genesis_5006ddc6_receipt(), receipt_1_eced9d24_receipt(), - receipt_2_66765317_receipt() + receipt_2_66765317_receipt(), + receipt_3_5ee4892b_receipt() ] diff --git a/dag/test/claim/self_host_compile_phase_frontier_witness_test.dag b/dag/test/claim/self_host_compile_phase_frontier_witness_test.dag index 56ffed9e2d3..da4ff46b325 100644 --- a/dag/test/claim/self_host_compile_phase_frontier_witness_test.dag +++ b/dag/test/claim/self_host_compile_phase_frontier_witness_test.dag @@ -441,15 +441,21 @@ test fn the_persisted_genesis_census_is_fully_attributed_to_its_phases() -> Bool } // The end-to-end claim, asserted on the exact string the ledger projection publishes: the standing -// the document renders may not say the emitted compiler clears typeck or borrowck while the latest -// validated receipt's census says otherwise. -test fn the_published_frontier_standing_does_not_claim_typeck_or_borrowck_passed() -> Bool { +// the document renders must agree with the latest validated receipt's census. This row was born as +// the frontier-state pin `the_published_frontier_standing_does_not_claim_typeck_or_borrowck_passed`, +// asserting the red state while the census carried typeck errors. Receipt 3's census is EMPTY -- +// the emitted closure's cargo check reports zero error diagnostics, furthest phase borrowck -- so +// per DESIGN 4b(4) the guard flips to its permanent regression control: the rendered standing must +// now claim typeck and borrowck passed, and any future receipt whose census re-enters either phase +// renders "N remaining" here and reds this row. +test fn the_published_frontier_standing_claims_typeck_and_borrowck_passed() -> Bool { match current_compile_phase_frontier_standing() { CompilePhaseFrontierValidated { latest: latest } => { let rendered = render_phase_board(board: latest.board) - !string_contains(s: rendered, pattern: "typeck PASSES") - && !string_contains(s: rendered, pattern: "borrowck PASSES") - && string_contains(s: rendered, pattern: "borrowck not yet reached") + string_contains(s: rendered, pattern: "typeck PASSES") + && string_contains(s: rendered, pattern: "borrowck PASSES") + && !string_contains(s: rendered, pattern: "not yet reached") + && !string_contains(s: rendered, pattern: "remaining") } standing => false } From 771dd6fe49f0a76c0e3a59f5299053d9bfc26be8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 23:48:51 +0000 Subject: [PATCH 22/89] chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-failure-modes.md Ledger-Repair-Judged: docs/design-rung-drops.md --- docs/design-rung-drops.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index 716b5591959..d2157e286f4 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -70,15 +70,15 @@ CI — **RUNG DROP, DECLARED (2026-08-15, the floor cut).** WHAT WAS HERE: one c ### Whole-corpus compile-phase frontier receipts — latest validated row -Milestones: parse PASSES / expand PASSES / resolve PASSES / typeck 155 remaining / borrowck not yet reached +Milestones: parse PASSES / expand PASSES / resolve PASSES / typeck PASSES / borrowck PASSES -Tree: `sha1:d424154bb6fb8c40b149be07f2ddd8d61e89108e` +Tree: `sha1:07eee038259e268e1a543edddb9d3f105e2d11cf` -Invocation: `buildbuddy-44d90e35-ccba-41d1-9a90-928a914b26a2` +Invocation: `local-8b8875a1-eac3-47b1-a041-aa04fa22b2dd` -Census identity digest (self-host-compile-phase-census-canonical-identity-set-v1): `049d0a54eea70534` +Census identity digest (self-host-compile-phase-census-canonical-identity-set-v1): `4325dac985bbe897` -Error identities: 155 total = 155 coded + 0 codeless +Error identities: 0 total = 0 coded + 0 codeless Producer: `tools.emission_entry_instrument::measure_entry_emission` From e00f0ba67ba0b0eb93de0ae407b7f1c317d03955 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 02:10:17 +0000 Subject: [PATCH 23/89] Remove the stale PointwisePower inhabitant rows from the four language rosters First native-parity divergence class found by running the emitted closure on a discriminating fixture: the algebra inhabitant rosters still carried PointwisePower after its authority row was cut, so the emitted compiler panicked at 12 record-shaped carrier sites while the interpreted seed refused cleanly. The roster rows are removed in rust/python/go/typescript types.dag, the derived coercion assertions in compiler_tests.rs regenerate without them, and two witnesses pin the boundary: the record shape constructs its structural carrier, and FinitePowerSet still refuses while its row stands. Mirrors regenerated by a converged regen round (fixed point Reached, stage-1 PromoteGenerationInputs over the three language types mirrors). --- dag/extdeps/languages/go/types.dag | 12 +++---- dag/extdeps/languages/python/types.dag | 12 +++---- dag/extdeps/languages/rust/types.dag | 24 +++++++++---- dag/extdeps/languages/typescript/types.dag | 12 +++---- ...d_call_target_realization_witness_test.dag | 35 +++++++++++++++++++ src/v1/stage0/src/compiler_tests.rs | 12 ------- .../stage0/src/extdeps_languages_go_types.rs | 2 +- .../src/extdeps_languages_python_types.rs | 2 +- .../src/extdeps_languages_rust_types.rs | 2 +- 9 files changed, 70 insertions(+), 43 deletions(-) diff --git a/dag/extdeps/languages/go/types.dag b/dag/extdeps/languages/go/types.dag index 0c8575c91e9..34b5ffc5366 100644 --- a/dag/extdeps/languages/go/types.dag +++ b/dag/extdeps/languages/go/types.dag @@ -24,6 +24,11 @@ data go_type_checkpoints: List = [ { dag_name: "Json", target_type: "interface{}", grounding_type: "interface{}", default_expr: "nil", is_copy: none, literal_suffix: none } ] +// NO PointwisePower ROW — see extdeps.languages.rust.types (the note above +// rust_algebra_inhabitants) for the full reasoning. PointwisePower is the open-support +// characteristic-function carrier; a finite map[...]struct{} realizes FinitePowerSet, not it. +// The fossil row's only live effect was the record-literal refusal panic, measured on the +// emitted 00_compile closure's first native run (2026-09-07). data go_algebra_inhabitants: List = [ { algebra: "FreeMonoid", @@ -40,13 +45,6 @@ data go_algebra_inhabitants: List = [ import_path: none, is_copy: none }, - { algebra: "PointwisePower", - template: "map[\{0\}]struct{}", - arity: 1, - identity_expr: "nil", - import_path: none, - is_copy: none }, - { algebra: "PartialFunction", template: "map[\{0\}]\{1\}", arity: 2, diff --git a/dag/extdeps/languages/python/types.dag b/dag/extdeps/languages/python/types.dag index adb244c0533..5aa2b545bef 100644 --- a/dag/extdeps/languages/python/types.dag +++ b/dag/extdeps/languages/python/types.dag @@ -24,6 +24,11 @@ data python_type_checkpoints: List = [ { dag_name: "Json", target_type: "dict", grounding_type: "dict", default_expr: "{}", is_copy: none, literal_suffix: none } ] +// NO PointwisePower ROW — see extdeps.languages.rust.types (the note above +// rust_algebra_inhabitants) for the full reasoning. PointwisePower is the open-support +// characteristic-function carrier; a finite set[...] realizes FinitePowerSet, not it. The fossil +// row's only live effect was the record-literal refusal panic, measured on the emitted +// 00_compile closure's first native run (2026-09-07). data python_algebra_inhabitants: List = [ { algebra: "FreeMonoid", template: "list[\{0\}]", @@ -39,13 +44,6 @@ data python_algebra_inhabitants: List = [ import_path: none, is_copy: none }, - { algebra: "PointwisePower", - template: "set[\{0\}]", - arity: 1, - identity_expr: "set()", - import_path: none, - is_copy: none }, - { algebra: "PartialFunction", template: "dict[\{0\}, \{1\}]", arity: 2, diff --git a/dag/extdeps/languages/rust/types.dag b/dag/extdeps/languages/rust/types.dag index 88b6c90a8b7..8f8d7692381 100644 --- a/dag/extdeps/languages/rust/types.dag +++ b/dag/extdeps/languages/rust/types.dag @@ -51,6 +51,23 @@ data rust_checkpoint_scalar_arity_note: String = "A TypeCheckpoint row states on // authority for how a Rust primitive is spelled is `extdeps.languages.rust.primitives` // `rust_grounding_primitives`, through its `target_name` rows. A name list here would be // a second spelling authority for one fact. +// NO PointwisePower ROW IN THIS ROSTER, BY CONSTRUCTION. PointwisePower is the OPEN-support +// power set presented as a characteristic function (std.algebra, profile OpenSupport): its only +// construction is a record literal carrying `member: fn(T) -> Bool`, and no finite enumerable +// BTreeSet can realize an open membership oracle. The row that stood here was a pre-split +// fossil — std.algebra's carrier-split note records the byte-identical "BooleanAlgebra" / +// "PointwisePower" rows that prompted the split, and the FinitePowerSet row below keeps the +// finite-set realization the fossil actually described. Its one live effect was measured on the +// emitted 00_compile closure's first native run (2026-09-07): +// keyed_container_has_target_inhabitant answered true for the record literal, so twelve +// language-model and nullable-fixpoint construction sites emitted the record-shaped-carrier +// refusal panic where the interpreted pipeline evaluates — while the type renderer, keyed on +// declaration provenance, already rendered PointwisePower structurally, so the row made the two +// emission paths disagree. Removing it routes construction to the same structural path the type +// renderer already takes (the bool_boolean_algebra pattern: Rc'd closure fields, _phantom +// filled). Regression control: +// test.claim.self_host_emitted_call_target_realization_witness_test +// w_pointwise_power_record_shape_constructs_its_structural_carrier. data rust_algebra_inhabitants: List = [ { algebra: "FreeMonoid", @@ -67,13 +84,6 @@ data rust_algebra_inhabitants: List = [ import_path: none, is_copy: false }, - { algebra: "PointwisePower", - template: "BTreeSet<\{0\}>", - arity: 1, - identity_expr: "BTreeSet::new()", - import_path: none, - is_copy: false }, - { algebra: "FinitelySupportedFunction", template: "HashMap<\{0\}, \{1\}>", arity: 2, diff --git a/dag/extdeps/languages/typescript/types.dag b/dag/extdeps/languages/typescript/types.dag index 76593826367..aacefcf5b4f 100644 --- a/dag/extdeps/languages/typescript/types.dag +++ b/dag/extdeps/languages/typescript/types.dag @@ -24,6 +24,11 @@ data ts_type_checkpoints: List = [ { dag_name: "Json", target_type: "unknown", grounding_type: "unknown", default_expr: "null", is_copy: false, literal_suffix: none } ] +// NO PointwisePower ROW — see extdeps.languages.rust.types (the note above +// rust_algebra_inhabitants) for the full reasoning. PointwisePower is the open-support +// characteristic-function carrier; a finite Set<...> realizes FinitePowerSet, not it. The fossil +// row's only live effect was the record-literal refusal panic, measured on the emitted +// 00_compile closure's first native run (2026-09-07). data ts_algebra_inhabitants: List = [ { algebra: "FreeMonoid", @@ -40,13 +45,6 @@ data ts_algebra_inhabitants: List = [ import_path: none, is_copy: false }, - { algebra: "PointwisePower", - template: "Set<\{0\}>", - arity: 1, - identity_expr: "new Set()", - import_path: none, - is_copy: false }, - { algebra: "PartialFunction", template: "Map<\{0\}, \{1\}>", arity: 2, diff --git a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag index 3528743b7a3..85285f11632 100644 --- a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag +++ b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag @@ -64,6 +64,41 @@ test fn w_host_map_record_shape_refuses_instead_of_constructing_a_second_carrier ) } +// THE OPEN-SUPPORT CARRIER CONSTRUCTS STRUCTURALLY, THE DISCRIMINATING RED for the fossil-row +// class. PointwisePower is the power set presented as a characteristic function (std.algebra, +// OpenSupport) — no finite BTreeSet can realize an open membership oracle — but all four target +// inhabitant rosters carried a pre-split "PointwisePower" -> finite-set row, and the construction +// arm consulted it while the type renderer (keyed on declaration provenance) did not: the type +// rendered structurally and the literal emitted the record-shaped-carrier refusal panic. Measured +// on the emitted 00_compile closure's first native run (2026-09-07): twelve such panic sites, hit +// through dag_language_model().canonical_symbols. The rows are removed; the literal now takes the +// same structural path the type renderer already took (Rc'd closure field, phantom filled — the +// bool_boolean_algebra pattern). +test fn w_pointwise_power_record_shape_constructs_its_structural_carrier() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.pointwise_power_record_shape_probe\nimport std.algebra { PointwisePower }\nimport std.types { Int }\nfn probe(only: Int) -> PointwisePower { PointwisePower { member: fn(n) { n == only } } }\n", + "src/test_claim_pointwise_power_record_shape_probe.rs", + ["PointwisePower {", "member:"], + ["record-shaped carrier has no realization", "BTreeSet"] + ) +} + +// THE BOUNDARY CONTROL. FinitePowerSet — the finite counterpart the fossil row actually described +// — keeps its BTreeSet inhabitant row, and with it the construction refusal: a record literal +// still cannot construct the finite-set host inhabitant from characteristic-function fields. A +// repair that cleared the set-carrier rows wholesale instead of removing only the open-support +// fossil greens the panic arm and reds here. (Type position is not the discriminator: the +// provenance-keyed renderer emits both carriers structurally today — measured 2026-09-07, the +// type-position form of this probe renders FinitePowerSet, not BTreeSet.) +test fn w_finite_power_set_record_shape_still_refuses_while_its_row_stands() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.finite_power_set_record_shape_probe\nimport std.algebra { FinitePowerSet }\nimport std.types { Int }\nfn probe(s: FinitePowerSet) -> FinitePowerSet { FinitePowerSet { member: fn(n) { s.member(n) }, union: fn(other) { s }, intersect: fn(other) { s }, diff: fn(other) { s }, contains: fn(n) { s.contains(n) }, count: fn() { s.count() }, length: fn() { s.length() } } }\n", + "src/test_claim_finite_power_set_record_shape_probe.rs", + ["record-shaped carrier has no realization"], + ["FinitePowerSet {"] + ) +} + // THE PROBE IMPORTS v2.std.collection AND THEN CALLS IT BY ITS QUALIFIED NAME, which looks // redundant and is not. compile_dag_rust_emit_check compiles through the witness harness's // restricted pool (2973 modules against the corpus's 4261), and a qualified spelling alone does not diff --git a/src/v1/stage0/src/compiler_tests.rs b/src/v1/stage0/src/compiler_tests.rs index 3d03a246dc3..47b5f38decb 100644 --- a/src/v1/stage0/src/compiler_tests.rs +++ b/src/v1/stage0/src/compiler_tests.rs @@ -3162,10 +3162,6 @@ mod compiler_tests { coerce_container_template(RenderTarget::Rust, "Map".into()), Some("HashMap<{0}, {1}>".to_string()) ); - assert_eq!( - coerce_container_template(RenderTarget::Rust, "PointwisePower".into()), - Some("BTreeSet<{0}>".to_string()) - ); assert_eq!( coerce_container_template(RenderTarget::Rust, "Set".into()), Some("BTreeSet<{0}>".to_string()) @@ -3199,10 +3195,6 @@ mod compiler_tests { coerce_container_template(RenderTarget::Python, "PartialFunction".into()), Some("dict[{0}, {1}]".to_string()) ); - assert_eq!( - coerce_container_template(RenderTarget::Python, "PointwisePower".into()), - Some("set[{0}]".to_string()) - ); assert_eq!( coerce_container_template(RenderTarget::Python, "Set".into()), Some("set[{0}]".to_string()) @@ -3236,10 +3228,6 @@ mod compiler_tests { coerce_container_template(RenderTarget::Go, "PartialFunction".into()), Some("map[{0}]{1}".to_string()) ); - assert_eq!( - coerce_container_template(RenderTarget::Go, "PointwisePower".into()), - Some("map[{0}]struct{}".to_string()) - ); assert_eq!( coerce_container_template(RenderTarget::Go, "Set".into()), Some("map[{0}]struct{}".to_string()) diff --git a/src/v1/stage0/src/extdeps_languages_go_types.rs b/src/v1/stage0/src/extdeps_languages_go_types.rs index 189a6fc56e2..28c135ffc1c 100644 --- a/src/v1/stage0/src/extdeps_languages_go_types.rs +++ b/src/v1/stage0/src/extdeps_languages_go_types.rs @@ -39,7 +39,7 @@ pub fn go_type_checkpoints() -> Rc>> { pub fn go_algebra_inhabitants() -> Rc>> { thread_local! { static CACHED: Rc>> = { - serde_json::from_value(serde_json::json!([{"algebra": "FreeMonoid", "template": "[]{0}", "arity": 1, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "FinitePowerSet", "template": "map[{0}]struct{}", "arity": 1, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "PointwisePower", "template": "map[{0}]struct{}", "arity": 1, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "PartialFunction", "template": "map[{0}]{1}", "arity": 2, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "FinitelySupportedFunction", "template": "map[{0}]{1}", "arity": 2, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "OrderedRing", "template": "int64", "arity": 0, "identity_expr": "0", "import_path": null, "is_copy": null}, {"algebra": "ApproximateField", "template": "float64", "arity": 0, "identity_expr": "0.0", "import_path": null, "is_copy": null}])) + serde_json::from_value(serde_json::json!([{"algebra": "FreeMonoid", "template": "[]{0}", "arity": 1, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "FinitePowerSet", "template": "map[{0}]struct{}", "arity": 1, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "PartialFunction", "template": "map[{0}]{1}", "arity": 2, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "FinitelySupportedFunction", "template": "map[{0}]{1}", "arity": 2, "identity_expr": "nil", "import_path": null, "is_copy": null}, {"algebra": "OrderedRing", "template": "int64", "arity": 0, "identity_expr": "0", "import_path": null, "is_copy": null}, {"algebra": "ApproximateField", "template": "float64", "arity": 0, "identity_expr": "0.0", "import_path": null, "is_copy": null}])) .expect("valid data definition") }; } diff --git a/src/v1/stage0/src/extdeps_languages_python_types.rs b/src/v1/stage0/src/extdeps_languages_python_types.rs index 427946724ea..e34a1cca65a 100644 --- a/src/v1/stage0/src/extdeps_languages_python_types.rs +++ b/src/v1/stage0/src/extdeps_languages_python_types.rs @@ -40,7 +40,7 @@ pub fn python_type_checkpoints() -> Rc>> { pub fn python_algebra_inhabitants() -> Rc>> { thread_local! { static CACHED: Rc>> = { - serde_json::from_value(serde_json::json!([{"algebra": "FreeMonoid", "template": "list[{0}]", "arity": 1, "identity_expr": "[]", "import_path": null, "is_copy": null}, {"algebra": "FinitePowerSet", "template": "set[{0}]", "arity": 1, "identity_expr": "set()", "import_path": null, "is_copy": null}, {"algebra": "PointwisePower", "template": "set[{0}]", "arity": 1, "identity_expr": "set()", "import_path": null, "is_copy": null}, {"algebra": "PartialFunction", "template": "dict[{0}, {1}]", "arity": 2, "identity_expr": "{}", "import_path": null, "is_copy": null}, {"algebra": "FinitelySupportedFunction", "template": "dict[{0}, {1}]", "arity": 2, "identity_expr": "{}", "import_path": null, "is_copy": null}, {"algebra": "OrderedRing", "template": "int", "arity": 0, "identity_expr": "0", "import_path": null, "is_copy": null}, {"algebra": "ApproximateField", "template": "float", "arity": 0, "identity_expr": "0.0", "import_path": null, "is_copy": null}])) + serde_json::from_value(serde_json::json!([{"algebra": "FreeMonoid", "template": "list[{0}]", "arity": 1, "identity_expr": "[]", "import_path": null, "is_copy": null}, {"algebra": "FinitePowerSet", "template": "set[{0}]", "arity": 1, "identity_expr": "set()", "import_path": null, "is_copy": null}, {"algebra": "PartialFunction", "template": "dict[{0}, {1}]", "arity": 2, "identity_expr": "{}", "import_path": null, "is_copy": null}, {"algebra": "FinitelySupportedFunction", "template": "dict[{0}, {1}]", "arity": 2, "identity_expr": "{}", "import_path": null, "is_copy": null}, {"algebra": "OrderedRing", "template": "int", "arity": 0, "identity_expr": "0", "import_path": null, "is_copy": null}, {"algebra": "ApproximateField", "template": "float", "arity": 0, "identity_expr": "0.0", "import_path": null, "is_copy": null}])) .expect("valid data definition") }; } diff --git a/src/v1/stage0/src/extdeps_languages_rust_types.rs b/src/v1/stage0/src/extdeps_languages_rust_types.rs index 9b540d985a0..3b45c56e5ab 100644 --- a/src/v1/stage0/src/extdeps_languages_rust_types.rs +++ b/src/v1/stage0/src/extdeps_languages_rust_types.rs @@ -59,7 +59,7 @@ pub fn rust_checkpoint_scalar_arity_note() -> String { pub fn rust_algebra_inhabitants() -> Rc>> { thread_local! { static CACHED: Rc>> = { - serde_json::from_value(serde_json::json!([{"algebra": "FreeMonoid", "template": "Vec<{0}>", "arity": 1, "identity_expr": "Vec::new()", "import_path": null, "is_copy": false}, {"algebra": "FinitePowerSet", "template": "BTreeSet<{0}>", "arity": 1, "identity_expr": "BTreeSet::new()", "import_path": null, "is_copy": false}, {"algebra": "PointwisePower", "template": "BTreeSet<{0}>", "arity": 1, "identity_expr": "BTreeSet::new()", "import_path": null, "is_copy": false}, {"algebra": "FinitelySupportedFunction", "template": "HashMap<{0}, {1}>", "arity": 2, "identity_expr": "HashMap::new()", "import_path": null, "is_copy": false}, {"algebra": "OrderedRing", "template": "i64", "arity": 0, "identity_expr": "0i64", "import_path": null, "is_copy": true}, {"algebra": "ApproximateField", "template": "f64", "arity": 0, "identity_expr": "0.0f64", "import_path": null, "is_copy": true}])) + serde_json::from_value(serde_json::json!([{"algebra": "FreeMonoid", "template": "Vec<{0}>", "arity": 1, "identity_expr": "Vec::new()", "import_path": null, "is_copy": false}, {"algebra": "FinitePowerSet", "template": "BTreeSet<{0}>", "arity": 1, "identity_expr": "BTreeSet::new()", "import_path": null, "is_copy": false}, {"algebra": "FinitelySupportedFunction", "template": "HashMap<{0}, {1}>", "arity": 2, "identity_expr": "HashMap::new()", "import_path": null, "is_copy": false}, {"algebra": "OrderedRing", "template": "i64", "arity": 0, "identity_expr": "0i64", "import_path": null, "is_copy": true}, {"algebra": "ApproximateField", "template": "f64", "arity": 0, "identity_expr": "0.0f64", "import_path": null, "is_copy": true}])) .expect("valid data definition") }; } From e912cc941a13b69afd10c2d7756a7915d6143900 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 02:11:35 +0000 Subject: [PATCH 24/89] Regen gen-2 gate: compare executable digests in one spelling The admitted side of run_built_seed_regen carries the executable-digest spelling (current_exe_digest, next_pass_executable_digest) while the observed side hashed the file through path_digest, which prepends the fnv1a64: tag. Same bytes, two spellings, so the gate could never pass -- unpassable since fa2d403dc8 (#9771). Factor current_exe_on_disk as the single path authority and read the observed digest through current_exe_digest so both sides spell the same bytes the same way. --- src/v1/stage0/src/required_regen_host.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/v1/stage0/src/required_regen_host.rs b/src/v1/stage0/src/required_regen_host.rs index 038675ba959..6cab136347b 100644 --- a/src/v1/stage0/src/required_regen_host.rs +++ b/src/v1/stage0/src/required_regen_host.rs @@ -3746,10 +3746,16 @@ fn seed_cargo_build(workspace: &Path, label: &str) -> Result (deleted)`; digesting the path itself /// compares "installed there now" against "installed there before the build" -- the refusal's /// question. -fn current_exe_digest() -> Result { +fn current_exe_on_disk() -> Result { let exe = std::env::current_exe().map_err(|e| format!("current_exe: {e}"))?; let shown = exe.to_string_lossy().into_owned(); - let on_disk = PathBuf::from(shown.strip_suffix(" (deleted)").unwrap_or(&shown)); + Ok(PathBuf::from( + shown.strip_suffix(" (deleted)").unwrap_or(&shown), + )) +} + +fn current_exe_digest() -> Result { + let on_disk = current_exe_on_disk()?; let bytes = fs::read(&on_disk).map_err(|e| format!("read {}: {e}", on_disk.display()))?; Ok(v1_rt::bytes_identity_hash(&bytes)) } @@ -4163,10 +4169,11 @@ fn run_built_seed_regen( ) })?; } - let exe = std::env::current_exe().map_err(|e| format!("current_exe: {e}"))?; - let shown = exe.to_string_lossy().into_owned(); - let on_disk = PathBuf::from(shown.strip_suffix(" (deleted)").unwrap_or(&shown)); - let observed_executable_digest = path_digest(&on_disk)?; + let on_disk = current_exe_on_disk()?; + // The admitted side carries the executable-digest spelling (`current_exe_digest`, + // `next_pass_executable_digest`); the tagged file digest (`path_digest`) spells the same + // bytes differently, and this gate compared them verbatim -- unpassable since #9771. + let observed_executable_digest = current_exe_digest()?; if observed_executable_digest != admitted_executable_digest { return Err(format!( "CandidateGeneratedByDifferentSeed: stage admitted executable {} but next generation would run {} at {}", From 9d1986b30d66acd0eb05623559d9986a907a7904 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 02:11:49 +0000 Subject: [PATCH 25/89] Model ReleaseScopeEmpty for release-excluded mirrors, end to end A regen round whose only stage-2 drift was compiler_tests.rs (the PointwisePower roster removal rewrote its derived coercion assertions) refused the rebuild MirrorHasNoOwningPackage: the mirror is owned by no partition package, because every item it defines is #[cfg(test)] and no release unit elaborates it. The refusal conflated two different states -- unowned (a coverage hole) and excluded from the release build by construction (a precise empty scope). The model now names the class: rebuild_scope_release_excluded_mirrors rosters its members (compiler_tests.rs, cited to emit_compiler_tests_module), the decision answers ReleaseScopeEmpty when the whole change set is excluded, and the actuation shape is actuatable with an empty package closure and every partition package excluded -- the build still runs as verification, and a compiled partition package refuses the stage. The host admits the empty closure only when the new stage0_partition_rebuild_release_scope_empty_today query answers true; any other empty closure still refuses. A mixed change set scopes on its release-visible members alone. Verified by execution: the 2026-09-08 round converged (fixed point Reached) with stage-2 installing compiler_tests.rs alone; cargo recompiled the shell crate on its fingerprint (the outer mod line is ungated, so rustc reads the file) while the produced executable was byte-identical -- stage input seed digest == output seed digest. Four new witnesses pin the arm, its actuation shape, the mixed set, and the host-facing query's two arms; the boundary witness (unowned cli_run.rs still refuses) keeps the roster from decaying into the absorbing fallback. --- dag/gunbc/stage0_partition_rebuild_scope.dag | 72 ++++++++++++++++++- src/v1/stage0/src/required_regen_host.rs | 16 ++++- ...0_partition_rebuild_scope_witness_test.dag | 71 ++++++++++++++++++ 3 files changed, 155 insertions(+), 4 deletions(-) diff --git a/dag/gunbc/stage0_partition_rebuild_scope.dag b/dag/gunbc/stage0_partition_rebuild_scope.dag index 76b773eb95c..c4f55e6ba4c 100644 --- a/dag/gunbc/stage0_partition_rebuild_scope.dag +++ b/dag/gunbc/stage0_partition_rebuild_scope.dag @@ -85,6 +85,7 @@ type PartitionRebuildDecision package_closure: List executable_assembly: PartitionExecutableAssembly } + | ReleaseScopeEmpty { changed_mirrors: List } | WholeCompilerRebuildRequired { cause: WholeCompilerRebuildCause } | RebuildScopeRefused { cause: RebuildScopeRefusalCause } @@ -112,6 +113,30 @@ data rebuild_scope_shared_build_input_mirrors: List = [ "v1_compiler_workspace_members.rs" ] +// THE THIRD POSITIVELY MODELED CLASS, reached like the two above by NAMING its members, never by +// failing to name something else: emitted mirrors whose bytes cannot reach the next-pass +// executable, so their precise rebuild scope is the EMPTY set -- derived, not widened to. The +// authority for membership is the emitter itself: v1.compiler.emit_rust +// emit_compiler_tests_module emits compiler_tests.rs with every item it defines behind +// #[cfg(test)], and the round's only build invocation is `cargo build --release --bin +// claim_executor` (required_regen_host RegenConvergenceStageReceipt.build_invocation), under +// which cfg(test) is off and the file contributes no item to any release unit. THE MECHANISM IS +// ONE LEVEL FINER THAN THE MOD LINE: emit_lib_rs_from_paths declares the OUTER `mod +// compiler_tests;` ungated, so rustc does read and parse the file in a release build and the +// shell crate's fingerprint recompiles it -- measured 2026-09-08, convergence stage-2 compiled +// v1-compiler with this file as the only installed surface -- while elaborating zero items from +// it, and the produced executable was byte-identical (stage input seed digest == output seed +// digest on the same receipt). So the empty scope is a claim about the EXECUTABLE, verified by +// the digest comparison the round already performs, not a claim that nothing recompiles. A +// mirror is on this roster only while that citation holds; an unowned mirror NOT named here +// still refuses MirrorHasNoOwningPackage, so the roster cannot decay into the absorbing fallback +// this module exists to refuse. The host verifies the partition half by execution: under +// ReleaseScopeEmpty every partition package is excluded, the build still runs, and a compiled +// partition package refuses the stage. +data rebuild_scope_release_excluded_mirrors: List = [ + "compiler_tests.rs" +] + // THE EXECUTABLE THE SECOND GENERATION RUNS, stated as a typed state rather than assumed. // // IT IS ASSEMBLED NOW, and the capability its old trigger named is what changed. The host shell @@ -181,6 +206,10 @@ fn stage0_partition_rebuild_is_actuatable(decision: PartitionRebuildDecision) -> PartitionAssembledExecutable { package_name: _, bin_name: _ } => true ExecutableAssemblyUnavailable { trigger: _, detail: _ } => false } + // ACTUATABLE BECAUSE THERE IS NOTHING TO COMPILE, not because there is nothing to verify: + // the host still runs the build, and the exclusion set (every package) makes any + // compilation a located refusal rather than an unnoticed widening. + ReleaseScopeEmpty { changed_mirrors: _ } => true WholeCompilerRebuildRequired { cause: _ } => false RebuildScopeRefused { cause: _ } => false } @@ -296,6 +325,10 @@ fn rebuild_scope_changed_in_roster(changed_mirrors: List, roster: List filter(m => rebuild_scope_list_has(items: roster, item: m)) } +fn rebuild_scope_changed_outside_roster(changed_mirrors: List, roster: List) -> List { + changed_mirrors |> filter(m => !rebuild_scope_list_has(items: roster, item: m)) +} + // THE DECISION. `unlocatable` is the host's list of changed paths it could not name as a compared // mirror; a non-empty list refuses before anything else is asked, exactly as // gunbc.regen_affected_set EditedSetUnlocatable does, and for the same reason. @@ -322,8 +355,18 @@ fn stage0_partition_rebuild_decision( } else if count(changed_mirrors) == 0 { RebuildScopeRefused { cause: NoChangedMirrorsToRebuild } } else { - let generation = rebuild_scope_changed_in_roster(changed_mirrors: changed_mirrors, roster: rebuild_scope_partition_generation_mirrors) - let shared = rebuild_scope_changed_in_roster(changed_mirrors: changed_mirrors, roster: rebuild_scope_shared_build_input_mirrors) + // RELEASE-EXCLUDED MIRRORS ARE PARTITIONED OFF FIRST: they contribute no owning package to + // any build the round can run, so owner resolution must not be asked about them -- its + // refusal is for mirrors whose release scope EXISTS and cannot be answered, and a + // cfg(test)-gated file is not one. When nothing release-visible remains, the answer is the + // empty scope as a first-class verdict, not the ExecutableAssemblyNotCovered refusal an + // empty closure would otherwise meet below: the executable is UNAFFECTED, not uncovered. + let release_changed = rebuild_scope_changed_outside_roster(changed_mirrors: changed_mirrors, roster: rebuild_scope_release_excluded_mirrors) + if count(release_changed) == 0 { + ReleaseScopeEmpty { changed_mirrors: changed_mirrors } + } else { + let generation = rebuild_scope_changed_in_roster(changed_mirrors: release_changed, roster: rebuild_scope_partition_generation_mirrors) + let shared = rebuild_scope_changed_in_roster(changed_mirrors: release_changed, roster: rebuild_scope_shared_build_input_mirrors) if count(generation) > 0 { WholeCompilerRebuildRequired { cause: PartitionGenerationAuthorityChanged { mirrors: generation } } } else if count(shared) > 0 { @@ -338,7 +381,7 @@ fn stage0_partition_rebuild_decision( } } } else { - match rebuild_scope_resolve_owners(changed_mirrors: changed_mirrors, rows: rows) { + match rebuild_scope_resolve_owners(changed_mirrors: release_changed, rows: rows) { OwnerRefused { cause: c } => RebuildScopeRefused { cause: c } OwnerResolved { packages: owners } => { let partition_closure = rebuild_scope_reverse_closure(seeds: owners, rows: rows) @@ -372,6 +415,7 @@ fn stage0_partition_rebuild_decision( } } } + } } } @@ -433,6 +477,8 @@ fn stage0_partition_rebuild_decision_line(decision: PartitionRebuildDecision) -> "partition-rebuild: RebuildScopeRefused NoChangedMirrorsToRebuild" RebuildScopeRefused { cause: ChangedMirrorUnlocatable { mirrors: ms, reason: r } } => concat(concat("partition-rebuild: RebuildScopeRefused ChangedMirrorUnlocatable mirrors=[", rebuild_scope_join(items: ms)), concat("] reason=", r)) + ReleaseScopeEmpty { changed_mirrors: m } => + concat("partition-rebuild: ReleaseScopeEmpty changed_mirrors=[", concat(rebuild_scope_join(items: m), "] -- every changed mirror is excluded from the release build by construction; the executable is unaffected and the build runs as verification")) RebuildScopeRefused { cause: MirrorHasNoOwningPackage { mirror: m } } => concat("partition-rebuild: RebuildScopeRefused MirrorHasNoOwningPackage mirror=", m) RebuildScopeRefused { cause: MirrorHasMultipleOwningPackages { mirror: m, packages: ps } } => @@ -477,11 +523,28 @@ fn stage0_partition_rebuild_packages_today( ) -> List { match stage0_partition_rebuild_decision_today(changed_mirrors: changed_mirrors, unlocatable: unlocatable) { PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: c, executable_assembly: _ } => c + ReleaseScopeEmpty { changed_mirrors: _ } => [] WholeCompilerRebuildRequired { cause: _ } => [] RebuildScopeRefused { cause: _ } => [] } } +// THE FOURTH HOST QUESTION, and the reason the empty closure below is legal: an empty package +// list from ReleaseScopeEmpty is the derived answer, while an empty list from the refusing arms +// is no answer at all. The host distinguishes them HERE -- by the variant, never by counting -- +// so the guard that refuses an empty closure under a derived scope stays load-bearing. +fn stage0_partition_rebuild_release_scope_empty_today( + changed_mirrors: List, + unlocatable: List +) -> Bool { + match stage0_partition_rebuild_decision_today(changed_mirrors: changed_mirrors, unlocatable: unlocatable) { + ReleaseScopeEmpty { changed_mirrors: _ } => true + PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false + WholeCompilerRebuildRequired { cause: _ } => false + RebuildScopeRefused { cause: _ } => false + } +} + // THE PACKAGES THAT MUST NOT BE COMPILED. Reported to the host as identities so a build that // touches one of them is a located refusal rather than an unnoticed widening: an exclusion no // consumer can read is not evidence of anything, and this is the exclusion the whole @@ -491,6 +554,9 @@ fn stage0_partition_rebuild_excluded_today( changed_mirrors: List, unlocatable: List ) -> List { + // UNDER ReleaseScopeEmpty THE CLOSURE IS EMPTY BY CONSTRUCTION, so this answers EVERY package: + // the guarantee the host enforces is that the verification build compiles nothing, and an + // exclusion that names every package is what makes a single compiled crate a located refusal. rebuild_scope_excluded_packages( closure: stage0_partition_rebuild_packages_today( changed_mirrors: changed_mirrors, diff --git a/src/v1/stage0/src/required_regen_host.rs b/src/v1/stage0/src/required_regen_host.rs index 6cab136347b..d07f5eb3f49 100644 --- a/src/v1/stage0/src/required_regen_host.rs +++ b/src/v1/stage0/src/required_regen_host.rs @@ -3979,6 +3979,10 @@ struct PartitionRebuildActuation { package_closure: Vec, excluded_packages: Vec, decision_line: String, + /// The model's ReleaseScopeEmpty arm: every changed mirror is excluded from the release + /// build by construction (e.g. a `#[cfg(test)]`-gated module), so the correct package + /// closure IS the empty set and the build runs purely as verification of that claim. + release_scope_empty: bool, } type ModelValue = crate::v1_interpreter::Value; @@ -4060,11 +4064,21 @@ fn partition_rebuild_actuation( )) } }; + let release_scope_empty = match call("stage0_partition_rebuild_release_scope_empty_today")? { + ModelValue::Bool(value) => value, + other => { + return Err(format!( + "refusal: stage0_partition_rebuild_release_scope_empty_today returned {} instead of Bool", + other.type_label_public() + )) + } + }; Ok(PartitionRebuildActuation { actuatable, package_closure, excluded_packages, decision_line, + release_scope_empty, }) } @@ -4078,7 +4092,7 @@ fn partitioned_rebuild_from_installed( actuation.decision_line )); } - if actuation.package_closure.is_empty() { + if actuation.package_closure.is_empty() && !actuation.release_scope_empty { return Err(format!( "StageSeedBuildRefused: actuation admitted an empty package closure -- {}", actuation.decision_line diff --git a/src/v2/test/claim/stage0_partition_rebuild_scope_witness_test.dag b/src/v2/test/claim/stage0_partition_rebuild_scope_witness_test.dag index 76bbd9948b1..3574768c461 100644 --- a/src/v2/test/claim/stage0_partition_rebuild_scope_witness_test.dag +++ b/src/v2/test/claim/stage0_partition_rebuild_scope_witness_test.dag @@ -19,12 +19,16 @@ import gunbc.stage0_partition_rebuild_scope { PartitionRebuildDecision, PartitionRebuildScopeDerived, RebuildScopeRefused, + ReleaseScopeEmpty, SharedCompilerBuildInputChanged, WholeCompilerRebuildRequired, rebuild_scope_excluded_packages, stage0_partition_rebuild_is_actuatable, stage0_partition_rebuild_decision, stage0_partition_rebuild_decision_today, + stage0_partition_rebuild_excluded_today, + stage0_partition_rebuild_packages_today, + stage0_partition_rebuild_release_scope_empty_today, stage0_next_pass_executable_assembly } @@ -72,6 +76,7 @@ fn witness_closure_of(changed: List) -> List { PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: c, executable_assembly: _ } => c WholeCompilerRebuildRequired { cause: _ } => [] RebuildScopeRefused { cause: _ } => [] + ReleaseScopeEmpty { changed_mirrors: _ } => [] } } @@ -97,6 +102,7 @@ test fn witness_leaf_edit_compiles_owner_and_downstream_holds() -> Bool { }) WholeCompilerRebuildRequired { cause: _ } => false RebuildScopeRefused { cause: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -135,6 +141,7 @@ test fn witness_partition_generation_edit_requires_whole_rebuild_holds() -> Bool WholeCompilerRebuildRequired { cause: SharedCompilerBuildInputChanged { mirrors: _ } } => false PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false RebuildScopeRefused { cause: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -144,6 +151,7 @@ test fn witness_shared_build_input_edit_requires_whole_rebuild_holds() -> Bool { WholeCompilerRebuildRequired { cause: PartitionGenerationAuthorityChanged { mirrors: _ } } => false PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false RebuildScopeRefused { cause: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -179,6 +187,7 @@ test fn witness_unowned_mirror_refuses_holds() -> Bool { RebuildScopeRefused { cause: NoChangedMirrorsToRebuild } => false WholeCompilerRebuildRequired { cause: _ } => false PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -200,6 +209,7 @@ test fn witness_compiler_tests_rust_rebuild_owner_is_v1_infer_holds() -> Bool { && witness_has(items: closure, item: "v1-compiler") WholeCompilerRebuildRequired { cause: _ } => false RebuildScopeRefused { cause: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -243,6 +253,7 @@ test fn witness_duplicate_owner_refuses_holds() -> Bool { RebuildScopeRefused { cause: NoChangedMirrorsToRebuild } => false WholeCompilerRebuildRequired { cause: _ } => false PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -263,6 +274,7 @@ test fn witness_unlocatable_changed_path_refuses_holds() -> Bool { RebuildScopeRefused { cause: NoChangedMirrorsToRebuild } => false WholeCompilerRebuildRequired { cause: _ } => false PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -286,6 +298,7 @@ test fn witness_empty_changed_set_is_nothing_to_rebuild_holds() -> Bool { RebuildScopeRefused { cause: PackageDependencyClosureUnderivable { package_name: _, detail: _ } } => false WholeCompilerRebuildRequired { cause: _ } => false PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -306,6 +319,7 @@ test fn witness_unreached_executable_package_refuses_holds() -> Bool { RebuildScopeRefused { cause: PackageDependencyClosureUnderivable { package_name: _, detail: _ } } => false WholeCompilerRebuildRequired { cause: _ } => false PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -331,6 +345,7 @@ test fn witness_today_derives_scope_with_assembled_executable_holds() -> Bool { }) WholeCompilerRebuildRequired { cause: _ } => false RebuildScopeRefused { cause: _ } => false + ReleaseScopeEmpty { changed_mirrors: _ } => false } } @@ -370,3 +385,59 @@ test fn witness_next_pass_executable_assembly_is_assembled_holds() -> Bool { ExecutableAssemblyUnavailable { trigger: PartitionedClaimExecutorAssembly { capability: _ }, detail: _ } => false } } + +// CONTROL 7 -- THE RELEASE-EXCLUDED CLASS, AND ITS BOUNDARY. compiler_tests.rs is emitted by +// v1.compiler.emit_rust emit_compiler_tests_module with every item behind #[cfg(test)], so its +// bytes contribute nothing to the release executable: its precise rebuild scope is the empty +// set, answered as ReleaseScopeEmpty rather than refused. (The outer `mod compiler_tests;` IS +// ungated, so the shell crate still recompiles on the fingerprint -- the claim is about the +// executable bytes, and the 2026-09-08 convergence stage-2 receipt shows input seed digest == +// output seed digest with this file as the only installed surface.) THE BOUNDARY IS CONTROL 4 +// ABOVE: cli_run.rs is unowned and NOT on the release-excluded roster, and it still refuses +// MirrorHasNoOwningPackage -- the roster names its members, so "unowned" and "excluded by +// construction" can never collapse into one arm. Measured live 2026-09-08: a regen round whose +// generation-2 drift was compiler_tests.rs alone (the PointwisePower inhabitant-row removal +// rewrote the derived coercion assertions) refused its stage-2 rebuild MirrorHasNoOwningPackage +// before this arm existed. +test fn witness_release_excluded_mirror_scope_is_empty_holds() -> Bool { + match stage0_partition_rebuild_decision_today(changed_mirrors: ["compiler_tests.rs"], unlocatable: []) { + ReleaseScopeEmpty { changed_mirrors: ms } => (count(ms) == 1) && witness_has(items: ms, item: "compiler_tests.rs") + PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: _ } => false + WholeCompilerRebuildRequired { cause: _ } => false + RebuildScopeRefused { cause: _ } => false + } +} + +// The actuation shape of the empty scope: actuatable (the verification build still runs), no +// package may compile, and EVERY package is excluded -- so one compiled crate is a located +// refusal, not an unnoticed widening. +test fn witness_release_excluded_scope_actuation_shape_holds() -> Bool { + let decision = stage0_partition_rebuild_decision_today(changed_mirrors: ["compiler_tests.rs"], unlocatable: []) + stage0_partition_rebuild_is_actuatable(decision: decision) + && (count(stage0_partition_rebuild_packages_today(changed_mirrors: ["compiler_tests.rs"], unlocatable: [])) == 0) + && (count(stage0_partition_rebuild_excluded_today(changed_mirrors: ["compiler_tests.rs"], unlocatable: [])) == count(generated_partition_crate_rows)) +} + +// A mixed change set scopes on its release-visible members alone: the excluded mirror rides the +// install without adding a package and without refusing. +test fn witness_mixed_change_set_scopes_on_release_visible_mirrors_holds() -> Bool { + match stage0_partition_rebuild_decision_today(changed_mirrors: ["compiler_tests.rs", "std_effects.rs"], unlocatable: []) { + PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: o, package_closure: c, executable_assembly: _ } => + (count(o) == 1) + && witness_has(items: o, item: "v1-stage0-std-surface") + && witness_has(items: c, item: "v1-stage0-std-surface") + ReleaseScopeEmpty { changed_mirrors: _ } => false + WholeCompilerRebuildRequired { cause: _ } => false + RebuildScopeRefused { cause: _ } => false + } +} + +// THE HOST-FACING PROJECTION, pinned by name because required_regen_host.rs calls exactly +// stage0_partition_rebuild_release_scope_empty_today to admit an empty package closure as the +// verification-build answer rather than refuse it. Both arms are asserted: the excluded-only +// set answers true, and a release-visible change answers false -- a constant-true projection +// would silently discharge the host's empty-closure guard for every decision. +test fn witness_release_scope_empty_query_answers_both_arms_hold() -> Bool { + stage0_partition_rebuild_release_scope_empty_today(changed_mirrors: ["compiler_tests.rs"], unlocatable: []) + && !stage0_partition_rebuild_release_scope_empty_today(changed_mirrors: ["std_effects.rs"], unlocatable: []) +} From 10a68a3cc6bbd1ed396e6ce1b3f2f2f73452f7bc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 02:11:57 +0000 Subject: [PATCH 26/89] Round-cost receipt: project installed mirrors to the model's vocabulary The receipt's partition-rebuild line is rendered by the model over receipt.installed_mirrors, which the host populated from the stages' projected_paths -- full paths -- while the partition rows and rosters key on basenames. Every drifted round's receipt therefore rendered a spurious RebuildScopeRefused MirrorHasNoOwningPackage line naming a full path, a false claim on the round's own receipt. Route the projection through emit_path_basename, the module's single path-to-basename bridge, so the field carries the mirror names the model's vocabulary means. --- src/v1/stage0/src/required_regen_host.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/required_regen_host.rs b/src/v1/stage0/src/required_regen_host.rs index d07f5eb3f49..323cf0f5e12 100644 --- a/src/v1/stage0/src/required_regen_host.rs +++ b/src/v1/stage0/src/required_regen_host.rs @@ -5724,11 +5724,15 @@ pub fn run_regen_round_cost( .iter() .map(|stage| stage.build_compiled_crates) .sum(); + // The receipt field is named in the model's vocabulary: a "mirror" is the basename the + // partition rows and rosters key on. The stages carry projected PATHS, so project through + // the single bridge -- feeding paths to the decision model rendered a spurious + // MirrorHasNoOwningPackage line on every drifted round's receipt. let installed_mirrors = transaction_receipt .stages .iter() .flat_map(|stage| stage.surfaces.iter()) - .map(|surface| surface.projected_path.clone()) + .map(|surface| emit_path_basename(&surface.projected_path).to_string()) .collect::>() .into_iter() .collect::>(); From 25d81979d4081bc3b1d3fcbadcdfefe5a7112856 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 17:52:51 +0000 Subject: [PATCH 27/89] Hoist ReleaseScopeEmpty annotations to module-item grain The ReleaseScopeEmpty modeling commit placed three // blocks inside declaration bodies (stage0_partition_rebuild_is_actuatable, stage0_partition_rebuild_decision, stage0_partition_rebuild_excluded_today). The .dag realization admits annotations at module-item grain only, so the floor lane's parse phase refused the file with 12 located errors and the run ended floor refused. The prose is unchanged; each block now sits above the declaration it describes. --- dag/gunbc/stage0_partition_rebuild_scope.dag | 29 ++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/dag/gunbc/stage0_partition_rebuild_scope.dag b/dag/gunbc/stage0_partition_rebuild_scope.dag index c4f55e6ba4c..38c53c5d371 100644 --- a/dag/gunbc/stage0_partition_rebuild_scope.dag +++ b/dag/gunbc/stage0_partition_rebuild_scope.dag @@ -198,7 +198,10 @@ fn rebuild_scope_extend_with_executable( // so a probe may build them and must terminate as packages-built-executable-unassembled: it may // not feed the fixed-point receipt or the second pass. Making that a typed question the host asks // -- rather than a sentence in a comment the host is trusted to have read -- is what keeps -// "we derived the scope" from being spent as "we rebuilt from it". +// "we derived the scope" from being spent as "we rebuilt from it". ReleaseScopeEmpty is +// ACTUATABLE BECAUSE THERE IS NOTHING TO COMPILE, not because there is nothing to verify: the +// host still runs the build, and the exclusion set (every package) makes any compilation a +// located refusal rather than an unnoticed widening. fn stage0_partition_rebuild_is_actuatable(decision: PartitionRebuildDecision) -> Bool { match decision { PartitionRebuildScopeDerived { changed_mirrors: _, owning_packages: _, package_closure: _, executable_assembly: assembly } => @@ -206,9 +209,6 @@ fn stage0_partition_rebuild_is_actuatable(decision: PartitionRebuildDecision) -> PartitionAssembledExecutable { package_name: _, bin_name: _ } => true ExecutableAssemblyUnavailable { trigger: _, detail: _ } => false } - // ACTUATABLE BECAUSE THERE IS NOTHING TO COMPILE, not because there is nothing to verify: - // the host still runs the build, and the exclusion set (every package) makes any - // compilation a located refusal rather than an unnoticed widening. ReleaseScopeEmpty { changed_mirrors: _ } => true WholeCompilerRebuildRequired { cause: _ } => false RebuildScopeRefused { cause: _ } => false @@ -339,6 +339,13 @@ fn rebuild_scope_changed_outside_roster(changed_mirrors: List, roster: L // the executable-coverage check with an empty closure and report ExecutableAssemblyNotCovered, // which is a true sentence about a question nobody asked and a false one about the tree. A round // that installed nothing is not a round whose executable is uncovered. +// +// RELEASE-EXCLUDED MIRRORS ARE PARTITIONED OFF FIRST inside the non-empty arm: they contribute +// no owning package to any build the round can run, so owner resolution must not be asked about +// them -- its refusal is for mirrors whose release scope EXISTS and cannot be answered, and a +// cfg(test)-gated file is not one. When nothing release-visible remains, the answer is the +// empty scope as a first-class verdict, not the ExecutableAssemblyNotCovered refusal an empty +// closure would otherwise meet below: the executable is UNAFFECTED, not uncovered. fn stage0_partition_rebuild_decision( changed_mirrors: List, unlocatable: List, @@ -355,12 +362,6 @@ fn stage0_partition_rebuild_decision( } else if count(changed_mirrors) == 0 { RebuildScopeRefused { cause: NoChangedMirrorsToRebuild } } else { - // RELEASE-EXCLUDED MIRRORS ARE PARTITIONED OFF FIRST: they contribute no owning package to - // any build the round can run, so owner resolution must not be asked about them -- its - // refusal is for mirrors whose release scope EXISTS and cannot be answered, and a - // cfg(test)-gated file is not one. When nothing release-visible remains, the answer is the - // empty scope as a first-class verdict, not the ExecutableAssemblyNotCovered refusal an - // empty closure would otherwise meet below: the executable is UNAFFECTED, not uncovered. let release_changed = rebuild_scope_changed_outside_roster(changed_mirrors: changed_mirrors, roster: rebuild_scope_release_excluded_mirrors) if count(release_changed) == 0 { ReleaseScopeEmpty { changed_mirrors: changed_mirrors } @@ -549,14 +550,14 @@ fn stage0_partition_rebuild_release_scope_empty_today( // touches one of them is a located refusal rather than an unnoticed widening: an exclusion no // consumer can read is not evidence of anything, and this is the exclusion the whole // change-denominated claim rests on. The emit shell is a partition package, so it appears here -// whenever the changed mirrors do not reach it. +// whenever the changed mirrors do not reach it. UNDER ReleaseScopeEmpty THE CLOSURE IS EMPTY BY +// CONSTRUCTION, so this answers EVERY package: the guarantee the host enforces is that the +// verification build compiles no partition package, and an exclusion that names every package +// is what makes a single compiled crate a located refusal. fn stage0_partition_rebuild_excluded_today( changed_mirrors: List, unlocatable: List ) -> List { - // UNDER ReleaseScopeEmpty THE CLOSURE IS EMPTY BY CONSTRUCTION, so this answers EVERY package: - // the guarantee the host enforces is that the verification build compiles nothing, and an - // exclusion that names every package is what makes a single compiled crate a located refusal. rebuild_scope_excluded_packages( closure: stage0_partition_rebuild_packages_today( changed_mirrors: changed_mirrors, From 77fb1980b3c0445617b1baa57e98287ddf0e8bd8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 18:08:30 +0000 Subject: [PATCH 28/89] Spell the PointwisePower witness's finite-set exclusion as the applied realization The witness added with the fossil-row removal excluded the bare spelling "BTreeSet", but every emitted file's preamble imports OrdSet as BTreeSet, so the row could never green. The exclusion's subject is the finite-set REALIZATION the fossil row would have asserted; spell it applied (BTreeSet Bool { compile_dag_rust_emit_check( "module test.claim.pointwise_power_record_shape_probe\nimport std.algebra { PointwisePower }\nimport std.types { Int }\nfn probe(only: Int) -> PointwisePower { PointwisePower { member: fn(n) { n == only } } }\n", "src/test_claim_pointwise_power_record_shape_probe.rs", ["PointwisePower {", "member:"], - ["record-shaped carrier has no realization", "BTreeSet"] + ["record-shaped carrier has no realization", "BTreeSet Date: Tue, 8 Sep 2026 18:08:38 +0000 Subject: [PATCH 29/89] Emit fieldless-record data values as null for the unit-struct carrier The second native-parity divergence class, measured 2026-09-08 on the native run of the emitted 00_compile closure: emit_data_value_json spelled EVERY record literal as a JSON map, including the zero-field record, while emit_struct_from_children renders that same declaration as a Rust unit struct (pub struct BoolEncodingFact;). serde's derived unit-struct Deserialize reads null and rejects {}, so the emitted compiler panicked at first touch of v2.std.logic's bool_primitive_facts: "invalid type: map, expected unit struct BoolEncodingFact". The JSON spelling of a data value must deserialize into the Rust type the same declaration emitted; the record arm now spells the zero-field value null and keeps the map spelling for non-empty records. The mirror is taken from the required-regen candidate, not hand-edited. Two witnesses enroll: the discriminating red (zero-field record spells null, never {}) and the boundary control (a record with fields keeps the map spelling). --- ...d_call_target_realization_witness_test.dag | 30 ++++++++ src/v1/05_emit.dag | 13 +++- src/v1/stage0/src/v1_compiler_emit.rs | 69 +++++++++++-------- 3 files changed, 82 insertions(+), 30 deletions(-) diff --git a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag index 77d0ac60f3a..a78e9057939 100644 --- a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag +++ b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag @@ -101,6 +101,36 @@ test fn w_finite_power_set_record_shape_still_refuses_while_its_row_stands() -> ) } +// FIELDLESS-RECORD DATA UNDER THE JSON PATH, THE DISCRIMINATING RED for the second native-parity +// divergence class. A data definition of record type with no cross-refs is emitted as a +// serde_json round-trip (emit_data_value_json), and the value side spelled EVERY record as a +// JSON map -- including the zero-field record, whose emitted Rust type is a unit struct +// (emit_struct_from_children, `pub struct Marker;`). serde's unit-struct Deserialize reads +// `null` and rejects `{}`, so the emitted compiler panicked at first touch of the data: +// "invalid type: map, expected unit struct BoolEncodingFact", measured 2026-09-08 on the native +// run of the emitted 00_compile closure (v2.std.logic bool_primitive_facts). The value side now +// spells the zero-field record `null`; this row reds if the map spelling returns. +test fn w_fieldless_record_data_value_spells_null_for_the_unit_struct() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.fieldless_record_json_probe\ntype Marker {}\ntype Holder {\n marker: Marker\n}\ndata probe: Holder = Holder { marker: Marker {} }\n", + "src/test_claim_fieldless_record_json_probe.rs", + ["pub struct Marker;", "\"marker\": null"], + ["\"marker\": {}"] + ) +} + +// THE BOUNDARY CONTROL. The null spelling is the zero-field case ONLY: a record with fields +// keeps the map spelling its braced struct deserializes from. A repair that emitted `null` for +// every record literal greens the row above and reds here. +test fn w_nonempty_record_data_value_keeps_the_map_spelling() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.nonempty_record_json_probe\nimport std.types { String }\ntype Holder {\n name: String\n}\ndata probe: Holder = Holder { name: \"kept\" }\n", + "src/test_claim_nonempty_record_json_probe.rs", + ["\"name\": \"kept\""], + ["\"name\": null"] + ) +} + // THE PROBE IMPORTS v2.std.collection AND THEN CALLS IT BY ITS QUALIFIED NAME, which looks // redundant and is not. compile_dag_rust_emit_check compiles through the witness harness's // restricted pool (2973 modules against the corpus's 4261), and a qualified spelling alone does not diff --git a/src/v1/05_emit.dag b/src/v1/05_emit.dag index e0815980bfd..0929eb16498 100644 --- a/src/v1/05_emit.dag +++ b/src/v1/05_emit.dag @@ -441,6 +441,14 @@ fn accumulate_json_field(acc: JsonFragmentsAccum, name: String, outcome: Emitter } } +// The JSON spelling of a data value must deserialize into the RUST TYPE the same declaration +// emitted, so the two sides are one agreement, not two choices. The case that forces the issue +// is the fieldless record: emit_struct_from_children renders it as a unit struct (`pub struct +// Name;`), and serde's derived unit-struct Deserialize reads `null` -- an empty map `{}` is a +// different, rejected shape ("invalid type: map, expected unit struct", measured 2026-09-08 as +// the second native-parity divergence, on v2.std.logic bool_primitive_facts). The record arm +// therefore spells the zero-field value `null`; non-empty records keep the map spelling their +// braced struct expects. fn emit_data_value_json(value: Node, source_indices: Map) -> EmitterOutcome { match value.expr_data { ExprLiteral { value: v } => match v { @@ -460,7 +468,10 @@ fn emit_data_value_json(value: Node, source_indices: Map) FragmentsAccumulated { pieces: ps } => Emitted { json: concat("[", ps |> join(separator: ", "), "]") } } } - ExprRecordLit { parent_enum: _ } => { + ExprRecordLit { parent_enum: _ } => + if (value.children |> count) == 0 { + Emitted { json: "null" } + } else { let accum = value.children |> fold(init: FragmentsAccumulated { pieces: [] }, f: (acc, fld) => accumulate_json_field( acc: acc, diff --git a/src/v1/stage0/src/v1_compiler_emit.rs b/src/v1/stage0/src/v1_compiler_emit.rs index 274bf2942a2..4c6237a32bf 100644 --- a/src/v1/stage0/src/v1_compiler_emit.rs +++ b/src/v1/stage0/src/v1_compiler_emit.rs @@ -1067,35 +1067,46 @@ pub fn emit_data_value_json( } } ExprData::ExprRecordLit { parent_enum: _, .. } => { - let accum = value.children.clone().iter().cloned().fold( - Rc::new(JsonFragmentsAccum::FragmentsAccumulated { - pieces: Rc::new(vec![]), - }), - |acc: Rc, fld: Rc| { - accumulate_json_field( - acc, - crate::v1_std_core::field_init_node_name_at( - fld.clone(), - source_indices.clone(), - ), - emit_data_value_json( - crate::v1_std_core::field_init_node_value(fld.clone()), - source_indices.clone(), - ), - ) - }, - ); - match (*accum.clone()).clone() { - JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => { - Rc::new(EmitterOutcome::Refused { reason: r.clone() }) - } - JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => { - Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat( - v1_rt::concat("{".to_string(), ps.clone().join(&", ".to_string())), - "}".to_string(), - ), - }) + if ((value.children.clone().len() as i64) == 0) { + Rc::new(EmitterOutcome::Emitted { + json: "null".to_string(), + }) + } else { + { + let accum = value.children.clone().iter().cloned().fold( + Rc::new(JsonFragmentsAccum::FragmentsAccumulated { + pieces: Rc::new(vec![]), + }), + |acc: Rc, fld: Rc| { + accumulate_json_field( + acc, + crate::v1_std_core::field_init_node_name_at( + fld.clone(), + source_indices.clone(), + ), + emit_data_value_json( + crate::v1_std_core::field_init_node_value(fld.clone()), + source_indices.clone(), + ), + ) + }, + ); + match (*accum.clone()).clone() { + JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => { + Rc::new(EmitterOutcome::Refused { reason: r.clone() }) + } + JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => { + Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat( + v1_rt::concat( + "{".to_string(), + ps.clone().join(&", ".to_string()), + ), + "}".to_string(), + ), + }) + } + } } } } From 6f7d3a215917b744a4933ddac884bd50895238ca Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 18:58:33 +0000 Subject: [PATCH 30/89] Bind the duplicate-definition filter ahead of its branch condition Main's FilterInBranchCondition wall (#10699) refuses to publish a module whose filter call sits in a branch condition, and the v2 00_compile closure emission names primitive_duplicate_semantic_definition_violation as such a site. The filter is pure and total; binding it with a let ahead of the branch is the authored remediation the wall exists to force, and the emitted closure is unchanged in behavior. --- dag/std/primitive_identity.dag | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dag/std/primitive_identity.dag b/dag/std/primitive_identity.dag index 57d1bce881f..e314a2629a5 100644 --- a/dag/std/primitive_identity.dag +++ b/dag/std/primitive_identity.dag @@ -875,13 +875,15 @@ fn primitive_definition_identity(definition: PrimitiveDefinition) -> PrimitiveId definition.identity } +// The filter is let-bound, not written in the branch condition: a filter in a condition is an +// unprojectable construct (the FilterInBranchCondition wall), so the condition reads the bound +// name. fn primitive_duplicate_semantic_definition_violation( definitions: List, target: PrimitiveIdentity ) -> PrimitiveJoinViolation? { - if count( - definitions |> filter(fn(d) { primitive_identity_eq(left: d.identity, right: target) }) - ) > 1 { + let matches = definitions |> filter(fn(d) { primitive_identity_eq(left: d.identity, right: target) }) + if count(matches) > 1 { Present { value: DuplicateSemanticDefinition { identity: target } } } else { none From fd4a572d35b8d1e9ada1c9376560bdd692ad483e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 8 Sep 2026 19:20:31 +0000 Subject: [PATCH 31/89] chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-rung-drops.md Ledger-Rows-Repaired: docs/design-rung-drops.md rust_unit_tests_off_the_merge_path Ledger-Rows-Repaired: docs/design-rung-drops.md determinism_transitive_reachability Ledger-Rows-Repaired: docs/design-rung-drops.md transitional_admission_exception --- docs/design-rung-drops.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index 716b5591959..e2ec935d048 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -70,15 +70,15 @@ CI — **RUNG DROP, DECLARED (2026-08-15, the floor cut).** WHAT WAS HERE: one c ### Whole-corpus compile-phase frontier receipts — latest validated row -Milestones: parse PASSES / expand PASSES / resolve PASSES / typeck 155 remaining / borrowck not yet reached +Milestones: parse PASSES / expand PASSES / resolve PASSES / typeck PASSES / borrowck PASSES -Tree: `sha1:d424154bb6fb8c40b149be07f2ddd8d61e89108e` +Tree: `sha1:07eee038259e268e1a543edddb9d3f105e2d11cf` -Invocation: `buildbuddy-44d90e35-ccba-41d1-9a90-928a914b26a2` +Invocation: `local-8b8875a1-eac3-47b1-a041-aa04fa22b2dd` -Census identity digest (self-host-compile-phase-census-canonical-identity-set-v1): `049d0a54eea70534` +Census identity digest (self-host-compile-phase-census-canonical-identity-set-v1): `4325dac985bbe897` -Error identities: 155 total = 155 coded + 0 codeless +Error identities: 0 total = 0 coded + 0 codeless Producer: `tools.emission_entry_instrument::measure_entry_emission` @@ -130,7 +130,7 @@ CORRECTION NOTICE, recorded because the wrong version travelled: an earlier revi ### The v1 crate's Rust unit tests, and their job, on every CI path — declared 2026-09-04 -**THE RUST UNIT TESTS LEAVE CI ENTIRELY; THE ALL-TARGETS LINT DOES NOT (2026-09-04).** gunbc#10078 promoted `rust-unit-tests` into `required_lanes_roster`, making `cargo test --release -p v1-compiler --lib` and `cargo clippy --all-targets -- -D warnings` merge-blocking. The 2026-09-04 operator ruling on runner capacity deletes the JOB. The two commands are separated rather than dropped together, and that separation is the whole of this row. PREVIOUS RUNG: mechanically preventable — a regression in any of the 774 `#[test]`s under src/v1/stage0 was exposed by an enrolled test and blocked the merge through the aggregate's `$UNIT` verdict fold. TEMPORARY RUNG: mitigatable, and only barely — the tests still exist, still refuse loudly, and `cargo test --release -p v1-compiler --lib` still runs them, but NO CI STEP EXECUTES THEM, so they run when a human chooses to and not otherwise. This is the exact state gunbc#9663 was created to end, and #9886's two failing tests landing on main with every required check green is the measured harm of it; that harm is re-admitted knowingly here, not rediscovered. WHAT IS NOT DROPPED, and a reader must not infer it from the job's absence: `repo_self_clippy_command` moved to `required-witnesses-build` as `rust_clippy_all_targets_step`, keeping its step id, its verdict and its position on a REQUIRED lane. That command is the only one on any CI path that compiles the integration-test and example targets — twelve of them sat red on main (2026-08-30) behind a green required run — so dropping it would have been a below-baseline floor regression under DESIGN §4b rather than a declared drop, and the ruling did not ask for it. REASON, and it is runner supply rather than doubt about the tests: the witnesses workflow carried seven jobs against a fleet that could not serve seven, each paying its own checkout, toolchain install and release build of one tree. The required context's wall is the MAX over its lanes, so contention among jobs that could have been steps was displacing the lanes that gate. The unit tests were cut rather than folded into an existing lane because the ruling asked for the Rust test population to leave CI, not to be relocated; folding them would have preserved the cost this row exists to remove. BOUNDED POPULATION: every `#[test]` in the v1-compiler crate reached by `--lib`, and every witness whose enrollment routed through that command — including `emit_import_lines_follow_resolved_binding_identity`, whose own drop row `emitted_bytes_witness_required_lane` was RETIRED on the strength of this lane being required and is un-retired in the same motion. No other lane, phase or claim changes rung; the build and floor lanes keep every edge they had. COUNTABLE: `test.claim.witness_floor_workflow_consolidation_witness_test` `w_RED_the_deleted_lanes_do_not_return` reds if the job returns while this row stands, and `test.claim.required_lane_claim_agreement_witness_test` `w_the_live_roster_is_read_and_carries_both_lanes` asserts its absence from the roster at identity grain. RESTORATION TRIGGER, named at capability grain and not as an artifact: RUNNER SUPPLY SUFFICIENT to execute the v1-compiler `--lib` test population on the real acceptance path within the required context's wall budget, concurrently with the build and floor lanes and without displacing either — concretely, a fleet that admits a third required lane at the observed unit-test wall without raising the max over lanes. THIS IS NOT RETIRED BY SOMEONE RE-ADDING THE JOB, which the roster comment in `gunbc.witness_floor_workflow` now requires operator sign-off for; a job re-added into the same shortage reproduces the contention that caused the cut. Nor is it retired by running the tests somewhere unmeasured, or on a cadence — a cadence is a different drop and would need its own row. +**THE RUST UNIT TESTS LEAVE CI ENTIRELY; THE ALL-TARGETS LINT DOES NOT (2026-09-04).** gunbc#10078 promoted `rust-unit-tests` into `required_lanes_roster`, making `cargo test --release -p v1-compiler --lib` and `cargo clippy --all-targets -- -D warnings` merge-blocking. The 2026-09-04 operator ruling on runner capacity deletes the JOB. The two commands are separated rather than dropped together, and that separation is the whole of this row. PREVIOUS RUNG: mechanically preventable — a regression in any of the 774 `#[test]`s under src/v1/stage0 was exposed by an enrolled test and blocked the merge through the aggregate's `$UNIT` verdict fold. TEMPORARY RUNG: mitigatable, and only barely — the tests still exist, still refuse loudly, and `cargo test --release -p v1-compiler --lib` still runs them, but NO CI STEP EXECUTES THEM, so they run when a human chooses to and not otherwise. This is the exact state gunbc#9663 was created to end, and #9886's two failing tests landing on main with every required check green is the measured harm of it; that harm is re-admitted knowingly here, not rediscovered. WHAT IS NOT DROPPED, and a reader must not infer it from the job's absence: `repo_self_clippy_command` moved to `required-witnesses-build` as `rust_clippy_all_targets_step`, keeping its step id, its verdict and its position on a REQUIRED lane. That command is the only one on any CI path that compiles the integration-test and example targets — twelve of them sat red on main (2026-08-30) behind a green required run — so dropping it would have been a below-baseline floor regression under DESIGN §4b rather than a declared drop, and the ruling did not ask for it. REASON, and it is runner supply rather than doubt about the tests: the witnesses workflow carried seven jobs against a fleet that could not serve seven, each paying its own checkout, toolchain install and release build of one tree. The required context's wall is the MAX over its lanes, so contention among jobs that could have been steps was displacing the lanes that gate. The unit tests were cut rather than folded into an existing lane because the ruling asked for the Rust test population to leave CI, not to be relocated; folding them would have preserved the cost this row exists to remove. BOUNDED POPULATION: every `#[test]` in the v1-compiler crate reached by `--lib`, and every witness whose enrollment routed through that command — including `emit_import_lines_follow_resolved_binding_identity`, whose own drop row `emitted_bytes_witness_required_lane` was RETIRED on the strength of this lane being required and is un-retired in the same motion. No other lane, phase or claim changes rung; the build and floor lanes keep every edge they had. COUNTABLE: `test.claim.witness_floor_workflow_consolidation_witness_test` `w_RED_the_deleted_lanes_do_not_return` reds if the job returns while this row stands, and `test.claim.required_lane_claim_agreement_witness_test` `w_the_live_roster_is_read_and_carries_both_lanes` asserts its absence from the roster at identity grain. RESTORATION TRIGGER, named at capability grain and not as an artifact: RUNNER SUPPLY SUFFICIENT to execute the v1-compiler `--lib` test population on the real acceptance path within the required context's wall budget, concurrently with the build and floor lanes and without displacing either — concretely, a fleet that admits a third required lane at the observed unit-test wall without raising the max over lanes. THIS IS NOT RETIRED BY SOMEONE RE-ADDING THE JOB, which the roster comment in `gunbc.witness_floor_workflow` now requires operator sign-off for; a job re-added into the same shortage reproduces the contention that caused the cut. Nor is it retired by running the tests somewhere unmeasured, or on a cadence — a cadence is a different drop and would need its own row. ENROLMENT 2026-09-08 (gunbc#10822): this change adds `v1_compiler.cli_run.rostered_row_join` `file_roster_join_tests` to that `--lib` population. The restoration trigger is unchanged and covers them: they are `--lib` tests, so runner supply sufficient to execute that population restores them with the rest. ### The EMIT-COST-QUAL-0 wet battery's only sanctioned executing consumer — declared 2026-09-04 @@ -192,4 +192,8 @@ two passing produced-declaration emission witnesses whose charged CPU straddles ### Transitive determinism reachability over the fn-arrow call graph — declared 2026-09-05 -Transitive determinism reachability over the fn-arrow call graph: RUNG DROP, mechanically preventable -> mitigatable (deleted without replacement). Population: v2.lens.affected_set.corpus_dependency_view (fn-arrow composition, deleted), v2.lens.affected_set.entry_selection (fn-arrow declarations, deleted). Restored when: CAPABILITY: determinism_transitive_holds with pool_roots=['src/v2/lens'] and pool_files=[] passes within the 500ms per-claim safety deadline (required_floor_claim_cpu_safety_limit_ms, which preempted the claim and caused the interrupted_before_verdict). The existing call_reachable_decls already seeds from the entry module and follows reachable callee edges only (it does not iterate all closure declarations). The budget pressure comes from the NUMBER OF REACHABLE DECLARATIONS across ~25 lens modules when closure_modules includes the full lens directory — the callee walk from affected_set.dag's 78 entry-module declarations reaches enough targets to exceed 500ms. The repair is either: (i) optimize determinism_findings or callees_from_node on the fn-arrow skeleton to reduce per-declaration cost, so the same reachable population completes within 500ms; or (ii) scope pool_roots to exactly the modules reachable from affected_set.dag (rather than the entire src/v2/lens directory) so the walk is bounded while still following actual callee edges — re-test with pool_roots=['src/v2/lens'] as the passing condition that verifies the optimization is sufficient for the full lens directory. SUFFICIENT FOR: transitive determinism over the fn-arrow callee graph, at corpus grain, reaching a verdict (Holds or Findings) under the 500ms safety deadline. Until then, determinism over the fn-arrow call graph is unverified.. +Transitive determinism reachability over the fn-arrow call graph: RUNG DROP, mechanically preventable -> mitigatable (deleted without replacement). Population: v2.lens.affected_set.corpus_dependency_view (fn-arrow composition, deleted), v2.lens.affected_set.entry_selection (fn-arrow declarations, deleted). Restored when: CAPABILITY: THE CALLEE LOOKUP ANSWERS BY INDEX RATHER THAN BY SCANNING THE CANDIDATE POOL — v2.std.fn_index.decls_matching_callee resolves (module set, callee name) to its matching declarations through an index built once per acquisition, so the walk's cost is a function of the declarations it REACHES and not of the pool it was handed. SUFFICIENT FOR: determinism_transitive_holds with pool_roots=['src/v2/lens'] and pool_files=[] reaching a verdict (Holds or Findings) within the 500ms required_floor_claim_cpu_safety_limit_ms, at directory grain, without narrowing the subject. MEASURED CAUSE, replacing this row's previous attribution to the number of reachable declarations, which is refuted: at directory grain the walk reaches FEWER THAN 100 declarations against a candidate pool of 1500-2000 and costs 10137ms — about 90ms per reached declaration, against 2.9ms per reached declaration at file grain where the pool equals the reached set. The two cost models separate cleanly across the two grains: O(reached x pool) agrees within 1.9x, O(reached squared) disagrees by 44x. The mechanism is that decls_matching_callee runs filter(decls_in_modules(decls, modules), name == callee) — a full scan of the candidate pool plus a module-membership scan — once per callee per reached declaration. At file grain pool equals n, so the shape LOOKS quadratic in the reached set; at directory grain the two decouple and the pool dominates. Re-derive the instrument rather than trusting these figures: claim_batch over a probe calling call_reachable_decls at both grains, bracketing the reached count and the pool size by threshold ladder. AND READ THE ENFORCING POPULATION FROM THE ARTIFACT RATHER THAN FROM A CLOCK: required-floor-claim-cost is published by every completed witnesses run, so this claim's cost on the population that actually enforces it is readable with no push, no lane and no window -- an earlier reading of this row treated a merge freeze as preventing measurement, which confuses producing a new head with reading a receipt from a run that already finished. PARTITION THOSE ROWS BY eval_steps AND BY NOTHING ELSE. eval_steps is deterministic work rather than a clock, so it is immune to runner variance and pool contention, it states the effect of a work-removing repair directly instead of inferring it from a noisy distribution, and it is what makes the partition AUDITABLE: a run classified as pre-repair by its id and date was found to be a post-repair sample only because its eval_steps disagreed with its neighbours. Partitioning by run id, by date or by branch silently mixes the arms and yields a smaller, believable, wrong delta. AND READ THE PRE-REPAIR ARM AS A DISTRIBUTION THAT WORSENS AS IT GROWS, which is the strongest form of the original complaint rather than a restatement of it: as the pre-repair sample grew the top of the range rose and the margin under the 500ms limit fell, with a growing minority of runs landing within 50ms of the line. A shrinking margin under a GROWING sample is not noise -- noise widens a range symmetrically around a stable centre, while this arm's maximum climbs, which is what an intermittent blocker looks like before it becomes a constant one. That is why this class was worth repairing at a realized n that looked survivable. BOTH REPAIRS THIS ROW PREVIOUSLY PRESCRIBED ARE REFUTED BY THAT MEASUREMENT AND ARE NAMED HERE SO THEY ARE NOT RE-ATTEMPTED. (i) Reducing PER-DECLARATION cost in determinism_findings or callees_from_node cannot reach the line: the reached population is under 100 and the walk is 10137ms, so even halving per-declaration work leaves seconds. (ii) Scoping pool_roots to exactly the modules reachable from affected_set.dag WOULD lower the number, and that is the trap rather than the repair — it lowers it by SHRINKING THE POOL, leaving the shape intact, and it is the same narrow-the-subject move that created this drop in the first place. A drop retired that way re-breaks the moment the subject widens, and the widening is the thing the drop exists to restore. COST SHAPES IN THIS WALK ARE REAL AND ARE EXPLICITLY NOT THE COST, recorded so a later reader does not fix them and believe the defect is gone. THREE OF THEM ARE NOW REPAIRED and are named in the past tense because their symbols no longer resolve: call_reachable_decls_go scanned `reached` linearly per seed through a decl_in_list helper that is deleted, and now answers membership by keyed lookup over a seen map; it appended to `reached` with a copied accumulator and now conses and reverses once at the end; and list_append(rest, callee_decls) copied the worklist every step and is now guarded so an empty callee list does not copy it. REPAIRING THEM DID NOT RETIRE THIS DROP AND WAS NEVER EXPECTED TO: the pool scan in decls_matching_callee named by this trigger's CAPABILITY clause is untouched, and this row is retired by that capability and by nothing else. ONE SHAPE IN callees_from_node IS AN ADMITTED, UNRESOLVED OBLIGATION rather than a repair: its duplicate suppression is a linear scan of the accumulated list per callee, which is quadratic in the callees of one declaration and is subject to the same bare-minimum-cost rule. A map-keyed arm was written and measured SLOWER than the list scan at the realized callee counts and was reverted on that measurement; big-O did not grant permission to ship the slower realization, and equally the slower first attempt did not retire the defect it was aimed at. The obligation dissolves when the walk is asked at a grain whose per-declaration callee counts are large enough for the keyed arm to win, and it is recorded here rather than in the source so that it is folded with the rest of this row. THE REMAINING UNREPAIRED SHAPES: determinism_transitive_findings folds with concat(acc, determinism_findings(...)), a copied accumulator that is FREE while the corpus is clean and degrades quadratically exactly when findings appear, so it is invisible in every green run and expensive only when something is already wrong. determinism_live_closure_modules folds list_snoc_item over module_declaration_facts_live(pool_roots: []), a copied accumulator over a list that is empty today. Until this trigger fires, determinism over the fn-arrow call graph is unverified.. + +### admission rows on falsifier-family cadences with no scheduled route, awaiting transition to a cadence that executes — declared 2026-09-08 + +admission rows on falsifier-family cadences with no scheduled route, awaiting transition to a cadence that executes: RUNG DROP, mitigatable -> outside the ladder (lost as a passenger of .github/workflows/falsifier.yml, deleted at 611fd02770 (#8283)). Population: dag/test/claim/method_arg_declared_contract_witness_test.dag::w_method_arg_infers_against_declared_contract_not_element_type_test, src/v2/test/claim/manual/english_emit_add_test.dag::english_emit_add_ingest_round_trip_holds, dag/test/claim/legacy_test_behavior_disposition_acceptance_test.dag::legacy_test_behavior_unclassified_frontier_is_zero, dag/test/claim/observation_raw_print_retirement_acceptance_test.dag::observation_emit_frontier_is_zero, dag/test/claim/long/g2_data_reference_under_selection_witness_test.dag::specimen_classifies_as_runtime_read, dag/test/claim/long/g2_data_reference_under_selection_witness_test.dag::specimen_does_not_classify_as_local_read, dag/test/claim/source_integration_proof_kernel_acceptance_test.dag::witness_p1_proof_kernel_acceptance_contract_holds, dag/test/claim/namespace_reference_derived_closure_acceptance_test.dag::witness_namespace_reference_derived_closure_closing_contract_holds, src/v2/test/claim/long/direct_rust_door_production_group_test.dag::direct_rust_door_production_group_closing_expectation_holds, dag/test/claim/self_host_parse_engine_hooks_behavioral_witness_test.dag::self_host_parse_engine_hooks_behavioral_receipt_holds, dag/test/claim/self_host_use_site_verdict_behavioral_witness_test.dag::self_host_use_site_verdict_behavioral_receipt_holds, dag/test/claim/self_host_body_producer_behavioral_witness_test.dag::self_host_body_producer_behavioral_receipt_holds, dag/test/claim/self_host_discovery_enumeration_behavioral_witness_test.dag::self_host_discovery_enumeration_behavioral_receipt_holds, dag/test/claim/self_host_target_carriers_behavioral_witness_test.dag::self_host_target_carriers_behavioral_receipt_holds, dag/test/claim/self_host_03_normalize_behavioral_witness_test.dag::self_host_03_normalize_behavioral_receipt_holds, src/v2/test/claim/long/production_qualification_origin_probe_witness_test.dag::witness_live_scope_fixture_origin_discoverable_in_entry_closure_reds, src/v2/test/claim/long/production_qualification_origin_probe_witness_test.dag::witness_live_structural_fixture_mint_site_discovered_reds, src/v2/test/claim/long/production_qualification_origin_probe_witness_test.dag::witness_ingest_mint_surface_requires_call_occurrence_frontier_reds, src/v2/test/claim/long/production_qualification_origin_probe_witness_test.dag::witness_direct_door_smoke_requires_closure_or_call_occurrence_frontier_reds, src/v2/test/claim/self_host/compiler_closure_emit_from_ingest_test.dag::compiler_closure_scoped_ingest_module_count_ok_holds, src/v2/test/claim/self_host/compiler_closure_emit_from_ingest_test.dag::compiler_closure_ingest_overflow_refuses_never_empty_holds, src/v2/test/claim/self_host/compiler_closure_ingest_boundary_witness_test.dag::compiler_closure_ingest_boundary_exact_cap_transport_holds, src/v2/test/claim/long/infer_transform_binary_infix_witness_test.dag::infer_transform_add_vertical_witness_holds. Restored when: each named identity carries a cadence with a live scheduled route, at which point each row above is removed from transitional_admission_exception_identities and the identity's exception in explicit_witness_admission.dag is deleted (the gate consumes the list, so removing from the list retires the exception) SHARED-CAPABILITY HAZARD, extending the same hazard recorded on three sibling drops (source_root_ingest_gate_rung_drop, deleted_cadence_reference_drop, witness_deferral_freeze_forward_rule_rung_drop): this trigger's cadence half is the SAME capability — a workflow or scheduled entry point that runs a predict-only cold comparison over the corpus — that those three drops wait on, over DISJOINT populations. Measured 2026-09-03: the required run's own phase announcement enumerates exactly five phases across two lanes (build owns regen and generated-artifact, witnesses owns parse, namespace-wave-admission and floor), and schedule:/cron: appear zero times across all three workflow files, so the shared blocker is a MISSING CATEGORY rather than a missing invocation. The hazard is therefore not that the trigger is wrong but that ONE event fires four triggers while one author watches one row: whoever lands the first cadence must retire all four or leave the others standing on a trigger that has already fired, which is the precise inverse of retired-by-its-trigger-and-by-nothing-else.. From fecef051dc53ca8cbee284319685879270dfc583 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 21:10:59 +0000 Subject: [PATCH 32/89] Emitter: three native-parity repairs for the post-merge 00_compile closure build Three divergence classes measured as the 21 rustc errors on the natively emitted 00_compile closure after the main merge, each repaired at the .dag source with a discriminating witness: - Locality wins over a foreign ambiguity (12 E0433 in v2_std_integer.rs): alias_rhs_base_module_filename asked the global leaf index, saw LeafAmbiguous for Compose, and emitted the poison marker even inside v2.std.integer itself, where source resolution binds the local declaration before any cross-module lookup. The local physical declaration now shadows foreign declarers; the poison marker still stands for a leaf two FOREIGN modules declare. - The qualifier is the disambiguator (8 E0425/E0433 in v2_lens_fact_density.rs): the qualified use-line route declined any globally-ambiguous leaf, but a qualified reference names its provider in its own spelling. The route now resolves by DeclaredCallableIdentity at the qualifier, keeping the type-declared and export-proof walls. The dotted spelling reaches the route through the value surface (a qualified value projection's borrowed type stamps the match patterns' parent_enum); the witness reproduces that chain exactly, and its exclude half pins the E0603 boundary (the dotted VARIANT head must still be declined). - Clone-bound forwarding is transitive (1 E0277 in std_realization_measurement.rs): the call-forwarding derivation re-derived only each callee's SELF-derived half, so a callee whose bound is itself forwarded re-derived to empty. The derivation now recurses over the call graph with the module's visited-set termination; the equality half stays one-hop as declared. Witnesses: 56/56 PASS on the rebuilt seed; regen fixed point holds. --- ...d_call_target_realization_witness_test.dag | 73 ++++++++ dag/test/fixture/nat_qualified_provider.dag | 17 ++ src/v1/05_emit_rust.dag | 123 ++++++++++--- src/v1/stage0/src/v1_compiler_emit_rust.rs | 171 +++++++++++++----- 4 files changed, 316 insertions(+), 68 deletions(-) create mode 100644 dag/test/fixture/nat_qualified_provider.dag diff --git a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag index a78e9057939..9bdbfce55d8 100644 --- a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag +++ b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag @@ -131,6 +131,79 @@ test fn w_nonempty_record_data_value_keeps_the_map_spelling() -> Bool { ) } + +// LOCALITY WINS OVER A FOREIGN AMBIGUITY, the third native-parity divergence class. The leaf +// index is global: a leaf two closure modules declare answers LeafAmbiguous, and the alias-RHS +// module resolver used to end at the poison marker for EVERY such reference -- including a +// reference inside one of the declaring modules itself, where source resolution binds the local +// declaration before any cross-module lookup. Measured on the native 00_compile closure build as +// twelve E0433 sites in v2_std_integer.rs, whose own `type Compose` co-resides +// with std.machine_constraints' `Compose` alias. The probe reproduces that shape exactly: a local +// parametric opaque Compose, a foreign Compose pulled in by the MachineWidth import, and an alias +// whose RHS applies the local one. It reds if the poison marker returns, and if the local +// declaration stops being the resolution. +test fn w_alias_rhs_leaf_declared_locally_shadows_foreign_declarer() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.ambiguous_leaf_locality_probe\nimport std.machine_constraints { MachineWidth }\ntype Compose\ntype Word = Compose, MachineWidth<8>>\n", + "src/test_claim_ambiguous_leaf_locality_probe.rs", + ["pub type Word = Compose<"], + ["__ambiguous_leaf__Compose"] + ) +} + +// THE QUALIFIER IS THE DISAMBIGUATOR, the fourth native-parity divergence class. A qualified type +// reference names its provider in its own spelling, so a global leaf ambiguity never attaches to +// it -- but the qualified use-line route asked the leaf index, saw LeafAmbiguous, and declined to +// synthesize any line, leaving the bare leaf unimported in the emitted file. Measured on the +// native 00_compile closure build as eight E0425/E0433 `Nat` sites in v2_lens_fact_density.rs, +// which references v2.std.nat.Nat qualified while the std.nat alias co-resides in the closure. +// +// HOW THE DOTTED SPELLING REACHES THE ROUTE constrained the probe shape, because a signature +// annotation alone does NOT produce it: the resolver binds `n: v2.std.nat.Nat` to the declaration +// node and the surface walk collects the bare leaf (measured: a signature-only probe delivered no +// dotted name). The dotted spelling enters through the VALUE surface -- a qualified value +// projection (`v2.std.nat.Zero` as match scrutinee) takes its inferred type from +// qualify_borrowed_type_names, whose borrowed view carries the qualified name, and +// annotate_pattern_parent_enums stamps that spelling as the patterns' parent_enum, which +// collect_pattern_ref_names emits. That is the fact_density chain in miniature. The record-lit +// head contributes the dotted VARIANT `v2.std.nat.Succ` to the same route, so the exclude half +// pins the E0603 boundary: the variant must still be declined (it is not a type the qualifier +// declares), or the route re-opens the sixteen-site false-import class its comment records. +// The pool admits a module only through an import edge, so +// test.fixture.nat_qualified_provider holds the v2.std.nat edge while std.nat is imported +// directly; both Nat declarers co-reside and the leaf is globally ambiguous, exactly the closure +// condition the fix answers. It reds if the qualifier stops resolving the ambiguity, and if the +// variant leaf starts being imported. +test fn w_qualified_type_reference_qualifier_disambiguates_the_leaf() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.qualified_leaf_disambiguation_probe\nimport test.fixture.nat_qualified_provider { nat_qualified_provider_touch }\nimport std.nat { nat_max }\nfn probe() -> v2.std.nat.Nat {\n match v2.std.nat.Zero {\n v2.std.nat.Zero => v2.std.nat.Zero\n v2.std.nat.Succ { prev: p } => v2.std.nat.Succ { prev: p }\n }\n}\n", + "src/test_claim_qualified_leaf_disambiguation_probe.rs", + ["pub use crate::v2_std_nat::Nat;", "pub fn probe() -> Rc"], + ["pub use crate::v2_std_nat::Succ;"] + ) +} + +// THE SECOND HOP OF CLONE-BOUND FORWARDING, the fifth native-parity divergence class. The +// call-forwarding derivation re-derived each callee's bound from the callee's SELF-derived half +// only, so a callee whose bound is itself forwarded re-derived to empty and the wrapper emitted +// bare -- declared one-hop until the specimen arrived: std.realization_measurement +// witness_measured_rollup_differs_from_predicted_zero through cost_account_time_count (itself +// forwarded from cost_time_axis_established's match-scrutinee clone), E0277 on the native +// 00_compile closure build. The probe is that chain in miniature: inner earns the bound by +// matching its Rc'd coproduct by value, middle forwards it one hop, outer needs two. The +// derivation is now transitive over the call graph (visited-set termination, the module's existing +// pattern); this row reds if the second hop is lost, and also pins the two boundary controls -- +// middle's one-hop bound must stand and wrap, which constructs rather than clones, must stay bare +// (a spurious bound tightens a signature and breaks its callers). +test fn w_clone_bound_forwards_transitively_over_two_call_hops() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.clone_bound_two_hop_probe\ntype Measure { count: M }\ntype CostTimeAxis\n = CostTimeMeasured { value: Measure }\n | CostTimeUnestablished\n\ntype CostAccount {\n time: CostTimeAxis\n}\nfn inner(axis: CostTimeAxis) -> Bool {\n match axis {\n CostTimeMeasured { value: _ } => true\n CostTimeUnestablished => false\n }\n}\nfn middle(account: CostAccount) -> Bool {\n inner(axis: account.time)\n}\nfn wrap(m: Measure) -> CostAccount {\n CostAccount { time: CostTimeMeasured { value: m } }\n}\nfn outer(wall_time: Measure) -> Bool {\n middle(account: wrap(m: wall_time))\n}\n", + "src/test_claim_clone_bound_two_hop_probe.rs", + ["pub fn inner", "pub fn middle", "pub fn outer", "pub fn wrap("], + ["pub fn outer"] + ) +} + // THE PROBE IMPORTS v2.std.collection AND THEN CALLS IT BY ITS QUALIFIED NAME, which looks // redundant and is not. compile_dag_rust_emit_check compiles through the witness harness's // restricted pool (2973 modules against the corpus's 4261), and a qualified spelling alone does not diff --git a/dag/test/fixture/nat_qualified_provider.dag b/dag/test/fixture/nat_qualified_provider.dag new file mode 100644 index 00000000000..0e0d79a9c7f --- /dev/null +++ b/dag/test/fixture/nat_qualified_provider.dag @@ -0,0 +1,17 @@ +module test.fixture.nat_qualified_provider + +import v2.std.nat { Nat, nat_add } + +// Pool-edge fixture for +// test.claim.self_host_emitted_call_target_realization_witness_test's +// w_qualified_type_reference_qualifier_disambiguates_the_leaf. The mechanism under test is the +// emitter's qualified-type use-line route, which only sees a reference when the resolver leaves it +// AUTHORED -- and the resolver leaves v2.std.nat.Nat authored only in a module that does NOT import +// v2.std.nat (the v2.lens.fact_density shape measured on the 00_compile closure). The witness pool +// admits a module to the closure only through an import edge, so this fixture holds the edge: the +// probe imports this module, v2.std.nat enters the closure transitively, and the probe's own +// qualified reference stays authored. The bare import-and-reference below keeps the fixture's own +// emission clean through the authored-import route rather than the route under test. +fn nat_qualified_provider_touch(a: Nat) -> Nat { + nat_add(a: a, b: a) +} diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index c9bf8efbe58..1b87b408f5f 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -5126,6 +5126,17 @@ fn reference_derived_use_line_plan(items: List, unlisted_type_names: List< // the named qualifier before any line is synthesized; every other qualified name is the value // route's, and this route says nothing about it. Each decision this route does take is a census row // beside the bare-name rows -- the qualified spelling is the row's name, so the two never collide. +// +// THE QUALIFIER IS THE DISAMBIGUATOR. The leaf index is global: `LeafAmbiguous` says two modules in +// the closure declare the leaf, and this route used to decline to the ambiguous row on that answer +// alone. A QUALIFIED reference already names its provider in its own spelling, so the global +// ambiguity never attaches to it -- the question is only whether the named qualifier declares the +// leaf as a type and exports it, the same construction wall the ItemFound arm passes, now asked by +// identity lookup at the qualifier. Declining used to drop the use-line for a reference the author +// had fully resolved: measured as the eight E0425/E0433 `Nat` sites in v2_lens_fact_density.rs, +// whose `v2.std.nat.Nat` co-resides with the std.nat alias in the 00_compile closure. The ambiguous +// row still stands for the reference whose qualifier does NOT declare the leaf -- that one is +// genuinely unresolvable here. fn qualified_type_reference_rows(names: List, this_module_name: String, emit_info: EmitGraphInfo, already: Map, names_already_in_lines: List, registry: Map, export_sets: Map>, typed_modules: List, source_indices: Map, module_index: ModuleIndex) -> List { let leaves_done = names_already_in_lines |> fold(init: empty_map(), f: (acc, nm) => map_insert(acc, nm, true)) names |> flat_map(name => @@ -5138,21 +5149,33 @@ fn qualified_type_reference_rows(names: List, this_module_name: String, } else { match lookup_item_by_leaf(leaf: leaf, registry: registry, emit_info: emit_info) { ItemNotFound => [ReferenceDerivedCandidateRow { module_name: this_module_name, name: name, disposition: CandidateRegistryAbsent }] - ItemLeafAmbiguous { leaf: _ } => [ReferenceDerivedCandidateRow { module_name: this_module_name, name: name, disposition: CandidateLeafAmbiguous }] - ItemFound { info: info } => - if !(registry_row_is_type_declared_in(info: info, module_name: qualifier)) { - [] - } else if provider_proven_exports_symbol(name: leaf, provider_module: qualifier, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) { - [ReferenceDerivedCandidateRow { module_name: this_module_name, name: name, disposition: CandidateSurvived { provider_module: qualifier } }] - } else { - [ReferenceDerivedCandidateRow { module_name: this_module_name, name: name, disposition: CandidateExportProofFailed { provider_module: qualifier } }] + ItemLeafAmbiguous { leaf: _ } => + match lookup_item_by_identity(registry: registry, id: DeclaredCallableIdentity { owner_module_path: qualifier, decl_name: leaf }) { + Present { value: info } => + qualified_type_reference_row_for_info(name: name, leaf: leaf, qualifier: qualifier, this_module_name: this_module_name, info: info, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) + Absent => [ReferenceDerivedCandidateRow { module_name: this_module_name, name: name, disposition: CandidateLeafAmbiguous }] } + ItemFound { info: info } => + qualified_type_reference_row_for_info(name: name, leaf: leaf, qualifier: qualifier, this_module_name: this_module_name, info: info, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) } } } ) } +// The qualifier-keyed decision both lookup arms share (DESIGN section 2 -- one derivation, two +// call sites): the leaf must be a TYPE the qualifier declares, and the qualifier must prove it +// exports the symbol, before any use-line is synthesized. +fn qualified_type_reference_row_for_info(name: String, leaf: String, qualifier: String, this_module_name: String, info: ItemInfo, export_sets: Map>, typed_modules: List, source_indices: Map, module_index: ModuleIndex) -> List { + if !(registry_row_is_type_declared_in(info: info, module_name: qualifier)) { + [] + } else if provider_proven_exports_symbol(name: leaf, provider_module: qualifier, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) { + [ReferenceDerivedCandidateRow { module_name: this_module_name, name: name, disposition: CandidateSurvived { provider_module: qualifier } }] + } else { + [ReferenceDerivedCandidateRow { module_name: this_module_name, name: name, disposition: CandidateExportProofFailed { provider_module: qualifier } }] + } +} + // Named because `info.kind == TypeItem` is a comparison the .dag surface reads as a record literal. fn registry_row_is_type_declared_in(info: ItemInfo, module_name: String) -> Bool { if info.module_name != module_name { false } else { match info.kind { TypeItem => true _ => false } } @@ -6012,6 +6035,17 @@ fn alias_rhs_rust_qualify_module_filename(name: String, alias_module: String, im // The leaf index says which module owns the spelling; the ROW still comes from the registry, by the // identity that index just completed. Two lookups, one authority. +// +// THE INDEX IS GLOBAL AND THE RESOLUTION IS MODULE-LOCAL. `LeafAmbiguous` records that two modules +// in the closure declare the spelling; it does NOT record that the module under emission is one of +// them, and when it is, the question is not ambiguous at all: source resolution binds an +// unqualified reference to the module's own declaration before any cross-module lookup, so the +// local declaration shadows every foreign declarer of the same leaf. Answering +// `ambiguous_leaf_module_refusal` there poisons a reference the source resolved exactly -- measured +// on the 00_compile closure as twelve E0433 sites in v2_std_integer.rs, whose `Compose` is declared +// by v2.std.integer itself and shadowed nothing else. The refusal still stands when the module +// under emission has no claim: a leaf two FOREIGN modules declare has no resolution here, and the +// import-or-registry arm below still ends at the poison marker for it. fn alias_rhs_base_module_filename(name: String, alias_module: String, imports: List, source_indices: Map, scope: InferScope, registry: Map, export_sets: Map>, typed_modules: List, module_index: ModuleIndex) -> String { let local_mod = module_to_filename(name: alias_module) let owner_info = match map_get(module_index.leaf_owner_modules, name) { @@ -6031,7 +6065,11 @@ fn alias_rhs_base_module_filename(name: String, alias_module: String, imports: L alias_rhs_base_module_from_import_or_registry(name: name, imports: imports, source_indices: source_indices, registry: registry, local_mod: local_mod, export_sets: export_sets, typed_modules: typed_modules, module_index: module_index) } Absent => - alias_rhs_base_module_from_import_or_registry(name: name, imports: imports, source_indices: source_indices, registry: registry, local_mod: local_mod, export_sets: export_sets, typed_modules: typed_modules, module_index: module_index) + if has_physical_type_def_in_module_filename(rhs_name: name, mod_filename: local_mod, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) { + local_mod + } else { + alias_rhs_base_module_from_import_or_registry(name: name, imports: imports, source_indices: source_indices, registry: registry, local_mod: local_mod, export_sets: export_sets, typed_modules: typed_modules, module_index: module_index) + } } } @@ -8526,7 +8564,8 @@ fn v1_call_forwarding_clone_bound_param_names( fn_decl_items: Map, emit_info: EmitGraphInfo, shared_types: Set, - source_indices: Map + source_indices: Map, + visited: List ) -> List { if (generic_param_names |> count) == 0 { return [] @@ -8538,7 +8577,8 @@ fn v1_call_forwarding_clone_bound_param_names( fn_decl_items: fn_decl_items, emit_info: emit_info, shared_types: shared_types, - source_indices: source_indices + source_indices: source_indices, + visited: visited ) _ => [] } @@ -8551,25 +8591,44 @@ fn v1_call_forwarding_clone_bound_param_names( fn_decl_items: fn_decl_items, emit_info: emit_info, shared_types: shared_types, - source_indices: source_indices + source_indices: source_indices, + visited: visited ) ) ) } -// One call site: resolve the callee's declaration, re-derive the callee's OWN clone bound with the +// One call site: resolve the callee's declaration, re-derive the callee's clone bound with the // same derivation emit_fn_def runs on itself (DESIGN.md S2 -- one derivation, two call sites, never // a re-implementation), then forward it onto the wrapper's params. +// +// THE DERIVATION IS THE FULL ONE, TRANSITIVE OVER THE CALL GRAPH. It was previously the +// self-derived half only (v1_fn_body_derived_clone_param_names), declared one-hop: a callee whose +// own bound is itself only FORWARDED re-derived to empty here, and the wrapper emitted bare. The +// specimen arrived in the 00_compile closure: std.realization_measurement +// witness_measured_rollup_differs_from_predicted_zero calls std.realization_schedule +// cost_account_time_count, whose S: Clone is itself forwarded from cost_time_axis_established's +// match-scrutinee clone -- two hops, so the wrapper's E0277 was the one-hop boundary firing, not a +// new class. The recursion is the same walk this module already runs, applied to the callee's body +// exactly as emit_fn_def applies it to its own. Termination is the visited set every cyclic-graph +// walk in this module already carries (name_in_transitive_export_surface): a recursive callee +// answers its self-derived half alone, which is the pre-existing reading of a cycle and stands. +// THE EQUALITY HALF BELOW STAYS ONE-HOP: no specimen has needed its second hop, and widening it +// without one is the hypothetical-arity move v1_call_forwarding_bound_wrapper_param_names refuses. fn v1_call_site_clone_forwarded_param_names( call: Node, generic_param_names: List, fn_decl_items: Map, emit_info: EmitGraphInfo, shared_types: Set, - source_indices: Map + source_indices: Map, + visited: List ) -> List { let si = source_indices let callee_name = expr_call_func_at(texpr: call, source_indices: si) + if visited |> any(v => v == callee_name) { + return [] + } match map_get(fn_decl_items, callee_name) { Absent => [] Present { value: callee_item } => @@ -8579,13 +8638,24 @@ fn v1_call_site_clone_forwarded_param_names( let callee_params = callee_item.params let callee_type_params = function_type_params(params: callee_params) let callee_generic_param_names = callee_type_params |> map(p => generic_param_name_at(n: p, source_indices: si)) - let callee_clone_param_names = v1_fn_body_derived_clone_param_names( - params: callee_params, - inferred: resolved_type(n: callee_item), - body: callee_body, - emit_info: emit_info, - shared_types: shared_types, - source_indices: si + let callee_clone_param_names = v1_union_bound_param_names( + base: v1_fn_body_derived_clone_param_names( + params: callee_params, + inferred: resolved_type(n: callee_item), + body: callee_body, + emit_info: emit_info, + shared_types: shared_types, + source_indices: si + ), + extra: v1_call_forwarding_clone_bound_param_names( + generic_param_names: callee_generic_param_names, + body: callee_body, + fn_decl_items: fn_decl_items, + emit_info: emit_info, + shared_types: shared_types, + source_indices: si, + visited: concat(visited, [callee_name]) + ) ) v1_call_forwarding_forwarded_param_names( call: call, @@ -8630,9 +8700,11 @@ fn v1_call_site_clone_forwarded_param_names( // BoundedToDirectSingleCallLambdaBody already names and is measured on its own regen, not smuggled // in beside an equality fix. // -// ONE HOP, DECLARED. A callee that earns its own bound only by forwarding two hops up is not -// bounded here in one pass; it is discovered as each intermediate fn is itself re-emitted, exactly -// as the clone half's note describes, and a specimen needing a fixpoint over the call graph is the +// ONE HOP, DECLARED -- and now the equality half alone. The Clone half above went transitive over +// the call graph when its two-hop specimen arrived (std.realization_measurement's witness wrapper +// through cost_account_time_count, named in v1_call_site_clone_forwarded_param_names). This half +// keeps the boundary: a callee that earns its own equality bound only by forwarding two hops up is +// not bounded here in one pass, and a specimen needing the same transitivity for equality is the // next-rung trigger rather than a silent gap. fn v1_call_forwarding_equality_bound_param_names( generic_param_names: List, @@ -8808,7 +8880,8 @@ fn emit_fn_def(name: String, params: List, inferred: Node, body: Node, reg fn_decl_items: emit_info.fn_decl_items, emit_info: emit_info, shared_types: shared_types, - source_indices: si + source_indices: si, + visited: [name] ) let derived_clone_param_names_with_rc_match = v1_union_bound_param_names( base: self_derived_clone_param_names, diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index fac5921074c..46212e50edc 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -9970,45 +9970,46 @@ pub fn qualified_type_reference_rows( })]) } ItemLookup::ItemLeafAmbiguous { leaf: _, .. } => { - Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { - module_name: this_module_name.clone(), - name: name.clone(), - disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateLeafAmbiguous), -})]) - } - ItemLookup::ItemFound { info: info, .. } => { - if !registry_row_is_type_declared_in( - info.clone(), - qualifier.clone(), + match crate::v1_compiler_emit::lookup_item_by_identity( + registry.clone(), + Rc::new(DeclaredCallableIdentity { + owner_module_path: qualifier.clone(), + decl_name: leaf.clone(), + }), ) { - Rc::new(vec![]) - } else { - if provider_proven_exports_symbol( + Some(info) => qualified_type_reference_row_for_info( + name.clone(), leaf.clone(), qualifier.clone(), + this_module_name.clone(), + info.clone(), export_sets.clone(), typed_modules.clone(), source_indices.clone(), module_index.clone(), - ) { - Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { - module_name: this_module_name.clone(), - name: name.clone(), - disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { - provider_module: qualifier.clone(), -}), -})]) - } else { + ), + std::option::Option::None => { Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { module_name: this_module_name.clone(), name: name.clone(), - disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { - provider_module: qualifier.clone(), -}), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateLeafAmbiguous), })]) } } } + ItemLookup::ItemFound { info: info, .. } => { + qualified_type_reference_row_for_info( + name.clone(), + leaf.clone(), + qualifier.clone(), + this_module_name.clone(), + info.clone(), + export_sets.clone(), + typed_modules.clone(), + source_indices.clone(), + module_index.clone(), + ) + } } } } @@ -10022,6 +10023,49 @@ pub fn qualified_type_reference_rows( } } +pub fn qualified_type_reference_row_for_info( + name: String, + leaf: String, + qualifier: String, + this_module_name: String, + info: Rc, + export_sets: Rc>>>, + typed_modules: Rc>>, + source_indices: Rc>>, + module_index: Rc, +) -> Rc>> { + if !registry_row_is_type_declared_in(info.clone(), qualifier.clone()) { + Rc::new(vec![]) + } else { + if provider_proven_exports_symbol( + leaf.clone(), + qualifier.clone(), + export_sets.clone(), + typed_modules.clone(), + source_indices.clone(), + module_index.clone(), + ) { + Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { + module_name: this_module_name.clone(), + name: name.clone(), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: qualifier.clone(), + }), + })]) + } else { + Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { + module_name: this_module_name.clone(), + name: name.clone(), + disposition: Rc::new( + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: qualifier.clone(), + }, + ), + })]) + } + } +} + pub fn registry_row_is_type_declared_in(info: Rc, module_name: String) -> bool { if (info.module_name.clone() != module_name.clone()) { false @@ -12146,16 +12190,28 @@ pub fn alias_rhs_base_module_filename( ) } } - std::option::Option::None => alias_rhs_base_module_from_import_or_registry( - name.clone(), - imports.clone(), - source_indices.clone(), - registry.clone(), - local_mod.clone(), - export_sets.clone(), - typed_modules.clone(), - module_index.clone(), - ), + std::option::Option::None => { + if has_physical_type_def_in_module_filename( + name.clone(), + local_mod.clone(), + typed_modules.clone(), + source_indices.clone(), + module_index.clone(), + ) { + local_mod.clone() + } else { + alias_rhs_base_module_from_import_or_registry( + name.clone(), + imports.clone(), + source_indices.clone(), + registry.clone(), + local_mod.clone(), + export_sets.clone(), + typed_modules.clone(), + module_index.clone(), + ) + } + } } } } @@ -18158,6 +18214,7 @@ pub fn v1_call_forwarding_clone_bound_param_names( emit_info: Rc, shared_types: Rc>, source_indices: Rc>>, + visited: Rc>, ) -> Rc> { stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { if ((generic_param_names.clone().len() as i64) == 0) { @@ -18171,6 +18228,7 @@ pub fn v1_call_forwarding_clone_bound_param_names( emit_info.clone(), shared_types.clone(), source_indices.clone(), + visited.clone(), ), _ => Rc::new(vec![]), }; @@ -18186,6 +18244,7 @@ pub fn v1_call_forwarding_clone_bound_param_names( emit_info.clone(), shared_types.clone(), source_indices.clone(), + visited.clone(), ), ) }, @@ -18200,10 +18259,23 @@ pub fn v1_call_site_clone_forwarded_param_names( emit_info: Rc, shared_types: Rc>, source_indices: Rc>>, + visited: Rc>, ) -> Rc> { { let si = source_indices.clone(); let callee_name = crate::v1_std_core::expr_call_func_at(call.clone(), si.clone()); + if { + let mut __found = false; + for v in visited.iter().cloned() { + if (v.clone() == callee_name.clone()) { + __found = true; + break; + } + } + __found + } { + return Rc::new(vec![]); + } match v1_rt::map_get(&fn_decl_items, callee_name.clone()) { std::option::Option::None => Rc::new(vec![]), Some(callee_item) => match callee_item.body.clone() { @@ -18221,14 +18293,26 @@ pub fn v1_call_site_clone_forwarded_param_names( } __result }); - let callee_clone_param_names = v1_fn_body_derived_clone_param_names( - callee_params.clone(), - crate::v1_compiler_infer_types::resolved_type(callee_item.clone()), - callee_body.clone(), - emit_info.clone(), - shared_types.clone(), - si.clone(), - ); + let callee_clone_param_names = + crate::v1_compiler_trait_bound_witness::v1_union_bound_param_names( + v1_fn_body_derived_clone_param_names( + callee_params.clone(), + crate::v1_compiler_infer_types::resolved_type(callee_item.clone()), + callee_body.clone(), + emit_info.clone(), + shared_types.clone(), + si.clone(), + ), + v1_call_forwarding_clone_bound_param_names( + callee_generic_param_names.clone(), + callee_body.clone(), + fn_decl_items.clone(), + emit_info.clone(), + shared_types.clone(), + si.clone(), + v1_rt::concat(visited.clone(), Rc::new(vec![callee_name.clone()])), + ), + ); v1_call_forwarding_forwarded_param_names( call.clone(), function_value_params(callee_params.clone()), @@ -18451,6 +18535,7 @@ pub fn emit_fn_def( emit_info.clone(), shared_types.clone(), si.clone(), + Rc::new(vec![name.clone()]), ); let derived_clone_param_names_with_rc_match = crate::v1_compiler_trait_bound_witness::v1_union_bound_param_names( From 73b582dea6bae46fce08a545d51f78218f1d0b30 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 21:14:58 +0000 Subject: [PATCH 33/89] Refuse variant record literals on the serde_json data path fail-closed A record literal with parent_enum present is a variant construction whose wire spelling is the parent coproduct's declared VariantEncoding policy -- a module-local fact of the parent's home module that emit_data_value_json does not carry. The zero-field arm's null and the map arm's untagged fields are both measured to fail serde deserialization under the internal-tag default, so the arm now refuses and the caller renders compile_error!, a build-time located refusal where a runtime panic on the data definition's expect was the latent alternative. The refusal names its trigger: a closure-wide wire-policy index beside EmitGraphInfo.type_decl_items. Witness: w_variant_record_lit_on_the_json_data_path_refuses_fail_closed forces the JSON path with a nested-record Holder and asserts the compile_error! spelling while excluding the former null mis-serialization. --- ...d_call_target_realization_witness_test.dag | 24 ++ src/v1/05_emit.dag | 51 +++-- src/v1/stage0/src/v1_compiler_emit.rs | 216 ++++++------------ 3 files changed, 135 insertions(+), 156 deletions(-) diff --git a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag index 9bdbfce55d8..2dab115fa48 100644 --- a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag +++ b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag @@ -132,6 +132,30 @@ test fn w_nonempty_record_data_value_keeps_the_map_spelling() -> Bool { } +// THE VARIANT BOUNDARY OF THE JSON DATA PATH, FAIL-CLOSED. A record literal with parent_enum +// PRESENT is a variant construction, and its correct wire spelling is the parent coproduct's +// declared VariantEncoding policy -- measured against serde 2026-09-08: the internal-tag default +// accepts only the tag map (`{"_variant": "A"}`), while `null` and the bare string are both +// rejected, so the zero-field record arm's `null` and the map arm's untagged fields are each +// measured-wrong for a variant. The policy is a module-local fact of the parent's home module +// (resolve_local_coproduct_wire_policy scans that module's items), which the JSON value path +// does not carry, so the arm REFUSES and the caller renders compile_error! -- a build-time +// located refusal where a runtime panic on `.expect("valid data definition")` was the latent +// alternative. The probe forces the JSON path (the Holder record nests Marker) and constructs +// the fieldless variant with braces; the bare spelling never reaches the arm (an ExprVar routes +// the whole value to direct construction via data_value_has_cross_refs). This row reds if the +// refusal is lost -- and greens the day the named trigger lands (a closure-wide wire-policy +// index beside EmitGraphInfo.type_decl_items), at which point it is rewritten to assert the +// spelled tag map. +test fn w_variant_record_lit_on_the_json_data_path_refuses_fail_closed() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.variant_json_refusal_probe\ntype E = A | B { x: Int }\ntype Marker {}\ntype Holder {\n e: E\n m: Marker\n}\ndata probe: Holder = Holder { e: A {}, m: Marker {} }\n", + "src/test_claim_variant_json_refusal_probe.rs", + ["compile_error!(\"variant record literal on the serde_json data path"], + ["\"e\": null"] + ) +} + // LOCALITY WINS OVER A FOREIGN AMBIGUITY, the third native-parity divergence class. The leaf // index is global: a leaf two closure modules declare answers LeafAmbiguous, and the alias-RHS // module resolver used to end at the poison marker for EVERY such reference -- including a diff --git a/src/v1/05_emit.dag b/src/v1/05_emit.dag index bedbf3dbccf..c13d66b9611 100644 --- a/src/v1/05_emit.dag +++ b/src/v1/05_emit.dag @@ -461,6 +461,22 @@ fn accumulate_json_field(acc: JsonFragmentsAccum, name: String, outcome: Emitter // the second native-parity divergence, on v2.std.logic bool_primitive_facts). The record arm // therefore spells the zero-field value `null`; non-empty records keep the map spelling their // braced struct expects. +// +// THE VARIANT BOUNDARY OF THAT ARM, made fail-closed the same day the arm learned `null`. A +// record literal with parent_enum PRESENT is a variant construction, and its wire spelling is +// the parent coproduct's declared VariantEncoding policy: the internal-tag default spells the +// tag map (`{"_variant": "A"}`, measured against serde 2026-09-08 -- `null` and the bare string +// are both rejected, and the field-carrying map without the tag is rejected), while +// StringVariant, UntaggedVariant and TaggedVariant each spell differently. That policy is a +// module-local fact of the parent's HOME module (resolve_local_coproduct_wire_policy scans that +// module's items for the CoproductWireContract row), and this value path carries no cross-module +// view of it -- so no spelling this arm could write is established. The arm therefore REFUSES +// (the caller renders compile_error!, a build-time located refusal) rather than emitting a +// measured-wrong spelling. The bare fieldless variant never reaches here at all: it is an +// ExprVar, and data_value_has_cross_refs routes any ExprVar-bearing value to direct +// construction. Trigger for spelling instead of refusing: a closure-wide wire-policy index +// beside EmitGraphInfo.type_decl_items, at which point this arm reads the parent's policy and +// spells accordingly. fn emit_data_value_json(value: Node, source_indices: Map) -> EmitterOutcome { match value.expr_data { ExprLiteral { value: v } => match v { @@ -480,22 +496,27 @@ fn emit_data_value_json(value: Node, source_indices: Map) FragmentsAccumulated { pieces: ps } => Emitted { json: concat("[", ps |> join(separator: ", "), "]") } } } - ExprRecordLit { parent_enum: _ } => - if (value.children |> count) == 0 { - Emitted { json: "null" } - } else { - let accum = value.children |> fold(init: FragmentsAccumulated { pieces: [] }, f: (acc, fld) => - accumulate_json_field( - acc: acc, - name: field_init_node_name_at(n: fld, source_indices: source_indices), - outcome: emit_data_value_json(value: field_init_node_value(n: fld), source_indices: source_indices) - ) - ) - match accum { - FragmentsRefused { reason: r } => Refused { reason: r } - FragmentsAccumulated { pieces: ps } => Emitted { json: concat("{", ps |> join(separator: ", "), "}") } + ExprRecordLit { parent_enum: pe } => + match pe { + Present { value: parent } => + Refused { reason: concat("variant record literal on the serde_json data path (parent coproduct: ", parent, "): the variant wire spelling is the parent's declared VariantEncoding policy, a module-local fact of the parent's home module this path does not carry; null and the untagged map are both measured to fail serde deserialization. Trigger for spelling instead of refusing: a closure-wide wire-policy index beside EmitGraphInfo.type_decl_items") } + Absent => + if (value.children |> count) == 0 { + Emitted { json: "null" } + } else { + let accum = value.children |> fold(init: FragmentsAccumulated { pieces: [] }, f: (acc, fld) => + accumulate_json_field( + acc: acc, + name: field_init_node_name_at(n: fld, source_indices: source_indices), + outcome: emit_data_value_json(value: field_init_node_value(n: fld), source_indices: source_indices) + ) + ) + match accum { + FragmentsRefused { reason: r } => Refused { reason: r } + FragmentsAccumulated { pieces: ps } => Emitted { json: concat("{", ps |> join(separator: ", "), "}") } + } + } } - } ExprVar { binding_kind: _ } => Emitted { json: concat("\"", escape_json_string(s: expr_var_name_at(texpr: value, source_indices: source_indices)), "\"") } ExprUnaryOp { op: Neg } => match emit_data_value_json(value: unaryop_operand(texpr: value), source_indices: source_indices) { Emitted { json: j } => Emitted { json: concat("-", j) } diff --git a/src/v1/stage0/src/v1_compiler_emit.rs b/src/v1/stage0/src/v1_compiler_emit.rs index bd0471829ae..fffac16e57e 100644 --- a/src/v1/stage0/src/v1_compiler_emit.rs +++ b/src/v1/stage0/src/v1_compiler_emit.rs @@ -1007,148 +1007,82 @@ pub fn emit_data_value_json( ) -> Rc { stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { match (*value.expr_data.clone()).clone() { - ExprData::ExprLiteral { value: v, .. } => match (*v.clone()).clone() { - LiteralValue::LitStr { value: s, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat( - v1_rt::concat( - "\"".to_string(), - crate::v1_compiler_emit_core_support::escape_json_string(s.clone()), - ), - "\"".to_string(), - ), - }), - LiteralValue::LitSymbol { value: s, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat( - v1_rt::concat( - "\"".to_string(), - crate::v1_compiler_emit_core_support::escape_json_string(s.clone()), - ), - "\"".to_string(), - ), - }), - LiteralValue::LitInt { value: i, .. } => Rc::new(EmitterOutcome::Emitted { - json: crate::v1_compiler_emit_core_support::to_string(i.clone()), - }), - LiteralValue::LitFloat { value: f, .. } => { - Rc::new(EmitterOutcome::Emitted { json: f.clone() }) - } - LiteralValue::LitBool { value: b, .. } => Rc::new(EmitterOutcome::Emitted { - json: if b.clone() { - "true".to_string() - } else { - "false".to_string() - }, - }), - LiteralValue::LitNull => Rc::new(EmitterOutcome::Emitted { - json: "null".to_string(), - }), - }, - ExprData::ExprListLit => { - let accum = value.children.clone().iter().cloned().fold( - Rc::new(JsonFragmentsAccum::FragmentsAccumulated { - pieces: Rc::new(vec![]), - }), - |acc: Rc, e: Rc| { - accumulate_json_fragment( - acc, - emit_data_value_json(e.clone(), source_indices.clone()), - ) - }, - ); - match (*accum.clone()).clone() { - JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => { - Rc::new(EmitterOutcome::Refused { reason: r.clone() }) - } - JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => { - Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat( - v1_rt::concat("[".to_string(), ps.clone().join(&", ".to_string())), - "]".to_string(), - ), - }) - } - } - } - ExprData::ExprRecordLit { parent_enum: _, .. } => { - if ((value.children.clone().len() as i64) == 0) { - Rc::new(EmitterOutcome::Emitted { - json: "null".to_string(), - }) - } else { - { - let accum = value.children.clone().iter().cloned().fold( - Rc::new(JsonFragmentsAccum::FragmentsAccumulated { - pieces: Rc::new(vec![]), - }), - |acc: Rc, fld: Rc| { - accumulate_json_field( - acc, - crate::v1_std_core::field_init_node_name_at( - fld.clone(), - source_indices.clone(), - ), - emit_data_value_json( - crate::v1_std_core::field_init_node_value(fld.clone()), - source_indices.clone(), - ), - ) - }, - ); - match (*accum.clone()).clone() { - JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => { - Rc::new(EmitterOutcome::Refused { reason: r.clone() }) - } - JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => { - Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat( - v1_rt::concat( - "{".to_string(), - ps.clone().join(&", ".to_string()), - ), - "}".to_string(), - ), - }) - } - } - } - } - } - ExprData::ExprVar { - binding_kind: _, .. - } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat( - v1_rt::concat( - "\"".to_string(), - crate::v1_compiler_emit_core_support::escape_json_string( - crate::v1_std_core::expr_var_name_at( - value.clone(), - source_indices.clone(), - ), - ), - ), - "\"".to_string(), - ), - }), - ExprData::ExprUnaryOp { - op: UnaryOpKind::Neg, - .. - } => match (*emit_data_value_json( - crate::v1_std_core::unaryop_operand(value.clone()), - source_indices.clone(), - )) - .clone() + ExprData::ExprLiteral { value: v, .. } => match (*v.clone()).clone() { + LiteralValue::LitStr { value: s, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat(v1_rt::concat("\"".to_string(), crate::v1_compiler_emit_core_support::escape_json_string(s.clone())), "\"".to_string()), +}), + LiteralValue::LitSymbol { value: s, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat(v1_rt::concat("\"".to_string(), crate::v1_compiler_emit_core_support::escape_json_string(s.clone())), "\"".to_string()), +}), + LiteralValue::LitInt { value: i, .. } => Rc::new(EmitterOutcome::Emitted { + json: crate::v1_compiler_emit_core_support::to_string(i.clone()), +}), + LiteralValue::LitFloat { value: f, .. } => Rc::new(EmitterOutcome::Emitted { + json: f.clone(), +}), + LiteralValue::LitBool { value: b, .. } => Rc::new(EmitterOutcome::Emitted { + json: if b.clone() { + "true".to_string() + } else { + "false".to_string() + }, +}), + LiteralValue::LitNull => Rc::new(EmitterOutcome::Emitted { + json: "null".to_string(), +}), +}, + ExprData::ExprListLit => { + let accum = value.children.clone().iter().cloned().fold(Rc::new(JsonFragmentsAccum::FragmentsAccumulated { + pieces: Rc::new(vec![]), +}), |acc: Rc, e: Rc| accumulate_json_fragment(acc, emit_data_value_json(e.clone(), source_indices.clone()))); +match (*accum.clone()).clone() { + JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => Rc::new(EmitterOutcome::Refused { + reason: r.clone(), +}), + JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat(v1_rt::concat("[".to_string(), ps.clone().join(&", ".to_string())), "]".to_string()), +}), +} +}, + ExprData::ExprRecordLit { parent_enum: pe, .. } => match pe.clone() { + Some(parent) => Rc::new(EmitterOutcome::Refused { + reason: v1_rt::concat(v1_rt::concat("variant record literal on the serde_json data path (parent coproduct: ".to_string(), parent.clone()), "): the variant wire spelling is the parent's declared VariantEncoding policy, a module-local fact of the parent's home module this path does not carry; null and the untagged map are both measured to fail serde deserialization. Trigger for spelling instead of refusing: a closure-wide wire-policy index beside EmitGraphInfo.type_decl_items".to_string()), +}), + std::option::Option::None => if ((value.children.clone().len() as i64) == 0) { + Rc::new(EmitterOutcome::Emitted { + json: "null".to_string(), +}) + } else { { - EmitterOutcome::Emitted { json: j, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat("-".to_string(), j.clone()), - }), - EmitterOutcome::Refused { reason: r, .. } => { - Rc::new(EmitterOutcome::Refused { reason: r.clone() }) - } - }, - _ => Rc::new(EmitterOutcome::Refused { - reason: "unsupported mock expression".to_string(), - }), - } + let accum = value.children.clone().iter().cloned().fold(Rc::new(JsonFragmentsAccum::FragmentsAccumulated { + pieces: Rc::new(vec![]), +}), |acc: Rc, fld: Rc| accumulate_json_field(acc, crate::v1_std_core::field_init_node_name_at(fld.clone(), source_indices.clone()), emit_data_value_json(crate::v1_std_core::field_init_node_value(fld.clone()), source_indices.clone()))); +match (*accum.clone()).clone() { + JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => Rc::new(EmitterOutcome::Refused { + reason: r.clone(), +}), + JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat(v1_rt::concat("{".to_string(), ps.clone().join(&", ".to_string())), "}".to_string()), +}), +} +} + }, +}, + ExprData::ExprVar { binding_kind: _, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat(v1_rt::concat("\"".to_string(), crate::v1_compiler_emit_core_support::escape_json_string(crate::v1_std_core::expr_var_name_at(value.clone(), source_indices.clone()))), "\"".to_string()), +}), + ExprData::ExprUnaryOp { op: UnaryOpKind::Neg, .. } => match (*emit_data_value_json(crate::v1_std_core::unaryop_operand(value.clone()), source_indices.clone())).clone() { + EmitterOutcome::Emitted { json: j, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat("-".to_string(), j.clone()), +}), + EmitterOutcome::Refused { reason: r, .. } => Rc::new(EmitterOutcome::Refused { + reason: r.clone(), +}), +}, + _ => Rc::new(EmitterOutcome::Refused { + reason: "unsupported mock expression".to_string(), +}), +} }) } From 49a277de9246eba2eac2b0f9053832c6f1f2f909 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 22:09:04 +0000 Subject: [PATCH 34/89] Spell variant record literals on the serde_json data path from a closure-wide wire-policy index The fail-closed refusal landed in 73b582dea6 fired on 5 real corpus sites (SugarKey x2, CopiedPortCitationFrontierDisposition x3), proving variant record literals reach the JSON data path in the 00_compile closure. This change replaces the refusal with the correct spelling, driven by a new closure-wide index: - v1.compiler.infer_emit_info gains DataVariantWireSpelling, the language-general projection of a coproduct's Rust wire serde policy for one variant (InternalTagged { tag_field, tag } | BareString { tag } | Untagged | SpellingRefused { reason }), and EmitGraphInfo carries data_variant_wire_spellings: Map keyed by coproduct.variant. - v1.compiler.emit_rust builds the index once per emission root via build_data_variant_wire_spellings, resolving each coproduct's policy through the new shared resolve_emission_coproduct_wire_policy (the same function the type-emission side now calls, so the two cannot drift), projecting each variant through data_path_wire_variant_tag (rename_all and StripAffix aware), and poisoning collisions as SpellingRefused so ambiguity stays fail-closed. - v1.compiler.emit's emit_data_value_json variant arm reads the index: internal-tagged spells {"_variant": tag, ...fields}, bare-string spells "tag" for nullary and refuses fielded, untagged spells the bare fields or null; unindexed keys and stored refusals remain compile-time errors. The service mock-property chain threads emit_info through so dry-run data spells identically. Witnesses: w_variant_record_lit_on_the_json_data_path_refuses_fail_closed is rewritten as ..._spells_the_internal_tag (asserts the internal-tag map, excludes the former null mis-serialization and the refusal), and w_fielded_variant_record_lit_on_the_json_data_path_spells_tag_and_fields pins the fielded case. 57/57 witnesses pass; regen fixed-point holds. --- ...d_call_target_realization_witness_test.dag | 57 ++- src/v1/04_emit_info.dag | 22 + src/v1/04_infer.dag | 1 + src/v1/05_emit.dag | 89 +++-- src/v1/05_emit_rust.dag | 219 ++++++++-- src/v1/stage0/src/v1_compiler_emit.rs | 270 ++++++++++--- src/v1/stage0/src/v1_compiler_emit_rust.rs | 376 ++++++++++++++++-- src/v1/stage0/src/v1_compiler_infer.rs | 4 + .../stage0/src/v1_compiler_infer_emit_info.rs | 15 + 9 files changed, 878 insertions(+), 175 deletions(-) diff --git a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag index 2dab115fa48..34858f9b93a 100644 --- a/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag +++ b/dag/test/claim/self_host_emitted_call_target_realization_witness_test.dag @@ -132,27 +132,42 @@ test fn w_nonempty_record_data_value_keeps_the_map_spelling() -> Bool { } -// THE VARIANT BOUNDARY OF THE JSON DATA PATH, FAIL-CLOSED. A record literal with parent_enum -// PRESENT is a variant construction, and its correct wire spelling is the parent coproduct's -// declared VariantEncoding policy -- measured against serde 2026-09-08: the internal-tag default -// accepts only the tag map (`{"_variant": "A"}`), while `null` and the bare string are both -// rejected, so the zero-field record arm's `null` and the map arm's untagged fields are each -// measured-wrong for a variant. The policy is a module-local fact of the parent's home module -// (resolve_local_coproduct_wire_policy scans that module's items), which the JSON value path -// does not carry, so the arm REFUSES and the caller renders compile_error! -- a build-time -// located refusal where a runtime panic on `.expect("valid data definition")` was the latent -// alternative. The probe forces the JSON path (the Holder record nests Marker) and constructs -// the fieldless variant with braces; the bare spelling never reaches the arm (an ExprVar routes -// the whole value to direct construction via data_value_has_cross_refs). This row reds if the -// refusal is lost -- and greens the day the named trigger lands (a closure-wide wire-policy -// index beside EmitGraphInfo.type_decl_items), at which point it is rewritten to assert the -// spelled tag map. -test fn w_variant_record_lit_on_the_json_data_path_refuses_fail_closed() -> Bool { - compile_dag_rust_emit_check( - "module test.claim.variant_json_refusal_probe\ntype E = A | B { x: Int }\ntype Marker {}\ntype Holder {\n e: E\n m: Marker\n}\ndata probe: Holder = Holder { e: A {}, m: Marker {} }\n", - "src/test_claim_variant_json_refusal_probe.rs", - ["compile_error!(\"variant record literal on the serde_json data path"], - ["\"e\": null"] +// THE VARIANT BOUNDARY OF THE JSON DATA PATH, SPELLED. A record literal with parent_enum PRESENT +// is a variant construction, and its wire spelling is the parent coproduct's declared +// VariantEncoding policy -- measured against serde 2026-09-08: the internal-tag default accepts +// only the tag map (`{"_variant": "A"}`), while `null` and the bare string are both rejected. +// This row stood as a fail-closed REFUSAL for one day, because the policy is a module-local fact +// of the parent's home module and the JSON value path carried no cross-module view of it; it +// greened the day its named trigger landed -- the closure-wide wire-spelling index +// (EmitGraphInfo.data_variant_wire_spellings, built beside type_decl_items by +// build_data_variant_wire_spellings), which this pair of rows now pins. The probe forces the JSON +// path (the Holder record nests Marker) and constructs the fieldless variant with braces; the +// bare spelling never reaches the arm (an ExprVar routes the whole value to direct construction +// via data_value_has_cross_refs). It reds if the tag map is lost, and if the former +// mis-serializations (null, or the compile_error! refusal) return. +test fn w_variant_record_lit_on_the_json_data_path_spells_the_internal_tag() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.variant_json_spelling_probe\ntype E = A | B { x: Int }\ntype Marker {}\ntype Holder {\n e: E\n m: Marker\n}\ndata probe: Holder = Holder { e: A {}, m: Marker {} }\n", + "src/test_claim_variant_json_spelling_probe.rs", + ["{\"e\": {\"_variant\": \"A\"}, \"m\": null}"], + ["compile_error!", "\"e\": null"] + ) +} + +// THE FIELD-CARRYING HALF OF THE SAME BOUNDARY. The internal-tag default spells a record variant +// as the tag map with the payload fields INLINE (`{"_variant": "B", "x": 3}`), not the bare field +// map (`{"x": 3}`) the pre-index arm produced -- measured against serde 2026-09-08: the untagged +// field map is rejected ("missing field `_variant`"). This is the shape the five real closure +// sites take (SurfaceAtomKey { atom } in v2.std.compilers.sugar, CopiedPortCitationRetained { +// combiner, migration_trigger } in v2.lens.cost.copied_port_citations), so this row is the +// discriminating control for the population the index actually serves. It reds if the tag or the +// inline fields are dropped, and if the compile_error! refusal returns. +test fn w_fielded_variant_record_lit_on_the_json_data_path_spells_tag_and_fields() -> Bool { + compile_dag_rust_emit_check( + "module test.claim.variant_json_fielded_probe\ntype E = A | B { x: Int }\ntype Marker {}\ntype Holder {\n e: E\n m: Marker\n}\ndata probe: Holder = Holder { e: B { x: 3 }, m: Marker {} }\n", + "src/test_claim_variant_json_fielded_probe.rs", + ["{\"e\": {\"_variant\": \"B\", \"x\": 3}, \"m\": null}"], + ["compile_error!", "\"e\": {\"x\": 3}"] ) } diff --git a/src/v1/04_emit_info.dag b/src/v1/04_emit_info.dag index 0180e253d74..0e2e98c61ce 100644 --- a/src/v1/04_emit_info.dag +++ b/src/v1/04_emit_info.dag @@ -108,9 +108,27 @@ fn collect_type_node_import_surface_names(n: Node, source_indices: Map type_decl_items: Map + data_variant_wire_spellings: Map fn_decl_items: Map recursive_type_set: Set fielded_variants: Set @@ -150,6 +168,7 @@ fn empty_emit_graph_info() -> EmitGraphInfo { item_leaf_owner_modules: empty_map(), type_summaries: empty_map(), type_decl_items: empty_map(), + data_variant_wire_spellings: empty_map(), fn_decl_items: empty_map(), recursive_type_set: empty_set(), fielded_variants: empty_set(), @@ -177,6 +196,7 @@ fn emit_info_with_fn_type_context(emit_info: EmitGraphInfo, generic_param_names: item_leaf_owner_modules: emit_info.item_leaf_owner_modules, type_summaries: emit_info.type_summaries, type_decl_items: emit_info.type_decl_items, + data_variant_wire_spellings: emit_info.data_variant_wire_spellings, fn_decl_items: emit_info.fn_decl_items, recursive_type_set: emit_info.recursive_type_set, fielded_variants: emit_info.fielded_variants, @@ -203,6 +223,7 @@ fn emit_info_with_fn_return(emit_info: EmitGraphInfo, fn_return_type: Node?) -> item_leaf_owner_modules: emit_info.item_leaf_owner_modules, type_summaries: emit_info.type_summaries, type_decl_items: emit_info.type_decl_items, + data_variant_wire_spellings: emit_info.data_variant_wire_spellings, fn_decl_items: emit_info.fn_decl_items, recursive_type_set: emit_info.recursive_type_set, fielded_variants: emit_info.fielded_variants, @@ -234,6 +255,7 @@ fn emit_info_with_expected_type(emit_info: EmitGraphInfo, expected_type: Node?) item_leaf_owner_modules: emit_info.item_leaf_owner_modules, type_summaries: emit_info.type_summaries, type_decl_items: emit_info.type_decl_items, + data_variant_wire_spellings: emit_info.data_variant_wire_spellings, fn_decl_items: emit_info.fn_decl_items, recursive_type_set: emit_info.recursive_type_set, fielded_variants: emit_info.fielded_variants, diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index e78846c5e20..3abca2650f2 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -14006,6 +14006,7 @@ fn build_emit_graph_info(modules: List, registry: Map) -> EmitterOutcome { +// THE VARIANT ARM READS THE CLOSURE-WIDE WIRE-SPELLING INDEX. A record literal with parent_enum +// PRESENT is a variant construction, and its wire spelling is the parent coproduct's declared +// VariantEncoding policy: the internal-tag default spells the tag map (`{"_variant": "A"}`, +// measured against serde 2026-09-08 -- `null` and the bare string are both rejected, and the +// field-carrying map without the tag is rejected), while StringVariant, UntaggedVariant and +// TaggedVariant each spell differently. That policy is a module-local fact of the parent's home +// module (plus its `_contracts` sibling), which this value path cannot see -- so the spelling is +// derived ONCE per (coproduct, variant) at the emission roots +// (build_data_variant_wire_spellings, beside EmitGraphInfo.type_decl_items) and carried in +// EmitGraphInfo.data_variant_wire_spellings, keyed `Parent.variant`. This arm is a lookup, never +// a re-derivation: an unindexed key or a stored DataVariantSpellingRefused fails closed (the +// caller renders compile_error!, a build-time located refusal) rather than spelling a guess. +// The bare fieldless variant never reaches here at all: it is an ExprVar, and +// data_value_has_cross_refs routes any ExprVar-bearing value to direct construction. +fn emit_data_fields_json(value: Node, source_indices: Map, variant_wire: Map) -> JsonFragmentsAccum { + value.children |> fold(init: FragmentsAccumulated { pieces: [] }, f: (acc, fld) => + accumulate_json_field( + acc: acc, + name: field_init_node_name_at(n: fld, source_indices: source_indices), + outcome: emit_data_value_json(value: field_init_node_value(n: fld), source_indices: source_indices, variant_wire: variant_wire) + ) + ) +} + +fn emit_data_value_json(value: Node, source_indices: Map, variant_wire: Map) -> EmitterOutcome { match value.expr_data { ExprLiteral { value: v } => match v { LitStr { value: s } => Emitted { json: concat("\"", escape_json_string(s: s), "\"") } @@ -489,7 +500,7 @@ fn emit_data_value_json(value: Node, source_indices: Map) } ExprListLit => { let accum = value.children |> fold(init: FragmentsAccumulated { pieces: [] }, f: (acc, e) => - accumulate_json_fragment(acc: acc, outcome: emit_data_value_json(value: e, source_indices: source_indices)) + accumulate_json_fragment(acc: acc, outcome: emit_data_value_json(value: e, source_indices: source_indices, variant_wire: variant_wire)) ) match accum { FragmentsRefused { reason: r } => Refused { reason: r } @@ -499,26 +510,50 @@ fn emit_data_value_json(value: Node, source_indices: Map) ExprRecordLit { parent_enum: pe } => match pe { Present { value: parent } => - Refused { reason: concat("variant record literal on the serde_json data path (parent coproduct: ", parent, "): the variant wire spelling is the parent's declared VariantEncoding policy, a module-local fact of the parent's home module this path does not carry; null and the untagged map are both measured to fail serde deserialization. Trigger for spelling instead of refusing: a closure-wide wire-policy index beside EmitGraphInfo.type_decl_items") } + match record_lit_type_name_at(texpr: value, source_indices: source_indices) { + Absent => Refused { reason: concat("variant record literal with parent coproduct ", parent, " carries no authored head name to key the wire-spelling index") } + Present { value: head } => + let key = concat(parent, ".", qualified_last_segment(name: head)) + match map_get(variant_wire, key) { + Absent => Refused { reason: concat("no wire spelling indexed for ", key, ": the index covers every coproduct declared in the emission closure it was built from, so this parent is outside that closure") } + Present { value: spelling } => match spelling { + DataVariantSpellingRefused { reason: r } => Refused { reason: r } + DataVariantUntagged => + if (value.children |> count) == 0 { + Emitted { json: "null" } + } else { + match emit_data_fields_json(value: value, source_indices: source_indices, variant_wire: variant_wire) { + FragmentsRefused { reason: r } => Refused { reason: r } + FragmentsAccumulated { pieces: ps } => Emitted { json: concat("{", ps |> join(separator: ", "), "}") } + } + } + DataVariantBareString { tag: t } => + if (value.children |> count) == 0 { + Emitted { json: concat("\"", escape_json_string(s: t), "\"") } + } else { + Refused { reason: concat("variant ", key, " carries fields under a StringVariant wire policy, which is nullary-only; the type-side emission of the parent coproduct refuses it too") } + } + DataVariantInternalTagged { tag_field: tf, tag: t } => + let tag_piece = concat("\"", escape_json_string(s: tf), "\": \"", escape_json_string(s: t), "\"") + match emit_data_fields_json(value: value, source_indices: source_indices, variant_wire: variant_wire) { + FragmentsRefused { reason: r } => Refused { reason: r } + FragmentsAccumulated { pieces: ps } => Emitted { json: concat("{", concat([tag_piece], ps) |> join(separator: ", "), "}") } + } + } + } + } Absent => if (value.children |> count) == 0 { Emitted { json: "null" } } else { - let accum = value.children |> fold(init: FragmentsAccumulated { pieces: [] }, f: (acc, fld) => - accumulate_json_field( - acc: acc, - name: field_init_node_name_at(n: fld, source_indices: source_indices), - outcome: emit_data_value_json(value: field_init_node_value(n: fld), source_indices: source_indices) - ) - ) - match accum { + match emit_data_fields_json(value: value, source_indices: source_indices, variant_wire: variant_wire) { FragmentsRefused { reason: r } => Refused { reason: r } FragmentsAccumulated { pieces: ps } => Emitted { json: concat("{", ps |> join(separator: ", "), "}") } } } } ExprVar { binding_kind: _ } => Emitted { json: concat("\"", escape_json_string(s: expr_var_name_at(texpr: value, source_indices: source_indices)), "\"") } - ExprUnaryOp { op: Neg } => match emit_data_value_json(value: unaryop_operand(texpr: value), source_indices: source_indices) { + ExprUnaryOp { op: Neg } => match emit_data_value_json(value: unaryop_operand(texpr: value), source_indices: source_indices, variant_wire: variant_wire) { Emitted { json: j } => Emitted { json: concat("-", j) } Refused { reason: r } => Refused { reason: r } } diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 1b87b408f5f..653586c9f91 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -2645,6 +2645,175 @@ fn resolve_local_coproduct_wire_policy(coproduct_name: String, all_unit_variants } } +// THE ONE POLICY DECISION FOR AN EMITTED COPRODUCT, extracted so the type side and the data-path +// index ask the same question of the same authority (DESIGN section 2 -- one derivation, two call +// sites). The answer order is: a CoproductWireContract row in the coproduct's own module or its +// `_contracts` sibling (resolve_local_coproduct_wire_policy scans the concatenated context); then, +// for a nullary-only coproduct, the module's legacy `wire_contract` data item; otherwise the +// internal-tag default. The wire_items/wire_imports arguments ARE the emitting module's items and +// imports concatenated with its contracts sibling's (contracts_items_for_module / +// contracts_imports_for_module), and scoped_data_items is the module's scoped data index augmented +// with the contracts sibling's imports -- both call sites build them identically. +fn resolve_emission_coproduct_wire_policy(coproduct_name: String, item: Node, wire_items: List, wire_imports: List, wire_contract_item: Node?, scoped_data_items: Map>, source_indices: Map) -> RustEnumWireSerde { + let all_unit_variants = item.children |> all(child => child.children |> count == 0) + match resolve_local_coproduct_wire_policy(coproduct_name: coproduct_name, all_unit_variants: all_unit_variants, module_items: wire_items, imports: wire_imports, source_indices: source_indices) { + Present { value: local_policy } => local_policy + Absent => + if all_unit_variants { + match wire_contract_item { + Present { value: _ } => resolve_wire_serde_policy_for_coproduct(wire_contract_item: wire_contract_item, source_indices: source_indices, data_items: scoped_data_items) + Absent => rust_tagged_object_policy() + } + } else { + rust_tagged_object_policy() + } + } +} + +// THE TOTAL WIRE-TAG QUESTION. wire_variant_tag_for_policy answers the ATTRIBUTE question -- does +// this variant need a per-variant #[serde(rename)] -- and its `none` means "no attr needed", which +// conflates "renamed by the container's rename_all" with "as authored". The data path needs the +// TAG itself: the exact string serde places on the wire, which under a container rename_all is the +// renamed spelling and under no renaming is the authored one. The two functions read the same +// policy fields in the same order so the answers cannot drift: StripAffix style defers to the +// attribute function's computation (its none -- a failed strip the type side already refuses -- +// propagates as none here), then rename_all snake_case / SCREAMING_SNAKE_CASE, then as-authored. +fn data_path_wire_variant_tag(authored: String, policy: RustEnumWireSerde) -> String? { + if policy.error_message != none { + none + } else { + match policy.rename_style { + Present { value: _ } => wire_variant_tag_for_policy(authored: authored, policy: policy) + Absent => + if contains(policy.enum_attr, "rename_all = \"snake_case\"") { + Present { value: to_snake(name: authored) } + } else if contains(policy.enum_attr, "rename_all = \"SCREAMING_SNAKE_CASE\"") { + Present { value: to_screaming_snake(name: authored) } + } else { + Present { value: authored } + } + } + } +} + +// Projection from the Rust policy carrier to the language-general spelling the data path reads +// (the type's comment at v1.compiler.infer_emit_info DataVariantWireSpelling names why the +// projection exists). Untagged is tested FIRST: policy_is_string_variant's shape test (no +// "tag =" substring) also matches the untagged attribute, so the order is load-bearing. +fn project_data_variant_spelling(authored_variant: String, policy: RustEnumWireSerde) -> DataVariantWireSpelling { + match policy.error_message { + Present { value: message } => DataVariantSpellingRefused { reason: message } + Absent => + if policy_is_untagged(policy: policy) { + DataVariantUntagged + } else { + match data_path_wire_variant_tag(authored: authored_variant, policy: policy) { + Absent => DataVariantSpellingRefused { reason: concat("no wire tag derivable for variant ", authored_variant, " under the parent coproduct's naming policy") } + Present { value: tag } => + if policy_is_string_variant(policy: policy) { + DataVariantBareString { tag: tag } + } else { + match policy_serde_tag_field(policy: policy) { + Present { value: tag_field } => DataVariantInternalTagged { tag_field: tag_field, tag: tag } + Absent => DataVariantSpellingRefused { reason: concat("wire policy for variant ", authored_variant, " carries no tag field and is neither untagged nor string-variant") } + } + } + } + } + } +} + +fn data_variant_wire_spelling_eq(a: DataVariantWireSpelling, b: DataVariantWireSpelling) -> Bool { + match a { + DataVariantInternalTagged { tag_field: a_tf, tag: a_t } => + match b { + DataVariantInternalTagged { tag_field: b_tf, tag: b_t } => a_tf == b_tf && a_t == b_t + _ => false + } + DataVariantBareString { tag: a_t } => + match b { + DataVariantBareString { tag: b_t } => a_t == b_t + _ => false + } + DataVariantUntagged => + match b { + DataVariantUntagged => true + _ => false + } + DataVariantSpellingRefused { reason: a_r } => + match b { + DataVariantSpellingRefused { reason: b_r } => a_r == b_r + _ => false + } + } +} + +// The index is keyed by BARE coproduct and variant names because that is what a variant record +// literal carries: infer stamps parent_enum as the parent declaration's authored name +// (lookup_variant_parent_enum), never module-qualified. Two modules in one closure declaring the +// same coproduct leaf therefore share a key. Where their resolved policies agree the answer is one +// fact and either row serves; where they differ the key is POISONED with a stored refusal, so the +// data path fails closed with the cause instead of silently serving whichever module folded last. +fn insert_data_variant_spelling(index: Map, key: String, spelling: DataVariantWireSpelling, coproduct_name: String) -> Map { + match map_get(index, key) { + Absent => map_insert(index, key, spelling) + Present { value: existing } => + if data_variant_wire_spelling_eq(a: existing, b: spelling) { + index + } else { + map_insert(index, key, DataVariantSpellingRefused { reason: concat("ambiguous wire policy for coproduct ", coproduct_name, ": two modules in the emission closure declare it and their resolved policies differ, so a bare parent_enum cannot name one policy") }) + } + } +} + +// THE CLOSURE-WIDE WIRE-POLICY INDEX the variant arm of emit_data_value_json reads. Built once per +// emission root from the same inputs the type side resolves against, one row per (coproduct, +// variant) pair, beside EmitGraphInfo.type_decl_items. This is the trigger the fail-closed +// variant refusal named on 2026-09-08, landed the same day its first real population measured out: +// five sites in the 00_compile closure (SugarKey x2 in v2.std.compilers.sugar, +// CopiedPortCitationFrontierDisposition x3 in v2.lens.cost.copied_port_citations). +fn build_data_variant_wire_spellings(modules: List, data_items: Map, module_index: ModuleIndex) -> Map { + modules |> fold(init: empty_map(), f: (acc, tm) => + let si = tm.type_env.source_indices + let module_name = authored_name_at(source_indices: si, node: tm.module) + let contracts_imports = contracts_imports_for_module(module_name: module_name, typed_modules: modules, source_indices: si, module_index: module_index) + let wire_items = concat(tm.items, contracts_items_for_module(module_name: module_name, typed_modules: modules, source_indices: si, module_index: module_index)) + let wire_imports = concat(module_imports(n: tm.module), contracts_imports) + let scoped_data = augment_scoped_data_item_index_with_imports( + scoped: build_scoped_data_item_index(typed_module: tm, data_items: data_items), + imports: contracts_imports, + source_indices: si, + data_items: data_items + ) + let wire_contract_item = wire_items + |> filter(i => i.module_item_kind == ModuleItemDataValue && authored_name_at(source_indices: si, node: i) == "wire_contract") + |> first + tm.items + |> filter(item => is_type_def_item(item: item) && is_coproduct_type(n: item)) + |> fold(init: acc, f: (item_acc, item) => + let coproduct_name = authored_name_at(source_indices: si, node: item) + let policy = resolve_emission_coproduct_wire_policy( + coproduct_name: coproduct_name, + item: item, + wire_items: wire_items, + wire_imports: wire_imports, + wire_contract_item: wire_contract_item, + scoped_data_items: scoped_data, + source_indices: si + ) + item.children |> fold(init: item_acc, f: (variant_acc, variant) => + let variant_name = authored_name_at(source_indices: si, node: variant) + insert_data_variant_spelling( + index: variant_acc, + key: concat(coproduct_name, ".", variant_name), + spelling: project_data_variant_spelling(authored_variant: variant_name, policy: policy), + coproduct_name: coproduct_name + ) + ) + ) + ) +} + fn build_data_item_index(modules: List) -> Map { modules |> fold(init: empty_map(), f: (acc, tm) => let module_name = authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module) @@ -3166,10 +3335,13 @@ fn build_emit_rust_context(typed: ResolvedGraph) -> EmitRustContext { type_decl_items: base_info.type_decl_items, source_indices: merged_module_source_indices(modules: typed.modules) ) + let data_items = build_data_item_index(modules: typed.modules) + let module_index = build_module_index(modules: typed.modules, leaf_owner_modules: base_info.item_leaf_owner_modules) let emit_info = EmitGraphInfo { item_leaf_owner_modules: base_info.item_leaf_owner_modules, type_summaries: base_info.type_summaries, type_decl_items: base_info.type_decl_items, + data_variant_wire_spellings: build_data_variant_wire_spellings(modules: typed.modules, data_items: data_items, module_index: module_index), fn_decl_items: base_info.fn_decl_items, recursive_type_set: base_info.recursive_type_set, fielded_variants: base_info.fielded_variants, @@ -3190,7 +3362,6 @@ fn build_emit_rust_context(typed: ResolvedGraph) -> EmitRustContext { expected_type: none } let registry = typed.item_registry - let data_items = build_data_item_index(modules: typed.modules) let workflow_funcs = collect_workflow_funcs(modules: typed.modules, registry: registry, read_only_params_index: emit_info.read_only_params_index) let workflow_default_diags = validate_workflow_param_defaults(workflow_funcs: workflow_funcs) let anonymous_record_diags = typed.modules |> flat_map(tm => tm.items |> flat_map(item => ambiguous_anonymous_record_literal_diagnostics(n: item, type_summaries: emit_info.type_summaries, source_indices: tm.type_env.source_indices, module_name: authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module)))) @@ -3203,7 +3374,6 @@ fn build_emit_rust_context(typed: ResolvedGraph) -> EmitRustContext { ) let test_projections = extract_test_projections(typed: typed) let export_sets = build_module_export_sets(modules: typed.modules) - let module_index = build_module_index(modules: typed.modules, leaf_owner_modules: emit_info.item_leaf_owner_modules) let unlisted_type_names_by_module = group_unlisted_type_names(diags: typed.diagnostics) EmitRustContext { emit_info: emit_info, @@ -3816,10 +3986,13 @@ fn emit_module(typed_module: TypedModule, registry: Map) -> Te type_decl_items: base_info.type_decl_items, source_indices: merged_module_source_indices(modules: [typed_module]) ) + let data_items = build_data_item_index(modules: [typed_module]) + let module_index = build_module_index(modules: [typed_module], leaf_owner_modules: base_info.item_leaf_owner_modules) let emit_info = EmitGraphInfo { item_leaf_owner_modules: base_info.item_leaf_owner_modules, type_summaries: base_info.type_summaries, type_decl_items: base_info.type_decl_items, + data_variant_wire_spellings: build_data_variant_wire_spellings(modules: [typed_module], data_items: data_items, module_index: module_index), fn_decl_items: base_info.fn_decl_items, recursive_type_set: base_info.recursive_type_set, fielded_variants: base_info.fielded_variants, @@ -3841,8 +4014,7 @@ fn emit_module(typed_module: TypedModule, registry: Map) -> Te } let shared_types = emit_info.shared_types let export_sets = build_module_export_sets(modules: [typed_module]) - let module_index = build_module_index(modules: [typed_module], leaf_owner_modules: emit_info.item_leaf_owner_modules) - emit_module_full(typed_module: typed_module, registry: registry, emit_info: emit_info, shared_types: shared_types, svc_module_map: empty_map(), data_items: build_data_item_index(modules: [typed_module]), export_sets: export_sets, typed_modules: [typed_module], module_index: module_index, unlisted_type_names: []).file + emit_module_full(typed_module: typed_module, registry: registry, emit_info: emit_info, shared_types: shared_types, svc_module_map: empty_map(), data_items: data_items, export_sets: export_sets, typed_modules: [typed_module], module_index: module_index, unlisted_type_names: []).file } fn build_module_export_sets(modules: List) -> Map> { @@ -5243,6 +5415,7 @@ fn emit_module_full(typed_module: TypedModule, registry: Map, item_leaf_owner_modules: emit_info.item_leaf_owner_modules, type_summaries: emit_info.type_summaries, type_decl_items: emit_info.type_decl_items, + data_variant_wire_spellings: emit_info.data_variant_wire_spellings, fn_decl_items: emit_info.fn_decl_items, recursive_type_set: emit_info.recursive_type_set, fielded_variants: emit_info.fielded_variants, @@ -7307,6 +7480,7 @@ fn emit_typed_item(item: Node, module_name: String, registry: Map count == 0 { emit_rust_item_refusal(item_text: item_text, reason: "service declares no operations") } else { - emit_service_def(item: item, registry: registry, shared_types: shared_types, env: env) + emit_service_def(item: item, registry: registry, shared_types: shared_types, env: env, emit_info: emit_info) } ModuleItemResource => emit_resource_def(item: item, shared_types: shared_types, env: env) @@ -7653,19 +7827,7 @@ fn emit_type_def_from_connective(item: Node, recursive_types: Set, share let type_params = emit_type_params(params: item.params, source_indices: env.source_indices) concat(rust_visibility_prefix(), "type ", item_text, type_params, " = ", render_rust_diagnostics_carrier_applied(shared_types: shared_types), ";") } else { - let all_unit_variants = item.children |> all(child => child.children |> count == 0) - let serde_policy = match resolve_local_coproduct_wire_policy(coproduct_name: item_text, all_unit_variants: all_unit_variants, module_items: module_items, imports: imports, source_indices: env.source_indices) { - Present { value: local_policy } => local_policy - Absent => - if all_unit_variants { - match wire_contract_item { - Present { value: _ } => resolve_wire_serde_policy_for_coproduct(wire_contract_item: wire_contract_item, source_indices: env.source_indices, data_items: data_items) - Absent => rust_tagged_object_policy() - } - } else { - rust_tagged_object_policy() - } - } + let serde_policy = resolve_emission_coproduct_wire_policy(coproduct_name: item_text, item: item, wire_items: module_items, wire_imports: imports, wire_contract_item: wire_contract_item, scoped_data_items: data_items, source_indices: env.source_indices) let rename_validations = variant_rename_validations_for_policy(children: item.children, env: env, serde_policy: serde_policy) let policy_validation = match serde_policy.error_message { Present { value: message } => emit_rust_compile_error_item(message: message) @@ -11829,6 +11991,7 @@ fn emit_rust_fold_method_call(method_call_node: Node, fold_accumulator_type: Nod item_leaf_owner_modules: emit_info.item_leaf_owner_modules, type_summaries: emit_info.type_summaries, type_decl_items: emit_info.type_decl_items, + data_variant_wire_spellings: emit_info.data_variant_wire_spellings, fn_decl_items: emit_info.fn_decl_items, recursive_type_set: emit_info.recursive_type_set, fielded_variants: emit_info.fielded_variants, @@ -11862,6 +12025,7 @@ fn emit_rust_fold_method_call(method_call_node: Node, fold_accumulator_type: Nod item_leaf_owner_modules: emit_info.item_leaf_owner_modules, type_summaries: emit_info.type_summaries, type_decl_items: emit_info.type_decl_items, + data_variant_wire_spellings: emit_info.data_variant_wire_spellings, fn_decl_items: emit_info.fn_decl_items, recursive_type_set: emit_info.recursive_type_set, fielded_variants: emit_info.fielded_variants, @@ -14010,13 +14174,14 @@ fn emit_service_def( item: Node, registry: Map, shared_types: Set, - env: TypeEnv + env: TypeEnv, + emit_info: EmitGraphInfo ) -> String { let safe_name = sanitize_service_name(name: authored_name(env: env, node: item)) let fallback_transport = service_fallback_transport(item: item) let op_children = item.children let struct_def = emit_service_struct(name: safe_name, fallback_transport: fallback_transport, op_children: op_children, service_item: item, source_indices: env.source_indices) - let impl_block = emit_service_impl(name: safe_name, transport: fallback_transport, op_children: op_children, registry: registry, shared_types: shared_types, env: env, service_item: item) + let impl_block = emit_service_impl(name: safe_name, transport: fallback_transport, op_children: op_children, registry: registry, shared_types: shared_types, env: env, service_item: item, emit_info: emit_info) concat(struct_def, "\n\n", impl_block) } @@ -14037,11 +14202,11 @@ fn emit_service_config_fields(fallback_transport: Node, op_children: List, else { decls |> join(separator: "") } } -fn emit_service_impl(name: String, transport: Node, op_children: List, registry: Map, shared_types: Set, env: TypeEnv, service_item: Node) -> String { +fn emit_service_impl(name: String, transport: Node, op_children: List, registry: Map, shared_types: Set, env: TypeEnv, service_item: Node, emit_info: EmitGraphInfo) -> String { let depth = 0 let new_method = emit_service_new_method(name: name, fallback_transport: transport, op_children: op_children, service_item: service_item, source_indices: env.source_indices) let method_strs = op_children |> map(op_node => - emit_operation_method(service_name: name, transport: transport, op_node: op_node, registry: registry, depth: depth + 1, shared_types: shared_types, env: env, service_item: service_item) + emit_operation_method(service_name: name, transport: transport, op_node: op_node, registry: registry, depth: depth + 1, shared_types: shared_types, env: env, service_item: service_item, emit_info: emit_info) ) let all_methods = concat([new_method], method_strs) let methods_str = all_methods |> join(separator: "\n\n") @@ -14124,7 +14289,7 @@ fn emit_modifier_doc_from_props(properties: List, source_indices: Map, depth: Int, shared_types: Set, env: TypeEnv, service_item: Node) -> String { +fn emit_operation_method(service_name: String, transport: Node, op_node: Node, registry: Map, depth: Int, shared_types: Set, env: TypeEnv, service_item: Node, emit_info: EmitGraphInfo) -> String { let op_text = authored_name(env: env, node: op_node) let input_params = op_node.params |> map(p => concat(emit_ident(name: param_node_name_at(n: p, source_indices: env.source_indices), target: Rust), rust_items().param_type_sep, emit_rust_param_type(n: param_node_type_expr(n: p), generic_param_names: [], shared_types: shared_types, source_indices: env.source_indices, variant_to_enum: empty_map(), env: env, enclosing_returns_arrow: false)) @@ -14140,7 +14305,7 @@ fn emit_operation_method(service_name: String, transport: Node, op_node: Node, r let bound = bind_operation_transport(t: eff_transport, op_node: op_node, source_indices: env.source_indices) let real_body = emit_transport_call(bound: bound, op_name: op_text, registry: registry, depth: depth + 2, service_item: service_item, op_node: op_node, source_indices: env.source_indices, shared_types: shared_types, env: env) let mock_props = op_node.properties |> filter(p => has_mock_prefix(name: field_init_node_name_at(n: p, source_indices: env.source_indices))) - let dry_run_body = emit_dry_run_branch_from_props(op_name: op_text, inferred: resolved_type(n: op_node), mock_props: mock_props, registry: registry, source_indices: env.source_indices, op_node: op_node, env: env, shared_types: shared_types) + let dry_run_body = emit_dry_run_branch_from_props(op_name: op_text, inferred: resolved_type(n: op_node), mock_props: mock_props, registry: registry, source_indices: env.source_indices, op_node: op_node, env: env, shared_types: shared_types, emit_info: emit_info) let body = concat( "if self.dry_run.is_dry_run() {\n", make_indent(level: depth + 2),dry_run_body, "\n", @@ -14154,12 +14319,12 @@ fn emit_operation_method(service_name: String, transport: Node, op_node: Node, r make_indent(level: depth + 1),body, "\n}") } -fn emit_dry_run_branch_from_props(op_name: String, inferred: Node, mock_props: List, registry: Map, source_indices: Map, op_node: Node, env: TypeEnv, shared_types: Set) -> String { +fn emit_dry_run_branch_from_props(op_name: String, inferred: Node, mock_props: List, registry: Map, source_indices: Map, op_node: Node, env: TypeEnv, shared_types: Set, emit_info: EmitGraphInfo) -> String { let log_line = concat("eprintln!(\"[dry-run] ", op_name, "\");") if mock_props |> count > 0 { let first_mock = mock_props |> first match first_mock { - Present { value: mp } => match emit_data_value_json(value: field_init_node_value(n: mp), source_indices: source_indices) { + Present { value: mp } => match emit_data_value_json(value: field_init_node_value(n: mp), source_indices: source_indices, variant_wire: emit_info.data_variant_wire_spellings) { Refused { reason: r } => concat(log_line, "\ncompile_error!(\"", escape_string_literal_body(s: r), "\");") Emitted { json: mock_json } => { let is_multi_field_conj = inferred.connective == Conj && inferred.children |> count > 1 @@ -15333,7 +15498,7 @@ fn emit_data_def_body(type_node: Node, value: Node, registry: Map true _ => false } { concat(" ", emit_typed_expr(texpr: value, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info, fuel: 1024)) } else if has_nested_records_node(n: type_node, source_indices: scope.type_env.source_indices) && !data_value_has_cross_refs(value: value) { - match emit_data_value_json(value: value, source_indices: scope.type_env.source_indices) { + match emit_data_value_json(value: value, source_indices: scope.type_env.source_indices, variant_wire: emit_info.data_variant_wire_spellings) { Refused { reason: r } => concat(" compile_error!(\"", escape_string_literal_body(s: r), "\")") Emitted { json: json_str } => concat(" serde_json::from_value(serde_json::json!(", json_str, "))\n", " .expect(\"valid data definition\")") diff --git a/src/v1/stage0/src/v1_compiler_emit.rs b/src/v1/stage0/src/v1_compiler_emit.rs index fffac16e57e..90ddfd14eea 100644 --- a/src/v1/stage0/src/v1_compiler_emit.rs +++ b/src/v1/stage0/src/v1_compiler_emit.rs @@ -57,7 +57,11 @@ pub use crate::v1_compiler_infer::InferScope; pub use crate::v1_compiler_infer::{ build_params_scope, call_param_caller_labels, extend_scope, is_where_refinement_type, }; -pub use crate::v1_compiler_infer_emit_info::{EmitGraphInfo, TypeSummary}; +use crate::v1_compiler_infer_emit_info::DataVariantWireSpelling::{ + DataVariantBareString, DataVariantInternalTagged, DataVariantSpellingRefused, + DataVariantUntagged, +}; +pub use crate::v1_compiler_infer_emit_info::{DataVariantWireSpelling, EmitGraphInfo, TypeSummary}; use crate::v1_compiler_infer_env::GlobalBareLookupState::*; pub use crate::v1_compiler_infer_env::UnitVariantContribution; pub use crate::v1_compiler_infer_env::{authored_name, empty_symbol_index, lookup_type_for}; @@ -1001,88 +1005,238 @@ pub fn accumulate_json_field( } } +pub fn emit_data_fields_json( + value: Rc, + source_indices: Rc>>, + variant_wire: Rc>>, +) -> Rc { + value.children.clone().iter().cloned().fold( + Rc::new(JsonFragmentsAccum::FragmentsAccumulated { + pieces: Rc::new(vec![]), + }), + |acc: Rc, fld: Rc| { + accumulate_json_field( + acc, + crate::v1_std_core::field_init_node_name_at(fld.clone(), source_indices.clone()), + emit_data_value_json( + crate::v1_std_core::field_init_node_value(fld.clone()), + source_indices.clone(), + variant_wire.clone(), + ), + ) + }, + ) +} + pub fn emit_data_value_json( value: Rc, source_indices: Rc>>, + variant_wire: Rc>>, ) -> Rc { stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { match (*value.expr_data.clone()).clone() { - ExprData::ExprLiteral { value: v, .. } => match (*v.clone()).clone() { - LiteralValue::LitStr { value: s, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat(v1_rt::concat("\"".to_string(), crate::v1_compiler_emit_core_support::escape_json_string(s.clone())), "\"".to_string()), -}), - LiteralValue::LitSymbol { value: s, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat(v1_rt::concat("\"".to_string(), crate::v1_compiler_emit_core_support::escape_json_string(s.clone())), "\"".to_string()), -}), - LiteralValue::LitInt { value: i, .. } => Rc::new(EmitterOutcome::Emitted { - json: crate::v1_compiler_emit_core_support::to_string(i.clone()), -}), - LiteralValue::LitFloat { value: f, .. } => Rc::new(EmitterOutcome::Emitted { - json: f.clone(), + ExprData::ExprLiteral { value: v, .. } => match (*v.clone()).clone() { + LiteralValue::LitStr { value: s, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat( + v1_rt::concat( + "\"".to_string(), + crate::v1_compiler_emit_core_support::escape_json_string(s.clone()), + ), + "\"".to_string(), + ), + }), + LiteralValue::LitSymbol { value: s, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat( + v1_rt::concat( + "\"".to_string(), + crate::v1_compiler_emit_core_support::escape_json_string(s.clone()), + ), + "\"".to_string(), + ), + }), + LiteralValue::LitInt { value: i, .. } => Rc::new(EmitterOutcome::Emitted { + json: crate::v1_compiler_emit_core_support::to_string(i.clone()), + }), + LiteralValue::LitFloat { value: f, .. } => { + Rc::new(EmitterOutcome::Emitted { json: f.clone() }) + } + LiteralValue::LitBool { value: b, .. } => Rc::new(EmitterOutcome::Emitted { + json: if b.clone() { + "true".to_string() + } else { + "false".to_string() + }, + }), + LiteralValue::LitNull => Rc::new(EmitterOutcome::Emitted { + json: "null".to_string(), + }), + }, + ExprData::ExprListLit => { + let accum = value.children.clone().iter().cloned().fold( + Rc::new(JsonFragmentsAccum::FragmentsAccumulated { + pieces: Rc::new(vec![]), + }), + |acc: Rc, e: Rc| { + accumulate_json_fragment( + acc, + emit_data_value_json( + e.clone(), + source_indices.clone(), + variant_wire.clone(), + ), + ) + }, + ); + match (*accum.clone()).clone() { + JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => { + Rc::new(EmitterOutcome::Refused { reason: r.clone() }) + } + JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => { + Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat( + v1_rt::concat("[".to_string(), ps.clone().join(&", ".to_string())), + "]".to_string(), + ), + }) + } + } + } + ExprData::ExprRecordLit { + parent_enum: pe, .. + } => match pe.clone() { + Some(parent) => match crate::v1_std_core::record_lit_type_name_at( + value.clone(), + source_indices.clone(), + ) { + std::option::Option::None => Rc::new(EmitterOutcome::Refused { + reason: v1_rt::concat( + v1_rt::concat( + "variant record literal with parent coproduct ".to_string(), + parent.clone(), + ), + " carries no authored head name to key the wire-spelling index" + .to_string(), + ), + }), + Some(head) => { + let key = v1_rt::concat( + v1_rt::concat(parent.clone(), ".".to_string()), + crate::v1_std_core::qualified_last_segment(head.clone()), + ); + match v1_rt::map_get(&variant_wire, key.clone()) { + std::option::Option::None => Rc::new(EmitterOutcome::Refused { + reason: v1_rt::concat(v1_rt::concat("no wire spelling indexed for ".to_string(), key.clone()), ": the index covers every coproduct declared in the emission closure it was built from, so this parent is outside that closure".to_string()), }), - LiteralValue::LitBool { value: b, .. } => Rc::new(EmitterOutcome::Emitted { - json: if b.clone() { - "true".to_string() - } else { - "false".to_string() - }, + Some(spelling) => match (*spelling.clone()).clone() { + DataVariantWireSpelling::DataVariantSpellingRefused { reason: r, .. } => Rc::new(EmitterOutcome::Refused { + reason: r.clone(), }), - LiteralValue::LitNull => Rc::new(EmitterOutcome::Emitted { + DataVariantWireSpelling::DataVariantUntagged => if ((value.children.clone().len() as i64) == 0) { + Rc::new(EmitterOutcome::Emitted { json: "null".to_string(), -}), -}, - ExprData::ExprListLit => { - let accum = value.children.clone().iter().cloned().fold(Rc::new(JsonFragmentsAccum::FragmentsAccumulated { - pieces: Rc::new(vec![]), -}), |acc: Rc, e: Rc| accumulate_json_fragment(acc, emit_data_value_json(e.clone(), source_indices.clone()))); -match (*accum.clone()).clone() { +}) + } else { + match (*emit_data_fields_json(value.clone(), source_indices.clone(), variant_wire.clone())).clone() { JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => Rc::new(EmitterOutcome::Refused { reason: r.clone(), }), JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat(v1_rt::concat("[".to_string(), ps.clone().join(&", ".to_string())), "]".to_string()), + json: v1_rt::concat(v1_rt::concat("{".to_string(), ps.clone().join(&", ".to_string())), "}".to_string()), }), } -}, - ExprData::ExprRecordLit { parent_enum: pe, .. } => match pe.clone() { - Some(parent) => Rc::new(EmitterOutcome::Refused { - reason: v1_rt::concat(v1_rt::concat("variant record literal on the serde_json data path (parent coproduct: ".to_string(), parent.clone()), "): the variant wire spelling is the parent's declared VariantEncoding policy, a module-local fact of the parent's home module this path does not carry; null and the untagged map are both measured to fail serde deserialization. Trigger for spelling instead of refusing: a closure-wide wire-policy index beside EmitGraphInfo.type_decl_items".to_string()), -}), - std::option::Option::None => if ((value.children.clone().len() as i64) == 0) { - Rc::new(EmitterOutcome::Emitted { - json: "null".to_string(), + }, + DataVariantWireSpelling::DataVariantBareString { tag: t, .. } => if ((value.children.clone().len() as i64) == 0) { + Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat(v1_rt::concat("\"".to_string(), crate::v1_compiler_emit_core_support::escape_json_string(t.clone())), "\"".to_string()), }) - } else { - { - let accum = value.children.clone().iter().cloned().fold(Rc::new(JsonFragmentsAccum::FragmentsAccumulated { - pieces: Rc::new(vec![]), -}), |acc: Rc, fld: Rc| accumulate_json_field(acc, crate::v1_std_core::field_init_node_name_at(fld.clone(), source_indices.clone()), emit_data_value_json(crate::v1_std_core::field_init_node_value(fld.clone()), source_indices.clone()))); -match (*accum.clone()).clone() { + } else { + Rc::new(EmitterOutcome::Refused { + reason: v1_rt::concat(v1_rt::concat("variant ".to_string(), key.clone()), " carries fields under a StringVariant wire policy, which is nullary-only; the type-side emission of the parent coproduct refuses it too".to_string()), +}) + }, + DataVariantWireSpelling::DataVariantInternalTagged { tag_field: tf, tag: t, .. } => { + let tag_piece = v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("\"".to_string(), crate::v1_compiler_emit_core_support::escape_json_string(tf.clone())), "\": \"".to_string()), crate::v1_compiler_emit_core_support::escape_json_string(t.clone())), "\"".to_string()); +match (*emit_data_fields_json(value.clone(), source_indices.clone(), variant_wire.clone())).clone() { JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => Rc::new(EmitterOutcome::Refused { reason: r.clone(), }), JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat(v1_rt::concat("{".to_string(), ps.clone().join(&", ".to_string())), "}".to_string()), + json: v1_rt::concat(v1_rt::concat("{".to_string(), v1_rt::concat(Rc::new(vec![tag_piece.clone()]), ps.clone()).join(&", ".to_string())), "}".to_string()), }), } -} - }, }, - ExprData::ExprVar { binding_kind: _, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat(v1_rt::concat("\"".to_string(), crate::v1_compiler_emit_core_support::escape_json_string(crate::v1_std_core::expr_var_name_at(value.clone(), source_indices.clone()))), "\"".to_string()), -}), - ExprData::ExprUnaryOp { op: UnaryOpKind::Neg, .. } => match (*emit_data_value_json(crate::v1_std_core::unaryop_operand(value.clone()), source_indices.clone())).clone() { - EmitterOutcome::Emitted { json: j, .. } => Rc::new(EmitterOutcome::Emitted { - json: v1_rt::concat("-".to_string(), j.clone()), -}), - EmitterOutcome::Refused { reason: r, .. } => Rc::new(EmitterOutcome::Refused { - reason: r.clone(), -}), }, - _ => Rc::new(EmitterOutcome::Refused { - reason: "unsupported mock expression".to_string(), -}), } + } + }, + std::option::Option::None => { + if ((value.children.clone().len() as i64) == 0) { + Rc::new(EmitterOutcome::Emitted { + json: "null".to_string(), + }) + } else { + match (*emit_data_fields_json( + value.clone(), + source_indices.clone(), + variant_wire.clone(), + )) + .clone() + { + JsonFragmentsAccum::FragmentsRefused { reason: r, .. } => { + Rc::new(EmitterOutcome::Refused { reason: r.clone() }) + } + JsonFragmentsAccum::FragmentsAccumulated { pieces: ps, .. } => { + Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat( + v1_rt::concat( + "{".to_string(), + ps.clone().join(&", ".to_string()), + ), + "}".to_string(), + ), + }) + } + } + } + } + }, + ExprData::ExprVar { + binding_kind: _, .. + } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat( + v1_rt::concat( + "\"".to_string(), + crate::v1_compiler_emit_core_support::escape_json_string( + crate::v1_std_core::expr_var_name_at( + value.clone(), + source_indices.clone(), + ), + ), + ), + "\"".to_string(), + ), + }), + ExprData::ExprUnaryOp { + op: UnaryOpKind::Neg, + .. + } => match (*emit_data_value_json( + crate::v1_std_core::unaryop_operand(value.clone()), + source_indices.clone(), + variant_wire.clone(), + )) + .clone() + { + EmitterOutcome::Emitted { json: j, .. } => Rc::new(EmitterOutcome::Emitted { + json: v1_rt::concat("-".to_string(), j.clone()), + }), + EmitterOutcome::Refused { reason: r, .. } => { + Rc::new(EmitterOutcome::Refused { reason: r.clone() }) + } + }, + _ => Rc::new(EmitterOutcome::Refused { + reason: "unsupported mock expression".to_string(), + }), + } }) } diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 46212e50edc..503740cf544 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -176,6 +176,8 @@ pub use crate::v1_compiler_infer::{ expand_type_for_field_access, expr_span, extend_scope, is_where_refinement_type, resolved_type_name, }; +pub use crate::v1_compiler_infer_emit_info::DataVariantWireSpelling; +use crate::v1_compiler_infer_emit_info::DataVariantWireSpelling::*; use crate::v1_compiler_infer_emit_info::TypeRepr::{EnumRepr, StructRepr}; pub use crate::v1_compiler_infer_emit_info::{ collect_type_node_import_surface_names, collect_type_node_import_surface_occurrences, @@ -5069,6 +5071,294 @@ pub fn resolve_local_coproduct_wire_policy( } } +pub fn resolve_emission_coproduct_wire_policy( + coproduct_name: String, + item: Rc, + wire_items: Rc>>, + wire_imports: Rc>>, + wire_contract_item: Option>, + scoped_data_items: Rc>>>>, + source_indices: Rc>>, +) -> Rc { + { + let all_unit_variants = { + let mut __all = true; + for child in item.children.clone().iter().cloned() { + if !((child.children.clone().len() as i64) == 0) { + __all = false; + break; + } + } + __all + }; + match resolve_local_coproduct_wire_policy( + coproduct_name.clone(), + all_unit_variants.clone(), + wire_items.clone(), + wire_imports.clone(), + source_indices.clone(), + ) { + Some(local_policy) => local_policy.clone(), + std::option::Option::None => { + if all_unit_variants.clone() { + match wire_contract_item.clone() { + Some(_) => resolve_wire_serde_policy_for_coproduct( + wire_contract_item.clone(), + source_indices.clone(), + scoped_data_items.clone(), + ), + std::option::Option::None => rust_tagged_object_policy(), + } + } else { + rust_tagged_object_policy() + } + } + } + } +} + +pub fn data_path_wire_variant_tag( + authored: String, + policy: Rc, +) -> Option { + if (policy.error_message.clone() != std::option::Option::None) { + std::option::Option::None + } else { + match policy.rename_style.clone() { + Some(_) => wire_variant_tag_for_policy(authored.clone(), policy.clone()), + std::option::Option::None => { + if v1_rt::contains( + policy.enum_attr.clone(), + "rename_all = \"snake_case\"".to_string(), + ) { + Some(crate::v1_compiler_emit_core_support::to_snake( + authored.clone(), + )) + } else { + if v1_rt::contains( + policy.enum_attr.clone(), + "rename_all = \"SCREAMING_SNAKE_CASE\"".to_string(), + ) { + Some(crate::v1_compiler_emit_core_support::to_screaming_snake( + authored.clone(), + )) + } else { + Some(authored.clone()) + } + } + } + } + } +} + +pub fn project_data_variant_spelling( + authored_variant: String, + policy: Rc, +) -> Rc { + match policy.error_message.clone() { + Some(message) => Rc::new(DataVariantWireSpelling::DataVariantSpellingRefused { + reason: message.clone(), + }), + std::option::Option::None => { + if policy_is_untagged(policy.clone()) { + Rc::new(DataVariantWireSpelling::DataVariantUntagged) + } else { + match data_path_wire_variant_tag(authored_variant.clone(), policy.clone()) { + std::option::Option::None => { + Rc::new(DataVariantWireSpelling::DataVariantSpellingRefused { + reason: v1_rt::concat( + v1_rt::concat( + "no wire tag derivable for variant ".to_string(), + authored_variant.clone(), + ), + " under the parent coproduct's naming policy".to_string(), + ), + }) + } + Some(tag) => { + if policy_is_string_variant(policy.clone()) { + Rc::new(DataVariantWireSpelling::DataVariantBareString { + tag: tag.clone(), + }) + } else { + match policy_serde_tag_field(policy.clone()) { + Some(tag_field) => Rc::new(DataVariantWireSpelling::DataVariantInternalTagged { + tag_field: tag_field.clone(), + tag: tag.clone(), +}), + std::option::Option::None => Rc::new(DataVariantWireSpelling::DataVariantSpellingRefused { + reason: v1_rt::concat(v1_rt::concat("wire policy for variant ".to_string(), authored_variant.clone()), " carries no tag field and is neither untagged nor string-variant".to_string()), +}), +} + } + } + } + } + } + } +} + +pub fn data_variant_wire_spelling_eq( + a: Rc, + b: Rc, +) -> bool { + match (*a.clone()).clone() { + DataVariantWireSpelling::DataVariantInternalTagged { + tag_field: a_tf, + tag: a_t, + .. + } => match (*b.clone()).clone() { + DataVariantWireSpelling::DataVariantInternalTagged { + tag_field: b_tf, + tag: b_t, + .. + } => ((a_tf.clone() == b_tf.clone()) && (a_t.clone() == b_t.clone())), + _ => false, + }, + DataVariantWireSpelling::DataVariantBareString { tag: a_t, .. } => { + match (*b.clone()).clone() { + DataVariantWireSpelling::DataVariantBareString { tag: b_t, .. } => { + (a_t.clone() == b_t.clone()) + } + _ => false, + } + } + DataVariantWireSpelling::DataVariantUntagged => match (*b.clone()).clone() { + DataVariantWireSpelling::DataVariantUntagged => true, + _ => false, + }, + DataVariantWireSpelling::DataVariantSpellingRefused { reason: a_r, .. } => { + match (*b.clone()).clone() { + DataVariantWireSpelling::DataVariantSpellingRefused { reason: b_r, .. } => { + (a_r.clone() == b_r.clone()) + } + _ => false, + } + } + } +} + +pub fn insert_data_variant_spelling( + index: Rc>>, + key: String, + spelling: Rc, + coproduct_name: String, +) -> Rc>> { + match v1_rt::map_get(&index, key.clone()) { + std::option::Option::None => { + v1_rt::rc_map_insert(index.clone(), key.clone(), spelling.clone()) + } + Some(existing) => { + if data_variant_wire_spelling_eq(existing.clone(), spelling.clone()) { + index.clone() + } else { + v1_rt::rc_map_insert(index.clone(), key.clone(), Rc::new(DataVariantWireSpelling::DataVariantSpellingRefused { + reason: v1_rt::concat(v1_rt::concat("ambiguous wire policy for coproduct ".to_string(), coproduct_name.clone()), ": two modules in the emission closure declare it and their resolved policies differ, so a bare parent_enum cannot name one policy".to_string()), +})) + } + } + } +} + +pub fn build_data_variant_wire_spellings( + modules: Rc>>, + data_items: Rc>>, + module_index: Rc, +) -> Rc>> { + modules.clone().iter().cloned().fold( + v1_rt::rc_empty_map::>(), + |acc: Rc>>, tm: Rc| { + let si = tm.type_env.clone().source_indices.clone(); + let module_name = crate::v1_std_core::authored_name_at(si.clone(), tm.module.clone()); + let contracts_imports = contracts_imports_for_module( + module_name.clone(), + modules.clone(), + si.clone(), + module_index.clone(), + ); + let wire_items = v1_rt::concat( + tm.items.clone(), + contracts_items_for_module( + module_name.clone(), + modules.clone(), + si.clone(), + module_index.clone(), + ), + ); + let wire_imports = v1_rt::concat( + crate::v1_std_core::module_imports(tm.module.clone()), + contracts_imports.clone(), + ); + let scoped_data = augment_scoped_data_item_index_with_imports( + build_scoped_data_item_index(tm.clone(), data_items.clone()), + contracts_imports.clone(), + si.clone(), + data_items.clone(), + ); + let wire_contract_item = Rc::new({ + let mut __result = Vec::new(); + for i in wire_items.iter().cloned() { + if ((i.module_item_kind.clone() == ParsedModuleItemKind::ModuleItemDataValue) + && (crate::v1_std_core::authored_name_at(si.clone(), i.clone()) + == "wire_contract".to_string())) + { + __result.push(i); + } + } + __result + }) + .first() + .cloned(); + Rc::new({ + let mut __result = Vec::new(); + for item in tm.items.clone().iter().cloned() { + if (crate::v1_compiler_emit_core_support::is_type_def_item(item.clone()) + && crate::v1_compiler_infer_types::is_coproduct_type(item.clone())) + { + __result.push(item); + } + } + __result + }) + .iter() + .cloned() + .fold( + acc, + |item_acc: Rc>>, item: Rc| { + let coproduct_name = + crate::v1_std_core::authored_name_at(si.clone(), item.clone()); + let policy = resolve_emission_coproduct_wire_policy( + coproduct_name.clone(), + item.clone(), + wire_items.clone(), + wire_imports.clone(), + wire_contract_item.clone(), + scoped_data.clone(), + si.clone(), + ); + item.children.clone().iter().cloned().fold( + item_acc, + |variant_acc: Rc>>, + variant: Rc| { + let variant_name = + crate::v1_std_core::authored_name_at(si.clone(), variant.clone()); + insert_data_variant_spelling( + variant_acc, + v1_rt::concat( + v1_rt::concat(coproduct_name.clone(), ".".to_string()), + variant_name.clone(), + ), + project_data_variant_spelling(variant_name.clone(), policy.clone()), + coproduct_name.clone(), + ) + }, + ) + }, + ) + }, + ) +} + pub fn build_data_item_index(modules: Rc>>) -> Rc>> { modules.iter().cloned().fold( v1_rt::rc_empty_map::>(), @@ -6161,10 +6451,20 @@ pub fn build_emit_rust_context(typed: Rc) -> Rc base_info.type_decl_items.clone(), merged_module_source_indices(typed.modules.clone()), ); + let data_items = build_data_item_index(typed.modules.clone()); + let module_index = build_module_index( + typed.modules.clone(), + base_info.item_leaf_owner_modules.clone(), + ); let emit_info = Rc::new(EmitGraphInfo { item_leaf_owner_modules: base_info.item_leaf_owner_modules.clone(), type_summaries: base_info.type_summaries.clone(), type_decl_items: base_info.type_decl_items.clone(), + data_variant_wire_spellings: build_data_variant_wire_spellings( + typed.modules.clone(), + data_items.clone(), + module_index.clone(), + ), fn_decl_items: base_info.fn_decl_items.clone(), recursive_type_set: base_info.recursive_type_set.clone(), fielded_variants: base_info.fielded_variants.clone(), @@ -6185,7 +6485,6 @@ pub fn build_emit_rust_context(typed: Rc) -> Rc expected_type: std::option::Option::None, }); let registry = typed.item_registry.clone(); - let data_items = build_data_item_index(typed.modules.clone()); let workflow_funcs = collect_workflow_funcs( typed.modules.clone(), registry.clone(), @@ -6259,10 +6558,6 @@ pub fn build_emit_rust_context(typed: Rc) -> Rc let test_projections = crate::v1_compiler_emit_core_support::extract_test_projections(typed.clone()); let export_sets = build_module_export_sets(typed.modules.clone()); - let module_index = build_module_index( - typed.modules.clone(), - emit_info.item_leaf_owner_modules.clone(), - ); let unlisted_type_names_by_module = group_unlisted_type_names(typed.diagnostics.clone()); Rc::new(EmitRustContext { emit_info: emit_info.clone(), @@ -7287,10 +7582,20 @@ pub fn emit_module( base_info.type_decl_items.clone(), merged_module_source_indices(Rc::new(vec![typed_module.clone()])), ); + let data_items = build_data_item_index(Rc::new(vec![typed_module.clone()])); + let module_index = build_module_index( + Rc::new(vec![typed_module.clone()]), + base_info.item_leaf_owner_modules.clone(), + ); let emit_info = Rc::new(EmitGraphInfo { item_leaf_owner_modules: base_info.item_leaf_owner_modules.clone(), type_summaries: base_info.type_summaries.clone(), type_decl_items: base_info.type_decl_items.clone(), + data_variant_wire_spellings: build_data_variant_wire_spellings( + Rc::new(vec![typed_module.clone()]), + data_items.clone(), + module_index.clone(), + ), fn_decl_items: base_info.fn_decl_items.clone(), recursive_type_set: base_info.recursive_type_set.clone(), fielded_variants: base_info.fielded_variants.clone(), @@ -7312,17 +7617,13 @@ pub fn emit_module( }); let shared_types = emit_info.shared_types.clone(); let export_sets = build_module_export_sets(Rc::new(vec![typed_module.clone()])); - let module_index = build_module_index( - Rc::new(vec![typed_module.clone()]), - emit_info.item_leaf_owner_modules.clone(), - ); emit_module_full( typed_module.clone(), registry.clone(), emit_info.clone(), shared_types.clone(), v1_rt::rc_empty_map::(), - build_data_item_index(Rc::new(vec![typed_module.clone()])), + data_items.clone(), export_sets.clone(), Rc::new(vec![typed_module.clone()]), module_index.clone(), @@ -10210,6 +10511,7 @@ pub fn emit_module_full( item_leaf_owner_modules: emit_info.item_leaf_owner_modules.clone(), type_summaries: emit_info.type_summaries.clone(), type_decl_items: emit_info.type_decl_items.clone(), + data_variant_wire_spellings: emit_info.data_variant_wire_spellings.clone(), fn_decl_items: emit_info.fn_decl_items.clone(), recursive_type_set: emit_info.recursive_type_set.clone(), fielded_variants: emit_info.fielded_variants.clone(), @@ -15331,6 +15633,7 @@ pub fn emit_typed_item( item_leaf_owner_modules: emit_info.item_leaf_owner_modules.clone(), type_summaries: emit_info.type_summaries.clone(), type_decl_items: emit_info.type_decl_items.clone(), + data_variant_wire_spellings: emit_info.data_variant_wire_spellings.clone(), fn_decl_items: emit_info.fn_decl_items.clone(), recursive_type_set: emit_info.recursive_type_set.clone(), fielded_variants: emit_info.fielded_variants.clone(), @@ -15432,6 +15735,7 @@ pub fn emit_typed_item( registry.clone(), shared_types.clone(), env.clone(), + emit_info.clone(), ) } } @@ -15977,41 +16281,15 @@ pub fn emit_type_def_from_connective( } } else { { - let all_unit_variants = { - let mut __all = true; - for child in item.children.clone().iter().cloned() { - if !((child.children.clone().len() as i64) == 0) { - __all = false; - break; - } - } - __all - }; - let serde_policy = match resolve_local_coproduct_wire_policy( + let serde_policy = resolve_emission_coproduct_wire_policy( item_text.clone(), - all_unit_variants.clone(), + item.clone(), module_items.clone(), imports.clone(), + wire_contract_item.clone(), + data_items.clone(), env.source_indices.clone(), - ) { - Some(local_policy) => local_policy.clone(), - std::option::Option::None => { - if all_unit_variants.clone() { - match wire_contract_item.clone() { - Some(_) => resolve_wire_serde_policy_for_coproduct( - wire_contract_item.clone(), - env.source_indices.clone(), - data_items.clone(), - ), - std::option::Option::None => { - rust_tagged_object_policy() - } - } - } else { - rust_tagged_object_policy() - } - } - }; + ); let rename_validations = variant_rename_validations_for_policy( item.children.clone(), env.clone(), @@ -25960,6 +26238,9 @@ pub fn emit_rust_fold_method_call( item_leaf_owner_modules: emit_info.item_leaf_owner_modules.clone(), type_summaries: emit_info.type_summaries.clone(), type_decl_items: emit_info.type_decl_items.clone(), + data_variant_wire_spellings: emit_info + .data_variant_wire_spellings + .clone(), fn_decl_items: emit_info.fn_decl_items.clone(), recursive_type_set: emit_info.recursive_type_set.clone(), fielded_variants: emit_info.fielded_variants.clone(), @@ -26006,6 +26287,9 @@ pub fn emit_rust_fold_method_call( item_leaf_owner_modules: emit_info.item_leaf_owner_modules.clone(), type_summaries: emit_info.type_summaries.clone(), type_decl_items: emit_info.type_decl_items.clone(), + data_variant_wire_spellings: emit_info + .data_variant_wire_spellings + .clone(), fn_decl_items: emit_info.fn_decl_items.clone(), recursive_type_set: emit_info.recursive_type_set.clone(), fielded_variants: emit_info.fielded_variants.clone(), @@ -33155,6 +33439,7 @@ pub fn emit_service_def( registry: Rc>>, shared_types: Rc>, env: Rc, + emit_info: Rc, ) -> String { { let safe_name = crate::v1_compiler_emit_core_support::sanitize_service_name( @@ -33177,6 +33462,7 @@ pub fn emit_service_def( shared_types.clone(), env.clone(), item.clone(), + emit_info.clone(), ); v1_rt::concat( v1_rt::concat(struct_def.clone(), "\n\n".to_string()), @@ -33271,6 +33557,7 @@ pub fn emit_service_impl( shared_types: Rc>, env: Rc, service_item: Rc, + emit_info: Rc, ) -> String { { let depth = 0; @@ -33293,6 +33580,7 @@ pub fn emit_service_impl( shared_types.clone(), env.clone(), service_item.clone(), + emit_info.clone(), )); } __result @@ -33518,6 +33806,7 @@ pub fn emit_operation_method( shared_types: Rc>, env: Rc, service_item: Rc, + emit_info: Rc, ) -> String { { let op_text = crate::v1_compiler_infer_env::authored_name(env.clone(), op_node.clone()); @@ -33603,6 +33892,7 @@ pub fn emit_operation_method( op_node.clone(), env.clone(), shared_types.clone(), + emit_info.clone(), ); let body = v1_rt::concat( v1_rt::concat( @@ -33689,6 +33979,7 @@ pub fn emit_dry_run_branch_from_props( op_node: Rc, env: Rc, shared_types: Rc>, + emit_info: Rc, ) -> String { { let log_line = v1_rt::concat( @@ -33699,7 +33990,7 @@ pub fn emit_dry_run_branch_from_props( { let first_mock = mock_props.clone().first().cloned(); match first_mock.clone() { - Some(mp) => match (*crate::v1_compiler_emit::emit_data_value_json(crate::v1_std_core::field_init_node_value(mp.clone()), source_indices.clone())).clone() { + Some(mp) => match (*crate::v1_compiler_emit::emit_data_value_json(crate::v1_std_core::field_init_node_value(mp.clone()), source_indices.clone(), emit_info.data_variant_wire_spellings.clone())).clone() { EmitterOutcome::Refused { reason: r, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(log_line.clone(), "\ncompile_error!(\"".to_string()), crate::v1_compiler_emit_core_support::escape_string_literal_body(r.clone())), "\");".to_string()), EmitterOutcome::Emitted { json: mock_json, .. } => { let is_multi_field_conj = ((inferred.connective.clone() == Connective::Conj) && ((inferred.children.clone().len() as i64) > 1)); @@ -36648,6 +36939,7 @@ pub fn emit_data_def_body( match (*crate::v1_compiler_emit::emit_data_value_json( value.clone(), scope.type_env.clone().source_indices.clone(), + emit_info.data_variant_wire_spellings.clone(), )) .clone() { diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index 5b456fa6d58..bfedadcfdeb 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -83,6 +83,8 @@ pub use crate::v1_compiler_coercion::provenance_realizes_natively; pub use crate::v1_compiler_infer_access::AccessCheckResultNode; pub use crate::v1_compiler_infer_access::{check_index_access_node, check_slice_access_node}; pub use crate::v1_compiler_infer_cycle::detect_type_cycles_kahn; +pub use crate::v1_compiler_infer_emit_info::DataVariantWireSpelling; +use crate::v1_compiler_infer_emit_info::DataVariantWireSpelling::*; use crate::v1_compiler_infer_emit_info::TypeRepr::{EnumRepr, StructRepr}; pub use crate::v1_compiler_infer_emit_info::{ add_emit_item_summary, build_enum_field_summaries, build_struct_field_summaries, @@ -25817,6 +25819,8 @@ pub fn build_emit_graph_info( crate::v1_compiler_infer_items::leaf_owner_modules_from_registry(registry.clone()), type_summaries: built.type_summaries.clone(), type_decl_items: built.type_decl_items.clone(), + data_variant_wire_spellings: v1_rt::rc_empty_map::>( + ), fn_decl_items: built.fn_decl_items.clone(), recursive_type_set: all_recursive.clone(), fielded_variants: variant_shapes.fielded.clone(), diff --git a/src/v1/stage0/src/v1_compiler_infer_emit_info.rs b/src/v1/stage0/src/v1_compiler_infer_emit_info.rs index 9b268cd43c5..3f6f0bb301d 100644 --- a/src/v1/stage0/src/v1_compiler_infer_emit_info.rs +++ b/src/v1/stage0/src/v1_compiler_infer_emit_info.rs @@ -1,6 +1,7 @@ // Generated by v1 compiler -- do not edit. // Source module: v1.compiler.infer_emit_info +use self::DataVariantWireSpelling::*; use self::TypeRepr::*; pub use crate::std_dissolution::DissolutionCondition; use crate::std_dissolution::DissolutionCondition::*; @@ -194,10 +195,20 @@ pub fn collect_type_node_import_surface_names( })) } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum DataVariantWireSpelling { + DataVariantInternalTagged { tag_field: String, tag: String }, + DataVariantBareString { tag: String }, + DataVariantUntagged, + DataVariantSpellingRefused { reason: String }, +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct EmitGraphInfo { pub type_summaries: Rc>>, pub type_decl_items: Rc>>, + pub data_variant_wire_spellings: Rc>>, pub fn_decl_items: Rc>>, pub recursive_type_set: Rc>, pub fielded_variants: Rc>, @@ -233,6 +244,7 @@ pub fn empty_emit_graph_info() -> Rc { item_leaf_owner_modules: v1_rt::rc_empty_map::>(), type_summaries: v1_rt::rc_empty_map::>(), type_decl_items: v1_rt::rc_empty_map::>(), + data_variant_wire_spellings: v1_rt::rc_empty_map::>(), fn_decl_items: v1_rt::rc_empty_map::>(), recursive_type_set: v1_rt::rc_empty_set::(), fielded_variants: v1_rt::rc_empty_set::(), @@ -263,6 +275,7 @@ pub fn emit_info_with_fn_type_context( item_leaf_owner_modules: emit_info.item_leaf_owner_modules.clone(), type_summaries: emit_info.type_summaries.clone(), type_decl_items: emit_info.type_decl_items.clone(), + data_variant_wire_spellings: emit_info.data_variant_wire_spellings.clone(), fn_decl_items: emit_info.fn_decl_items.clone(), recursive_type_set: emit_info.recursive_type_set.clone(), fielded_variants: emit_info.fielded_variants.clone(), @@ -292,6 +305,7 @@ pub fn emit_info_with_fn_return( item_leaf_owner_modules: emit_info.item_leaf_owner_modules.clone(), type_summaries: emit_info.type_summaries.clone(), type_decl_items: emit_info.type_decl_items.clone(), + data_variant_wire_spellings: emit_info.data_variant_wire_spellings.clone(), fn_decl_items: emit_info.fn_decl_items.clone(), recursive_type_set: emit_info.recursive_type_set.clone(), fielded_variants: emit_info.fielded_variants.clone(), @@ -321,6 +335,7 @@ pub fn emit_info_with_expected_type( item_leaf_owner_modules: emit_info.item_leaf_owner_modules.clone(), type_summaries: emit_info.type_summaries.clone(), type_decl_items: emit_info.type_decl_items.clone(), + data_variant_wire_spellings: emit_info.data_variant_wire_spellings.clone(), fn_decl_items: emit_info.fn_decl_items.clone(), recursive_type_set: emit_info.recursive_type_set.clone(), fielded_variants: emit_info.fielded_variants.clone(), From caec92ce6c742c04814ea8cda764cfcf1e25cc70 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 22:48:34 +0000 Subject: [PATCH 35/89] Promote field_access to SelfEmittedNative on the emit coverage frontier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourth native-eval construct promotion, after classical_not, add, and complement. The native-only verdict arm pair lands in the already file-grain-enrolled long/ entry, so the backing citation is enrolled by construction: - emit_host_native_only_field_access_holds pins the family one-build cache run's stdout to octet 9 (the byte the family witness's warm leg pins on the same build), eval() never called. - emit_host_native_only_field_access_wrong_octet_mismatch_detected_holds breaks the expectation side with octet 1, the alt tree's byte. Both arms verified wet locally (real cargo build + native run, sharing the field_access family one-build cache key). The roster row flips to SelfEmittedNative; the two census guards update per 4b(4) — the split moves to 4 native / 11 retained and the identity-grain membership guard is renamed to name the four-member population. The family's equals_eval agreement pair stays enrolled as its program-side discrimination leg. --- .tmp-parity-probe/parity_probe.dag | 74 +++++++++++++++++++ .../self_host/emit_coverage_frontier.dag | 23 +++--- .../emit_host_native_only_verdict_test.dag | 60 +++++++++++++++ .../self_host/emit_coverage_frontier_test.dag | 12 +-- 4 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 .tmp-parity-probe/parity_probe.dag diff --git a/.tmp-parity-probe/parity_probe.dag b/.tmp-parity-probe/parity_probe.dag new file mode 100644 index 00000000000..d4781da5bd7 --- /dev/null +++ b/.tmp-parity-probe/parity_probe.dag @@ -0,0 +1,74 @@ +module test.parity.probe + +import std.process { ProcessExit, ExitSuccess, ExitFailure } +import v2.compiler.compile { compile_dag_source_to_target_text } +import v2.std.diagnostic { Outcome, Diagnostic, NonEmptyDiagnostics, None } +import v2.test.execution.dag_add_emit_round_trip { dag_add_emit_receipt } +import v2.compiler.translate { target_serialize_source_from_model } +import v2.extdeps.languages.dag { dag_target_model, dag_fixture_emitted_add_fn } + +type ReasonCounts { + overlap: Int + module_not_found: Int + hollow_alias: Int + unbound_symbol: Int + other: Int +} + +fn count_reason(c: ReasonCounts, d: Diagnostic) -> ReasonCounts { + if d.reason == ^parse_grammar_choice_overlap_residue { + ReasonCounts { overlap: c.overlap + 1, module_not_found: c.module_not_found, hollow_alias: c.hollow_alias, unbound_symbol: c.unbound_symbol, other: c.other } + } else if d.reason == ^resolve_module_not_found { + ReasonCounts { overlap: c.overlap, module_not_found: c.module_not_found + 1, hollow_alias: c.hollow_alias, unbound_symbol: c.unbound_symbol, other: c.other } + } else if d.reason == ^fact_density_hollow_alias_locus { + ReasonCounts { overlap: c.overlap, module_not_found: c.module_not_found, hollow_alias: c.hollow_alias + 1, unbound_symbol: c.unbound_symbol, other: c.other } + } else if d.reason == ^resolve_reason_unbound_symbol { + ReasonCounts { overlap: c.overlap, module_not_found: c.module_not_found, hollow_alias: c.hollow_alias, unbound_symbol: c.unbound_symbol + 1, other: c.other } + } else { + ReasonCounts { overlap: c.overlap, module_not_found: c.module_not_found, hollow_alias: c.hollow_alias, unbound_symbol: c.unbound_symbol, other: c.other + 1 } + } +} + +fn zero_counts() -> ReasonCounts { + ReasonCounts { overlap: 0, module_not_found: 0, hollow_alias: 0, unbound_symbol: 0, other: 0 } +} + +fn summarize(ds: NonEmptyDiagnostics) -> ReasonCounts { + fold(ds.tail, init: count_reason(c: zero_counts(), d: ds.head), f: fn(c, d) { + count_reason(c: c, d: d) + }) +} + +fn render_counts(c: ReasonCounts) -> String { + "overlap=" + to_string(c.overlap) + + " module_not_found=" + to_string(c.module_not_found) + + " hollow_alias=" + to_string(c.hollow_alias) + + " unbound_symbol=" + to_string(c.unbound_symbol) + + " other=" + to_string(c.other) +} + +fn compile_probe(source_text: String) -> ProcessExit { + match compile_dag_source_to_target_text(source_text) { + Accepted { value, diagnostics } => ExitSuccess + Rejected { diagnostics } => { + let c = summarize(ds: diagnostics) + ExitFailure { code: 1, reason: render_counts(c: c) } + } + } +} + +fn emit_source_probe(unused: String) -> ProcessExit { + match dag_add_emit_receipt { + Accepted { value, diagnostics } => + match target_serialize_source_from_model(target: dag_target_model(), emitted: dag_fixture_emitted_add_fn) { + Accepted { value: authority, diagnostics: ad } => + if (value.carried == authority.carried) && (diagnostics == None) && (ad == None) { + ExitFailure { code: 1, reason: "REPORT emit_bytes=[" + value.carried + "] authority_bytes=[" + authority.carried + "]" } + } else { + ExitFailure { code: 1, reason: "DIVERGE emit_bytes=[" + value.carried + "] authority_bytes=[" + authority.carried + "]" } + } + Rejected { diagnostics } => ExitFailure { code: 1, reason: "authority rejected" } + } + Rejected { diagnostics } => ExitFailure { code: 1, reason: "emit rejected" } + } +} diff --git a/src/v2/compiler/self_host/emit_coverage_frontier.dag b/src/v2/compiler/self_host/emit_coverage_frontier.dag index f51c144fbc6..cf57d4c4761 100644 --- a/src/v2/compiler/self_host/emit_coverage_frontier.dag +++ b/src/v2/compiler/self_host/emit_coverage_frontier.dag @@ -75,14 +75,15 @@ fn retained_via_eval_agreement( } } -// Two roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07, each on a -// native-only verdict arm (positive + wrong-octet broken control) landed in the already +// Three roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07/08, each on +// a native-only verdict arm (positive + wrong-octet broken control) landed in the already // file-grain-enrolled native-only verdict entry, so each backing citation is enrolled by // construction (coverage_row_backing_is_enrolled): the add family row (first element, backing -// emit_host_native_only_add_holds) and the complement family row (backing +// emit_host_native_only_add_holds), the complement family row (backing // emit_host_native_only_complement_holds, run through the logic family crate per the -// witness_family_build_grain_ruling). Each family's equals_eval agreement pair stays enrolled as -// its program-side discrimination leg. +// witness_family_build_grain_ruling), and the field_access family row (backing +// emit_host_native_only_field_access_holds, octet 9 pinned against the family one-build cache). Each +// family's equals_eval agreement pair stays enrolled as its program-side discrimination leg. data emit_coverage_frontier_roster: List = [ EmitCoverageFrontierRow { subject: coverage_subject( @@ -120,14 +121,18 @@ data emit_coverage_frontier_roster: List = [ backing_function: "emit_host_native_only_complement_holds" } }, - retained_via_eval_agreement( + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.execution.emit_host_field_access_equals_eval", decl_name: "emit_field_eval_subgraph_node" ), - entry: "src/v2/test/claim/execution/emit_host_field_access_equals_eval_test.dag", - f: "emit_host_field_access_equals_eval_holds" - ), + witness_entry: "src/v2/test/claim/execution/emit_host_field_access_equals_eval_test.dag", + witness_function: "emit_host_field_access_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_field_access_holds" + } + }, retained_via_eval_agreement( subject: coverage_subject( module_path: "v2.extdeps.languages.rust_test", diff --git a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag index fba82773768..84dda7a739c 100644 --- a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag +++ b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag @@ -9,6 +9,7 @@ import v2.extdeps.languages.rust { rust_target_model_staging } import v2.extdeps.languages.rust_test { rust_classical_not_ingested_target_model_staging, rust_family_member_run_args, + rust_field_access_target_model_staging, rust_logic_family_target_model_staging, } import extdeps.realization.emit_on_demand_host { native_cache_workspace_root } @@ -35,6 +36,14 @@ import v2.test.execution.emit_on_demand_family_crate_witness { family_crate_members, family_crate_native_key, } +import v2.test.execution.emit_host_field_access_equals_eval { + emit_field_eval_producer_tree, +} +import v2.test.execution.emit_on_demand_field_access_family_witness { + field_access_family_cache_root_for, + field_access_family_fixture_inputs, + field_access_family_native_key_for, +} import v2.std.diagnostic { Accepted, Rejected, Outcome, None } import v2.std.host_run { HostLogicalRun, HostRunStdout } import v2.std.logic { Bool } @@ -223,3 +232,54 @@ test fn emit_host_native_only_complement_wrong_octet_mismatch_detected_holds() - complement_native_only_matches_expected(expected_octet: 1) == false } +// FIELD-ACCESS-FAMILY ARM: the same native-only verdict shape for the emit_host_field_access +// family — the fourth construct promoted off InterpreterRetained, after classical_not, add, and +// complement. The family differences are facts of that family, not new machinery: the tree is +// FIXTURE-PRODUCED (emit_field_eval_producer_tree, the family's equals_eval tree constructor — no +// eval leg is reachable from this file despite the constructor's home module's name), and the run +// shares the field_access family's one-build cache key (field_access_family_cache_root_for + +// field_access_family_native_key_for, the same key emit_on_demand_field_access_native_one_build_holds +// colds) — an uncached leg here would duplicate a full cargo build the family witness already +// performs (DESIGN S2). The expected octet is 9, the byte the family witness's warm leg pins on +// this same build. Program-side discrimination stays where the family already carries it: the +// equals_eval pair and the family witness's wrong-fixture mismatch leg prove the native run's +// actual byte participates in a verdict (the wrong-field alt tree flips the octet 9 -> 1). A +// no-eval verdict has no oracle leg to break, so this arm's broken control breaks the EXPECTATION +// side: octet 1 is the alt tree's byte and can never match the primary run, and the comparator +// mocks above pin the comparator itself. +fn field_access_native_only_matches_expected(expected_octet: Int) -> Bool { + match emit_field_eval_producer_tree() { + Accepted { value: tree, diagnostics: _ } => + match emit(tree: tree, target: rust_field_access_target_model_staging()) { + Accepted { value: emitted, diagnostics: _ } => + match run_emit_host_cached( + workspace_dir: native_cache_workspace_root( + cache_root: field_access_family_cache_root_for(test_id: "native_one_build"), + key: field_access_family_native_key_for(tree: tree) + ), + target: rust_field_access_target_model_staging(), + source: emitted, + fixture_inputs: field_access_family_fixture_inputs() + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + native_only_stdout_matches(stdout: stdout, expected_octet: expected_octet) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_field_access_holds() -> Bool { + field_access_native_only_matches_expected(expected_octet: 9) +} + +test fn emit_host_native_only_field_access_wrong_octet_mismatch_detected_holds() -> Bool { + field_access_native_only_matches_expected(expected_octet: 1) == false +} + diff --git a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag index a04abffc815..8bc17225a16 100644 --- a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag +++ b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag @@ -43,18 +43,19 @@ test fn witness_unlisted_subject_has_no_row() -> Bool { } test fn witness_split_matches_expected() -> Bool { - (length(xs: self_emitted_native_rows()) == 3) - && (length(xs: interpreter_retained_rows()) == 12) + (length(xs: self_emitted_native_rows()) == 4) + && (length(xs: interpreter_retained_rows()) == 11) } // Identity-grain census of the native rows (DESIGN section 5: completeness is an identity join, // not a count equality — the count leg above is the budget cross-check, this leg names the // members). 2026-09-07: the add family joined classical_not when its native-only verdict arm // landed, and the complement family followed on the same arm shape run through the logic family -// crate; the three decl names are the closed membership. -test fn witness_native_rows_are_exactly_classical_not_add_and_complement() -> Bool { +// crate. 2026-09-08: the field_access family followed on the same arm shape against its own +// family one-build cache (octet 9 pinned); the four decl names are the closed membership. +test fn witness_native_rows_are_exactly_classical_not_add_complement_and_field_access() -> Bool { let natives = self_emitted_native_rows() - (length(xs: natives) == 3) + (length(xs: natives) == 4) && fold( natives, init: true, @@ -63,6 +64,7 @@ test fn witness_native_rows_are_exactly_classical_not_add_and_complement() -> Bo (row.subject.decl_name == "ingested_classical_not_source") || (row.subject.decl_name == "rust_add_inferred_tree") || (row.subject.decl_name == "emit_complement_eval_subject") + || (row.subject.decl_name == "emit_field_eval_subgraph_node") ) } ) From b91d8de1c3189d4a640d30063db4662b356c0a79 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 22:48:42 +0000 Subject: [PATCH 36/89] Drop the scratch parity probe from the tree The probe is a manual parity-loop instrument (the interpreted leg of the native-vs-interpreted comparison), not a corpus declaration with an executing consumer (DESIGN 6 experimental residue). It stays in use locally as an untracked file. --- .tmp-parity-probe/parity_probe.dag | 74 ------------------------------ 1 file changed, 74 deletions(-) delete mode 100644 .tmp-parity-probe/parity_probe.dag diff --git a/.tmp-parity-probe/parity_probe.dag b/.tmp-parity-probe/parity_probe.dag deleted file mode 100644 index d4781da5bd7..00000000000 --- a/.tmp-parity-probe/parity_probe.dag +++ /dev/null @@ -1,74 +0,0 @@ -module test.parity.probe - -import std.process { ProcessExit, ExitSuccess, ExitFailure } -import v2.compiler.compile { compile_dag_source_to_target_text } -import v2.std.diagnostic { Outcome, Diagnostic, NonEmptyDiagnostics, None } -import v2.test.execution.dag_add_emit_round_trip { dag_add_emit_receipt } -import v2.compiler.translate { target_serialize_source_from_model } -import v2.extdeps.languages.dag { dag_target_model, dag_fixture_emitted_add_fn } - -type ReasonCounts { - overlap: Int - module_not_found: Int - hollow_alias: Int - unbound_symbol: Int - other: Int -} - -fn count_reason(c: ReasonCounts, d: Diagnostic) -> ReasonCounts { - if d.reason == ^parse_grammar_choice_overlap_residue { - ReasonCounts { overlap: c.overlap + 1, module_not_found: c.module_not_found, hollow_alias: c.hollow_alias, unbound_symbol: c.unbound_symbol, other: c.other } - } else if d.reason == ^resolve_module_not_found { - ReasonCounts { overlap: c.overlap, module_not_found: c.module_not_found + 1, hollow_alias: c.hollow_alias, unbound_symbol: c.unbound_symbol, other: c.other } - } else if d.reason == ^fact_density_hollow_alias_locus { - ReasonCounts { overlap: c.overlap, module_not_found: c.module_not_found, hollow_alias: c.hollow_alias + 1, unbound_symbol: c.unbound_symbol, other: c.other } - } else if d.reason == ^resolve_reason_unbound_symbol { - ReasonCounts { overlap: c.overlap, module_not_found: c.module_not_found, hollow_alias: c.hollow_alias, unbound_symbol: c.unbound_symbol + 1, other: c.other } - } else { - ReasonCounts { overlap: c.overlap, module_not_found: c.module_not_found, hollow_alias: c.hollow_alias, unbound_symbol: c.unbound_symbol, other: c.other + 1 } - } -} - -fn zero_counts() -> ReasonCounts { - ReasonCounts { overlap: 0, module_not_found: 0, hollow_alias: 0, unbound_symbol: 0, other: 0 } -} - -fn summarize(ds: NonEmptyDiagnostics) -> ReasonCounts { - fold(ds.tail, init: count_reason(c: zero_counts(), d: ds.head), f: fn(c, d) { - count_reason(c: c, d: d) - }) -} - -fn render_counts(c: ReasonCounts) -> String { - "overlap=" + to_string(c.overlap) - + " module_not_found=" + to_string(c.module_not_found) - + " hollow_alias=" + to_string(c.hollow_alias) - + " unbound_symbol=" + to_string(c.unbound_symbol) - + " other=" + to_string(c.other) -} - -fn compile_probe(source_text: String) -> ProcessExit { - match compile_dag_source_to_target_text(source_text) { - Accepted { value, diagnostics } => ExitSuccess - Rejected { diagnostics } => { - let c = summarize(ds: diagnostics) - ExitFailure { code: 1, reason: render_counts(c: c) } - } - } -} - -fn emit_source_probe(unused: String) -> ProcessExit { - match dag_add_emit_receipt { - Accepted { value, diagnostics } => - match target_serialize_source_from_model(target: dag_target_model(), emitted: dag_fixture_emitted_add_fn) { - Accepted { value: authority, diagnostics: ad } => - if (value.carried == authority.carried) && (diagnostics == None) && (ad == None) { - ExitFailure { code: 1, reason: "REPORT emit_bytes=[" + value.carried + "] authority_bytes=[" + authority.carried + "]" } - } else { - ExitFailure { code: 1, reason: "DIVERGE emit_bytes=[" + value.carried + "] authority_bytes=[" + authority.carried + "]" } - } - Rejected { diagnostics } => ExitFailure { code: 1, reason: "authority rejected" } - } - Rejected { diagnostics } => ExitFailure { code: 1, reason: "emit rejected" } - } -} From 2a32538112e5b01b33ce5e0fddce68e72aef8fc5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 22:53:39 +0000 Subject: [PATCH 37/89] Promote match, loop, and fold_closure to SelfEmittedNative Fifth, sixth, and seventh native-eval construct promotions. The three match_loop_fold family rows flip together on one shared family-crate arm shape, per the witness_family_build_grain_ruling: each arm emits the three-member family crate once and runs its own member through the argv dispatcher against the family one-build cache key. - emit_host_native_only_{match,loop,fold_closure}_holds pin the warm legs' stdout to the family's declared octet lists (match/loop [0,1,0,0,0], fold [0,7,0,0,0]), eval() never called. - The wrong-octet controls break the expectation side with each member's own alt octets (match/loop [0,2,0,0,0], fold [0,255,255,255,255]). All six arms verified wet locally. The census guards update per 4b(4): 7 native / 8 retained, and the identity-grain membership guard is renamed to witness_native_rows_closed_membership_holds so the name stops encoding the volatile population. --- .../self_host/emit_coverage_frontier.dag | 48 ++++++--- .../emit_host_native_only_verdict_test.dag | 97 +++++++++++++++++++ .../self_host/emit_coverage_frontier_test.dag | 17 ++-- 3 files changed, 140 insertions(+), 22 deletions(-) diff --git a/src/v2/compiler/self_host/emit_coverage_frontier.dag b/src/v2/compiler/self_host/emit_coverage_frontier.dag index cf57d4c4761..2f7c15b7486 100644 --- a/src/v2/compiler/self_host/emit_coverage_frontier.dag +++ b/src/v2/compiler/self_host/emit_coverage_frontier.dag @@ -75,15 +75,19 @@ fn retained_via_eval_agreement( } } -// Three roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07/08, each on +// Seven roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07/08, each on // a native-only verdict arm (positive + wrong-octet broken control) landed in the already // file-grain-enrolled native-only verdict entry, so each backing citation is enrolled by // construction (coverage_row_backing_is_enrolled): the add family row (first element, backing // emit_host_native_only_add_holds), the complement family row (backing // emit_host_native_only_complement_holds, run through the logic family crate per the -// witness_family_build_grain_ruling), and the field_access family row (backing -// emit_host_native_only_field_access_holds, octet 9 pinned against the family one-build cache). Each -// family's equals_eval agreement pair stays enrolled as its program-side discrimination leg. +// witness_family_build_grain_ruling), the field_access family row (backing +// emit_host_native_only_field_access_holds, octet 9 pinned against the family one-build cache), and +// the three match_loop_fold family rows (match, loop, fold_closure — backing +// emit_host_native_only_{match,loop,fold_closure}_holds, run through the three-member family crate +// per the witness_family_build_grain_ruling, octet lists pinned against the family one-build +// cache). Each family's equals_eval agreement pair stays enrolled as its program-side +// discrimination leg. data emit_coverage_frontier_roster: List = [ EmitCoverageFrontierRow { subject: coverage_subject( @@ -141,30 +145,42 @@ data emit_coverage_frontier_roster: List = [ entry: "src/v2/test/claim/execution/emit_host_filesystem_read_equals_eval_test.dag", f: "emit_host_filesystem_read_equals_eval_holds" ), - retained_via_eval_agreement( + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.emit.fold_call_closure_fixture", decl_name: "fold_fixture_param_call_body" ), - entry: "src/v2/test/claim/execution/emit_host_fold_closure_equals_eval_test.dag", - f: "emit_host_fold_closure_equals_eval_holds" - ), - retained_via_eval_agreement( + witness_entry: "src/v2/test/claim/execution/emit_host_fold_closure_equals_eval_test.dag", + witness_function: "emit_host_fold_closure_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_fold_closure_holds" + } + }, + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.complexity_gate.table_fixture_loop_subject_producer", decl_name: "table_fixture_loop_arrow_with_body" ), - entry: "src/v2/test/claim/execution/emit_host_loop_equals_eval_test.dag", - f: "emit_host_loop_equals_eval_holds" - ), - retained_via_eval_agreement( + witness_entry: "src/v2/test/claim/execution/emit_host_loop_equals_eval_test.dag", + witness_function: "emit_host_loop_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_loop_holds" + } + }, + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.complexity_gate.table_fixture_match_subject_producer", decl_name: "table_fixture_match_arrow_with_body" ), - entry: "src/v2/test/claim/execution/emit_host_match_equals_eval_test.dag", - f: "emit_host_match_equals_eval_holds" - ), + witness_entry: "src/v2/test/claim/execution/emit_host_match_equals_eval_test.dag", + witness_function: "emit_host_match_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_match_holds" + } + }, retained_via_eval_agreement( subject: coverage_subject( module_path: "v2.test.execution.emit_host_meet_join_equals_eval", diff --git a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag index 84dda7a739c..1aa67260c93 100644 --- a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag +++ b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag @@ -11,6 +11,7 @@ import v2.extdeps.languages.rust_test { rust_family_member_run_args, rust_field_access_target_model_staging, rust_logic_family_target_model_staging, + rust_match_loop_fold_family_target_model_staging, } import extdeps.realization.emit_on_demand_host { native_cache_workspace_root } import v2.test.long.emit_host_classical_not_ingested_equals_eval { @@ -44,6 +45,22 @@ import v2.test.execution.emit_on_demand_field_access_family_witness { field_access_family_fixture_inputs, field_access_family_native_key_for, } +import v2.test.execution.emit_on_demand_match_loop_fold_family_witness { + family_cache_root_for, + fold_alt_expected_octets, + fold_expected_octets, + fold_primary_tree, + loop_alt_expected_octets, + loop_expected_octets, + loop_primary_tree, + match_alt_expected_octets, + match_expected_octets, + match_fixture_inputs, + match_primary_tree, + mlf_family_members, + mlf_family_native_key, +} +import v2.std.collection { List } import v2.std.diagnostic { Accepted, Rejected, Outcome, None } import v2.std.host_run { HostLogicalRun, HostRunStdout } import v2.std.logic { Bool } @@ -283,3 +300,83 @@ test fn emit_host_native_only_field_access_wrong_octet_mismatch_detected_holds() field_access_native_only_matches_expected(expected_octet: 1) == false } +// MATCH/LOOP/FOLD-FAMILY ARMS: the same native-only verdict shape for the three match_loop_fold +// family roster rows — the fifth, sixth, and seventh constructs promoted off InterpreterRetained. +// The family difference is the build grain, not the verdict shape, exactly as complement: the +// family's native execution is FAMILY-grain by the operator-directed witness_family_build_grain_ruling, +// so each arm emits the three-member family crate (match + loop + fold under the argv-dispatched +// family main) and runs its own member through the family dispatcher, sharing the family witness's +// one-build cache key (family_cache_root_for + mlf_family_native_key over the same primary member +// list emit_on_demand_match_loop_fold_family_one_build_holds colds) — an uncached leg here would +// duplicate the family cargo build (DESIGN S2). The expected octet lists are the bytes the family +// witness's warm legs pin on this same build (match [0,1,0,0,0], loop [0,1,0,0,0], fold +// [0,7,0,0,0]). Program-side discrimination stays where the family already carries it: each +// member's equals_eval pair and the family witness's alt legs prove the native run's actual bytes +// participate in verdicts (the alt trees flip match 1 -> 2, loop 1 -> 2, fold 7 -> 255...). A +// no-eval verdict has no oracle leg to break, so each arm's broken control breaks the EXPECTATION +// side with the member's own alt octets, and the comparator mocks above pin the comparator itself. +fn mlf_native_only_member_matches_expected(member_id: String, expected_octets: List) -> Bool { + match match_primary_tree() { + Accepted { value: match_tree, diagnostics: _ } => + match loop_primary_tree() { + Accepted { value: loop_tree, diagnostics: _ } => + let members = mlf_family_members( + match_tree: match_tree, + loop_tree: loop_tree, + fold_tree: fold_primary_tree() + ) + match emit_family( + members: members, + transport_target: rust_match_loop_fold_family_target_model_staging() + ) { + Accepted { value: source, diagnostics: _ } => + match run_emit_host_cached_with_run_args( + workspace_dir: native_cache_workspace_root( + cache_root: family_cache_root_for(test_id: "family_one_build"), + key: mlf_family_native_key(members: members) + ), + target: rust_match_loop_fold_family_target_model_staging(), + source: source, + fixture_inputs: match_fixture_inputs(), + run_args: rust_family_member_run_args(member_id: member_id) + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + stdout.bytes == emit_host_octets_byte_string(octets: expected_octets) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_match_holds() -> Bool { + mlf_native_only_member_matches_expected(member_id: "match", expected_octets: match_expected_octets) +} + +test fn emit_host_native_only_match_wrong_octet_mismatch_detected_holds() -> Bool { + mlf_native_only_member_matches_expected(member_id: "match", expected_octets: match_alt_expected_octets) == false +} + +test fn emit_host_native_only_loop_holds() -> Bool { + mlf_native_only_member_matches_expected(member_id: "loop", expected_octets: loop_expected_octets) +} + +test fn emit_host_native_only_loop_wrong_octet_mismatch_detected_holds() -> Bool { + mlf_native_only_member_matches_expected(member_id: "loop", expected_octets: loop_alt_expected_octets) == false +} + +test fn emit_host_native_only_fold_closure_holds() -> Bool { + mlf_native_only_member_matches_expected(member_id: "fold", expected_octets: fold_expected_octets) +} + +test fn emit_host_native_only_fold_closure_wrong_octet_mismatch_detected_holds() -> Bool { + mlf_native_only_member_matches_expected(member_id: "fold", expected_octets: fold_alt_expected_octets) == false +} + diff --git a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag index 8bc17225a16..b850ca69001 100644 --- a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag +++ b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag @@ -43,19 +43,21 @@ test fn witness_unlisted_subject_has_no_row() -> Bool { } test fn witness_split_matches_expected() -> Bool { - (length(xs: self_emitted_native_rows()) == 4) - && (length(xs: interpreter_retained_rows()) == 11) + (length(xs: self_emitted_native_rows()) == 7) + && (length(xs: interpreter_retained_rows()) == 8) } // Identity-grain census of the native rows (DESIGN section 5: completeness is an identity join, // not a count equality — the count leg above is the budget cross-check, this leg names the // members). 2026-09-07: the add family joined classical_not when its native-only verdict arm // landed, and the complement family followed on the same arm shape run through the logic family -// crate. 2026-09-08: the field_access family followed on the same arm shape against its own -// family one-build cache (octet 9 pinned); the four decl names are the closed membership. -test fn witness_native_rows_are_exactly_classical_not_add_complement_and_field_access() -> Bool { +// crate. 2026-09-08: the field_access family followed against its own family one-build cache +// (octet 9 pinned), and the three match_loop_fold family rows (match, loop, fold_closure) +// followed on the same arm shape run through the three-member family crate; the seven decl names +// are the closed membership. +test fn witness_native_rows_closed_membership_holds() -> Bool { let natives = self_emitted_native_rows() - (length(xs: natives) == 4) + (length(xs: natives) == 7) && fold( natives, init: true, @@ -65,6 +67,9 @@ test fn witness_native_rows_are_exactly_classical_not_add_complement_and_field_a || (row.subject.decl_name == "rust_add_inferred_tree") || (row.subject.decl_name == "emit_complement_eval_subject") || (row.subject.decl_name == "emit_field_eval_subgraph_node") + || (row.subject.decl_name == "fold_fixture_param_call_body") + || (row.subject.decl_name == "table_fixture_loop_arrow_with_body") + || (row.subject.decl_name == "table_fixture_match_arrow_with_body") ) } ) From 26536fa43a4a9c13eda0d11e4f441b352a072509 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 23:01:37 +0000 Subject: [PATCH 38/89] Promote meet_join to SelfEmittedNative on the emit coverage frontier The meet_join family's native-only verdict arms land on the complement arm's helper, generalized to take the family member_id: meet and join run through the same argv-dispatched logic family crate (one-build cache key shared with complement, per the witness_family_build_grain_ruling), eval() never called, verdict decoded from stdout. Octets meet=1 join=1 are the bytes the family witness's warm legs pin on this same build; the wrong-octet control expects each member's alt byte (0), which the primary runs can never produce. Both arms verified wet: cold build then warm hits, PASS/PASS. The roster row flips InterpreterRetained -> SelfEmittedNative (eighth promotion); census guards move to 8 native / 7 retained with meet_join_eval_subject named in the closed membership. --- .../self_host/emit_coverage_frontier.dag | 20 ++++++++----- .../emit_host_native_only_verdict_test.dag | 30 +++++++++++++++++-- .../self_host/emit_coverage_frontier_test.dag | 14 +++++---- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/v2/compiler/self_host/emit_coverage_frontier.dag b/src/v2/compiler/self_host/emit_coverage_frontier.dag index 2f7c15b7486..2e093fc0171 100644 --- a/src/v2/compiler/self_host/emit_coverage_frontier.dag +++ b/src/v2/compiler/self_host/emit_coverage_frontier.dag @@ -75,18 +75,20 @@ fn retained_via_eval_agreement( } } -// Seven roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07/08, each on +// Eight roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07/08, each on // a native-only verdict arm (positive + wrong-octet broken control) landed in the already // file-grain-enrolled native-only verdict entry, so each backing citation is enrolled by // construction (coverage_row_backing_is_enrolled): the add family row (first element, backing // emit_host_native_only_add_holds), the complement family row (backing // emit_host_native_only_complement_holds, run through the logic family crate per the // witness_family_build_grain_ruling), the field_access family row (backing -// emit_host_native_only_field_access_holds, octet 9 pinned against the family one-build cache), and +// emit_host_native_only_field_access_holds, octet 9 pinned against the family one-build cache), // the three match_loop_fold family rows (match, loop, fold_closure — backing // emit_host_native_only_{match,loop,fold_closure}_holds, run through the three-member family crate // per the witness_family_build_grain_ruling, octet lists pinned against the family one-build -// cache). Each family's equals_eval agreement pair stays enrolled as its program-side +// cache), and the meet_join family row (backing emit_host_native_only_meet_join_holds, run through +// the logic family crate it shares with complement, octets meet=1 join=1 pinned against the family +// one-build cache). Each family's equals_eval agreement pair stays enrolled as its program-side // discrimination leg. data emit_coverage_frontier_roster: List = [ EmitCoverageFrontierRow { @@ -181,14 +183,18 @@ data emit_coverage_frontier_roster: List = [ backing_function: "emit_host_native_only_match_holds" } }, - retained_via_eval_agreement( + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.execution.emit_host_meet_join_equals_eval", decl_name: "meet_join_eval_subject" ), - entry: "src/v2/test/claim/execution/emit_host_meet_join_equals_eval_test.dag", - f: "emit_host_meet_equals_eval_holds" - ), + witness_entry: "src/v2/test/claim/execution/emit_host_meet_join_equals_eval_test.dag", + witness_function: "emit_host_meet_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_meet_join_holds" + } + }, retained_via_eval_agreement( subject: coverage_subject( module_path: "v2.test.execution.emit_host_module_equals_eval", diff --git a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag index 1aa67260c93..5fb87b31e8a 100644 --- a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag +++ b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag @@ -215,7 +215,7 @@ test fn emit_host_native_only_add_wrong_octet_mismatch_detected_holds() -> Bool // actual byte participates in a verdict (the all-alt family flips complement 0 -> 1). A no-eval // verdict has no oracle leg to break, so this arm's broken control breaks the EXPECTATION side: // octet 1 can never match this run, and the comparator mocks above pin the comparator itself. -fn complement_native_only_matches_expected(expected_octet: Int) -> Bool { +fn logic_family_native_only_matches_expected(member_id: String, expected_octet: Int) -> Bool { let members = family_crate_members() match emit_family(members: members, transport_target: rust_logic_family_target_model_staging()) { Accepted { value: source, diagnostics: _ } => @@ -227,7 +227,7 @@ fn complement_native_only_matches_expected(expected_octet: Int) -> Bool { target: rust_logic_family_target_model_staging(), source: source, fixture_inputs: family_crate_fixture_inputs(), - run_args: rust_family_member_run_args(member_id: "complement") + run_args: rust_family_member_run_args(member_id: member_id) ) { Accepted { value: receipt, diagnostics: _ } => match receipt.logical_run { @@ -241,6 +241,10 @@ fn complement_native_only_matches_expected(expected_octet: Int) -> Bool { } } +fn complement_native_only_matches_expected(expected_octet: Int) -> Bool { + logic_family_native_only_matches_expected(member_id: "complement", expected_octet: expected_octet) +} + test fn emit_host_native_only_complement_holds() -> Bool { complement_native_only_matches_expected(expected_octet: 0) } @@ -249,6 +253,28 @@ test fn emit_host_native_only_complement_wrong_octet_mismatch_detected_holds() - complement_native_only_matches_expected(expected_octet: 1) == false } +// MEET-JOIN-FAMILY ARM: the same native-only verdict shape for the emit_host_meet_join family — +// the eighth construct promoted off InterpreterRetained. The family shares the logic family crate +// with complement (meet + join + complement under the argv-dispatched family main, per the +// witness_family_build_grain_ruling), so this arm reuses the complement arm's member- +// parameterized helper — same one-build cache key, same dispatcher, eval() never called. The +// expected octets are the bytes the family witness's warm legs pin on this same build (meet 1, +// join 1). Program-side discrimination stays where the family already carries it: the equals_eval +// pairs and the family witness's all-alt leg prove the native runs' actual bytes participate in +// verdicts (the alt trees flip meet 1 -> 0 and join 1 -> 0). A no-eval verdict has no oracle leg +// to break, so this arm's broken control breaks the EXPECTATION side: octet 0 is each member's +// alt byte and can never match the primary runs, and the comparator mocks above pin the +// comparator itself. +test fn emit_host_native_only_meet_join_holds() -> Bool { + logic_family_native_only_matches_expected(member_id: "meet", expected_octet: 1) + && logic_family_native_only_matches_expected(member_id: "join", expected_octet: 1) +} + +test fn emit_host_native_only_meet_join_wrong_octet_mismatch_detected_holds() -> Bool { + logic_family_native_only_matches_expected(member_id: "meet", expected_octet: 0) == false + && logic_family_native_only_matches_expected(member_id: "join", expected_octet: 0) == false +} + // FIELD-ACCESS-FAMILY ARM: the same native-only verdict shape for the emit_host_field_access // family — the fourth construct promoted off InterpreterRetained, after classical_not, add, and // complement. The family differences are facts of that family, not new machinery: the tree is diff --git a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag index b850ca69001..c192b598623 100644 --- a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag +++ b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag @@ -43,8 +43,8 @@ test fn witness_unlisted_subject_has_no_row() -> Bool { } test fn witness_split_matches_expected() -> Bool { - (length(xs: self_emitted_native_rows()) == 7) - && (length(xs: interpreter_retained_rows()) == 8) + (length(xs: self_emitted_native_rows()) == 8) + && (length(xs: interpreter_retained_rows()) == 7) } // Identity-grain census of the native rows (DESIGN section 5: completeness is an identity join, @@ -52,12 +52,13 @@ test fn witness_split_matches_expected() -> Bool { // members). 2026-09-07: the add family joined classical_not when its native-only verdict arm // landed, and the complement family followed on the same arm shape run through the logic family // crate. 2026-09-08: the field_access family followed against its own family one-build cache -// (octet 9 pinned), and the three match_loop_fold family rows (match, loop, fold_closure) -// followed on the same arm shape run through the three-member family crate; the seven decl names -// are the closed membership. +// (octet 9 pinned), the three match_loop_fold family rows (match, loop, fold_closure) +// followed on the same arm shape run through the three-member family crate, and the meet_join +// family followed on the complement arm's member-parameterized helper against the same logic +// family crate (octets meet=1 join=1 pinned); the eight decl names are the closed membership. test fn witness_native_rows_closed_membership_holds() -> Bool { let natives = self_emitted_native_rows() - (length(xs: natives) == 7) + (length(xs: natives) == 8) && fold( natives, init: true, @@ -70,6 +71,7 @@ test fn witness_native_rows_closed_membership_holds() -> Bool { || (row.subject.decl_name == "fold_fixture_param_call_body") || (row.subject.decl_name == "table_fixture_loop_arrow_with_body") || (row.subject.decl_name == "table_fixture_match_arrow_with_body") + || (row.subject.decl_name == "meet_join_eval_subject") ) } ) From ab89d01dfb33717e7e5b0bc06cf0d6b9cee58280 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 23:15:40 +0000 Subject: [PATCH 39/89] Promote variant_construct to SelfEmittedNative on the emit coverage frontier The variant_construct family's native-only verdict arms follow the field_access arm shape exactly: the tree is the family's own equals_eval tree value (emit_variant_construct_eval_tree, no eval leg reachable), the run shares the family one-build cache key that emit_on_demand_variant_construct_native_one_build_holds colds, and the expected octet 9 is the byte the family witness's warm leg pins on this same build. The wrong-octet control expects the alt tree's byte (1), which the primary run can never produce; the wrong-value alt leg in the family witness keeps the program-side discrimination. Both arms verified wet: cold build then warm hit, PASS/PASS. The roster row flips InterpreterRetained -> SelfEmittedNative (ninth promotion); census guards move to 9 native / 6 retained with emit_variant_construct_eval_subgraph_node named in the closed membership. --- .../self_host/emit_coverage_frontier.dag | 20 ++++--- .../emit_host_native_only_verdict_test.dag | 57 +++++++++++++++++++ .../self_host/emit_coverage_frontier_test.dag | 12 ++-- 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src/v2/compiler/self_host/emit_coverage_frontier.dag b/src/v2/compiler/self_host/emit_coverage_frontier.dag index 2e093fc0171..52dbaf900c2 100644 --- a/src/v2/compiler/self_host/emit_coverage_frontier.dag +++ b/src/v2/compiler/self_host/emit_coverage_frontier.dag @@ -75,7 +75,7 @@ fn retained_via_eval_agreement( } } -// Eight roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07/08, each on +// Nine roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07/08, each on // a native-only verdict arm (positive + wrong-octet broken control) landed in the already // file-grain-enrolled native-only verdict entry, so each backing citation is enrolled by // construction (coverage_row_backing_is_enrolled): the add family row (first element, backing @@ -86,9 +86,11 @@ fn retained_via_eval_agreement( // the three match_loop_fold family rows (match, loop, fold_closure — backing // emit_host_native_only_{match,loop,fold_closure}_holds, run through the three-member family crate // per the witness_family_build_grain_ruling, octet lists pinned against the family one-build -// cache), and the meet_join family row (backing emit_host_native_only_meet_join_holds, run through +// cache), the meet_join family row (backing emit_host_native_only_meet_join_holds, run through // the logic family crate it shares with complement, octets meet=1 join=1 pinned against the family -// one-build cache). Each family's equals_eval agreement pair stays enrolled as its program-side +// one-build cache), and the variant_construct family row (backing +// emit_host_native_only_variant_construct_holds, octet 9 pinned against the family one-build +// cache). Each family's equals_eval agreement pair stays enrolled as its program-side // discrimination leg. data emit_coverage_frontier_roster: List = [ EmitCoverageFrontierRow { @@ -219,14 +221,18 @@ data emit_coverage_frontier_roster: List = [ entry: "src/v2/test/claim/execution/emit_host_shell_exec_run_equals_eval_test.dag", f: "emit_host_shell_exec_run_equals_eval_holds" ), - retained_via_eval_agreement( + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.execution.emit_host_variant_construct_equals_eval", decl_name: "emit_variant_construct_eval_subgraph_node" ), - entry: "src/v2/test/claim/execution/emit_host_variant_construct_equals_eval_test.dag", - f: "emit_host_variant_construct_equals_eval_holds" - ), + witness_entry: "src/v2/test/claim/execution/emit_host_variant_construct_equals_eval_test.dag", + witness_function: "emit_host_variant_construct_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_variant_construct_holds" + } + }, retained_via_eval_agreement( subject: coverage_subject( module_path: "v2.test.execution.rust_emit_host_call_equals_eval", diff --git a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag index 5fb87b31e8a..68187592a4f 100644 --- a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag +++ b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag @@ -12,6 +12,7 @@ import v2.extdeps.languages.rust_test { rust_field_access_target_model_staging, rust_logic_family_target_model_staging, rust_match_loop_fold_family_target_model_staging, + rust_variant_construct_target_model_staging, } import extdeps.realization.emit_on_demand_host { native_cache_workspace_root } import v2.test.long.emit_host_classical_not_ingested_equals_eval { @@ -60,6 +61,14 @@ import v2.test.execution.emit_on_demand_match_loop_fold_family_witness { mlf_family_members, mlf_family_native_key, } +import v2.test.execution.emit_host_variant_construct_equals_eval { + emit_variant_construct_eval_tree, +} +import v2.test.execution.emit_on_demand_variant_construct_family_witness { + variant_construct_family_cache_root_for, + variant_construct_family_fixture_inputs, + variant_construct_family_native_key_for, +} import v2.std.collection { List } import v2.std.diagnostic { Accepted, Rejected, Outcome, None } import v2.std.host_run { HostLogicalRun, HostRunStdout } @@ -406,3 +415,51 @@ test fn emit_host_native_only_fold_closure_wrong_octet_mismatch_detected_holds() mlf_native_only_member_matches_expected(member_id: "fold", expected_octets: fold_alt_expected_octets) == false } +// VARIANT-CONSTRUCT-FAMILY ARM: the same native-only verdict shape for the +// emit_host_variant_construct family — the ninth construct promoted off InterpreterRetained. The +// family differences are facts of that family, not new machinery: the tree is the family's own +// equals_eval tree VALUE (emit_variant_construct_eval_tree — no eval leg is reachable from this +// file despite the home module's equals_eval name), and the run shares the variant_construct +// family's one-build cache key (variant_construct_family_cache_root_for + +// variant_construct_family_native_key_for, the same key +// emit_on_demand_variant_construct_native_one_build_holds colds) — an uncached leg here would +// duplicate a full cargo build the family witness already performs (DESIGN S2). The expected +// octet is 9, the byte the family witness's warm leg pins on this same build. Program-side +// discrimination stays where the family already carries it: the equals_eval pair and the family +// witness's wrong-value alt leg prove the native run's actual byte participates in a verdict (the +// wrong-value alt tree flips the octet 9 -> 1). A no-eval verdict has no oracle leg to break, so +// this arm's broken control breaks the EXPECTATION side: octet 1 is the alt tree's byte and can +// never match the primary run, and the comparator mocks above pin the comparator itself. +fn variant_construct_native_only_matches_expected(expected_octet: Int) -> Bool { + let tree = emit_variant_construct_eval_tree + match emit(tree: tree, target: rust_variant_construct_target_model_staging()) { + Accepted { value: emitted, diagnostics: _ } => + match run_emit_host_cached( + workspace_dir: native_cache_workspace_root( + cache_root: variant_construct_family_cache_root_for(test_id: "native_one_build"), + key: variant_construct_family_native_key_for(tree: tree) + ), + target: rust_variant_construct_target_model_staging(), + source: emitted, + fixture_inputs: variant_construct_family_fixture_inputs() + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + native_only_stdout_matches(stdout: stdout, expected_octet: expected_octet) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_variant_construct_holds() -> Bool { + variant_construct_native_only_matches_expected(expected_octet: 9) +} + +test fn emit_host_native_only_variant_construct_wrong_octet_mismatch_detected_holds() -> Bool { + variant_construct_native_only_matches_expected(expected_octet: 1) == false +} + diff --git a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag index c192b598623..0385f244811 100644 --- a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag +++ b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag @@ -43,8 +43,8 @@ test fn witness_unlisted_subject_has_no_row() -> Bool { } test fn witness_split_matches_expected() -> Bool { - (length(xs: self_emitted_native_rows()) == 8) - && (length(xs: interpreter_retained_rows()) == 7) + (length(xs: self_emitted_native_rows()) == 9) + && (length(xs: interpreter_retained_rows()) == 6) } // Identity-grain census of the native rows (DESIGN section 5: completeness is an identity join, @@ -53,12 +53,13 @@ test fn witness_split_matches_expected() -> Bool { // landed, and the complement family followed on the same arm shape run through the logic family // crate. 2026-09-08: the field_access family followed against its own family one-build cache // (octet 9 pinned), the three match_loop_fold family rows (match, loop, fold_closure) -// followed on the same arm shape run through the three-member family crate, and the meet_join +// followed on the same arm shape run through the three-member family crate, the meet_join // family followed on the complement arm's member-parameterized helper against the same logic -// family crate (octets meet=1 join=1 pinned); the eight decl names are the closed membership. +// family crate (octets meet=1 join=1 pinned), and the variant_construct family followed against +// its own family one-build cache (octet 9 pinned); the nine decl names are the closed membership. test fn witness_native_rows_closed_membership_holds() -> Bool { let natives = self_emitted_native_rows() - (length(xs: natives) == 8) + (length(xs: natives) == 9) && fold( natives, init: true, @@ -72,6 +73,7 @@ test fn witness_native_rows_closed_membership_holds() -> Bool { || (row.subject.decl_name == "table_fixture_loop_arrow_with_body") || (row.subject.decl_name == "table_fixture_match_arrow_with_body") || (row.subject.decl_name == "meet_join_eval_subject") + || (row.subject.decl_name == "emit_variant_construct_eval_subgraph_node") ) } ) From 24c4c038546a0db5e035f89f752f003dc93db0c4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 23:22:06 +0000 Subject: [PATCH 40/89] Close the emit coverage frontier: final six rows to SelfEmittedNative The last six InterpreterRetained rows flip to SelfEmittedNative, taking the roster to 15 native / 0 retained: - filesystem_read and shell_exec_run (host-effect transport families, no translated arrow body): the arms reuse each family's own native leg with the expectation pinned as a literal grounded by the family's enrolled fixture pin (dag/extdeps/shell/exec.dag contains bash; its shell.Exec.Run argv materializes to exactly [bash, -s]), run through the families' fixed witness workspaces. - module and produced_module: the arms execute the exact sources the equals_eval pairs run (emit_module over the add fixture tree; produced_add_module_source's ingested two-fn module), octet 5 pinned against the add family's primitive-five/six oracle leg. - call and record_construct: the arms emit the families' own producer trees against their target models, octets 7 and 9 pinned against the primitive-seven/eight and wrong-field oracle legs. The four families without a one-build cache witness run under per-family fixed workspace roots; content-safety comes from the realization-digest nesting in run_host_process_admitted (changed source colds, never serves stale), the same mechanism the filesystem_read fixed workspace relies on. All twelve arms verified wet: PASS/PASS each, cold builds then warm hits. With zero retained rows the retained_via_eval_agreement constructor loses its last consumer and is deleted (DESIGN 3c); the InterpreterRetained variant stays as the disposition authority's other state. Census guards move to 15 native / 0 retained with all fifteen decl names in the closed membership. --- .../self_host/emit_coverage_frontier.dag | 113 +++++---- .../emit_host_native_only_verdict_test.dag | 223 +++++++++++++++++- .../self_host/emit_coverage_frontier_test.dag | 20 +- 3 files changed, 302 insertions(+), 54 deletions(-) diff --git a/src/v2/compiler/self_host/emit_coverage_frontier.dag b/src/v2/compiler/self_host/emit_coverage_frontier.dag index 52dbaf900c2..7ab8d5172c4 100644 --- a/src/v2/compiler/self_host/emit_coverage_frontier.dag +++ b/src/v2/compiler/self_host/emit_coverage_frontier.dag @@ -7,7 +7,7 @@ import v2.std.algebra { length, any } import std.decl_ref { DeclarationRef, WholeDeclaration } import std.realization_schedule { ScheduleWitnessEntry } import v2.compiler.self_host.wet_receipt_enrollment { falsifier_self_host_wet_template_entries } -import std.dissolution { DissolutionCondition, unbound_dissolution } +import std.dissolution { DissolutionCondition } // Kernel-D Stage 3 (session loyal-raven-94, 2026-07-24): a typed, counted roster over the // emit_host_*/rust_*_equals_eval witness families tracking NATIVE-EVAL CONSTRUCT COVERAGE — each @@ -59,26 +59,13 @@ fn coverage_subject(module_path: String, decl_name: String) -> DeclarationRef { } } -fn retained_via_eval_agreement( - subject: DeclarationRef, - entry: String, - f: String -) -> EmitCoverageFrontierRow { - EmitCoverageFrontierRow { - subject: subject, - witness_entry: entry, - witness_function: f, - disposition: InterpreterRetained { - reason: "witness_body_calls_eval", - dissolution: unbound_dissolution(description: "a native_only-verdict-style witness (real ingest to emit to compile to run native, verdict decoded from stdout, eval() never called — the emit_host_native_only_verdict_test.dag pattern) lands for this family and is enrolled in falsifier_self_host_wet_template_entries") - } - } -} - -// Nine roster rows were promoted InterpreterRetained -> SelfEmittedNative on 2026-09-07/08, each on -// a native-only verdict arm (positive + wrong-octet broken control) landed in the already -// file-grain-enrolled native-only verdict entry, so each backing citation is enrolled by -// construction (coverage_row_backing_is_enrolled): the add family row (first element, backing +// ALL FIFTEEN roster rows are SelfEmittedNative as of 2026-09-08 — the InterpreterRetained +// population is zero and the retained_via_eval_agreement row constructor was deleted with the last +// flip (DESIGN section 3c: a constructor no row consumes is dead data; the InterpreterRetained +// VARIANT stays as the disposition authority's other state, matched by coverage_row_is_native). +// Each promotion landed on a native-only verdict arm (positive + wrong-octet broken control) in +// the already file-grain-enrolled native-only verdict entry, so each backing citation is enrolled +// by construction (coverage_row_backing_is_enrolled): the add family row (first element, backing // emit_host_native_only_add_holds), the complement family row (backing // emit_host_native_only_complement_holds, run through the logic family crate per the // witness_family_build_grain_ruling), the field_access family row (backing @@ -88,10 +75,16 @@ fn retained_via_eval_agreement( // per the witness_family_build_grain_ruling, octet lists pinned against the family one-build // cache), the meet_join family row (backing emit_host_native_only_meet_join_holds, run through // the logic family crate it shares with complement, octets meet=1 join=1 pinned against the family -// one-build cache), and the variant_construct family row (backing +// one-build cache), the variant_construct family row (backing // emit_host_native_only_variant_construct_holds, octet 9 pinned against the family one-build -// cache). Each family's equals_eval agreement pair stays enrolled as its program-side -// discrimination leg. +// cache), the two host-effect transport rows (filesystem_read, shell_exec_run — backing +// emit_host_native_only_{filesystem_read,shell_exec_run}_holds, literal octets grounded by the +// families' own enrolled fixture pins, run through the families' fixed witness workspaces), and +// the four emit-path rows (module, produced_module, call, record_construct — backing +// emit_host_native_only_{module,produced_module,call,record_construct}_holds, octets 5/5/7/9 +// pinned against the equals_eval pairs' own oracle legs, run under per-family fixed workspace +// roots with realization-digest content-safety). Each family's equals_eval agreement pair stays +// enrolled as its program-side discrimination leg. data emit_coverage_frontier_roster: List = [ EmitCoverageFrontierRow { subject: coverage_subject( @@ -141,14 +134,18 @@ data emit_coverage_frontier_roster: List = [ backing_function: "emit_host_native_only_field_access_holds" } }, - retained_via_eval_agreement( + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.extdeps.languages.rust_test", decl_name: "rust_filesystem_read_family_target_model_staging" ), - entry: "src/v2/test/claim/execution/emit_host_filesystem_read_equals_eval_test.dag", - f: "emit_host_filesystem_read_equals_eval_holds" - ), + witness_entry: "src/v2/test/claim/execution/emit_host_filesystem_read_equals_eval_test.dag", + witness_function: "emit_host_filesystem_read_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_filesystem_read_holds" + } + }, EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.emit.fold_call_closure_fixture", @@ -197,30 +194,42 @@ data emit_coverage_frontier_roster: List = [ backing_function: "emit_host_native_only_meet_join_holds" } }, - retained_via_eval_agreement( + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.execution.emit_host_module_equals_eval", decl_name: "emit_host_module_add_equals_eval_holds" ), - entry: "src/v2/test/claim/execution/emit_host_module_equals_eval_test.dag", - f: "emit_host_module_add_equals_eval_holds" - ), - retained_via_eval_agreement( + witness_entry: "src/v2/test/claim/execution/emit_host_module_equals_eval_test.dag", + witness_function: "emit_host_module_add_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_module_holds" + } + }, + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.long.emit_host_produced_module_equals_eval", decl_name: "emit_host_produced_module_add_equals_eval_holds" ), - entry: "src/v2/test/claim/execution/long/emit_host_produced_module_equals_eval_test.dag", - f: "emit_host_produced_module_add_equals_eval_holds" - ), - retained_via_eval_agreement( + witness_entry: "src/v2/test/claim/execution/long/emit_host_produced_module_equals_eval_test.dag", + witness_function: "emit_host_produced_module_add_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_produced_module_holds" + } + }, + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.extdeps.languages.rust_test", decl_name: "rust_shell_exec_run_family_target_model_staging" ), - entry: "src/v2/test/claim/execution/emit_host_shell_exec_run_equals_eval_test.dag", - f: "emit_host_shell_exec_run_equals_eval_holds" - ), + witness_entry: "src/v2/test/claim/execution/emit_host_shell_exec_run_equals_eval_test.dag", + witness_function: "emit_host_shell_exec_run_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_shell_exec_run_holds" + } + }, EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.execution.emit_host_variant_construct_equals_eval", @@ -233,22 +242,30 @@ data emit_coverage_frontier_roster: List = [ backing_function: "emit_host_native_only_variant_construct_holds" } }, - retained_via_eval_agreement( + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.execution.rust_emit_host_call_equals_eval", decl_name: "rust_emit_host_call_equals_eval_holds" ), - entry: "src/v2/test/claim/execution/rust_emit_host_call_equals_eval_test.dag", - f: "rust_emit_host_call_equals_eval_holds" - ), - retained_via_eval_agreement( + witness_entry: "src/v2/test/claim/execution/rust_emit_host_call_equals_eval_test.dag", + witness_function: "rust_emit_host_call_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_call_holds" + } + }, + EmitCoverageFrontierRow { subject: coverage_subject( module_path: "v2.test.execution.rust_record_construct_emit_host_equals_eval", decl_name: "rust_record_construct_emit_host_equals_eval_holds" ), - entry: "src/v2/test/claim/execution/rust_record_construct_emit_host_equals_eval_test.dag", - f: "rust_record_construct_emit_host_equals_eval_holds" - ) + witness_entry: "src/v2/test/claim/execution/rust_record_construct_emit_host_equals_eval_test.dag", + witness_function: "rust_record_construct_emit_host_equals_eval_holds", + disposition: SelfEmittedNative { + backing_entry: "src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag", + backing_function: "emit_host_native_only_record_construct_holds" + } + } ] fn coverage_frontier_total_holds() -> Bool { diff --git a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag index 68187592a4f..ce7c2db19be 100644 --- a/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag +++ b/src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag @@ -2,16 +2,19 @@ module v2.test.long.emit_host_native_only_verdict import v2.compiler.emit { emit } import v2.compiler.emit_host { emit_host_octets_byte_string, run_emit_host_cached, run_emit_host_cached_with_run_args } -import v2.compiler.emit_module { emit_family } +import v2.compiler.emit_module { emit_family, emit_module } import v2.compiler.eval { inputs_root_only } import v2.compiler.infer { InferredTree } import v2.extdeps.languages.rust { rust_target_model_staging } import v2.extdeps.languages.rust_test { + rust_add_target_model_staging, + rust_call_target_model_staging, rust_classical_not_ingested_target_model_staging, rust_family_member_run_args, rust_field_access_target_model_staging, rust_logic_family_target_model_staging, rust_match_loop_fold_family_target_model_staging, + rust_rec_nine_target_model_staging, rust_variant_construct_target_model_staging, } import extdeps.realization.emit_on_demand_host { native_cache_workspace_root } @@ -69,6 +72,21 @@ import v2.test.execution.emit_on_demand_variant_construct_family_witness { variant_construct_family_fixture_inputs, variant_construct_family_native_key_for, } +import v2.test.execution.emit_host_filesystem_read_equals_eval { + filesystem_read_native_run, +} +import v2.test.execution.emit_host_shell_exec_run_equals_eval { + shell_exec_run_native_run, +} +import v2.test.long.emit_host_produced_module_equals_eval { + produced_add_module_source, +} +import v2.test.execution.rust_emit_host_call_equals_eval { + emit_call_eval_producer_tree, +} +import v2.test.execution.rust_record_construct_emit_host_equals_eval { + rust_rec_nine_eval_inferred_tree, +} import v2.std.collection { List } import v2.std.diagnostic { Accepted, Rejected, Outcome, None } import v2.std.host_run { HostLogicalRun, HostRunStdout } @@ -463,3 +481,206 @@ test fn emit_host_native_only_variant_construct_wrong_octet_mismatch_detected_ho variant_construct_native_only_matches_expected(expected_octet: 1) == false } +// FILESYSTEM-READ-FAMILY ARM: the same native-only verdict shape for the emit_host_filesystem_read +// family — the tenth construct promoted off InterpreterRetained. This family has no translated +// arrow body at all (the construct under test is the host-effect primitive itself), so the arm +// reuses the family's own native leg (filesystem_read_native_run) with the expectation pinned as a +// LITERAL: octet 1 is grounded by the family's own enrolled pin +// filesystem_read_eval_reads_real_bash_argv, which asserts the controlled fixture +// dag/extdeps/shell/exec.dag contains "bash" — a versioned repo file, not a measurement copied +// from the run (DESIGN S5's oracle rule). The fixture content load inside the family's target +// staging runs the interpreted filesystem_read builtin — harness plumbing of the same standing as +// every arm's interpreted fixture construction; the VERDICT path is native stdout vs the literal, +// and no InterpretationAlgebra oracle leg exists for this family to call. The run shares the +// family's fixed witness workspace (content-safety from the realization-digest nesting in +// run_host_process_admitted: changed content colds, never serves stale). The broken control breaks +// the EXPECTATION side: octet 0 is the byte the family's wrong-oracle control already proves the +// native run never produces, and the comparator mocks above pin the comparator itself. +test fn emit_host_native_only_filesystem_read_holds() -> Bool { + filesystem_read_native_run(expected_octet: 1) +} + +test fn emit_host_native_only_filesystem_read_wrong_octet_mismatch_detected_holds() -> Bool { + filesystem_read_native_run(expected_octet: 0) == false +} + +// MODULE-FAMILY ARM: the same native-only verdict shape for the emit_host_module family — the +// eleventh construct promoted off InterpreterRetained. The family difference is the emit entry: +// the source comes from emit_module (the module-concatenation path) over the add family's shared +// fixture tree, not from emit — exactly the source the family's equals_eval pair executes. The +// expected octet is 5, the byte the add family's primitive-five/primitive-six pair pins on this +// same subject (5 agrees, 6 diverges). This family has no one-build cache witness, so the arm runs +// under its own fixed workspace root; content-safety comes from the realization-digest nesting in +// run_host_process_admitted (changed source colds, never serves stale), the same mechanism the +// filesystem_read family's fixed workspace relies on. The broken control breaks the EXPECTATION +// side: octet 6 is the byte the equals_eval pair's wrong-oracle leg proves this run never +// produces, and the comparator mocks above pin the comparator itself. +fn module_native_only_matches_expected(expected_octet: Int) -> Bool { + match emit_module(decls: [emit_eval_subject_tree()], target: rust_add_target_model_staging()) { + Accepted { value: source, diagnostics: _ } => + match run_emit_host_cached( + workspace_dir: "/tmp/gunbc_emit_on_demand_module/native_only", + target: rust_add_target_model_staging(), + source: source, + fixture_inputs: kernel_add_fixture_inputs() + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + native_only_stdout_matches(stdout: stdout, expected_octet: expected_octet) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_module_holds() -> Bool { + module_native_only_matches_expected(expected_octet: 5) +} + +test fn emit_host_native_only_module_wrong_octet_mismatch_detected_holds() -> Bool { + module_native_only_matches_expected(expected_octet: 6) == false +} + +// PRODUCED-MODULE-FAMILY ARM: the same native-only verdict shape for the +// emit_host_produced_module family — the twelfth construct promoted off InterpreterRetained. The +// family difference is the source's provenance: produced_add_module_source() assembles a two-fn +// module (add + add2) from INGESTED decl nodes via emit_produced_module — exactly the source the +// family's equals_eval pair executes — and the family's own golden test +// (produced_module_two_distinct_fns_assemble) pins those bytes against a literal golden. The +// expected octet is 5, the byte the add family's primitive-five/primitive-six pair pins on this +// same subject. Like the module family there is no one-build cache witness, so the arm runs under +// its own fixed workspace root with the same realization-digest content-safety. The broken +// control breaks the EXPECTATION side: octet 6 can never match this run, and the comparator mocks +// above pin the comparator itself. +fn produced_module_native_only_matches_expected(expected_octet: Int) -> Bool { + match produced_add_module_source() { + Accepted { value: source, diagnostics: _ } => + match run_emit_host_cached( + workspace_dir: "/tmp/gunbc_emit_on_demand_produced_module/native_only", + target: rust_target_model_staging(), + source: source, + fixture_inputs: kernel_add_fixture_inputs() + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + native_only_stdout_matches(stdout: stdout, expected_octet: expected_octet) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_produced_module_holds() -> Bool { + produced_module_native_only_matches_expected(expected_octet: 5) +} + +test fn emit_host_native_only_produced_module_wrong_octet_mismatch_detected_holds() -> Bool { + produced_module_native_only_matches_expected(expected_octet: 6) == false +} + +// SHELL-EXEC-RUN-FAMILY ARM: the same native-only verdict shape for the emit_host_shell_exec_run +// family — the thirteenth construct promoted off InterpreterRetained. Like filesystem_read this +// family has no translated arrow body (the construct under test is the host-effect primitive), so +// the arm reuses the family's own native leg (shell_exec_run_native_run) with the expectation +// pinned as a LITERAL: octet 1 is grounded by the family's own enrolled pin +// shell_exec_run_eval_argv_is_bash_dash_s, which asserts the argv materialized from the controlled +// fixture dag/extdeps/shell/exec.dag's shell.Exec.Run operation is exactly ["bash", "-s"]. The +// native leg itself carries no interpreted oracle at all — the argv literals are baked into the +// target model at .dag-construction time from that same materialization, so the two legs cannot +// silently drift (rust_shell_exec_run_family_note). The run shares the family's fixed witness +// workspace with realization-digest content-safety. The broken control breaks the EXPECTATION +// side: octet 0 is the byte the family's wrong-oracle control already proves the native run never +// produces, and the comparator mocks above pin the comparator itself. +test fn emit_host_native_only_shell_exec_run_holds() -> Bool { + shell_exec_run_native_run(expected_octet: 1) +} + +test fn emit_host_native_only_shell_exec_run_wrong_octet_mismatch_detected_holds() -> Bool { + shell_exec_run_native_run(expected_octet: 0) == false +} + +// CALL-FAMILY ARM: the same native-only verdict shape for the rust_emit_host_call family — the +// fourteenth construct promoted off InterpreterRetained. The tree is the family's own producer +// (emit_call_eval_producer_tree over the rust_call_helper arrow — no eval leg is reachable from +// this file despite the constructor module's equals_eval name), emitted against the family's call +// target model. The expected octet is 7, the byte the family's primitive-seven/primitive-eight +// equals_eval pair pins on this same build (7 agrees, 8 diverges). This family has no one-build +// cache witness, so the arm runs under its own fixed workspace root with realization-digest +// content-safety, as the module families above. The broken control breaks the EXPECTATION side: +// octet 8 is the byte the equals_eval pair's wrong-oracle leg proves this run never produces, and +// the comparator mocks above pin the comparator itself. +fn call_native_only_matches_expected(expected_octet: Int) -> Bool { + match emit(tree: emit_call_eval_producer_tree(), target: rust_call_target_model_staging()) { + Accepted { value: emitted, diagnostics: _ } => + match run_emit_host_cached( + workspace_dir: "/tmp/gunbc_emit_on_demand_call/native_only", + target: rust_call_target_model_staging(), + source: emitted, + fixture_inputs: inputs_root_only(root: emit_call_eval_producer_tree().root) + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + native_only_stdout_matches(stdout: stdout, expected_octet: expected_octet) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_call_holds() -> Bool { + call_native_only_matches_expected(expected_octet: 7) +} + +test fn emit_host_native_only_call_wrong_octet_mismatch_detected_holds() -> Bool { + call_native_only_matches_expected(expected_octet: 8) == false +} + +// RECORD-CONSTRUCT-FAMILY ARM: the same native-only verdict shape for the +// rust_record_construct_emit_host family — the fifteenth construct promoted off +// InterpreterRetained. The tree is the family's own constructor (rust_rec_nine_eval_inferred_tree +// over the rec-nine arrow — no eval leg is reachable from this file despite the constructor +// module's equals_eval name), emitted against the family's rec-nine target model. The expected +// octet is 9, the byte the family's equals_eval pair pins on this same build; the family's +// wrong-field target model leg proves the native run's actual byte participates in a verdict +// (construct-then-field-access on the wrong field diverges). This family has no one-build cache +// witness, so the arm runs under its own fixed workspace root with realization-digest +// content-safety, as the module families above. The broken control breaks the EXPECTATION side: +// octet 0 can never match this run, and the comparator mocks above pin the comparator itself. +fn record_construct_native_only_matches_expected(expected_octet: Int) -> Bool { + match emit(tree: rust_rec_nine_eval_inferred_tree(), target: rust_rec_nine_target_model_staging()) { + Accepted { value: emitted, diagnostics: _ } => + match run_emit_host_cached( + workspace_dir: "/tmp/gunbc_emit_on_demand_record_construct/native_only", + target: rust_rec_nine_target_model_staging(), + source: emitted, + fixture_inputs: inputs_root_only(root: rust_rec_nine_eval_inferred_tree().root) + ) { + Accepted { value: receipt, diagnostics: _ } => + match receipt.logical_run { + Accepted { value: HostLogicalRun { stdout: stdout }, diagnostics: None } => + native_only_stdout_matches(stdout: stdout, expected_octet: expected_octet) + _ => false + } + Rejected { diagnostics: _ } => false + } + Rejected { diagnostics: _ } => false + } +} + +test fn emit_host_native_only_record_construct_holds() -> Bool { + record_construct_native_only_matches_expected(expected_octet: 9) +} + +test fn emit_host_native_only_record_construct_wrong_octet_mismatch_detected_holds() -> Bool { + record_construct_native_only_matches_expected(expected_octet: 0) == false +} + diff --git a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag index 0385f244811..76c0dd5444b 100644 --- a/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag +++ b/src/v2/test/claim/self_host/emit_coverage_frontier_test.dag @@ -43,8 +43,8 @@ test fn witness_unlisted_subject_has_no_row() -> Bool { } test fn witness_split_matches_expected() -> Bool { - (length(xs: self_emitted_native_rows()) == 9) - && (length(xs: interpreter_retained_rows()) == 6) + (length(xs: self_emitted_native_rows()) == 15) + && (length(xs: interpreter_retained_rows()) == 0) } // Identity-grain census of the native rows (DESIGN section 5: completeness is an identity join, @@ -55,11 +55,15 @@ test fn witness_split_matches_expected() -> Bool { // (octet 9 pinned), the three match_loop_fold family rows (match, loop, fold_closure) // followed on the same arm shape run through the three-member family crate, the meet_join // family followed on the complement arm's member-parameterized helper against the same logic -// family crate (octets meet=1 join=1 pinned), and the variant_construct family followed against -// its own family one-build cache (octet 9 pinned); the nine decl names are the closed membership. +// family crate (octets meet=1 join=1 pinned), the variant_construct family followed against +// its own family one-build cache (octet 9 pinned), and the final six rows closed the frontier: +// filesystem_read and shell_exec_run on literal octets grounded by their enrolled fixture pins, +// and module, produced_module, call, record_construct on per-family fixed workspace roots +// (octets 5/5/7/9 pinned against the equals_eval pairs' oracle legs). The fifteen decl names +// are the closed membership; the InterpreterRetained population is zero. test fn witness_native_rows_closed_membership_holds() -> Bool { let natives = self_emitted_native_rows() - (length(xs: natives) == 9) + (length(xs: natives) == 15) && fold( natives, init: true, @@ -74,6 +78,12 @@ test fn witness_native_rows_closed_membership_holds() -> Bool { || (row.subject.decl_name == "table_fixture_match_arrow_with_body") || (row.subject.decl_name == "meet_join_eval_subject") || (row.subject.decl_name == "emit_variant_construct_eval_subgraph_node") + || (row.subject.decl_name == "rust_filesystem_read_family_target_model_staging") + || (row.subject.decl_name == "emit_host_module_add_equals_eval_holds") + || (row.subject.decl_name == "emit_host_produced_module_add_equals_eval_holds") + || (row.subject.decl_name == "rust_shell_exec_run_family_target_model_staging") + || (row.subject.decl_name == "rust_emit_host_call_equals_eval_holds") + || (row.subject.decl_name == "rust_record_construct_emit_host_equals_eval_holds") ) } ) From f173aaa24860a0efe7b2142cefcedc69675ff66f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 23:24:37 +0000 Subject: [PATCH 41/89] Record the emit coverage frontier closure in the direct-path plan Axis C line: all fifteen roster rows are SelfEmittedNative as of 2026-09-08, interpreter_retained_rows() is empty, and the row constructor was deleted with the last flip. Notes explicitly that this closes axis (a) (witness-body-runs-native) only; axis (b) (the regen-grain production flip) remains operator-gated. --- docs/plans/v2-self-host-direct-path-2026-09-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plans/v2-self-host-direct-path-2026-09-06.md b/docs/plans/v2-self-host-direct-path-2026-09-06.md index c1e1d1a175f..ef0416f5ee0 100644 --- a/docs/plans/v2-self-host-direct-path-2026-09-06.md +++ b/docs/plans/v2-self-host-direct-path-2026-09-06.md @@ -32,7 +32,7 @@ Each line names the producer that re-derives the state. Run it; do not believe t - **Axis A — the real path, slice by slice.** The add slice (the `add` fn of `std.integer`) is green end-to-end on the dag path as of #10673: infer derives grounding by declared-inhabitant roster membership, and the ingest→compile round trip is byte-faithful. Instrument: `gunbc run --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/self_host_candidate_generation_stage_verdicts_test.dag --function add_slice_stage_verdicts_entry`. The **direct-rust-door** (roadmap `v2-emitter-direct-rust-door`) is **green by execution as of 2026-09-06**: the production walk reaches `ArtifactProduced`, all five leaf projections establish (source fidelity byte-equal to `direct_rust_door_expected_source`, producer identity, seed-emitter absence, `SourceProduced` qualification, missing-rule refusal), and the known-red admission row deleted per its own dissolution, promoting the group root to the lane's permanent regression wall. Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/long/direct_rust_door_production_group_test.dag --functions direct_rust_door_production_group_closing_expectation_holds` (a `long/` row — it reconstructs the pipeline per evaluation; the cost defect is declared in the test module's header). - **The grounding frontier.** The door specimen's frontier is closed, and the parse-product family followed on 2026-09-07: the declared-inhabitant roster-membership derivation widened from the dag roster to the closed ingest set (dag, python, typescript), closing the python and typescript fixtures' frontiers and greening the four same-language round-trips. The add-slice stall's trigger fired and it retired per §4b(4). Six derivation rules now cover declared-inhabitants roster membership (language-general), binding denotation, Conj/Arrow product introduction, canonical-operations and grammar-productions roster membership, and binding references from the enclosing arrow's domain. The remaining frontier is the six node kinds with no derivation rule yet (the `node_grounding_frontier_note` in `04_infer.dag` is the standing carrier). - **Axis B — the native closure (XL-N).** The XL-0 gate (`cargo check` = 0 on the emitted compiler closure) is **not satisfied**; the issue's own receipt table records that the closure reached complete rustc type checking for the first time at XL-0A and carries the honest error population there. gunbc#10266 (fabricated runtime panics on representation-identical casts) is filed as an independent compiler slice on this path. Instrument (the issue's own): `gunbc compile --source-root dag --source-root src/v2 --entry src/v2/compiler/00_compile.dag --target rust --output-dir /tmp/v2emit && cd /tmp/v2emit && cargo check --message-format short`. -- **Axis C — the seed dissolves.** `v2.compiler.self_host.seed_retention_frontier` holds every retained seed file as a declared row; `undeclared_reason_rows()` is the prioritizable debt, and the census refuses unrostered retention in three directions. `v2.compiler.self_host.emit_coverage_frontier`'s `interpreter_retained_rows()` and `v2.compiler.self_host.native_routing_frontier`'s roster cover the witness-execution axes. The first InterpreterRetained → SelfEmittedNative promotion after classical_not landed 2026-09-07: the add family's row flipped when `emit_host_native_only_add_holds` (+ its wrong-octet broken control) landed in the file-grain-enrolled native-only verdict entry — the emitted add crate's stdout pinned to its expected octet with `eval()` never called, program-side discrimination staying with the family's equals_eval primitive-five/six pair. Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag` (offline wet legs — real cargo build and native run per leg). +- **Axis C — the seed dissolves.** `v2.compiler.self_host.seed_retention_frontier` holds every retained seed file as a declared row; `undeclared_reason_rows()` is the prioritizable debt, and the census refuses unrostered retention in three directions. `v2.compiler.self_host.emit_coverage_frontier`'s `interpreter_retained_rows()` and `v2.compiler.self_host.native_routing_frontier`'s roster cover the witness-execution axes. The first InterpreterRetained → SelfEmittedNative promotion after classical_not landed 2026-09-07: the add family's row flipped when `emit_host_native_only_add_holds` (+ its wrong-octet broken control) landed in the file-grain-enrolled native-only verdict entry — the emitted add crate's stdout pinned to its expected octet with `eval()` never called, program-side discrimination staying with the family's equals_eval primitive-five/six pair. **The emit coverage frontier closed 2026-09-08: all fifteen roster rows are SelfEmittedNative, `interpreter_retained_rows()` is empty**, each row backed by a native-only verdict arm (positive + wrong-octet broken control) in the same enrolled entry, and the `retained_via_eval_agreement` row constructor was deleted with the last flip (DESIGN §3c). This closes axis (a) — witness-body-runs-native — only; it says nothing about which engine compiled the compiler itself (axis (b), the regen-grain flip, remains operator-gated below). Instrument: `claim_batch --source-root dag --source-root src/v2 --entry src/v2/test/claim/execution/long/emit_host_native_only_verdict_test.dag` (offline wet legs — real cargo build and native run per leg). - **Wave position.** Per the wave doc, Wave 1's four exit items all carry landed receipts; Wave 2 (first real flips — emit-surface tracks self-emit, behaviorally verify, and **replace** their v1 counterparts) is the open wave. The wave doc's header records that its roster carrier was deleted and per-module dispositions must be re-derived from the emitter — which is what the door-first sequence below does. ## 3. The sequence from here From 1b36cdc42052b3dbfcb96f71aa9f22f2bef417c7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 02:54:50 +0000 Subject: [PATCH 42/89] Model the required-v2-native lane authority: route receipt, exclusion taxonomy, admission, enrolment gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parallel track B (operator authorization 2026-09-09): one additional required CI job whose subject is the compiler/test execution route itself — the emitted-native compiler binary invoked by explicit path over a derived v2.test.* population. The lane is modelled in full in gunbc.witness_v2_native_route: the prefix universe derivation, the per-member verdict rows (head + fatal reason grain), the exclusion taxonomy delegating attribution to the door ledger's known_frontier_causes, the counted exclusion census with a totality check, the terminal-observation receipt carrier, the admission predicate (one predicate per contract clause, all causes collected), and the enrolment gate with today's standing as data. Enrolment is BLOCKED, as data with a named capability trigger: the measured census over the derived universe (882 members, seed withdrawn during the run) refused every member — the emitted DirectIngestDriver admits only the hard-coded compile_driver_subject name with empty imports, and the compile door is at its modelled frontier — so the contracted positive population is empty and native_route_admission over the real receipt executed to 'refused: positive_population_empty'. The exact enrolment edit (phase-roster variants, claim_executor mirror, workflow lane, aggregate join, YAML regen) is carried on the standing row. The census measured five fatal-grain refusal causes the door ledger's head-grain attribution table did not carry; they are added to known_frontier_causes with their owning lanes (three MigrationOwned under nimble-boar-198, two normalize/body-lowering SharedSelfHostCriticalPath). Seventeen floor witnesses (v2.test.v2_native_route) consume the authority and execute green through the seed interpreter. Co-authored-by: briansrls --- dag/gunbc/witness/v2_native_route.dag | 386 ++++++++++++++++++++++++ src/v2/test/v2_native_route_test.dag | 263 ++++++++++++++++ src/v2/workflow/compile_door_ledger.dag | 30 ++ 3 files changed, 679 insertions(+) create mode 100644 dag/gunbc/witness/v2_native_route.dag create mode 100644 src/v2/test/v2_native_route_test.dag diff --git a/dag/gunbc/witness/v2_native_route.dag b/dag/gunbc/witness/v2_native_route.dag new file mode 100644 index 00000000000..712de64a958 --- /dev/null +++ b/dag/gunbc/witness/v2_native_route.dag @@ -0,0 +1,386 @@ +module gunbc.witness_v2_native_route + +import v2.std.algebra { any, filter, fold_list, is_empty, length } +import v2.std.collection { List } +import v2.std.integer { Int } +import v2.std.logic { Bool } +import v2.std.node { Symbol } +import v2.workflow.compile_door_ledger { cause_is_attributed } + +// THE LANE AUTHORITY FOR THE required-v2-native CI JOB (operator authorization 2026-09-09: one +// additional required job, modelled beside the workflow authority that will project it — never +// a hand-edit of the generated YAML). The job's subject is the compiler/test EXECUTION ROUTE +// itself: the emitted-native compiler binary, invoked by explicit path, over a derived +// v2.test.* population. The 32 native-only construct arms are explicitly NOT this proof — they +// show generated Rust programs ran natively while the claim executor stayed seed-backed; this +// lane's receipt must show which binary answered. +// +// STANDING AT AUTHORING: the emitted native compiler — the DirectIngestDriver main emitted for +// the 00_compile closure — refuses every real v2.test.* module today. Its admission is +// hard-coded to the compile_driver_subject name with an empty import environment, and the v2 +// compile door is at its modelled frontier for the whole corpus (v2.workflow.compile_door_ledger +// carries the interpreted door's burn-down; the native and interpreted routes agree on the +// probed modules, so the blocker is the compiler frontier, not a native divergence). The +// contracted positive population is therefore EMPTY, and native_route_admission refuses an +// empty positive population by construction — the empty-observation narrow is the one verdict +// this lane may never issue. The lane is modelled in full here; ENROLMENT into the required +// workflow is blocked on the door frontier, with the trigger and the exact enrolment edit +// carried as data at the bottom of this module. + +// THE JOB IDENTITY IS ONE ROW. The workflow enrolment edit, the receipt, and the witnesses all +// cite this declaration; no second spelling of the job name anywhere. +data required_v2_native_job_name: String = "required-v2-native" + +// THE UNIVERSE IS A PREFIX, NOT A ROSTER. The population is DERIVED from the module census by +// prefix membership — a hand-listed roster would silently drift from the tree it claims to +// cover. The prefix is the same `v2.test.` the required floor gates on (v2.workflow.required_floor +// required_gate_prefixes): the native lane's universe is the floor's witness universe asked of +// a different route. The contract's "no silent remainder" binds INSIDE the universe: every +// universe member carries exactly one verdict row, and every refused verdict carries exactly +// one classified cause. +data native_route_universe_prefix: String = "v2.test." + +fn native_route_universe_member(module_qn: String) -> Bool { + starts_with(s: module_qn, prefix: native_route_universe_prefix) +} + +fn native_route_universe(module_qns: List) -> List { + filter(xs: module_qns, predicate: fn(qn) { native_route_universe_member(module_qn: qn) }) +} + +// ONE ROW PER UNIVERSE MEMBER. NativeAccepted is the positive population. NativeRefused carries +// TWO reason symbols because the compiler's rejection is a list with a grain the row must not +// hide: rejected_with_pending PREPENDS the non-fatal pending diagnostics, so the head reason is +// an advisory that rode along while the LAST diagnostic is the fatal cause the member is +// excluded for. The door ledger records the head; this lane records both and classifies on the +// fatal — the two instruments join on head_reason while the exclusion cause stays honest about +// what actually refused. +type NativeRouteVerdict + = NativeAccepted + | NativeRefused { head_reason: Symbol, fatal_reason: Symbol } + +type NativeRouteMemberRow { + module_path: String + verdict: NativeRouteVerdict +} + +// THE EXCLUSION TAXONOMY. A refused member is excluded for exactly one of three causes: the +// compiler's own frontier (attributed to its owning lane through the door ledger's +// known_frontier_causes — this lane delegates to that table, it does not re-enumerate it); the +// emitted driver's admission limit (a native-route fact the interpreted door never faces, +// owned here); or NOTHING — an unattributed refusal, which is never silently bucketed: it +// blocks admission until someone owns it, the same fail-closed reading the door ledger gives +// cause_is_attributed = false. +type NativeRouteExclusionCause + = CompilerFrontierAttributed { reason: Symbol } + | NativeDriverAdmissionLimit { reason: Symbol } + | UnattributedRefusal { reason: Symbol } + +// The emitted DirectIngestDriver hard-codes admission to the compile_driver_subject name with +// an empty import environment, so resolve_module_not_found on this route is the DRIVER's limit, +// not the module's defect: every real module is named something else, and every import is +// unresolvable under it. The roster is data so a driver widening deletes a row rather than +// editing a predicate. +data native_driver_limit_reasons: List = [ ^resolve_module_not_found ] + +fn native_driver_limit_reason(reason: Symbol) -> Bool { + any(xs: native_driver_limit_reasons, predicate: fn(r) { r == reason }) +} + +fn classify_native_refusal(reason: Symbol) -> NativeRouteExclusionCause { + if native_driver_limit_reason(reason: reason) { + NativeDriverAdmissionLimit { reason: reason } + } else { + if cause_is_attributed(cause: reason) { + CompilerFrontierAttributed { reason: reason } + } else { + UnattributedRefusal { reason: reason } + } + } +} + +// THE COUNTED PARTITION the contract names ("every excluded member typed and counted"). The +// census is DERIVED from the rows, never accumulated beside them — a counter that disagrees +// with the population it summarizes has no constructor here. +type NativeRouteExclusionCensus { + accepted: Int + excluded_compiler_frontier: Int + excluded_driver_limit: Int + excluded_unattributed: Int +} + +fn native_route_exclusion_census(rows: List) -> NativeRouteExclusionCensus { + fold_list( + xs: rows, + empty: NativeRouteExclusionCensus { + accepted: 0, + excluded_compiler_frontier: 0, + excluded_driver_limit: 0, + excluded_unattributed: 0 + }, + cons: fn(acc, row) { + match row.verdict { + NativeAccepted => NativeRouteExclusionCensus { + accepted: acc.accepted + 1, + excluded_compiler_frontier: acc.excluded_compiler_frontier, + excluded_driver_limit: acc.excluded_driver_limit, + excluded_unattributed: acc.excluded_unattributed + } + NativeRefused { head_reason: _, fatal_reason: r } => + match classify_native_refusal(reason: r) { + CompilerFrontierAttributed { reason: _ } => NativeRouteExclusionCensus { + accepted: acc.accepted, + excluded_compiler_frontier: acc.excluded_compiler_frontier + 1, + excluded_driver_limit: acc.excluded_driver_limit, + excluded_unattributed: acc.excluded_unattributed + } + NativeDriverAdmissionLimit { reason: _ } => NativeRouteExclusionCensus { + accepted: acc.accepted, + excluded_compiler_frontier: acc.excluded_compiler_frontier, + excluded_driver_limit: acc.excluded_driver_limit + 1, + excluded_unattributed: acc.excluded_unattributed + } + UnattributedRefusal { reason: _ } => NativeRouteExclusionCensus { + accepted: acc.accepted, + excluded_compiler_frontier: acc.excluded_compiler_frontier, + excluded_driver_limit: acc.excluded_driver_limit, + excluded_unattributed: acc.excluded_unattributed + 1 + } + } + } + } + ) +} + +// "No silent remainder" as an executing check: the four counters close over the population. +fn native_route_census_is_total( + census: NativeRouteExclusionCensus, + rows: List +) -> Bool { + census.accepted + census.excluded_compiler_frontier + + census.excluded_driver_limit + census.excluded_unattributed == length(xs: rows) +} + +// THE NO-FALLBACK CONTROL IS A RECEIVED FACT, NOT AN ASSUMPTION. After the emitted compiler +// exists, the old compiler/interpreter route is made unavailable — or an executing falsifier +// is armed that fails if the old route is touched — and the receipt records WHICH control +// held. OldRouteControlAbsent is representable so admission can refuse it by name: a receipt +// that cannot say how fallback was closed has not closed it. +type NativeRouteOldRouteControl + = OldRouteWithdrawn { withdrawn_executable: String } + | OldRouteFalsifierArmed { falsifier_identity: String } + | OldRouteControlAbsent + +// The deliberately broken specimen is the route's RED control: a source the compiler must +// refuse. If the emitted binary ACCEPTS it, the route's refusal arm is dead and every refusal +// verdict in the population is worthless — admission refuses. +type NativeRouteBrokenControl + = BrokenSpecimenRefused { reason: Symbol } + | BrokenSpecimenAccepted + +// THE TERMINAL OBSERVATION. Every identity the contract names is a field: the tested tree; the +// seed used ONLY to prepare the emitted compiler (recorded honestly — this job does not claim +// native bootstrap); the emitted closure's identity; the executed binary's path AND content +// identity (route identity is observed, never synthesized from the intended command); the +// per-member verdicts; and both controls. +type NativeRouteReceipt { + job: String + tested_tree: String + preparation_seed_identity: String + emitted_closure_identity: String + executable_path: String + executable_identity: String + population: List + broken_control: NativeRouteBrokenControl + old_route_control: NativeRouteOldRouteControl +} + +// ADMISSION IS THE CONTRACT, ONE AUTHORITY, ONE CLAUSE PER PREDICATE. The job greens only when +// all hold: the receipt is well-formed (right job, identities recorded, population actually +// derived — an empty population is a harness failure, not a result); the positive population +// is non-empty (NativeAccepted IS the positive population, so the load-bearing clause is +// non-emptiness: an all-refused census proves the route refused, not that it routed); the +// broken specimen still refused; every exclusion is classified — the lane does not green over +// unowned refusals; route identity is recorded; and the old-route control is present. The fold +// collects EVERY violated clause rather than stopping at the first, so one red run names +// everything that must change before the next run. +type NativeRouteAdmissionRefusalCause + = JobNameMismatch + | TestedTreeUnrecorded + | PreparationSeedUnrecorded + | EmittedClosureUnrecorded + | PopulationUnderived + | PositivePopulationEmpty + | BrokenSpecimenAcceptedByRoute + | UnattributedExclusionsPresent + | RouteIdentityUnrecorded + | OldRouteControlNeverInstalled + +type NativeRouteAdmission + = NativeRouteAdmitted + | NativeRouteRefused { causes: List } + +fn native_route_job_name_matches(receipt: NativeRouteReceipt) -> Bool { + receipt.job == required_v2_native_job_name +} + +fn native_route_tested_tree_recorded(receipt: NativeRouteReceipt) -> Bool { + receipt.tested_tree != "" +} + +fn native_route_preparation_recorded(receipt: NativeRouteReceipt) -> Bool { + receipt.preparation_seed_identity != "" && receipt.emitted_closure_identity != "" +} + +fn native_route_population_derived(receipt: NativeRouteReceipt) -> Bool { + !is_empty(xs: receipt.population) +} + +fn native_route_positive_population_present(receipt: NativeRouteReceipt) -> Bool { + length(xs: filter(xs: receipt.population, predicate: fn(row) { + match row.verdict { + NativeAccepted => true + NativeRefused { head_reason: _, fatal_reason: _ } => false + } + })) > 0 +} + +fn native_route_broken_control_holds(receipt: NativeRouteReceipt) -> Bool { + match receipt.broken_control { + BrokenSpecimenRefused { reason: _ } => true + BrokenSpecimenAccepted => false + } +} + +fn native_route_exclusions_all_classified(receipt: NativeRouteReceipt) -> Bool { + native_route_exclusion_census(rows: receipt.population).excluded_unattributed == 0 +} + +fn native_route_identity_recorded(receipt: NativeRouteReceipt) -> Bool { + receipt.executable_path != "" && receipt.executable_identity != "" +} + +fn native_route_old_route_closed(receipt: NativeRouteReceipt) -> Bool { + match receipt.old_route_control { + OldRouteWithdrawn { withdrawn_executable: _ } => true + OldRouteFalsifierArmed { falsifier_identity: _ } => true + OldRouteControlAbsent => false + } +} + +fn native_route_admission(receipt: NativeRouteReceipt) -> NativeRouteAdmission { + let c1 = if native_route_job_name_matches(receipt: receipt) { [] } else { [JobNameMismatch] } + let c2 = if native_route_tested_tree_recorded(receipt: receipt) { c1 } else { concat(c1, [TestedTreeUnrecorded]) } + let c3 = if native_route_preparation_recorded(receipt: receipt) { c2 } else { concat(c2, [PreparationSeedUnrecorded]) } + let c4 = if native_route_population_derived(receipt: receipt) { c3 } else { concat(c3, [PopulationUnderived]) } + let c5 = if native_route_positive_population_present(receipt: receipt) { c4 } else { concat(c4, [PositivePopulationEmpty]) } + let c6 = if native_route_broken_control_holds(receipt: receipt) { c5 } else { concat(c5, [BrokenSpecimenAcceptedByRoute]) } + let c7 = if native_route_exclusions_all_classified(receipt: receipt) { c6 } else { concat(c6, [UnattributedExclusionsPresent]) } + let c8 = if native_route_identity_recorded(receipt: receipt) { c7 } else { concat(c7, [RouteIdentityUnrecorded]) } + let causes = if native_route_old_route_closed(receipt: receipt) { c8 } else { concat(c8, [OldRouteControlNeverInstalled]) } + if is_empty(xs: causes) { NativeRouteAdmitted } else { NativeRouteRefused { causes: causes } } +} + +// The job's terminal print renders causes by name; the projection lives beside the coproduct, +// so a new cause refuses to compile until it is named (exhaustive match). The membership probe +// is how a witness — and the enrolment gate's report — asks for one clause's verdict without +// re-matching the whole coproduct. +fn native_route_admission_refusal_cause_name(cause: NativeRouteAdmissionRefusalCause) -> String { + match cause { + JobNameMismatch => "job_name_mismatch" + TestedTreeUnrecorded => "tested_tree_unrecorded" + PreparationSeedUnrecorded => "preparation_seed_unrecorded" + EmittedClosureUnrecorded => "emitted_closure_unrecorded" + PopulationUnderived => "population_underived" + PositivePopulationEmpty => "positive_population_empty" + BrokenSpecimenAcceptedByRoute => "broken_specimen_accepted_by_route" + UnattributedExclusionsPresent => "unattributed_exclusions_present" + RouteIdentityUnrecorded => "route_identity_unrecorded" + OldRouteControlNeverInstalled => "old_route_control_never_installed" + } +} + +fn native_route_admitted(admission: NativeRouteAdmission) -> Bool { + match admission { + NativeRouteAdmitted => true + NativeRouteRefused { causes: _ } => false + } +} + +fn native_route_admission_refused_with( + admission: NativeRouteAdmission, + cause_name: String +) -> Bool { + match admission { + NativeRouteAdmitted => false + NativeRouteRefused { causes: causes } => + any(xs: causes, predicate: fn(c) { + native_route_admission_refusal_cause_name(cause: c) == cause_name + }) + } +} + +// The terminal print of one run: every violated clause, one line's worth. Derived from the +// causes, never accumulated beside them. +fn native_route_admission_summary(admission: NativeRouteAdmission) -> String { + match admission { + NativeRouteAdmitted => "admitted" + NativeRouteRefused { causes: causes } => concat( + "refused: ", + fold_list( + xs: causes, + empty: "", + cons: fn(acc, c) { + let name = native_route_admission_refusal_cause_name(cause: c) + if acc == "" { name } else { concat(acc, ", ", name) } + } + ) + ) + } +} + +// ENROLMENT IS THE ADMISSION QUESTION ASKED OF THE REAL CENSUS. The gate derives the lane's +// enrolment state from a census receipt — the same predicate the enrolled job will gate on, +// asked before enrolment, so the enrolment decision and the standing verdict can never be two +// facts. +type NativeRouteEnrolment + = EnrolmentOpen + | EnrolmentBlocked { causes: List } + +fn native_route_enrolment(census: NativeRouteReceipt) -> NativeRouteEnrolment { + match native_route_admission(receipt: census) { + NativeRouteAdmitted => EnrolmentOpen + NativeRouteRefused { causes: causes } => EnrolmentBlocked { causes: causes } + } +} + +// TODAY'S STANDING, AS DATA. The lane is modelled in full and its enrolment is BLOCKED on the +// compile-door frontier. The trigger names the CAPABILITY that unblocks it (DESIGN 4b(3): a +// trigger names the capability, never an artifact that would contribute to one), and the edit +// row names the exact mechanical change that lands it, so the future lane author executes +// rather than re-derives. This row DELETES at enrolment: once the job stands in the required +// workflow, the standing it records here is the job's own receipt, not a row beside it. +type NativeRouteEnrolmentState + = EnrolmentOpenToday + | EnrolmentAwaitingDoorFrontier + +type NativeRouteEnrolmentStanding { + state: NativeRouteEnrolmentState + trigger: String + enrolment_edit: String + census_evidence: String +} + +data native_route_enrolment_standing: NativeRouteEnrolmentStanding = NativeRouteEnrolmentStanding { + state: EnrolmentAwaitingDoorFrontier, + trigger: "the emitted native compiler accepts at least one real v2.test.* module — the compile-door frontier (v2.workflow.compile_door_ledger known_frontier_causes burn-down) has closed far enough that the derived positive population is non-empty; re-derive by running the census recipe named in census_evidence and evaluating native_route_enrolment over its receipt", + enrolment_edit: "add V2NativeLane to gunbc.required_ci_phase_roster RequiredCiLane with its phase variant(s) enrolled in required_ci_phases and owned by the new lane in required_ci_phase_lane, mirror the variants in claim_executor (the per-run host join refuses a divergence in either direction), add WitnessFloorV2NativeLane to gunbc.witness_floor_workflow witness_floor_lane_roster with its job materialization (seed builds once, emits the compiler closure, cargo-builds it, withdraws the old route, runs the census and both controls, prints the receipt, gates on native_route_admission), add the lane to required_lanes_roster so the aggregate witnesses job needs it, regenerate .github/workflows/witnesses.yml", + census_evidence: "local census harness on the PR-#10692 head (tree f173aaa24860a0efe7b2142cefcedc69675ff66f): emit via `gunbc compile --entry src/v2/compiler/00_compile.dag --source-root dag --source-root src/v2 --target rust`, cargo build with the repo-pinned toolchain, withdraw the seed binary, invoke the emitted binary by explicit path over every derived universe member, classify each verdict with classify_native_refusal. Measured 2026-09-09 over the derived universe of 882 members: zero accepted, 882 refused with every fatal cause attributed (the five causes the door-ledger table lacked were added to it by this lane's measurement), the broken specimen refused, and native_route_admission over the real receipt executed to `refused: positive_population_empty`" +} + +fn native_route_enrolment_blocked_today(standing: NativeRouteEnrolmentStanding) -> Bool { + match standing.state { + EnrolmentAwaitingDoorFrontier => true + EnrolmentOpenToday => false + } +} diff --git a/src/v2/test/v2_native_route_test.dag b/src/v2/test/v2_native_route_test.dag new file mode 100644 index 00000000000..a8ce0e03e79 --- /dev/null +++ b/src/v2/test/v2_native_route_test.dag @@ -0,0 +1,263 @@ +module v2.test.v2_native_route + +import gunbc.witness_v2_native_route { + NativeRouteVerdict, NativeAccepted, NativeRefused, + NativeRouteMemberRow, + NativeRouteExclusionCause, CompilerFrontierAttributed, NativeDriverAdmissionLimit, UnattributedRefusal, + NativeRouteOldRouteControl, OldRouteWithdrawn, OldRouteFalsifierArmed, OldRouteControlAbsent, + NativeRouteBrokenControl, BrokenSpecimenRefused, BrokenSpecimenAccepted, + NativeRouteReceipt, + NativeRouteAdmission, NativeRouteAdmitted, NativeRouteRefused, + NativeRouteEnrolment, EnrolmentOpen, EnrolmentBlocked, + required_v2_native_job_name, + native_route_universe_member, + native_route_universe, + classify_native_refusal, + native_route_exclusion_census, + native_route_census_is_total, + native_route_admission, + native_route_admitted, + native_route_admission_refused_with, + native_route_admission_summary, + native_route_enrolment, + native_route_enrolment_standing, + native_route_enrolment_blocked_today, +} +import std.algebra { Cons, Empty } +import v2.std.algebra { length } +import v2.std.collection { List } +import v2.std.logic { Bool } +import v2.std.node { Symbol } + +fn receipt_with( + population: List, + broken_control: NativeRouteBrokenControl, + old_route_control: NativeRouteOldRouteControl +) -> NativeRouteReceipt { + NativeRouteReceipt { + job: required_v2_native_job_name, + tested_tree: "tree-sha", + preparation_seed_identity: "seed-sha", + emitted_closure_identity: "closure-sha", + executable_path: "/emitted/compiler", + executable_identity: "binary-sha", + population: population, + broken_control: broken_control, + old_route_control: old_route_control + } +} + +fn accepted_member(path: String) -> NativeRouteMemberRow { + NativeRouteMemberRow { module_path: path, verdict: NativeAccepted } +} + +fn refused_member(path: String, reason: Symbol) -> NativeRouteMemberRow { + NativeRouteMemberRow { + module_path: path, + verdict: NativeRefused { head_reason: reason, fatal_reason: reason } + } +} + +fn green_receipt() -> NativeRouteReceipt { + receipt_with( + population: [ + accepted_member(path: "v2.test.one"), + accepted_member(path: "v2.test.two"), + refused_member(path: "v2.test.three", reason: ^fact_density_hollow_alias_locus) + ], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + ) +} + +// TODAY'S FRONTIER SHAPE: every universe member refused with an attributed cause, the broken +// specimen refused, both controls present — and admission still refuses, because an all-refused +// census proves the route refused, not that it routed. +fn frontier_receipt() -> NativeRouteReceipt { + receipt_with( + population: [ + refused_member(path: "v2.test.one", reason: ^parse_grammar_choice_overlap_residue), + refused_member(path: "v2.test.two", reason: ^resolve_module_not_found) + ], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + ) +} + +test fn universe_membership_is_the_prefix_and_nothing_else() -> Bool { + native_route_universe_member(module_qn: "v2.test.foo") + && native_route_universe_member(module_qn: "v2.test.claim.long.bar") + && !native_route_universe_member(module_qn: "v2.test") + && !native_route_universe_member(module_qn: "v2.testlong.foo") + && !native_route_universe_member(module_qn: "test.claim.infer_x") + && !native_route_universe_member(module_qn: "v2.workflow.compile_door_ledger") +} + +test fn universe_derivation_selects_exactly_the_prefix_members() -> Bool { + let derived = native_route_universe(module_qns: [ + "v2.test.a", + "v2.workflow.compile_door_ledger", + "v2.test.claim.b", + "gunbc.witness_floor_workflow" + ]) + length(xs: derived) == 2 + && match derived { + Cons { head: first, tail: rest } => + match rest { + Cons { head: second, tail: Empty {} } => + first == "v2.test.a" && second == "v2.test.claim.b" + Cons { head: _, tail: _ } => false + Empty {} => false + } + Empty {} => false + } +} + +test fn refusal_classification_delegates_to_the_door_ledger_attribution() -> Bool { + match classify_native_refusal(reason: ^fact_density_hollow_alias_locus) { + CompilerFrontierAttributed { reason: r } => r == ^fact_density_hollow_alias_locus + NativeDriverAdmissionLimit { reason: _ } => false + UnattributedRefusal { reason: _ } => false + } +} + +test fn refusal_classification_owns_the_driver_admission_limit() -> Bool { + match classify_native_refusal(reason: ^resolve_module_not_found) { + NativeDriverAdmissionLimit { reason: r } => r == ^resolve_module_not_found + CompilerFrontierAttributed { reason: _ } => false + UnattributedRefusal { reason: _ } => false + } +} + +test fn refusal_classification_never_silently_buckets_an_unowned_cause() -> Bool { + match classify_native_refusal(reason: ^a_cause_nobody_owns) { + UnattributedRefusal { reason: r } => r == ^a_cause_nobody_owns + CompilerFrontierAttributed { reason: _ } => false + NativeDriverAdmissionLimit { reason: _ } => false + } +} + +test fn exclusion_census_counts_every_member_exactly_once() -> Bool { + let rows = [ + accepted_member(path: "v2.test.a"), + refused_member(path: "v2.test.b", reason: ^fact_density_hollow_alias_locus), + refused_member(path: "v2.test.c", reason: ^resolve_module_not_found), + refused_member(path: "v2.test.d", reason: ^a_cause_nobody_owns) + ] + let census = native_route_exclusion_census(rows: rows) + census.accepted == 1 + && census.excluded_compiler_frontier == 1 + && census.excluded_driver_limit == 1 + && census.excluded_unattributed == 1 + && native_route_census_is_total(census: census, rows: rows) +} + +test fn a_fully_met_contract_admits() -> Bool { + native_route_admitted(admission: native_route_admission(receipt: green_receipt())) +} + +test fn an_empty_positive_population_refuses_even_with_everything_else_held() -> Bool { + let admission = native_route_admission(receipt: frontier_receipt()) + !native_route_admitted(admission: admission) + && native_route_admission_refused_with(admission: admission, cause_name: "positive_population_empty") +} + +test fn a_broken_specimen_the_route_accepts_kills_the_whole_census() -> Bool { + let admission = native_route_admission(receipt: receipt_with( + population: [accepted_member(path: "v2.test.one")], + broken_control: BrokenSpecimenAccepted, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + )) + native_route_admission_refused_with(admission: admission, cause_name: "broken_specimen_accepted_by_route") +} + +test fn an_unattributed_exclusion_blocks_admission_until_someone_owns_it() -> Bool { + let admission = native_route_admission(receipt: receipt_with( + population: [ + accepted_member(path: "v2.test.one"), + refused_member(path: "v2.test.two", reason: ^a_cause_nobody_owns) + ], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + )) + native_route_admission_refused_with(admission: admission, cause_name: "unattributed_exclusions_present") +} + +test fn route_identity_is_observed_never_synthesized_from_the_intended_command() -> Bool { + let admission = native_route_admission(receipt: NativeRouteReceipt { + job: required_v2_native_job_name, + tested_tree: "tree-sha", + preparation_seed_identity: "seed-sha", + emitted_closure_identity: "closure-sha", + executable_path: "", + executable_identity: "", + population: [accepted_member(path: "v2.test.one")], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + }) + native_route_admission_refused_with(admission: admission, cause_name: "route_identity_unrecorded") +} + +test fn an_uninstalled_old_route_control_refuses_by_name() -> Bool { + let admission = native_route_admission(receipt: receipt_with( + population: [accepted_member(path: "v2.test.one")], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteControlAbsent + )) + native_route_admission_refused_with(admission: admission, cause_name: "old_route_control_never_installed") +} + +test fn one_red_run_names_every_violated_clause() -> Bool { + let admission = native_route_admission(receipt: receipt_with( + population: [refused_member(path: "v2.test.one", reason: ^a_cause_nobody_owns)], + broken_control: BrokenSpecimenAccepted, + old_route_control: OldRouteControlAbsent + )) + native_route_admission_refused_with(admission: admission, cause_name: "positive_population_empty") + && native_route_admission_refused_with(admission: admission, cause_name: "broken_specimen_accepted_by_route") + && native_route_admission_refused_with(admission: admission, cause_name: "unattributed_exclusions_present") + && native_route_admission_refused_with(admission: admission, cause_name: "old_route_control_never_installed") +} + +test fn a_receipt_naming_another_job_is_not_this_lanes_evidence() -> Bool { + let admission = native_route_admission(receipt: NativeRouteReceipt { + job: "some-other-job", + tested_tree: "tree-sha", + preparation_seed_identity: "seed-sha", + emitted_closure_identity: "closure-sha", + executable_path: "/emitted/compiler", + executable_identity: "binary-sha", + population: [accepted_member(path: "v2.test.one")], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + }) + native_route_admission_refused_with(admission: admission, cause_name: "job_name_mismatch") +} + +test fn enrolment_is_the_admission_question_asked_of_the_census() -> Bool { + match native_route_enrolment(census: green_receipt()) { + EnrolmentOpen => true + EnrolmentBlocked { causes: _ } => false + } + && match native_route_enrolment(census: frontier_receipt()) { + EnrolmentOpen => false + EnrolmentBlocked { causes: causes } => + native_route_admission_refused_with( + admission: NativeRouteRefused { causes: causes }, + cause_name: "positive_population_empty" + ) + } +} + +test fn the_admission_summary_names_every_violated_clause() -> Bool { + native_route_admission_summary(admission: native_route_admission(receipt: frontier_receipt())) + == "refused: positive_population_empty" + && native_route_admission_summary(admission: native_route_admission(receipt: green_receipt())) == "admitted" +} + +test fn the_standing_row_records_enrolment_blocked_on_the_door_frontier() -> Bool { + native_route_enrolment_blocked_today(standing: native_route_enrolment_standing) + && native_route_enrolment_standing.trigger != "" + && native_route_enrolment_standing.enrolment_edit != "" + && native_route_enrolment_standing.census_evidence != "" +} diff --git a/src/v2/workflow/compile_door_ledger.dag b/src/v2/workflow/compile_door_ledger.dag index ef3174e95d8..e68e771c64c 100644 --- a/src/v2/workflow/compile_door_ledger.dag +++ b/src/v2/workflow/compile_door_ledger.dag @@ -353,6 +353,36 @@ data known_frontier_causes: List = [ cause: ^parse_grammar_choice_overlap_residue, lane: MigrationOwned, flip_trigger: "no per-cause trigger — dissolves by construction at phase-3 import-grammar deletion / grammar-surgery (nimble-boar-198)" + }, + CauseOwnership { + cause: ^parse_g0_tokens_remain, + lane: MigrationOwned, + flip_trigger: "no per-cause trigger — same grammar-surgery class as parse_grammar_choice_overlap_residue (nimble-boar-198). Measured 2026-09-09 by the gunbc.witness_v2_native_route census as the FATAL refusal on real v2.test.* modules through the emitted native binary: rejected_with_pending prepends the non-fatal overlap residue, so this cause never surfaces at the head grain this table counts — the native-route census records it at fatal grain" + }, + CauseOwnership { + cause: ^tokenize_lex_e1_unrecognized_char, + lane: MigrationOwned, + flip_trigger: "no per-cause trigger — lexer coverage is the same grammar-surgery class (nimble-boar-198). Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (6 modules, the annotation-channel and comment-wall syntax among them)" + }, + CauseOwnership { + cause: ^namespace_graft_body_dissolved_refused, + lane: MigrationOwned, + flip_trigger: "no per-cause trigger — the graft is namespace machinery and dissolves with it (nimble-boar-198). Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (41 modules)" + }, + CauseOwnership { + cause: ^normalize_reason_post_normalize_not_well_formed, + lane: SharedSelfHostCriticalPath, + flip_trigger: "normalize stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (6 modules)" + }, + CauseOwnership { + cause: ^normalized_tree_reason_wrapper_retention_not_normalized, + lane: SharedSelfHostCriticalPath, + flip_trigger: "normalize stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (2 modules)" + }, + CauseOwnership { + cause: ^body_lowering_reason_match_arm_navigation_refused, + lane: SharedSelfHostCriticalPath, + flip_trigger: "body-lowering stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (2 modules)" } ] From 22b8303cb9fd2f292e5dd4f168d916dd31d53c38 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 03:47:58 +0000 Subject: [PATCH 43/89] Split preparation predicates so EmittedClosureUnrecorded is reachable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review on #10882 (briansrls, point 7): native_route_preparation_recorded folded the seed and closure observations into one && predicate, so a receipt with a recorded seed and an unrecorded closure misreported as preparation_seed_unrecorded and the emitted_closure_unrecorded cause had no reachable construction — the grain-mismatch class DESIGN 4b(3) names. One predicate per observation, one admission clause per predicate, and two witnesses pinning each refusal name against its own receipt shape (including the negative: each refuses ONLY by its own name). Co-authored-by: briansrls --- dag/gunbc/witness/v2_native_route.dag | 27 ++++++++++++------- src/v2/test/v2_native_route_test.dag | 37 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/dag/gunbc/witness/v2_native_route.dag b/dag/gunbc/witness/v2_native_route.dag index 712de64a958..50dae7481a1 100644 --- a/dag/gunbc/witness/v2_native_route.dag +++ b/dag/gunbc/witness/v2_native_route.dag @@ -228,8 +228,16 @@ fn native_route_tested_tree_recorded(receipt: NativeRouteReceipt) -> Bool { receipt.tested_tree != "" } -fn native_route_preparation_recorded(receipt: NativeRouteReceipt) -> Bool { - receipt.preparation_seed_identity != "" && receipt.emitted_closure_identity != "" +// The seed and the closure are DIFFERENT observations and refuse by DIFFERENT names: folding +// them into one predicate made EmittedClosureUnrecorded unreachable (a receipt with a recorded +// seed and an unrecorded closure reported preparation_seed_unrecorded), which is the +// grain-mismatch class DESIGN 4b(3) names. One predicate per observation. +fn native_route_preparation_seed_recorded(receipt: NativeRouteReceipt) -> Bool { + receipt.preparation_seed_identity != "" +} + +fn native_route_emitted_closure_recorded(receipt: NativeRouteReceipt) -> Bool { + receipt.emitted_closure_identity != "" } fn native_route_population_derived(receipt: NativeRouteReceipt) -> Bool { @@ -271,13 +279,14 @@ fn native_route_old_route_closed(receipt: NativeRouteReceipt) -> Bool { fn native_route_admission(receipt: NativeRouteReceipt) -> NativeRouteAdmission { let c1 = if native_route_job_name_matches(receipt: receipt) { [] } else { [JobNameMismatch] } let c2 = if native_route_tested_tree_recorded(receipt: receipt) { c1 } else { concat(c1, [TestedTreeUnrecorded]) } - let c3 = if native_route_preparation_recorded(receipt: receipt) { c2 } else { concat(c2, [PreparationSeedUnrecorded]) } - let c4 = if native_route_population_derived(receipt: receipt) { c3 } else { concat(c3, [PopulationUnderived]) } - let c5 = if native_route_positive_population_present(receipt: receipt) { c4 } else { concat(c4, [PositivePopulationEmpty]) } - let c6 = if native_route_broken_control_holds(receipt: receipt) { c5 } else { concat(c5, [BrokenSpecimenAcceptedByRoute]) } - let c7 = if native_route_exclusions_all_classified(receipt: receipt) { c6 } else { concat(c6, [UnattributedExclusionsPresent]) } - let c8 = if native_route_identity_recorded(receipt: receipt) { c7 } else { concat(c7, [RouteIdentityUnrecorded]) } - let causes = if native_route_old_route_closed(receipt: receipt) { c8 } else { concat(c8, [OldRouteControlNeverInstalled]) } + let c3 = if native_route_preparation_seed_recorded(receipt: receipt) { c2 } else { concat(c2, [PreparationSeedUnrecorded]) } + let c4 = if native_route_emitted_closure_recorded(receipt: receipt) { c3 } else { concat(c3, [EmittedClosureUnrecorded]) } + let c5 = if native_route_population_derived(receipt: receipt) { c4 } else { concat(c4, [PopulationUnderived]) } + let c6 = if native_route_positive_population_present(receipt: receipt) { c5 } else { concat(c5, [PositivePopulationEmpty]) } + let c7 = if native_route_broken_control_holds(receipt: receipt) { c6 } else { concat(c6, [BrokenSpecimenAcceptedByRoute]) } + let c8 = if native_route_exclusions_all_classified(receipt: receipt) { c7 } else { concat(c7, [UnattributedExclusionsPresent]) } + let c9 = if native_route_identity_recorded(receipt: receipt) { c8 } else { concat(c8, [RouteIdentityUnrecorded]) } + let causes = if native_route_old_route_closed(receipt: receipt) { c9 } else { concat(c9, [OldRouteControlNeverInstalled]) } if is_empty(xs: causes) { NativeRouteAdmitted } else { NativeRouteRefused { causes: causes } } } diff --git a/src/v2/test/v2_native_route_test.dag b/src/v2/test/v2_native_route_test.dag index a8ce0e03e79..d6b3cea9760 100644 --- a/src/v2/test/v2_native_route_test.dag +++ b/src/v2/test/v2_native_route_test.dag @@ -207,6 +207,43 @@ test fn an_uninstalled_old_route_control_refuses_by_name() -> Bool { native_route_admission_refused_with(admission: admission, cause_name: "old_route_control_never_installed") } +// The seed and the closure are separate observations with separate refusal names: a receipt +// recording the seed but not the closure must name emitted_closure_unrecorded, and one recording +// the closure but not the seed must name preparation_seed_unrecorded. Before the predicates were +// split, the first shape misreported as preparation_seed_unrecorded and the second cause had no +// reachable construction. +test fn an_unrecorded_closure_refuses_by_its_own_name() -> Bool { + let admission = native_route_admission(receipt: NativeRouteReceipt { + job: required_v2_native_job_name, + tested_tree: "tree-sha", + preparation_seed_identity: "seed-sha", + emitted_closure_identity: "", + executable_path: "/emitted/compiler", + executable_identity: "binary-sha", + population: [accepted_member(path: "v2.test.one")], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + }) + native_route_admission_refused_with(admission: admission, cause_name: "emitted_closure_unrecorded") + && !native_route_admission_refused_with(admission: admission, cause_name: "preparation_seed_unrecorded") +} + +test fn an_unrecorded_seed_refuses_by_its_own_name() -> Bool { + let admission = native_route_admission(receipt: NativeRouteReceipt { + job: required_v2_native_job_name, + tested_tree: "tree-sha", + preparation_seed_identity: "", + emitted_closure_identity: "closure-sha", + executable_path: "/emitted/compiler", + executable_identity: "binary-sha", + population: [accepted_member(path: "v2.test.one")], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + }) + native_route_admission_refused_with(admission: admission, cause_name: "preparation_seed_unrecorded") + && !native_route_admission_refused_with(admission: admission, cause_name: "emitted_closure_unrecorded") +} + test fn one_red_run_names_every_violated_clause() -> Bool { let admission = native_route_admission(receipt: receipt_with( population: [refused_member(path: "v2.test.one", reason: ^a_cause_nobody_owns)], From ca41065516486f532784f69514caa3c786606ed9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 04:01:31 +0000 Subject: [PATCH 44/89] Key cause ownership by diagnostic grain; classify native refusals at fatal grain The door ledger's known_frontier_causes was a head-grain authority; the native route classified fatal reasons through it, crossing grains (review on #10882). Generalize the ownership key with DiagnosticGrain so one table answers both grains: the door ledger's cause_is_attributed keeps its head-grain contract, and the native route's exclusion classifier asks the fatal-grain question of the same table. The head advisory is live receipt data again: every refused row's head reason must be owned at head grain (or by this lane's driver-limit roster), and an unowned advisory blocks admission by its own clause name. Co-authored-by: briansrls --- dag/gunbc/witness/v2_native_route.dag | 62 ++++++++++++++++---- src/v2/test/v2_native_route_test.dag | 77 +++++++++++++++++++++++-- src/v2/workflow/compile_door_ledger.dag | 60 +++++++++++++++++-- 3 files changed, 176 insertions(+), 23 deletions(-) diff --git a/dag/gunbc/witness/v2_native_route.dag b/dag/gunbc/witness/v2_native_route.dag index 50dae7481a1..2818574052e 100644 --- a/dag/gunbc/witness/v2_native_route.dag +++ b/dag/gunbc/witness/v2_native_route.dag @@ -5,7 +5,14 @@ import v2.std.collection { List } import v2.std.integer { Int } import v2.std.logic { Bool } import v2.std.node { Symbol } -import v2.workflow.compile_door_ledger { cause_is_attributed } +import v2.std.optional { Absent, Optional, Present } +import v2.workflow.compile_door_ledger { + CauseOwnership, + DiagnosticGrain, + FatalGrain, + HeadGrain, + cause_ownership_lookup +} // THE LANE AUTHORITY FOR THE required-v2-native CI JOB (operator authorization 2026-09-09: one // additional required job, modelled beside the workflow authority that will project it — never @@ -66,11 +73,11 @@ type NativeRouteMemberRow { // THE EXCLUSION TAXONOMY. A refused member is excluded for exactly one of three causes: the // compiler's own frontier (attributed to its owning lane through the door ledger's -// known_frontier_causes — this lane delegates to that table, it does not re-enumerate it); the -// emitted driver's admission limit (a native-route fact the interpreted door never faces, -// owned here); or NOTHING — an unattributed refusal, which is never silently bucketed: it -// blocks admission until someone owns it, the same fail-closed reading the door ledger gives -// cause_is_attributed = false. +// known_frontier_causes at FATAL grain — this lane delegates to that table, it does not +// re-enumerate it); the emitted driver's admission limit (a native-route fact the interpreted +// door never faces, owned here); or NOTHING — an unattributed refusal, which is never silently +// bucketed: it blocks admission until someone owns it, the same fail-closed reading the door +// ledger gives an unowned head reason. type NativeRouteExclusionCause = CompilerFrontierAttributed { reason: Symbol } | NativeDriverAdmissionLimit { reason: Symbol } @@ -87,18 +94,46 @@ fn native_driver_limit_reason(reason: Symbol) -> Bool { any(xs: native_driver_limit_reasons, predicate: fn(r) { r == reason }) } +// The exclusion question is asked at FATAL grain: the fatal reason is the cause that actually +// refused the member, so its ownership row is the FatalGrain one. Asking the head-grain +// question of a fatal reason was the grain crossing the review named — the same symbol can be +// owned at one grain and unowned at the other, and only the fatal-grain answer classifies an +// exclusion. fn classify_native_refusal(reason: Symbol) -> NativeRouteExclusionCause { if native_driver_limit_reason(reason: reason) { NativeDriverAdmissionLimit { reason: reason } } else { - if cause_is_attributed(cause: reason) { - CompilerFrontierAttributed { reason: reason } - } else { - UnattributedRefusal { reason: reason } + match cause_ownership_lookup(cause: reason, grain: FatalGrain) { + Present { value: _ } => CompilerFrontierAttributed { reason: reason } + Absent => UnattributedRefusal { reason: reason } } } } +// THE HEAD REASON IS NOT DEAD RECEIPT DATA. rejected_with_pending prepends the non-fatal +// advisory, so the head reason is a real observation of the frontier at a different grain — +// the same observation the door ledger's own rows carry. Every refused row's head reason must +// therefore be owned at HEAD grain: an unowned advisory is a new frontier cause surfacing +// where nobody counted it, and it blocks admission by its own name rather than riding along +// unclassified. +fn native_route_head_advisory_attributed(reason: Symbol) -> Bool { + native_driver_limit_reason(reason: reason) + || match cause_ownership_lookup(cause: reason, grain: HeadGrain) { + Present { value: _ } => true + Absent => false + } +} + +fn native_route_head_advisories_all_attributed(rows: List) -> Bool { + !any(xs: rows, predicate: fn(row) { + match row.verdict { + NativeAccepted => false + NativeRefused { head_reason: h, fatal_reason: _ } => + !native_route_head_advisory_attributed(reason: h) + } + }) +} + // THE COUNTED PARTITION the contract names ("every excluded member typed and counted"). The // census is DERIVED from the rows, never accumulated beside them — a counter that disagrees // with the population it summarizes has no constructor here. @@ -213,6 +248,7 @@ type NativeRouteAdmissionRefusalCause | PositivePopulationEmpty | BrokenSpecimenAcceptedByRoute | UnattributedExclusionsPresent + | UnattributedHeadAdvisoryPresent | RouteIdentityUnrecorded | OldRouteControlNeverInstalled @@ -285,8 +321,9 @@ fn native_route_admission(receipt: NativeRouteReceipt) -> NativeRouteAdmission { let c6 = if native_route_positive_population_present(receipt: receipt) { c5 } else { concat(c5, [PositivePopulationEmpty]) } let c7 = if native_route_broken_control_holds(receipt: receipt) { c6 } else { concat(c6, [BrokenSpecimenAcceptedByRoute]) } let c8 = if native_route_exclusions_all_classified(receipt: receipt) { c7 } else { concat(c7, [UnattributedExclusionsPresent]) } - let c9 = if native_route_identity_recorded(receipt: receipt) { c8 } else { concat(c8, [RouteIdentityUnrecorded]) } - let causes = if native_route_old_route_closed(receipt: receipt) { c9 } else { concat(c9, [OldRouteControlNeverInstalled]) } + let c9 = if native_route_head_advisories_all_attributed(rows: receipt.population) { c8 } else { concat(c8, [UnattributedHeadAdvisoryPresent]) } + let c10 = if native_route_identity_recorded(receipt: receipt) { c9 } else { concat(c9, [RouteIdentityUnrecorded]) } + let causes = if native_route_old_route_closed(receipt: receipt) { c10 } else { concat(c10, [OldRouteControlNeverInstalled]) } if is_empty(xs: causes) { NativeRouteAdmitted } else { NativeRouteRefused { causes: causes } } } @@ -304,6 +341,7 @@ fn native_route_admission_refusal_cause_name(cause: NativeRouteAdmissionRefusalC PositivePopulationEmpty => "positive_population_empty" BrokenSpecimenAcceptedByRoute => "broken_specimen_accepted_by_route" UnattributedExclusionsPresent => "unattributed_exclusions_present" + UnattributedHeadAdvisoryPresent => "unattributed_head_advisory_present" RouteIdentityUnrecorded => "route_identity_unrecorded" OldRouteControlNeverInstalled => "old_route_control_never_installed" } diff --git a/src/v2/test/v2_native_route_test.dag b/src/v2/test/v2_native_route_test.dag index d6b3cea9760..93f864dbcda 100644 --- a/src/v2/test/v2_native_route_test.dag +++ b/src/v2/test/v2_native_route_test.dag @@ -51,6 +51,8 @@ fn accepted_member(path: String) -> NativeRouteMemberRow { NativeRouteMemberRow { module_path: path, verdict: NativeAccepted } } +// A single-reason refusal: head and fatal are the same observation (no pending advisory rode +// along). Driver-limit refusals have this shape. fn refused_member(path: String, reason: Symbol) -> NativeRouteMemberRow { NativeRouteMemberRow { module_path: path, @@ -58,12 +60,25 @@ fn refused_member(path: String, reason: Symbol) -> NativeRouteMemberRow { } } +// The two-grain refusal shape the real census measures: rejected_with_pending prepends the +// non-fatal overlap advisory, so the HEAD is the advisory and the FATAL is what refused. +fn refused_member_at(path: String, head: Symbol, fatal: Symbol) -> NativeRouteMemberRow { + NativeRouteMemberRow { + module_path: path, + verdict: NativeRefused { head_reason: head, fatal_reason: fatal } + } +} + fn green_receipt() -> NativeRouteReceipt { receipt_with( population: [ accepted_member(path: "v2.test.one"), accepted_member(path: "v2.test.two"), - refused_member(path: "v2.test.three", reason: ^fact_density_hollow_alias_locus) + refused_member_at( + path: "v2.test.three", + head: ^parse_grammar_choice_overlap_residue, + fatal: ^parse_g0_tokens_remain + ) ], broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } @@ -76,7 +91,11 @@ fn green_receipt() -> NativeRouteReceipt { fn frontier_receipt() -> NativeRouteReceipt { receipt_with( population: [ - refused_member(path: "v2.test.one", reason: ^parse_grammar_choice_overlap_residue), + refused_member_at( + path: "v2.test.one", + head: ^parse_grammar_choice_overlap_residue, + fatal: ^parse_g0_tokens_remain + ), refused_member(path: "v2.test.two", reason: ^resolve_module_not_found) ], broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, @@ -113,14 +132,26 @@ test fn universe_derivation_selects_exactly_the_prefix_members() -> Bool { } } -test fn refusal_classification_delegates_to_the_door_ledger_attribution() -> Bool { - match classify_native_refusal(reason: ^fact_density_hollow_alias_locus) { - CompilerFrontierAttributed { reason: r } => r == ^fact_density_hollow_alias_locus +test fn refusal_classification_delegates_to_the_door_ledger_at_fatal_grain() -> Bool { + match classify_native_refusal(reason: ^parse_g0_tokens_remain) { + CompilerFrontierAttributed { reason: r } => r == ^parse_g0_tokens_remain NativeDriverAdmissionLimit { reason: _ } => false UnattributedRefusal { reason: _ } => false } } +// THE GRAINS ARE DIFFERENT OBSERVATIONS: fact_density_hollow_alias_locus is owned at HEAD grain +// (the door ledger's own rows) and has no FatalGrain row, so asking the fatal question of it is +// honestly unattributed — the lookup never confuses ownership at one grain for ownership at the +// other. +test fn a_head_owned_cause_is_not_thereby_fatal_owned() -> Bool { + match classify_native_refusal(reason: ^fact_density_hollow_alias_locus) { + UnattributedRefusal { reason: r } => r == ^fact_density_hollow_alias_locus + CompilerFrontierAttributed { reason: _ } => false + NativeDriverAdmissionLimit { reason: _ } => false + } +} + test fn refusal_classification_owns_the_driver_admission_limit() -> Bool { match classify_native_refusal(reason: ^resolve_module_not_found) { NativeDriverAdmissionLimit { reason: r } => r == ^resolve_module_not_found @@ -140,7 +171,11 @@ test fn refusal_classification_never_silently_buckets_an_unowned_cause() -> Bool test fn exclusion_census_counts_every_member_exactly_once() -> Bool { let rows = [ accepted_member(path: "v2.test.a"), - refused_member(path: "v2.test.b", reason: ^fact_density_hollow_alias_locus), + refused_member_at( + path: "v2.test.b", + head: ^parse_grammar_choice_overlap_residue, + fatal: ^parse_g0_tokens_remain + ), refused_member(path: "v2.test.c", reason: ^resolve_module_not_found), refused_member(path: "v2.test.d", reason: ^a_cause_nobody_owns) ] @@ -183,6 +218,35 @@ test fn an_unattributed_exclusion_blocks_admission_until_someone_owns_it() -> Bo native_route_admission_refused_with(admission: admission, cause_name: "unattributed_exclusions_present") } +// THE HEAD ADVISORY IS LIVE RECEIPT DATA: a row whose FATAL cause is owned at fatal grain but +// whose HEAD advisory is owned nowhere refuses by the head clause's own name — and does NOT +// misreport as an exclusion-cause failure, because the grains are different questions. +test fn an_unattributed_head_advisory_blocks_admission_by_its_own_name() -> Bool { + let admission = native_route_admission(receipt: receipt_with( + population: [ + accepted_member(path: "v2.test.one"), + refused_member_at( + path: "v2.test.two", + head: ^an_advisory_nobody_owns, + fatal: ^parse_g0_tokens_remain + ) + ], + broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + )) + native_route_admission_refused_with(admission: admission, cause_name: "unattributed_head_advisory_present") + && !native_route_admission_refused_with(admission: admission, cause_name: "unattributed_exclusions_present") +} + +// The driver-limit reason is owned by THIS lane at whichever grain it surfaces: a single- +// diagnostic driver refusal (head == fatal == resolve_module_not_found) passes the head join +// without a door-ledger row, because the door ledger never sees the driver's synthetic-subject +// refusal. +test fn a_driver_limit_head_advisory_is_owned_by_this_lane() -> Bool { + let admission = native_route_admission(receipt: frontier_receipt()) + !native_route_admission_refused_with(admission: admission, cause_name: "unattributed_head_advisory_present") +} + test fn route_identity_is_observed_never_synthesized_from_the_intended_command() -> Bool { let admission = native_route_admission(receipt: NativeRouteReceipt { job: required_v2_native_job_name, @@ -253,6 +317,7 @@ test fn one_red_run_names_every_violated_clause() -> Bool { native_route_admission_refused_with(admission: admission, cause_name: "positive_population_empty") && native_route_admission_refused_with(admission: admission, cause_name: "broken_specimen_accepted_by_route") && native_route_admission_refused_with(admission: admission, cause_name: "unattributed_exclusions_present") + && native_route_admission_refused_with(admission: admission, cause_name: "unattributed_head_advisory_present") && native_route_admission_refused_with(admission: admission, cause_name: "old_route_control_never_installed") } diff --git a/src/v2/workflow/compile_door_ledger.dag b/src/v2/workflow/compile_door_ledger.dag index e68e771c64c..59d9855a5bf 100644 --- a/src/v2/workflow/compile_door_ledger.dag +++ b/src/v2/workflow/compile_door_ledger.dag @@ -322,6 +322,22 @@ fn ledger_all_compiled(rows: List) -> Bool { // the door's fail-closed rejection IS compilation failure and this ledger becomes its stopped-line // audit. +// THE KEY CARRIES THE GRAIN (review on #10882, 2026-09-09: a head-grain authority must not become +// a general bag of reasons). A rejection is a LIST with two observed positions: the HEAD — what +// this ledger's own DoorRefused rows carry, where rejected_with_pending prepends non-fatal +// advisories — and the FATAL — the last diagnostic, the cause that actually refused. The same +// symbol can be owned at both grains (parse_grammar_choice_overlap_residue is the native route's +// head advisory on 876 modules and never its fatal; parse_g0_tokens_remain is the fatal under it +// and never surfaces at head), and ownership at one grain says NOTHING about the other — the +// grains are different observations of the same frontier class. cause_is_attributed keeps its +// head-grain contract (this ledger's rows are head-grain by construction); the fatal-grain lookup +// is the native route's instrument (gunbc.witness_v2_native_route), derived from the SAME table — +// one ownership authority, two grains, neither instrument re-enumerates the other. + +type DiagnosticGrain + = HeadGrain + | FatalGrain + type CauseLane = ThisLaneCalibration | SharedSelfHostCriticalPath @@ -329,6 +345,7 @@ type CauseLane type CauseOwnership { cause: Symbol + grain: DiagnosticGrain lane: CauseLane flip_trigger: String } @@ -336,51 +353,76 @@ type CauseOwnership { data known_frontier_causes: List = [ CauseOwnership { cause: ^fact_density_hollow_alias_locus, + grain: HeadGrain, lane: ThisLaneCalibration, flip_trigger: "fact_density subtree roster corpus-calibrated (smart-eagle burn-down); flips to Blocking when the count zeroes — calibration must never become relaxing the lens" }, CauseOwnership { cause: ^resolve_reason_unbound_symbol, + grain: HeadGrain, lane: SharedSelfHostCriticalPath, flip_trigger: "resolve grows generic type-param binding (sharp-bee std/logic thread); flips to Blocking when the count zeroes" }, CauseOwnership { cause: ^resolve_ambiguous_export, + grain: HeadGrain, lane: MigrationOwned, flip_trigger: "no per-cause trigger — dissolves by construction at the namespace-only containment flip (admit_named_exports flat-merge replaced by position-distinct containment; nimble-boar-198)" }, CauseOwnership { cause: ^parse_grammar_choice_overlap_residue, + grain: HeadGrain, lane: MigrationOwned, flip_trigger: "no per-cause trigger — dissolves by construction at phase-3 import-grammar deletion / grammar-surgery (nimble-boar-198)" }, + // The native route's head-grain observations (gunbc.witness_v2_native_route joins this table at + // head grain): the lexer-coverage cause surfaces at HEAD on the native route when it is the + // first diagnostic, even where a later fatal follows. Owned here so the native head join has no + // unowned advisory; the fatal-grain row below is the same cause at the position that refuses. + CauseOwnership { + cause: ^tokenize_lex_e1_unrecognized_char, + grain: HeadGrain, + lane: MigrationOwned, + flip_trigger: "no per-cause trigger — lexer coverage is the same grammar-surgery class (nimble-boar-198). Measured 2026-09-09 at HEAD grain by the gunbc.witness_v2_native_route census (6 modules, the annotation-channel and comment-wall syntax among them)" + }, + // FATAL-GRAIN ROWS: measured 2026-09-09 by the gunbc.witness_v2_native_route census as the LAST + // diagnostic — the cause that actually refused — on real v2.test.* modules through the emitted + // native binary. rejected_with_pending prepends the non-fatal overlap residue, so these never + // surface at the head grain the door ledger's own rows count; their ownership lives here at the + // grain they are observed at, and the native-route census classifies against exactly these rows. CauseOwnership { cause: ^parse_g0_tokens_remain, + grain: FatalGrain, lane: MigrationOwned, - flip_trigger: "no per-cause trigger — same grammar-surgery class as parse_grammar_choice_overlap_residue (nimble-boar-198). Measured 2026-09-09 by the gunbc.witness_v2_native_route census as the FATAL refusal on real v2.test.* modules through the emitted native binary: rejected_with_pending prepends the non-fatal overlap residue, so this cause never surfaces at the head grain this table counts — the native-route census records it at fatal grain" + flip_trigger: "no per-cause trigger — same grammar-surgery class as parse_grammar_choice_overlap_residue (nimble-boar-198). Measured 2026-09-09 by the gunbc.witness_v2_native_route census as the fatal refusal on 706 real v2.test.* modules through the emitted native binary" }, CauseOwnership { cause: ^tokenize_lex_e1_unrecognized_char, + grain: FatalGrain, lane: MigrationOwned, - flip_trigger: "no per-cause trigger — lexer coverage is the same grammar-surgery class (nimble-boar-198). Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (6 modules, the annotation-channel and comment-wall syntax among them)" + flip_trigger: "no per-cause trigger — lexer coverage is the same grammar-surgery class (nimble-boar-198). Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (6 modules)" }, CauseOwnership { cause: ^namespace_graft_body_dissolved_refused, + grain: FatalGrain, lane: MigrationOwned, flip_trigger: "no per-cause trigger — the graft is namespace machinery and dissolves with it (nimble-boar-198). Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (41 modules)" }, CauseOwnership { cause: ^normalize_reason_post_normalize_not_well_formed, + grain: FatalGrain, lane: SharedSelfHostCriticalPath, flip_trigger: "normalize stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (6 modules)" }, CauseOwnership { cause: ^normalized_tree_reason_wrapper_retention_not_normalized, + grain: FatalGrain, lane: SharedSelfHostCriticalPath, flip_trigger: "normalize stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (2 modules)" }, CauseOwnership { cause: ^body_lowering_reason_match_arm_navigation_refused, + grain: FatalGrain, lane: SharedSelfHostCriticalPath, flip_trigger: "body-lowering stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (2 modules)" } @@ -406,12 +448,20 @@ fn list_find_first(xs: FreeMonoid, predicate: fn(T) -> Bool) -> Optional Optional { - list_find_first(xs: known_frontier_causes, predicate: fn(row) { row.cause == cause }) +fn cause_ownership_lookup(cause: Symbol, grain: DiagnosticGrain) -> Optional { + list_find_first( + xs: known_frontier_causes, + predicate: fn(row) { + row.cause == cause && discriminant(v: row.grain) == discriminant(v: grain) + } + ) } +// This ledger's own contract stays HEAD-grain: its DoorRefused rows carry the rejection's head +// reason, so attribution for the door histogram asks the head-grain question. The fatal-grain +// question is the native route's to ask (gunbc.witness_v2_native_route), through the same lookup. fn cause_is_attributed(cause: Symbol) -> Bool { - match cause_ownership_lookup(cause: cause) { + match cause_ownership_lookup(cause: cause, grain: HeadGrain) { Present { value: _ } => true Absent => false } From a1b354e783ac1b2810122ebccb1bd902b3dd50fd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 04:27:07 +0000 Subject: [PATCH 45/89] Hoist known_frontier_causes row-group notes above the declaration The grain-keyed ownership change left its row-group commentary inside the list literal; the annotation channel admits only module-item grain, so the emitted closure refused with nine annotation-grain diagnostics. Move the notes to a single block above the declaration. No semantic change. Co-authored-by: briansrls --- src/v2/workflow/compile_door_ledger.dag | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/v2/workflow/compile_door_ledger.dag b/src/v2/workflow/compile_door_ledger.dag index 59d9855a5bf..a93e77605a7 100644 --- a/src/v2/workflow/compile_door_ledger.dag +++ b/src/v2/workflow/compile_door_ledger.dag @@ -350,6 +350,18 @@ type CauseOwnership { flip_trigger: String } +// ROW GROUPS, BY GRAIN. The first four rows are this ledger's own head-grain observations. The +// tokenize_lex_e1_unrecognized_char HEAD row is the native route's head-grain observation +// (gunbc.witness_v2_native_route joins this table at head grain): the lexer-coverage cause +// surfaces at HEAD on the native route when it is the first diagnostic, even where a later fatal +// follows — owned here so the native head join has no unowned advisory; the fatal-grain row of +// the same cause is the position that refuses. The six FATAL-grain rows were measured 2026-09-09 +// by the gunbc.witness_v2_native_route census as the LAST diagnostic — the cause that actually +// refused — on real v2.test.* modules through the emitted native binary. rejected_with_pending +// prepends the non-fatal overlap residue, so those six never surface at the head grain the door +// ledger's own rows count; their ownership lives here at the grain they are observed at, and the +// native-route census classifies against exactly those rows. + data known_frontier_causes: List = [ CauseOwnership { cause: ^fact_density_hollow_alias_locus, @@ -375,21 +387,12 @@ data known_frontier_causes: List = [ lane: MigrationOwned, flip_trigger: "no per-cause trigger — dissolves by construction at phase-3 import-grammar deletion / grammar-surgery (nimble-boar-198)" }, - // The native route's head-grain observations (gunbc.witness_v2_native_route joins this table at - // head grain): the lexer-coverage cause surfaces at HEAD on the native route when it is the - // first diagnostic, even where a later fatal follows. Owned here so the native head join has no - // unowned advisory; the fatal-grain row below is the same cause at the position that refuses. CauseOwnership { cause: ^tokenize_lex_e1_unrecognized_char, grain: HeadGrain, lane: MigrationOwned, flip_trigger: "no per-cause trigger — lexer coverage is the same grammar-surgery class (nimble-boar-198). Measured 2026-09-09 at HEAD grain by the gunbc.witness_v2_native_route census (6 modules, the annotation-channel and comment-wall syntax among them)" }, - // FATAL-GRAIN ROWS: measured 2026-09-09 by the gunbc.witness_v2_native_route census as the LAST - // diagnostic — the cause that actually refused — on real v2.test.* modules through the emitted - // native binary. rejected_with_pending prepends the non-fatal overlap residue, so these never - // surface at the head grain the door ledger's own rows count; their ownership lives here at the - // grain they are observed at, and the native-route census classifies against exactly these rows. CauseOwnership { cause: ^parse_g0_tokens_remain, grain: FatalGrain, From e8ed17102ca72885f1ae0209c0f2fa8e5f6c61ea Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 04:27:07 +0000 Subject: [PATCH 46/89] Parse test fn as a contextual production in the v2 dag grammar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The emitted native compiler could not parse any v2.test.* module: the modeled dag grammar had no test fn production, so every floor witness module refused with parse_g0_tokens_remain (706 of 882 in the census). test stays an ordinary identifier — typescript/program.dag models the TypeScript compiler's Cond.test field under real-upstream-names — so the production is the contextual sequence(ident, fn_decl): a new choice arm in top_level_item with no FIRST overlap with the keyword-led arms, a body-lowering arm that lifts the nested fn member after checking the marker lexeme is literally test (a typed refusal otherwise), and a forward-producer row for the new surface identity. Verified against the emitted native binary: probe_testfn.dag moves from parse_g0_tokens_remain to resolve_module_not_found (the driver's synthetic-subject limit, identical to a plain fn), and the standing choice-overlap residue roster is unchanged at seven rows. Co-authored-by: briansrls --- src/v2/compiler/body_lowering_fold.dag | 38 +++++++++++++++++++++++ src/v2/compiler/body_producer_forward.dag | 32 ++++++++----------- src/v2/extdeps/languages/dag.dag | 25 ++++++++++++++- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index e1b650f4508..dd6a8fd3c68 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -1472,6 +1472,8 @@ fn body_lower_reduce_emitted(folded: Node, emitted: Symbol) -> Outcome { body_lower_reduce_passthrough(folded: folded) } else if emitted == ^dag_surface_fn_decl { body_lower_reduce_or_keep(o: body_lower_fn_decl_to_arrow(shell: folded), folded: folded) + } else if emitted == ^dag_surface_test_fn_decl { + body_lower_reduce_or_keep(o: body_lower_test_fn_decl_to_member(shell: folded), folded: folded) } else { outcome_accepted(value: folded) } @@ -1801,6 +1803,40 @@ fn body_lower_fn_decl_named_member_wrap(arrow: Node, fn_name: Symbol) -> Node { ) } +// A `test fn` declaration lowers to the SAME named-member arrow a plain `fn` lowers to: the +// production is the contextual `sequence(ident("test"), fn_decl)` (see dag_grammar_test_fn_decl_expr +// for why `test` is not a reserved keyword), the fold has already lowered the nested fn_decl child +// by the time this shell reduces, and this arm lifts the member out of the sequence's right +// projection after checking the marker lexeme really is "test". A non-"test" marker is a typed +// refusal, never a retained wrapper. The TEST-NESS marker is deliberately not reified into the +// member node: the native route's universe derivation is the floor's own source-scan authority +// (v2.workflow.floor_discovery discover_floor_rows_for_source), which reads the surface text, and +// resolve/infer see an ordinary function — a stated divergence, not a silent one. +fn body_lower_test_fn_decl_to_member(shell: Node) -> Outcome { + match body_lower_unwrap_captured(shell: shell) { + Absent => body_lower_wrapper_retained_shell(shell: shell) + Present { value: captured } => + match body_lower_seq_child(node: captured, name: ^grammar_sequence_left_node_projection) { + Absent => body_lower_wrapper_retained_shell(shell: shell) + Present { value: marker_node } => + match dag_binding_branded_atom_identity(node: marker_node) { + Present { value: marker } => + if marker == ^test { + match body_lower_seq_child(node: captured, name: ^grammar_sequence_right_node_projection) { + Absent => body_lower_wrapper_retained_shell(shell: shell) + Present { value: member } => outcome_accepted(value: member) + } + } else { + outcome_rejected( + d: body_lower_diagnostic(reason: ^body_lowering_reason_test_marker_not_test, n: marker_node) + ) + } + Absent => body_lower_wrapper_retained_shell(shell: shell) + } + } + } +} + fn body_lower_fn_decl_to_arrow(shell: Node) -> Outcome { match body_lower_unwrap_captured(shell: shell) { Absent => body_lower_wrapper_retained_shell(shell: shell) @@ -4304,6 +4340,8 @@ fn body_lower_fn_body_shell(shell: Node) -> Outcome { fn body_lower_forward_row_apply(shell: Node, emitted: Symbol) -> Outcome { if emitted == ^dag_surface_fn_decl { body_lower_fn_decl_to_arrow(shell: shell) + } else if emitted == ^dag_surface_test_fn_decl { + body_lower_test_fn_decl_to_member(shell: shell) } else if emitted == ^dag_surface_fn_body { body_lower_fn_body_shell(shell: shell) } else if emitted == ^dag_surface_match_expr { diff --git a/src/v2/compiler/body_producer_forward.dag b/src/v2/compiler/body_producer_forward.dag index 96597a88c99..1273adca916 100644 --- a/src/v2/compiler/body_producer_forward.dag +++ b/src/v2/compiler/body_producer_forward.dag @@ -10,6 +10,10 @@ data body_producer_forward_row_fn_decl: BodyProducerForwardRow = BodyProducerFor surface_identity: ^dag_surface_fn_decl } +data body_producer_forward_row_test_fn_decl: BodyProducerForwardRow = BodyProducerForwardRow { + surface_identity: ^dag_surface_test_fn_decl +} + data body_producer_forward_row_fn_body: BodyProducerForwardRow = BodyProducerForwardRow { surface_identity: ^dag_surface_fn_body } @@ -31,25 +35,15 @@ data body_producer_forward_row_loop_expr: BodyProducerForwardRow = BodyProducerF } fn body_producer_forward_rows() -> List { - Cons { - head: body_producer_forward_row_fn_decl, - tail: Cons { - head: body_producer_forward_row_fn_body, - tail: Cons { - head: body_producer_forward_row_match_expr, - tail: Cons { - head: body_producer_forward_row_if_expr, - tail: Cons { - head: body_producer_forward_row_let_expr, - tail: Cons { - head: body_producer_forward_row_loop_expr, - tail: std.algebra.Empty - } - } - } - } - } - } + [ + body_producer_forward_row_fn_decl, + body_producer_forward_row_test_fn_decl, + body_producer_forward_row_fn_body, + body_producer_forward_row_match_expr, + body_producer_forward_row_if_expr, + body_producer_forward_row_let_expr, + body_producer_forward_row_loop_expr + ] } fn body_producer_forward_row_count() -> Int { diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 7a6a37a4c0f..0d75c0bc222 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2930,6 +2930,20 @@ fn dag_grammar_fn_decl_expr() -> GrammarExpr { ) } +// `test fn` IS A CONTEXTUAL PRODUCTION, NOT A KEYWORD. `test` stays an ordinary identifier +// everywhere else — dag/extdeps/languages/typescript/program.dag models the TypeScript +// compiler's own `Cond { test: ... }` field under §3's real-upstream-names rule, and a reserved +// kw_test would break that module's parse. So the production matches an ident lexeme followed by +// a fn_decl, and the body-lowering arm refuses any marker lexeme other than "test" with a typed +// diagnostic. No other top-level item starts with an ident, so the choice arm has no FIRST +// overlap with the keyword-led alternatives. +fn dag_grammar_test_fn_decl_expr() -> GrammarExpr { + dag_grammar_sequence( + left: dag_grammar_terminal_lexeme(token_class: ^dag_token_ident), + right: dag_grammar_nonterminal(production: ^dag_production_fn_decl) + ) +} + fn dag_grammar_top_level_item_expr() -> GrammarExpr { dag_grammar_choice( left: dag_grammar_nonterminal(production: ^dag_production_import_decl), @@ -2939,7 +2953,10 @@ fn dag_grammar_top_level_item_expr() -> GrammarExpr { left: dag_grammar_nonterminal(production: ^dag_production_type_decl), right: dag_grammar_choice( left: dag_grammar_nonterminal(production: ^dag_production_data_decl), - right: dag_grammar_nonterminal(production: ^dag_production_fn_decl) + right: dag_grammar_choice( + left: dag_grammar_nonterminal(production: ^dag_production_fn_decl), + right: dag_grammar_nonterminal(production: ^dag_production_test_fn_decl) + ) ) ) ) @@ -3187,6 +3204,11 @@ fn dag_grammar_root() -> GrammarRoot { expression: dag_grammar_fn_decl_expr(), emitted: ^dag_surface_fn_decl ) + let test_fn_decl = dag_grammar_production( + name: ^dag_production_test_fn_decl, + expression: dag_grammar_test_fn_decl_expr(), + emitted: ^dag_surface_test_fn_decl + ) let qualified_name = dag_grammar_production( name: ^dag_production_qualified_name, expression: dag_grammar_qualified_name_expr(), @@ -3348,6 +3370,7 @@ fn dag_grammar_root() -> GrammarRoot { type_decl, data_decl, fn_decl, + test_fn_decl, qualified_name, generic_params, param_list, From f81af428a73ab7bf574d2e5181b5cacd83b3e551 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 05:47:07 +0000 Subject: [PATCH 47/89] Add SourceRootEvalDriver: native whole-ingest test-execution route The required-v2-native lane's terminal subject is an exact test identity reaching a native Eval verdict, not a module accepted for translation. DirectIngestDriver (one source, no peers, synthetic subject) stays as the front-door census instrument; the new driver renders a main that reads a host-derived universe of qualified test identities plus the declared source roots, assembles the ingest once, prepares each module (resolve + infer), and Evals each named test body -- one typed verdict row per member, with a prepare-refusal fan-out so no member is silently dropped. Co-authored-by: briansrls --- dag/std/compiler_entry.dag | 19 ++- src/v1/05_emit_rust.dag | 204 ++++++++++++++++++++++++ src/v2/compiler/00_compile.dag | 273 +++++++++++++++++++++++++++++++-- 3 files changed, 480 insertions(+), 16 deletions(-) diff --git a/dag/std/compiler_entry.dag b/dag/std/compiler_entry.dag index da56d492bf7..4957df5f373 100644 --- a/dag/std/compiler_entry.dag +++ b/dag/std/compiler_entry.dag @@ -15,9 +15,9 @@ module std.compiler_entry // // THE DRIVER SHAPE IS A SECOND FACT AND IS NOT DERIVABLE FROM THE FIRST. DESIGN section 3 separates // interface, realization and policy: "this module is the compiler pipeline entry" is the interface -// fact, and WHICH DRIVER REALIZES IT is realization. Two realizations exist in the tree today and -// both are load-bearing, so the sum below is closed at two rather than opened for futures nobody -// has. A third would have to be authored deliberately, which is the point of a closed vocabulary. +// fact, and WHICH DRIVER REALIZES IT is realization. Three realizations exist in the tree today and +// all are load-bearing, so the sum below is closed at three rather than opened for futures nobody +// has. A fourth would have to be authored deliberately, which is the point of a closed vocabulary. // // RetainedHostCliKernel -- the seed's own entry: a clap CLI whose compile verb dispatches into // hand-authored host Rust (v1_compiler.cli_run). Only a crate that CONTAINS that retained host @@ -28,7 +28,20 @@ module std.compiler_entry // // DirectIngestDriver -- a driver that reaches the emitted compiler's own ingest entry, taking // source text and a target and returning emitted text, with no retained host kernel between them. +// ONE SOURCE, NO PEERS, SYNTHETIC SUBJECT: it is a census instrument for the standalone native +// front door, not a repository execution route -- a module whose imports resolve only against +// sibling roots cannot pass through it. +// +// SourceRootEvalDriver -- the repository test-execution route. The rendered main reads a universe +// of qualified test identities (module path + declaration name, one per line) and the declared +// source roots, builds the SourceRootIngest itself (the filesystem read is the host boundary the +// driver owns), then hands the whole ingest to the pipeline: assemble, resolve, infer ONCE, and +// per universe member resolve the named declaration and Eval its body, reporting one typed +// verdict row per member. This is the driver the required-v2-native CI lane executes; its +// contract is that every universe member reaches exactly one terminal observation, never a +// silent remainder. type CompilerEntryDriver = RetainedHostCliKernel | DirectIngestDriver + | SourceRootEvalDriver diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 653586c9f91..bb67a8c4f4a 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -16354,6 +16354,8 @@ data compiler_entry_driver_type_name: String = "CompilerEntryDriver" data compiler_entry_retained_host_driver: String = "RetainedHostCliKernel" +data compiler_entry_source_root_eval_driver: String = "SourceRootEvalDriver" + type CompilerPipelineEntryDecl { module_name: String driver: String @@ -16411,6 +16413,13 @@ fn compiler_pipeline_entry_is_retained_host(modules: List) -> Bool } } +fn compiler_pipeline_entry_is_source_root_eval(modules: List) -> Bool { + match compiler_pipeline_entry_decl(modules: modules) { + Present { value: d } => d.driver == compiler_entry_source_root_eval_driver + Absent => false + } +} + fn compiler_pipeline_entry_is_ambiguous(modules: List) -> Bool { (compiler_pipeline_entry_decls(modules: modules) |> count) > 1 } @@ -16482,6 +16491,9 @@ fn emit_main_rs(workflow_funcs: List, modules: List, ) } if corpus_has_compiler_pipeline(modules: modules) && (retained_host_pipeline == false) { + if compiler_pipeline_entry_is_source_root_eval(modules: modules) { + return emit_source_root_eval_driver_main_rs(crate_name: crate_name, pipeline_module: pipeline_module) + } return emit_direct_ingest_driver_main_rs(crate_name: crate_name, pipeline_module: pipeline_module) } if workflow_funcs |> count == 0 && retained_host_pipeline == false { @@ -16576,6 +16588,198 @@ fn emit_direct_ingest_driver_main_rs(crate_name: String, pipeline_module: String } } +// THE SOURCE-ROOT EVAL DRIVER'S MAIN, AND THE CONTRACT ITS PROCESS MAKES WITH THE LANE HOST. +// +// THE UNIVERSE FILE IS THE WHOLE CONTRACT. One line per qualified test identity -- +// `moduledeclaration` -- derived by the lane host from the exact tested tree. This binary +// performs NO discovery and NO filtering: it executes exactly the identities it is handed, so +// the host-side join (expected universe == observed rows, no duplicates, no foreign rows) is a +// join between what the host derived and what this process actually produced. A binary that +// discovered its own population would make that join tautological. +// +// PROCESS EXIT IS LANE-LEVEL, NEVER TEST-LEVEL. Exit 0 plus the terminal marker row means every +// universe member reached exactly one observation row; a failing or refused TEST is data +// (NativeTestReturnedFalse / NativeTestRefused), not a process failure. Exit 1 is a whole-context +// refusal (the ingest fold rejected -- one bad source anywhere fails the lane, never a silent +// skip). Exit 2 is harness-level: bad argv, unreadable files, malformed or duplicate universe +// lines. A crash mid-population shows up host-side as a missing terminal marker or a row-count +// shortfall, which is why the marker carries the count. +// +// THE PREPARE-REFUSAL FAN-OUT keeps the no-silent-remainder invariant when a whole module fails +// resolve/infer: every declaration of that module gets its own Prepare-stage refusal row naming +// the same head reason, so the row count still equals the universe size and the refusal stays +// located per test identity. +// +// THE FILESYSTEM READ IS THE HOST BOUNDARY THE DRIVER OWNS (std.compiler_entry): the rendered +// main walks the declared source roots, reads the bytes, and mints one DagSourceReadWitness per +// path -- the .dag pipeline itself never touches IO. +fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: String) -> TextFile { + let pipeline_mod = module_to_filename(name: pipeline_module) + TextFile { + path: concat(rust_source_root(), "main", rust_source_ext()), + content: concat( + "// Generated by v1 compiler -- do not edit.\n\n", + "#![allow(unused_parens, clippy::all, clippy::disallowed_macros)]\n\n", + "use std::rc::Rc;\n\n", + "use ", crate_name, "::extdeps_communication_medium::{DecodeFidelity, Medium};\n", + "use ", crate_name, "::", pipeline_mod, "::{\n", + " native_test_context_from_ingest, native_test_eval_one, native_test_prepare_module,\n", + " NativeTestObservation, NativeTestStage, NativeTestVerdict,\n", + "};\n", + "use ", crate_name, "::v2_compiler_source_authority::{source_root_for_storage_path, DagSourceReadWitness};\n", + "use ", crate_name, "::v2_std_artifact::{Artifact, ArtifactKind};\n", + "use ", crate_name, "::v2_std_diagnostic::Outcome;\n", + "\n", + "fn collect_dag_paths(dir: &std::path::Path, out: &mut Vec) -> Result<(), String> {\n", + " let entries = match std::fs::read_dir(dir) {\n", + " Ok(entries) => entries,\n", + " Err(cause) => return Err(format!(\"could not walk source root {}: {cause}\", dir.display())),\n", + " };\n", + " for entry in entries {\n", + " let entry = match entry {\n", + " Ok(entry) => entry,\n", + " Err(cause) => return Err(format!(\"could not read entry under {}: {cause}\", dir.display())),\n", + " };\n", + " let path = entry.path();\n", + " if path.is_dir() {\n", + " collect_dag_paths(&path, out)?;\n", + " } else if path.extension().map(|ext| ext == \"dag\").unwrap_or(false) {\n", + " out.push(path.to_string_lossy().into_owned());\n", + " }\n", + " }\n", + " Ok(())\n", + "}\n", + "\n", + "fn main() {\n", + " let mut args = std::env::args().skip(1);\n", + " let universe_path = match args.next() {\n", + " Some(path) => path,\n", + " None => {\n", + " eprintln!(\"REFUSED: no universe file given -- usage: ", crate_name, " ...\");\n", + " std::process::exit(2);\n", + " }\n", + " };\n", + " let source_roots: Vec = args.collect();\n", + " if source_roots.is_empty() {\n", + " eprintln!(\"REFUSED: no source roots given -- usage: ", crate_name, " ...\");\n", + " std::process::exit(2);\n", + " }\n", + " let universe_text = match std::fs::read_to_string(&universe_path) {\n", + " Ok(text) => text,\n", + " Err(cause) => {\n", + " eprintln!(\"REFUSED: could not read universe file {universe_path}: {cause}\");\n", + " std::process::exit(2);\n", + " }\n", + " };\n", + " let mut universe: Vec<(Rc>, String)> = Vec::new();\n", + " let mut seen: std::collections::HashSet = std::collections::HashSet::new();\n", + " for line in universe_text.lines() {\n", + " if line.is_empty() || line.starts_with('#') {\n", + " continue;\n", + " }\n", + " match line.split_once('\\t') {\n", + " Some((module, declaration)) if !module.is_empty() && !declaration.is_empty() => {\n", + " if !seen.insert(line.to_string()) {\n", + " eprintln!(\"REFUSED: duplicate universe line: {line}\");\n", + " std::process::exit(2);\n", + " }\n", + " universe.push((\n", + " Rc::new(module.split('.').map(|segment| segment.to_string()).collect::>()),\n", + " declaration.to_string(),\n", + " ));\n", + " }\n", + " _ => {\n", + " eprintln!(\"REFUSED: malformed universe line (expected moduledeclaration): {line}\");\n", + " std::process::exit(2);\n", + " }\n", + " }\n", + " }\n", + " if universe.is_empty() {\n", + " eprintln!(\"REFUSED: universe file {universe_path} carried no test identities\");\n", + " std::process::exit(2);\n", + " }\n", + " let mut paths: Vec = Vec::new();\n", + " for root in &source_roots {\n", + " if let Err(cause) = collect_dag_paths(std::path::Path::new(root), &mut paths) {\n", + " eprintln!(\"REFUSED: {cause}\");\n", + " std::process::exit(2);\n", + " }\n", + " }\n", + " paths.sort();\n", + " let mut reads: Vec> = Vec::new();\n", + " for path in &paths {\n", + " let text = match std::fs::read_to_string(path) {\n", + " Ok(text) => text,\n", + " Err(cause) => {\n", + " eprintln!(\"REFUSED: could not read {path}: {cause}\");\n", + " std::process::exit(2);\n", + " }\n", + " };\n", + " reads.push(Rc::new(DagSourceReadWitness {\n", + " source: Rc::new(Medium {\n", + " carried: text,\n", + " fidelity: DecodeFidelity::Lossless,\n", + " _phantom: std::marker::PhantomData,\n", + " }),\n", + " artifact: Rc::new(Artifact {\n", + " kind: ArtifactKind::SourceFile,\n", + " id: path.clone(),\n", + " file_path: path.clone(),\n", + " }),\n", + " compilation_unit: path.clone(),\n", + " source_root: source_root_for_storage_path(path.clone()),\n", + " }));\n", + " }\n", + " let context = match &*native_test_context_from_ingest(Rc::new(reads)) {\n", + " Outcome::Accepted { value, .. } => value.clone(),\n", + " Outcome::Rejected { diagnostics } => {\n", + " eprintln!(\"CONTEXT-REFUSED: {diagnostics:#?}\");\n", + " std::process::exit(1);\n", + " }\n", + " };\n", + " let mut module_order: Vec<(String, Rc>)> = Vec::new();\n", + " let mut module_decls: std::collections::HashMap> = std::collections::HashMap::new();\n", + " for (qn, declaration) in &universe {\n", + " let dotted = qn.join(\".\");\n", + " if !module_decls.contains_key(&dotted) {\n", + " module_order.push((dotted.clone(), qn.clone()));\n", + " module_decls.insert(dotted.clone(), Vec::new());\n", + " }\n", + " module_decls.get_mut(&dotted).unwrap().push(declaration.clone());\n", + " }\n", + " let mut rows: u64 = 0;\n", + " for (dotted, qn) in &module_order {\n", + " let declarations = &module_decls[dotted];\n", + " match &*native_test_prepare_module(context.clone(), qn.clone()) {\n", + " Outcome::Accepted { value: prepared, .. } => {\n", + " for declaration in declarations {\n", + " let observation = native_test_eval_one(prepared.clone(), declaration.clone());\n", + " println!(\"{}\", serde_json::to_string(&observation).unwrap());\n", + " rows += 1;\n", + " }\n", + " }\n", + " Outcome::Rejected { diagnostics } => {\n", + " let reason = diagnostics.head.reason.clone();\n", + " for declaration in declarations {\n", + " let observation = NativeTestObservation {\n", + " module: qn.clone(),\n", + " declaration: declaration.clone(),\n", + " verdict: Rc::new(NativeTestVerdict::NativeTestRefused {\n", + " stage: NativeTestStage::NativeTestStagePrepare,\n", + " reason: reason.clone(),\n", + " }),\n", + " };\n", + " println!(\"{}\", serde_json::to_string(&observation).unwrap());\n", + " rows += 1;\n", + " }\n", + " }\n", + " }\n", + " }\n", + " println!(\"{}\", serde_json::json!({ \"_terminal\": \"complete\", \"rows\": rows }));\n", + "}\n") + } +} + // THE ENTRY POINT A PIPELINE-FREE CORPUS GETS, AND WHY IT REFUSES RATHER THAN RETURNING. // // This arm used to emit `fn main() {}`. That crate builds, runs, exits zero and does nothing -- diff --git a/src/v2/compiler/00_compile.dag b/src/v2/compiler/00_compile.dag index d0bbd203a8b..66dba12ed77 100644 --- a/src/v2/compiler/00_compile.dag +++ b/src/v2/compiler/00_compile.dag @@ -1,16 +1,23 @@ module v2.compiler.compile -import std.compiler_entry { CompilerEntryDriver, DirectIngestDriver } +import std.compiler_entry { CompilerEntryDriver, SourceRootEvalDriver } import v2.compiler.emit { emit } -import v2.compiler.eval { eval_node, inputs_root_only } +import v2.compiler.eval { eval, eval_node, eval_default_interpretation, inputs_root_only } import v2.compiler.infer { infer, InferredTree } import v2.compiler.name_resolve { Admission, ResolutionSubject, - resolve_with_admission + resolve_with_admission, + resolve_with_admission_grounded } -import v2.compiler.program_assembly { assemble_program_from_ingest } -import v2.compiler.source_authority { SourceRootIngest } +import v2.compiler.program_assembly { + assemble_program_from_ingest, + program_assembly_fold_ingest, + ProgramAssemblyFoldState, + ProgramAssemblyFoldOk, + ProgramAssemblyFoldFailed +} +import v2.compiler.source_authority { SourceRootIngest, source_root_set_from_ingest } import v2.compiler.normalize { normalize } import v2.compiler.normalized_tree { NormalizedTree } import v2.compiler.parse { parse } @@ -55,7 +62,16 @@ import v2.std.diagnostic { outcome_rejected, outcome_with_diagnostics } -import v2.std.node { Edge, Node, NodeFold, Symbol, fold_node } +import v2.std.node { Arrow, Edge, Named, Node, NodeFold, Positional, Symbol, TypeNode, fold_node } +import v2.std.node_query { find_arrow_body_child } +import v2.std.optional { Absent, Optional, Present, optional_absent, optional_present } +import v2.std.qualified_name { QualifiedName } +import v2.std.cross_tree.import_model { SourceRootRef } +import v2.std.cross_tree.resolution { SourceRootIndex } +import v2.extdeps.runtimes.v2_evaluator { + v2_eval_runtime_bool_is_false, + v2_eval_runtime_bool_is_true +} import v2.std.text { String } import v2.std.witness { Holds, Violates, Witness, witness_violates } @@ -64,13 +80,17 @@ import v2.std.witness { Holds, Violates, Witness, witness_violates } // literal this module could never satisfy, so a crate emitted from this corpus was rendered // with the no-entry-point refusal main no matter what it contained. // -// DirectIngestDriver rather than RetainedHostCliKernel, and the difference is not a preference: -// a crate emitted from this corpus does not contain v1_compiler.cli_run, so the retained-host -// driver would name a module that is not there. The driver reaches compile_ingest_with_admission -// below -- source text and a target in, emitted text out -- which is the entry a command line -// can be wired to. compile() is not that entry: it takes an already-parsed CoreNode. - -data compiler_pipeline_entry: CompilerEntryDriver = DirectIngestDriver +// SourceRootEvalDriver, and the difference from both siblings is a fact about what the emitted +// binary is FOR. RetainedHostCliKernel is unrenderable here (a crate emitted from this corpus +// does not contain v1_compiler.cli_run). DirectIngestDriver renders a one-source translate +// probe -- a census instrument for the standalone front door, which cannot resolve a module +// against its real peers and so cannot execute a repository test. SourceRootEvalDriver renders +// the repository test-execution route: the host reads the declared source roots and a universe +// of qualified test identities, and the pipeline assembles the ingest once, prepares each +// module (resolve + infer), and Evals each named test body, one typed verdict row per member. +// The driver functions below are the contract the rendered main calls. + +data compiler_pipeline_entry: CompilerEntryDriver = SourceRootEvalDriver type CoreNode = Node @@ -600,3 +620,230 @@ fn compile_ingest_staging(source: Medium, target: TargetModel) -> Outcom ) pipeline(source) } + +// ═══════════════════════════════════════════════════════════════════════════════════════════ +// THE SOURCE-ROOT EVAL DRIVER — the terminal route the required-v2-native CI lane executes. +// ═══════════════════════════════════════════════════════════════════════════════════════════ +// +// THE TERMINAL SUBJECT IS A TEST VERDICT, NOT A TRANSLATION. DirectIngestDriver answers "the +// native compiler accepted this standalone source for translation"; this driver answers "this +// exact test identity, resolved against its real source roots, reached a native Eval verdict". +// The route mirrors the floor's execution mode — locate the named declaration, evaluate its +// body, require a Boolean — with the v2 pipeline's own evaluator standing in for the seed +// interpreter. +// +// THREE FUNCTIONS, ONE PER COST GRAIN. native_test_context_from_ingest pays the whole-ingest +// fold (tokenize/parse/normalize every source) ONCE per lane; its rejection is a lane-level +// refusal, never a per-test verdict. native_test_prepare_module pays resolve + infer ONCE per +// module. native_test_eval_one pays entry resolution + evaluation per declaration and NEVER +// rejects: every failure it can reach is a typed NativeTestRefused verdict carrying the stage +// and the diagnostic's head reason, so a census over observations has no silent remainder. +// +// NO LENS GATES ON THIS ROUTE, deliberately. validate_then_compile's required lenses are the +// COMPILE door's corpus invariants; the floor's seed-interpreter route does not run them per +// test either, and running them here would conflate the modeling door with the execution route +// and multiply their whole-tree cost by the test population. + +type NativeTestStage + = NativeTestStageContext + | NativeTestStagePrepare + | NativeTestStageEntry + | NativeTestStageEval + +type NativeTestVerdict + = NativeTestPassed + | NativeTestReturnedFalse + | NativeTestReturnedOther + | NativeTestRefused { stage: NativeTestStage, reason: Symbol } + +type NativeTestObservation { + module: QualifiedName + declaration: Symbol + verdict: NativeTestVerdict +} + +type NativeTestContext { + roots: FreeMonoid + index: SourceRootIndex + active_roots: FreeMonoid +} + +type NativeTestPreparedModule { + module: QualifiedName + inferred: InferredTree +} + +fn native_test_context_from_ingest(ingest: SourceRootIngest) -> Outcome { + match program_assembly_fold_ingest(ingest: ingest, lm: dag_language_model()) { + ProgramAssemblyFoldFailed { diagnostics: d } => Rejected { diagnostics: d } + ProgramAssemblyFoldOk { roots: roots, index: idx } => + outcome_accepted(value: NativeTestContext { + roots: roots, + index: idx, + active_roots: source_root_set_from_ingest(ingest: ingest) + }) + } +} + +fn native_test_prepare_module( + context: NativeTestContext, + module: QualifiedName +) -> Outcome { + bind_outcome( + o: resolve_with_admission_grounded( + lm: dag_language_model(), + roots: context.roots, + admission: Admission { + subject: ResolutionSubject { name: module }, + imports: Empty + }, + index: context.index, + active_roots: context.active_roots + ), + f: fn(resolved) { + bind_outcome( + o: infer(tree: resolved), + f: fn(inferred) { + outcome_accepted(value: NativeTestPreparedModule { + module: module, + inferred: inferred + }) + } + ) + } + ) +} + +// THE DECLARATION SEARCH IS SCOPED TO ONE PREPARED MODULE TREE, so a name match anywhere in it +// names this module's declaration — module scope is the only place named declaration edges live, +// and a duplicate declaration name is a resolve refusal upstream of here. Direct children are +// preferred at every level so the module-scope edge wins over any same-named edge nested inside +// a declaration body. + +fn native_test_decl_in_children(children: List, name: Symbol) -> Optional { + fold(children, init: optional_absent(), f: fn(acc, e) { + match acc { + Present { value: v } => optional_present(value: v) + Absent => + match e.label { + Named { name: edge_name } => + if edge_name == name { + optional_present(value: e.target) + } else { + optional_absent() + } + Positional => optional_absent() + } + } + }) +} + +fn native_test_decl_deeper(children: List, name: Symbol) -> Optional { + fold(children, init: optional_absent(), f: fn(acc, e) { + match acc { + Present { value: v } => optional_present(value: v) + Absent => native_test_find_decl_node(node: e.target, name: name) + } + }) +} + +fn native_test_find_decl_node(node: Node, name: Symbol) -> Optional { + match native_test_decl_in_children(children: node.children, name: name) { + Present { value: v } => optional_present(value: v) + Absent => native_test_decl_deeper(children: node.children, name: name) + } +} + +fn native_test_observation_refused( + module: QualifiedName, + declaration: Symbol, + stage: NativeTestStage, + reason: Symbol +) -> NativeTestObservation { + NativeTestObservation { + module: module, + declaration: declaration, + verdict: NativeTestRefused { stage: stage, reason: reason } + } +} + +fn native_test_eval_body( + prepared: NativeTestPreparedModule, + declaration: Symbol, + body: Node +) -> NativeTestObservation { + match eval( + tree: prepared.inferred, + interpretation: eval_default_interpretation(), + inputs: inputs_root_only(root: body) + ) { + Rejected { diagnostics: d } => + native_test_observation_refused( + module: prepared.module, + declaration: declaration, + stage: NativeTestStageEval, + reason: d.head.reason + ) + Accepted { value: rv, diagnostics: _ } => + if v2_eval_runtime_bool_is_true(value: rv) { + NativeTestObservation { + module: prepared.module, + declaration: declaration, + verdict: NativeTestPassed + } + } else if v2_eval_runtime_bool_is_false(value: rv) { + NativeTestObservation { + module: prepared.module, + declaration: declaration, + verdict: NativeTestReturnedFalse + } + } else { + NativeTestObservation { + module: prepared.module, + declaration: declaration, + verdict: NativeTestReturnedOther + } + } + } +} + +fn native_test_eval_one( + prepared: NativeTestPreparedModule, + declaration: Symbol +) -> NativeTestObservation { + match native_test_find_decl_node(node: prepared.inferred.root, name: declaration) { + Absent => + native_test_observation_refused( + module: prepared.module, + declaration: declaration, + stage: NativeTestStageEntry, + reason: ^native_test_entry_declaration_not_found + ) + Present { value: target } => + match target.kind { + TypeNode { connective: Arrow } => + match find_arrow_body_child(root: target) { + Rejected { diagnostics: d } => + native_test_observation_refused( + module: prepared.module, + declaration: declaration, + stage: NativeTestStageEntry, + reason: d.head.reason + ) + Accepted { value: body, diagnostics: _ } => + native_test_eval_body( + prepared: prepared, + declaration: declaration, + body: body + ) + } + _ => + native_test_observation_refused( + module: prepared.module, + declaration: declaration, + stage: NativeTestStageEntry, + reason: ^native_test_entry_declaration_not_arrow + ) + } + } +} From bebb2c28c9c4365dbeef6ea98a0638539d54e5d6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 06:08:35 +0000 Subject: [PATCH 48/89] Escape literal braces in SourceRootEvalDriver main.rs template The .dag string lexer reads '{' followed by an identifier as interpolation, so the emitted Rust use::-import lists and format! captures must spell literal braces as \{ \}. The single parse error desynced the file parse and cascaded into 2618 unattributed-annotation errors; with the escapes the emitter compiles clean (0 blocking, 107 files emitted). Co-authored-by: briansrls --- src/v1/05_emit_rust.dag | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index bb67a8c4f4a..80391982b33 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -16621,24 +16621,24 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str "// Generated by v1 compiler -- do not edit.\n\n", "#![allow(unused_parens, clippy::all, clippy::disallowed_macros)]\n\n", "use std::rc::Rc;\n\n", - "use ", crate_name, "::extdeps_communication_medium::{DecodeFidelity, Medium};\n", + "use ", crate_name, "::extdeps_communication_medium::\{DecodeFidelity, Medium\};\n", "use ", crate_name, "::", pipeline_mod, "::{\n", " native_test_context_from_ingest, native_test_eval_one, native_test_prepare_module,\n", " NativeTestObservation, NativeTestStage, NativeTestVerdict,\n", "};\n", - "use ", crate_name, "::v2_compiler_source_authority::{source_root_for_storage_path, DagSourceReadWitness};\n", - "use ", crate_name, "::v2_std_artifact::{Artifact, ArtifactKind};\n", + "use ", crate_name, "::v2_compiler_source_authority::\{source_root_for_storage_path, DagSourceReadWitness\};\n", + "use ", crate_name, "::v2_std_artifact::\{Artifact, ArtifactKind\};\n", "use ", crate_name, "::v2_std_diagnostic::Outcome;\n", "\n", "fn collect_dag_paths(dir: &std::path::Path, out: &mut Vec) -> Result<(), String> {\n", " let entries = match std::fs::read_dir(dir) {\n", " Ok(entries) => entries,\n", - " Err(cause) => return Err(format!(\"could not walk source root {}: {cause}\", dir.display())),\n", + " Err(cause) => return Err(format!(\"could not walk source root {}: \{cause\}\", dir.display())),\n", " };\n", " for entry in entries {\n", " let entry = match entry {\n", " Ok(entry) => entry,\n", - " Err(cause) => return Err(format!(\"could not read entry under {}: {cause}\", dir.display())),\n", + " Err(cause) => return Err(format!(\"could not read entry under {}: \{cause\}\", dir.display())),\n", " };\n", " let path = entry.path();\n", " if path.is_dir() {\n", @@ -16667,7 +16667,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " let universe_text = match std::fs::read_to_string(&universe_path) {\n", " Ok(text) => text,\n", " Err(cause) => {\n", - " eprintln!(\"REFUSED: could not read universe file {universe_path}: {cause}\");\n", + " eprintln!(\"REFUSED: could not read universe file \{universe_path\}: \{cause\}\");\n", " std::process::exit(2);\n", " }\n", " };\n", @@ -16680,7 +16680,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " match line.split_once('\\t') {\n", " Some((module, declaration)) if !module.is_empty() && !declaration.is_empty() => {\n", " if !seen.insert(line.to_string()) {\n", - " eprintln!(\"REFUSED: duplicate universe line: {line}\");\n", + " eprintln!(\"REFUSED: duplicate universe line: \{line\}\");\n", " std::process::exit(2);\n", " }\n", " universe.push((\n", @@ -16689,19 +16689,19 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " ));\n", " }\n", " _ => {\n", - " eprintln!(\"REFUSED: malformed universe line (expected moduledeclaration): {line}\");\n", + " eprintln!(\"REFUSED: malformed universe line (expected moduledeclaration): \{line\}\");\n", " std::process::exit(2);\n", " }\n", " }\n", " }\n", " if universe.is_empty() {\n", - " eprintln!(\"REFUSED: universe file {universe_path} carried no test identities\");\n", + " eprintln!(\"REFUSED: universe file \{universe_path\} carried no test identities\");\n", " std::process::exit(2);\n", " }\n", " let mut paths: Vec = Vec::new();\n", " for root in &source_roots {\n", " if let Err(cause) = collect_dag_paths(std::path::Path::new(root), &mut paths) {\n", - " eprintln!(\"REFUSED: {cause}\");\n", + " eprintln!(\"REFUSED: \{cause\}\");\n", " std::process::exit(2);\n", " }\n", " }\n", @@ -16711,7 +16711,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " let text = match std::fs::read_to_string(path) {\n", " Ok(text) => text,\n", " Err(cause) => {\n", - " eprintln!(\"REFUSED: could not read {path}: {cause}\");\n", + " eprintln!(\"REFUSED: could not read \{path\}: \{cause\}\");\n", " std::process::exit(2);\n", " }\n", " };\n", @@ -16733,7 +16733,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " let context = match &*native_test_context_from_ingest(Rc::new(reads)) {\n", " Outcome::Accepted { value, .. } => value.clone(),\n", " Outcome::Rejected { diagnostics } => {\n", - " eprintln!(\"CONTEXT-REFUSED: {diagnostics:#?}\");\n", + " eprintln!(\"CONTEXT-REFUSED: \{diagnostics:#?\}\");\n", " std::process::exit(1);\n", " }\n", " };\n", From 18e1adad31cd819b6b8a94235a89de6679f9f3a0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 06:31:52 +0000 Subject: [PATCH 49/89] Collect per-file front-end refusals in the native test context fold The SourceRootEvalDriver's context fold reused program_assembly_fold_ingest, which is wholesale fail-closed: one source hitting the v2 front-end's live corpus frontier would deny verdict rows for every other universe member. The fold now collects each source's tokenize/parse/normalize refusal as a NativeTestFileRefusal row (head and fatal reason grains, matching the door ledger's grain-keyed ownership) and keeps folding; a refused file contributes no root, so its test identities surface as Context-stage refusal rows and nothing is widened. The emitted main.rs prints the file-refusal rows and counts them in the terminal marker, and prepare/eval refusals now classify at the fatal (last diagnostic) grain consistently. Co-authored-by: briansrls --- src/v1/05_emit_rust.dag | 7 +- src/v2/compiler/00_compile.dag | 155 +++++++++++++++++++++++++++++---- 2 files changed, 142 insertions(+), 20 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 80391982b33..88099b02b22 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -16737,6 +16737,9 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " std::process::exit(1);\n", " }\n", " };\n", + " for refusal in context.file_refusals.iter() {\n", + " println!(\"{}\", serde_json::json!(\{ \"file_refusal\": refusal \}));\n", + " }\n", " let mut module_order: Vec<(String, Rc>)> = Vec::new();\n", " let mut module_decls: std::collections::HashMap> = std::collections::HashMap::new();\n", " for (qn, declaration) in &universe {\n", @@ -16759,7 +16762,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " }\n", " }\n", " Outcome::Rejected { diagnostics } => {\n", - " let reason = diagnostics.head.reason.clone();\n", + " let reason = diagnostics.tail.last().map(|d| d.reason.clone()).unwrap_or_else(|| diagnostics.head.reason.clone());\n", " for declaration in declarations {\n", " let observation = NativeTestObservation {\n", " module: qn.clone(),\n", @@ -16775,7 +16778,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " }\n", " }\n", " }\n", - " println!(\"{}\", serde_json::json!({ \"_terminal\": \"complete\", \"rows\": rows }));\n", + " println!(\"{}\", serde_json::json!(\{ \"_terminal\": \"complete\", \"rows\": rows, \"file_refusals\": context.file_refusals.len() \}));\n", "}\n") } } diff --git a/src/v2/compiler/00_compile.dag b/src/v2/compiler/00_compile.dag index 66dba12ed77..3a571ec8c80 100644 --- a/src/v2/compiler/00_compile.dag +++ b/src/v2/compiler/00_compile.dag @@ -13,14 +13,19 @@ import v2.compiler.name_resolve { import v2.compiler.program_assembly { assemble_program_from_ingest, program_assembly_fold_ingest, + program_assembly_read_to_normalized_root_prepared, ProgramAssemblyFoldState, ProgramAssemblyFoldOk, ProgramAssemblyFoldFailed } -import v2.compiler.source_authority { SourceRootIngest, source_root_set_from_ingest } +import v2.compiler.source_authority { + DagSourceReadWitness, + SourceRootIngest, + source_root_set_from_ingest +} import v2.compiler.normalize { normalize } import v2.compiler.normalized_tree { NormalizedTree } -import v2.compiler.parse { parse } +import v2.compiler.parse { parse, prepare_grammar, PreparedGrammar } import v2.compiler.resolve { resolve, ResolvedTree } import v2.compiler.tokenize { tokenize } import v2.std.staging { then_outcome, CacheProbe, Miss } @@ -33,7 +38,11 @@ import v2.std.grammar { ParseTree } import v2.std.language_model { LanguageModel } import extdeps.communication.medium { Lossless, Medium } import v2.std.compilers.target_model { TargetModel } -import v2.extdeps.languages.dag { dag_language_model, dag_target_model } +import v2.extdeps.languages.dag { + dag_language_model, + dag_target_model, + qualified_name_from_module_node +} import v2.lens.complexity_accumulator_copy.compile_gate { accumulator_copy_compile_gate } import v2.lens.determinism { determinism_compile_gate } import v2.lens.machine_shape { machine_shape_compile_gate } @@ -43,13 +52,15 @@ import v2.lens.fact_density { fact_density_hollow_alias_gate } import v2.lens.unit_modeling { unit_modeling_gate } import v2.lens.lifecycle_carrier { lifecycle_carrier_gate } import std.algebra { Cons, Empty, FreeMonoid } -import v2.std.algebra { list_append, list_reverse } +import v2.std.algebra { fold_list, list_append, list_reverse, list_snoc_item } import v2.std.collection { List } import v2.std.diagnostic { Accepted, Diagnostic, + Diagnostics, ExternalContractUnknown, Locus, + NonEmptyDiagnostics, Outcome, Some, invariant_port_locus, @@ -67,7 +78,11 @@ import v2.std.node_query { find_arrow_body_child } import v2.std.optional { Absent, Optional, Present, optional_absent, optional_present } import v2.std.qualified_name { QualifiedName } import v2.std.cross_tree.import_model { SourceRootRef } -import v2.std.cross_tree.resolution { SourceRootIndex } +import v2.std.cross_tree.resolution { + SourceRootIndex, + source_root_index_empty, + source_root_index_insert +} import v2.extdeps.runtimes.v2_evaluator { v2_eval_runtime_bool_is_false, v2_eval_runtime_bool_is_true @@ -633,11 +648,26 @@ fn compile_ingest_staging(source: Medium, target: TargetModel) -> Outcom // interpreter. // // THREE FUNCTIONS, ONE PER COST GRAIN. native_test_context_from_ingest pays the whole-ingest -// fold (tokenize/parse/normalize every source) ONCE per lane; its rejection is a lane-level -// refusal, never a per-test verdict. native_test_prepare_module pays resolve + infer ONCE per -// module. native_test_eval_one pays entry resolution + evaluation per declaration and NEVER -// rejects: every failure it can reach is a typed NativeTestRefused verdict carrying the stage -// and the diagnostic's head reason, so a census over observations has no silent remainder. +// fold (tokenize/parse/normalize every source) ONCE per lane; it rejects only on a LANE-LEVEL +// defect (a grammar that fails to prepare), never on corpus data. native_test_prepare_module +// pays resolve + infer ONCE per module. native_test_eval_one pays entry resolution + +// evaluation per declaration and NEVER rejects: every failure it can reach is a typed +// NativeTestRefused verdict carrying the stage and the diagnostic's head reason, so a census +// over observations has no silent remainder. +// +// THE PER-FILE FRONT-END REFUSAL IS CENSUS DATA, NOT A WHOLE-CONTEXT REFUSAL. The program +// assembly fold (v2.compiler.program_assembly) is wholesale fail-closed — one unparseable +// source fails the program — which is the right door for COMPILATION and the wrong shape for +// this census: the v2 front-end has a live corpus frontier (v2.workflow.compile_door_ledger), +// and a wholesale fold would let one frontier file deny verdict rows for every other member — +// the absorbing fallback in ingest form (DESIGN 5: the deficit's frequency reads zero because +// the whole lane is red). So the context fold below collects each source's tokenize/parse/ +// normalize refusal as a NativeTestFileRefusal row and keeps folding. Nothing is thereby +// WIDENED: a refused file contributes no root, so its module is absent from the index, every +// test identity living in it surfaces as a Context-stage refusal row minted by the lane host +// from exactly these rows, and a module that PARSED but imports a refused file fails at +// Prepare with the resolver's own diagnostic. The lane's admission still refuses over any +// unattributed row — collection changes the grain of the refusal, never its force. // // NO LENS GATES ON THIS ROUTE, deliberately. validate_then_compile's required lenses are the // COMPILE door's corpus invariants; the floor's seed-interpreter route does not run them per @@ -662,10 +692,22 @@ type NativeTestObservation { verdict: NativeTestVerdict } +// ONE SOURCE'S FRONT-END REFUSAL, AT THE TWO GRAINS THE CLASSIFICATION NEEDS. The rejection's +// diagnostic list prepends non-fatal pending advisories (rejected_with_pending), so the head +// reason and the fatal (last) reason are different observations of one refusal — the door +// ledger's cause-ownership table is keyed by exactly this grain distinction, and the lane's +// classifier asks both questions of every row. +type NativeTestFileRefusal { + path: Symbol + head_reason: Symbol + fatal_reason: Symbol +} + type NativeTestContext { roots: FreeMonoid index: SourceRootIndex active_roots: FreeMonoid + file_refusals: FreeMonoid } type NativeTestPreparedModule { @@ -673,15 +715,92 @@ type NativeTestPreparedModule { inferred: InferredTree } +type NativeTestContextFoldState { + roots: FreeMonoid + index: SourceRootIndex + file_refusals: FreeMonoid +} + +// The LAST diagnostic is the cause that actually refused the file; the head is the advisory +// that rode along. Both are carried because ownership is keyed by grain. +fn native_test_fatal_reason(d: NonEmptyDiagnostics) -> Symbol { + fold_list(xs: d.tail, empty: d.head.reason, cons: fn(_acc, diag) { diag.reason }) +} + +fn native_test_file_refusal(path: Symbol, d: NonEmptyDiagnostics) -> NativeTestFileRefusal { + NativeTestFileRefusal { + path: path, + head_reason: d.head.reason, + fatal_reason: native_test_fatal_reason(d: d) + } +} + +fn native_test_context_fold_step( + state: NativeTestContextFoldState, + read: DagSourceReadWitness, + lm: LanguageModel, + prepared: PreparedGrammar, + residue: Diagnostics +) -> NativeTestContextFoldState { + match program_assembly_read_to_normalized_root_prepared(read: read, lm: lm, prepared: prepared, residue: residue) { + Rejected { diagnostics: d } => + NativeTestContextFoldState { + roots: state.roots, + index: state.index, + file_refusals: list_snoc_item( + xs: state.file_refusals, + item: native_test_file_refusal(path: read.compilation_unit, d: d) + ) + } + Accepted { value: root, diagnostics: _ } => + match qualified_name_from_module_node(root: root.root) { + Rejected { diagnostics: d } => + NativeTestContextFoldState { + roots: state.roots, + index: state.index, + file_refusals: list_snoc_item( + xs: state.file_refusals, + item: native_test_file_refusal(path: read.compilation_unit, d: d) + ) + } + Accepted { value: qn, diagnostics: _ } => + NativeTestContextFoldState { + roots: list_snoc_item(xs: state.roots, item: root), + index: source_root_index_insert(index: state.index, module: qn, root: read.source_root), + file_refusals: state.file_refusals + } + } + } +} + fn native_test_context_from_ingest(ingest: SourceRootIngest) -> Outcome { - match program_assembly_fold_ingest(ingest: ingest, lm: dag_language_model()) { - ProgramAssemblyFoldFailed { diagnostics: d } => Rejected { diagnostics: d } - ProgramAssemblyFoldOk { roots: roots, index: idx } => + match prepare_grammar(grammar: dag_language_model().grammar) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: prepared, diagnostics: residue } => { + let folded = fold_list( + xs: ingest, + empty: NativeTestContextFoldState { + roots: Empty, + index: source_root_index_empty(), + file_refusals: Empty + }, + cons: fn(state, read) { + native_test_context_fold_step( + state: state, + read: read, + lm: dag_language_model(), + prepared: prepared, + residue: residue + ) + } + ) outcome_accepted(value: NativeTestContext { - roots: roots, - index: idx, - active_roots: source_root_set_from_ingest(ingest: ingest) + roots: folded.roots, + index: folded.index, + active_roots: source_root_set_from_ingest(ingest: ingest), + file_refusals: folded.file_refusals }) + } } } @@ -782,7 +901,7 @@ fn native_test_eval_body( module: prepared.module, declaration: declaration, stage: NativeTestStageEval, - reason: d.head.reason + reason: native_test_fatal_reason(d: d) ) Accepted { value: rv, diagnostics: _ } => if v2_eval_runtime_bool_is_true(value: rv) { @@ -828,7 +947,7 @@ fn native_test_eval_one( module: prepared.module, declaration: declaration, stage: NativeTestStageEntry, - reason: d.head.reason + reason: native_test_fatal_reason(d: d) ) Accepted { value: body, diagnostics: _ } => native_test_eval_body( From 854a19a5b1016778cbeb2a65e770f3fd0193df98 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 06:31:56 +0000 Subject: [PATCH 50/89] Add native lane control fixtures Two controls for the required-v2-native lane's host harness: src/v2/native_lane_fixture/control.dag carries the live-verdict pair (a well-formed false control and its true positive half) as plain fns outside the v2.test. prefix, so floor discovery enrolls no universe rows for them; fixtures/native_lane_malformed/poison.dag is a deliberately unterminating string that any honest front-end must refuse at tokenize, kept outside every declared source root so the broken bytes never enter an honest ingest. Co-authored-by: briansrls --- fixtures/native_lane_malformed/poison.dag | 13 ++++++++++ src/v2/native_lane_fixture/control.dag | 29 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 fixtures/native_lane_malformed/poison.dag create mode 100644 src/v2/native_lane_fixture/control.dag diff --git a/fixtures/native_lane_malformed/poison.dag b/fixtures/native_lane_malformed/poison.dag new file mode 100644 index 00000000000..8eb13c1fb21 --- /dev/null +++ b/fixtures/native_lane_malformed/poison.dag @@ -0,0 +1,13 @@ +module v2.native_lane_malformed.poison + +// THE required-v2-native LANE'S MALFORMED-SOURCE CONTROL (authority: +// gunbc.witness_v2_native_route). This file is deliberately NOT well-formed: the string below +// never terminates, so any honest front-end refuses it at tokenize. The lane's host harness +// runs the emitted native binary with this file's directory added as a source root and REQUIRES +// a whole-context refusal (exit 1) — a native route that "accepts" a poisoned context has a +// dead front-end refusal arm, and every refusal verdict in its census is then worthless. +// +// It lives outside dag/ and src/v2/ so the broken bytes never enter an honest ingest: the +// required parse sweep walks only the declared source roots, and this directory joins a source +// root set only in the lane's own control invocation. +data broken: String = "this string never terminates diff --git a/src/v2/native_lane_fixture/control.dag b/src/v2/native_lane_fixture/control.dag new file mode 100644 index 00000000000..65827fd7463 --- /dev/null +++ b/src/v2/native_lane_fixture/control.dag @@ -0,0 +1,29 @@ +module v2.native_lane_fixture.control + +import v2.std.logic { Bool } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +// THE required-v2-native LANE'S LIVE-VERDICT CONTROL PAIR (authority: +// gunbc.witness_v2_native_route). These two declarations exist so the lane can prove its +// verdict path is LIVE rather than green-by-construction: a route that cannot distinguish +// `false` from `true` must never report a pass. The lane's host harness names both identities +// explicitly — they are controls, never universe members: the module sits outside the +// `v2.test.` prefix the universe derivation filters on, and the declarations are plain `fn` +// so floor discovery enrolls no rows for them (the floor's required gate would — correctly — +// redden on a discovered test that returns false). +// +// THE FALSE CONTROL IS THE LOAD-BEARING ONE. The lane's admission refuses unless the emitted +// native binary observes `native_lane_false_control` returning FALSE (NativeTestReturnedFalse): +// a native route that reports it Passed has a dead or diverted verdict arm, and every pass +// verdict in the population is then worthless. The true control is the positive half of the +// same probe: the route must also observe TRUE where true is declared, so a route that +// refuses or falsifies everything cannot green the control either. +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +fn native_lane_false_control() -> Bool { + false +} + +fn native_lane_true_control() -> Bool { + true +} From e7f325b7411fdfdccec4182909c769e3e6ed6ebc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 09:09:12 +0000 Subject: [PATCH 51/89] Fix Vec/Vector type mismatches in the SourceRootEvalDriver main.rs template The emitted driver crate aliases im::Vector as Vec, so the template's std Vec-typed bindings and collect calls failed to compile in the emitted crate: universe rows and module order carry Rc>, the reads vector moves into the FreeMonoid parameter with .into(), and the dotted module name is built from an iterator collect. Co-authored-by: briansrls --- src/v1/05_emit_rust.dag | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 88099b02b22..fa983548408 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -16671,7 +16671,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " std::process::exit(2);\n", " }\n", " };\n", - " let mut universe: Vec<(Rc>, String)> = Vec::new();\n", + " let mut universe: Vec<(Rc>, String)> = Vec::new();\n", " let mut seen: std::collections::HashSet = std::collections::HashSet::new();\n", " for line in universe_text.lines() {\n", " if line.is_empty() || line.starts_with('#') {\n", @@ -16684,7 +16684,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " std::process::exit(2);\n", " }\n", " universe.push((\n", - " Rc::new(module.split('.').map(|segment| segment.to_string()).collect::>()),\n", + " Rc::new(module.split('.').map(|segment| segment.to_string()).collect::>()),\n", " declaration.to_string(),\n", " ));\n", " }\n", @@ -16730,7 +16730,7 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " source_root: source_root_for_storage_path(path.clone()),\n", " }));\n", " }\n", - " let context = match &*native_test_context_from_ingest(Rc::new(reads)) {\n", + " let context = match &*native_test_context_from_ingest(Rc::new(reads.into())) {\n", " Outcome::Accepted { value, .. } => value.clone(),\n", " Outcome::Rejected { diagnostics } => {\n", " eprintln!(\"CONTEXT-REFUSED: \{diagnostics:#?\}\");\n", @@ -16740,10 +16740,10 @@ fn emit_source_root_eval_driver_main_rs(crate_name: String, pipeline_module: Str " for refusal in context.file_refusals.iter() {\n", " println!(\"{}\", serde_json::json!(\{ \"file_refusal\": refusal \}));\n", " }\n", - " let mut module_order: Vec<(String, Rc>)> = Vec::new();\n", + " let mut module_order: Vec<(String, Rc>)> = Vec::new();\n", " let mut module_decls: std::collections::HashMap> = std::collections::HashMap::new();\n", " for (qn, declaration) in &universe {\n", - " let dotted = qn.join(\".\");\n", + " let dotted = qn.iter().cloned().collect::>().join(\".\");\n", " if !module_decls.contains_key(&dotted) {\n", " module_order.push((dotted.clone(), qn.clone()));\n", " module_decls.insert(dotted.clone(), Vec::new());\n", From 49cccbed7db8c9c40a47cc3373ec4f032da1ce44 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 09:09:18 +0000 Subject: [PATCH 52/89] Harden the v2-native route contract: test-identity grain, exact join, paired reference Reframe the terminal subject from module-grain acceptance to the exact test identity reaching a native verdict. The receipt's universe is a list of qualified NativeRouteTestIdentity rows; the observed population joins it exactly (uniqueness, no foreign rows, no omissions); every member verdict is paired against the floor's own expected-red and route-gap rosters for agreement, exclusion, or divergence; refusals are classified from stage and provenance with cause ownership at fatal and head grains; and the four controls (true, false, malformed specimen, old-route withdrawal) are admission clauses. The 46 tests cover universe derivation, identity qualification, reference pairing, refusal classification, disposition, census counting, and every admission clause. Co-authored-by: briansrls --- dag/gunbc/witness/v2_native_route.dag | 827 +++++++++++++------ src/v2/test/v2_native_route_test.dag | 1092 ++++++++++++++++++------- 2 files changed, 1402 insertions(+), 517 deletions(-) diff --git a/dag/gunbc/witness/v2_native_route.dag b/dag/gunbc/witness/v2_native_route.dag index 2818574052e..ae2faae77f8 100644 --- a/dag/gunbc/witness/v2_native_route.dag +++ b/dag/gunbc/witness/v2_native_route.dag @@ -1,223 +1,440 @@ module gunbc.witness_v2_native_route -import v2.std.algebra { any, filter, fold_list, is_empty, length } +import v2.std.algebra { any, filter, fold_list, is_empty, length, list_snoc_item } import v2.std.collection { List } import v2.std.integer { Int } import v2.std.logic { Bool } import v2.std.node { Symbol } import v2.std.optional { Absent, Optional, Present } +import v2.compiler.compile { + NativeTestVerdict, NativeTestPassed, NativeTestReturnedFalse, NativeTestReturnedOther, NativeTestRefused, + NativeTestStage, NativeTestStageContext, NativeTestStagePrepare, NativeTestStageEntry, NativeTestStageEval, + NativeTestFileRefusal +} import v2.workflow.compile_door_ledger { - CauseOwnership, - DiagnosticGrain, - FatalGrain, - HeadGrain, - cause_ownership_lookup + DiagnosticGrain, FatalGrain, HeadGrain, cause_ownership_lookup } +import v2.workflow.floor_expected_red { floor_expected_red_roster } +import v2.workflow.floor_route_gap { floor_route_gap_roster } // THE LANE AUTHORITY FOR THE required-v2-native CI JOB (operator authorization 2026-09-09: one -// additional required job, modelled beside the workflow authority that will project it — never -// a hand-edit of the generated YAML). The job's subject is the compiler/test EXECUTION ROUTE -// itself: the emitted-native compiler binary, invoked by explicit path, over a derived -// v2.test.* population. The 32 native-only construct arms are explicitly NOT this proof — they -// show generated Rust programs ran natively while the claim executor stayed seed-backed; this -// lane's receipt must show which binary answered. +// additional required job, modelled beside the workflow authority that projects it — never a +// hand-edit of the generated YAML). The job's subject is the compiler/test EXECUTION ROUTE +// itself: the emitted-native compiler binary, invoked by explicit path, executing a derived +// v2.test.* population through its own whole-source-root Eval driver +// (std.compiler_entry SourceRootEvalDriver — the v2.compiler.compile native_test_* fold), and +// reaching a TERMINAL TEST VERDICT per identity. The 32 native-only construct arms are +// explicitly NOT this proof — they show generated Rust programs ran natively while the claim +// executor stayed seed-backed; this lane's receipt must show which binary answered, and what +// verdict each named test reached. +// +// THE TERMINAL SUBJECT IS THE TEST IDENTITY, NOT THE MODULE. A module accepted for translation +// proves the front door opened; it says nothing about whether any declaration inside it was +// located, evaluated, or returned its expected verdict. Every row of this lane's population is +// keyed by the qualified `.` identity — the same grain the required floor +// gates on — and the verdict carried is the driver's own NativeTestVerdict, imported from +// v2.compiler.compile rather than re-declared here (DESIGN 3: one authority for the verdict +// vocabulary; a second route-local verdict type would be a nickname). // -// STANDING AT AUTHORING: the emitted native compiler — the DirectIngestDriver main emitted for -// the 00_compile closure — refuses every real v2.test.* module today. Its admission is -// hard-coded to the compile_driver_subject name with an empty import environment, and the v2 -// compile door is at its modelled frontier for the whole corpus (v2.workflow.compile_door_ledger -// carries the interpreted door's burn-down; the native and interpreted routes agree on the -// probed modules, so the blocker is the compiler frontier, not a native divergence). The -// contracted positive population is therefore EMPTY, and native_route_admission refuses an -// empty positive population by construction — the empty-observation narrow is the one verdict -// this lane may never issue. The lane is modelled in full here; ENROLMENT into the required -// workflow is blocked on the door frontier, with the trigger and the exact enrolment edit -// carried as data at the bottom of this module. - -// THE JOB IDENTITY IS ONE ROW. The workflow enrolment edit, the receipt, and the witnesses all -// cite this declaration; no second spelling of the job name anywhere. +// THE ROUTE IS EVAL, NOT TRANSLATION. The driver resolves the exact test declaration inside a +// whole-ingest context, infers it, and evaluates it under eval_default_interpretation — the +// same hermetic effect policy the floor's execution mode runs (every host-effect boundary +// disallowed). A test that reaches for a host effect refuses at the Eval stage on this route, +// exactly as it gaps on the floor; the floor_route_gap roster is the independently established +// expectation that pairing consumes. + +// THE JOB IDENTITY IS ONE ROW. The workflow job materialization, the receipt, and the witnesses +// all cite this declaration; no second spelling of the job name anywhere. data required_v2_native_job_name: String = "required-v2-native" -// THE UNIVERSE IS A PREFIX, NOT A ROSTER. The population is DERIVED from the module census by -// prefix membership — a hand-listed roster would silently drift from the tree it claims to -// cover. The prefix is the same `v2.test.` the required floor gates on (v2.workflow.required_floor -// required_gate_prefixes): the native lane's universe is the floor's witness universe asked of -// a different route. The contract's "no silent remainder" binds INSIDE the universe: every -// universe member carries exactly one verdict row, and every refused verdict carries exactly -// one classified cause. +// THE UNIVERSE IS A PREFIX, NOT A ROSTER. The population is DERIVED from the tree's test +// discovery by prefix membership — a hand-listed roster would silently drift from the tree it +// claims to cover. The prefix is the same `v2.test.` the required floor gates on +// (v2.workflow.required_floor required_gate_prefixes): the native lane's universe is the floor's +// witness universe asked of a different route. The universe is derived by the host harness from +// the floor's own discovery producer (v2.workflow.floor_discovery_producer) joined against each +// entry's authored module header, and carried on the receipt; admission checks its shape and +// joins the observed population against it exactly. data native_route_universe_prefix: String = "v2.test." fn native_route_universe_member(module_qn: String) -> Bool { starts_with(s: module_qn, prefix: native_route_universe_prefix) } -fn native_route_universe(module_qns: List) -> List { - filter(xs: module_qns, predicate: fn(qn) { native_route_universe_member(module_qn: qn) }) +// ONE TEST IDENTITY, STRUCTURED. The module qualified name and the declaration name are separate +// observations — the emitted binary reports them separately, and the dotted spelling is DERIVED +// here (never parsed back out of a string), the same derivation the floor's +// witness_identity_qualified_name makes. Function names carry no dots, so the dotted form's last +// segment is always the declaration; the split is unambiguous in both directions. +type NativeRouteTestIdentity { + module: String + declaration: String +} + +fn native_route_identity_qualified(identity: NativeRouteTestIdentity) -> String { + concat(concat(identity.module, "."), identity.declaration) +} + +fn native_route_identity_eq(a: NativeRouteTestIdentity, b: NativeRouteTestIdentity) -> Bool { + a.module == b.module && a.declaration == b.declaration +} + +fn native_route_identity_member(target: NativeRouteTestIdentity, xs: List) -> Bool { + any(xs: xs, predicate: fn(x) { native_route_identity_eq(a: x, b: target) }) +} + +// NO DUPLICATE IDENTITIES, asked the same way of the universe and of the observed population: an +// identity appearing twice means the derivation double-counted a declaration or the harness +// recorded one run twice, and both are harness defects, not results. Quadratic by construction — +// the interpreter measured this shape over the full derived population at tens of seconds, and +// the standing bare-minimum-cost ruling prices the fold, not the realized n. +fn native_route_identities_unique(xs: List) -> Bool { + !any(xs: xs, predicate: fn(x) { + length(xs: filter(xs: xs, predicate: fn(y) { native_route_identity_eq(a: x, b: y) })) > 1 + }) } -// ONE ROW PER UNIVERSE MEMBER. NativeAccepted is the positive population. NativeRefused carries -// TWO reason symbols because the compiler's rejection is a list with a grain the row must not -// hide: rejected_with_pending PREPENDS the non-fatal pending diagnostics, so the head reason is -// an advisory that rode along while the LAST diagnostic is the fatal cause the member is -// excluded for. The door ledger records the head; this lane records both and classifies on the -// fatal — the two instruments join on head_reason while the exclusion cause stays honest about -// what actually refused. -type NativeRouteVerdict - = NativeAccepted - | NativeRefused { head_reason: Symbol, fatal_reason: Symbol } +// THE REFERENCE EXPECTATION, PER MEMBER, FROM THE FLOOR'S OWN ROSTERS. Pairing a native verdict +// against nothing proves nothing about agreement; the independently established expectation is +// the required floor's, joined at the exact qualified identity. Three arms, derived — never +// transcribed: route-gap membership is asked FIRST because an identity on both rosters is a +// route-gap fact (the floor executes it and it gaps), and expected-red second; the remainder is +// the floor's standing expectation for every discovered v2.test.* member — pass — which is the +// expectation a green floor asserts of every unrostered identity by construction. +type NativeRouteReference + = FloorExpectedPass + | FloorExpectedRed + | FloorRouteGapRefusal + +fn native_route_reference_for( + qualified: String, + expected_red: List, + route_gap: List +) -> NativeRouteReference { + if any(xs: route_gap, predicate: fn(r) { r == qualified }) { + FloorRouteGapRefusal + } else if any(xs: expected_red, predicate: fn(r) { r == qualified }) { + FloorExpectedRed + } else { + FloorExpectedPass + } +} +// ONE ROW PER UNIVERSE MEMBER: the identity and the verdict the emitted native binary observed +// for it. The verdict is the driver's NativeTestVerdict verbatim — Passed, ReturnedFalse, +// ReturnedOther, or Refused at a named stage with the fatal reason — so the receipt carries what +// the route observed, not a summary of it. type NativeRouteMemberRow { - module_path: String - verdict: NativeRouteVerdict + identity: NativeRouteTestIdentity + verdict: NativeTestVerdict } -// THE EXCLUSION TAXONOMY. A refused member is excluded for exactly one of three causes: the -// compiler's own frontier (attributed to its owning lane through the door ledger's +// THE EXCLUSION TAXONOMY, CLASSIFIED FROM STAGE AND PROVENANCE — never from the reason symbol +// alone (the review's standing instruction: the same symbol is a driver limit on one route and a +// resolver defect on another). A refused member is excluded for exactly one of these causes: +// the compiler's own frontier (attributed to its owning lane through the door ledger's // known_frontier_causes at FATAL grain — this lane delegates to that table, it does not -// re-enumerate it); the emitted driver's admission limit (a native-route fact the interpreted -// door never faces, owned here); or NOTHING — an unattributed refusal, which is never silently -// bucketed: it blocks admission until someone owns it, the same fail-closed reading the door -// ledger gives an unowned head reason. +// re-enumerate it); the driver's entry resolution (the declaration was not an evaluable arrow — +// a test data row or an absent name — a driver fact the door never faces, owned here); the +// hermetic effect boundary (the test reached a host effect the Eval interpretation does not +// bind); or NOTHING — an unattributed refusal, which is never silently bucketed: it blocks +// admission until someone owns it, the same fail-closed reading the door ledger gives an +// unowned head reason. type NativeRouteExclusionCause = CompilerFrontierAttributed { reason: Symbol } - | NativeDriverAdmissionLimit { reason: Symbol } + | NativeEntryResolutionLimit { reason: Symbol } + | NativeEvalBoundary { reason: Symbol } | UnattributedRefusal { reason: Symbol } -// The emitted DirectIngestDriver hard-codes admission to the compile_driver_subject name with -// an empty import environment, so resolve_module_not_found on this route is the DRIVER's limit, -// not the module's defect: every real module is named something else, and every import is -// unresolvable under it. The roster is data so a driver widening deletes a row rather than -// editing a predicate. -data native_driver_limit_reasons: List = [ ^resolve_module_not_found ] - -fn native_driver_limit_reason(reason: Symbol) -> Bool { - any(xs: native_driver_limit_reasons, predicate: fn(r) { r == reason }) -} - -// The exclusion question is asked at FATAL grain: the fatal reason is the cause that actually -// refused the member, so its ownership row is the FatalGrain one. Asking the head-grain -// question of a fatal reason was the grain crossing the review named — the same symbol can be -// owned at one grain and unowned at the other, and only the fatal-grain answer classifies an -// exclusion. -fn classify_native_refusal(reason: Symbol) -> NativeRouteExclusionCause { - if native_driver_limit_reason(reason: reason) { - NativeDriverAdmissionLimit { reason: reason } - } else { - match cause_ownership_lookup(cause: reason, grain: FatalGrain) { - Present { value: _ } => CompilerFrontierAttributed { reason: reason } - Absent => UnattributedRefusal { reason: reason } - } - } +// The driver's entry stage refuses for exactly these reasons today: the named declaration was +// not found in the prepared module, or it was found and is not an arrow (a test data row carries +// no body to evaluate). The roster is data so a driver widening deletes a row rather than +// editing a predicate; an entry-stage refusal with any other reason is unattributed, not +// assumed. +data native_route_entry_limit_reasons: List = [ + ^native_test_entry_declaration_not_found, + ^native_test_entry_declaration_not_arrow +] + +// The hermetic boundary's reason family: eval_default_interpretation binds no host effect, so a +// test that reaches one refuses with one of these. An Eval-stage refusal with any OTHER reason +// is not a boundary observation — it is an eval defect or a compiler frontier cause, and it is +// classified through the door ledger or left unattributed, never waved through as a boundary. +data native_route_effect_boundary_reasons: List = [ + ^eval_rejected_effect_io_unbound, + ^eval_rejected_effect_io_write_payload_mismatch, + ^eval_rejected_effect_io_roundtrip_arity +] + +fn native_route_reason_member(reason: Symbol, roster: List) -> Bool { + any(xs: roster, predicate: fn(r) { r == reason }) } -// THE HEAD REASON IS NOT DEAD RECEIPT DATA. rejected_with_pending prepends the non-fatal -// advisory, so the head reason is a real observation of the frontier at a different grain — -// the same observation the door ledger's own rows carry. Every refused row's head reason must -// therefore be owned at HEAD grain: an unowned advisory is a new frontier cause surfacing -// where nobody counted it, and it blocks admission by its own name rather than riding along -// unclassified. -fn native_route_head_advisory_attributed(reason: Symbol) -> Bool { - native_driver_limit_reason(reason: reason) - || match cause_ownership_lookup(cause: reason, grain: HeadGrain) { - Present { value: _ } => true - Absent => false - } +// THE CLASSIFICATION QUESTION IS ASKED AT FATAL GRAIN for the compiler-frontier arm: the fatal +// reason is the cause that actually refused, so its ownership row is the FatalGrain one. Asking +// the head-grain question of a fatal reason was the grain crossing the review named — the same +// symbol can be owned at one grain and unowned at the other, and only the fatal-grain answer +// classifies an exclusion. +fn classify_native_refusal(stage: NativeTestStage, reason: Symbol) -> NativeRouteExclusionCause { + match stage { + NativeTestStageEntry => + if native_route_reason_member(reason: reason, roster: native_route_entry_limit_reasons) { + NativeEntryResolutionLimit { reason: reason } + } else { + UnattributedRefusal { reason: reason } + } + NativeTestStageEval => + if native_route_reason_member(reason: reason, roster: native_route_effect_boundary_reasons) { + NativeEvalBoundary { reason: reason } + } else { + match cause_ownership_lookup(cause: reason, grain: FatalGrain) { + Present { value: _ } => CompilerFrontierAttributed { reason: reason } + Absent => UnattributedRefusal { reason: reason } + } + } + NativeTestStageContext => + match cause_ownership_lookup(cause: reason, grain: FatalGrain) { + Present { value: _ } => CompilerFrontierAttributed { reason: reason } + Absent => UnattributedRefusal { reason: reason } + } + NativeTestStagePrepare => + match cause_ownership_lookup(cause: reason, grain: FatalGrain) { + Present { value: _ } => CompilerFrontierAttributed { reason: reason } + Absent => UnattributedRefusal { reason: reason } + } + } } -fn native_route_head_advisories_all_attributed(rows: List) -> Bool { - !any(xs: rows, predicate: fn(row) { - match row.verdict { - NativeAccepted => false - NativeRefused { head_reason: h, fatal_reason: _ } => - !native_route_head_advisory_attributed(reason: h) - } - }) +// THE DISPOSITION OF ONE MEMBER: the (verdict, reference) pair, decided whole. A VERDICT the +// native route reached must AGREE with the reference — a pass where the floor expects red, a +// false where the floor expects pass, a non-Boolean return, or any reached verdict on a +// route-gap identity (the hermetic native route reaching what the hermetic floor route could not +// is a discrepancy between two routes that share an effect policy, and it reds) — and every +// disagreement is a DIVERGENCE, never an exclusion. A REFUSAL is a capability limit, not a +// verdict: it agrees only where the reference is the route gap and the refusal is the hermetic +// boundary itself (Eval stage, effect-boundary reason); every other refusal is an EXCLUSION, +// typed and counted by its classified cause. +type NativeRouteDivergenceCause + = NativePassedExpectedRed + | NativeFalseExpectedPass + | NativeReturnedOtherVerdict + | NativeReachedRouteGap + +type NativeRouteDisposition + = NativeAgreementPass + | NativeAgreementExpectedRed + | NativeAgreementRouteGap + | NativeExclusion { cause: NativeRouteExclusionCause } + | NativeDivergence { cause: NativeRouteDivergenceCause } + +fn native_route_disposition( + verdict: NativeTestVerdict, + reference: NativeRouteReference +) -> NativeRouteDisposition { + match verdict { + NativeTestPassed => + match reference { + FloorExpectedPass => NativeAgreementPass + FloorExpectedRed => NativeDivergence { cause: NativePassedExpectedRed } + FloorRouteGapRefusal => NativeDivergence { cause: NativeReachedRouteGap } + } + NativeTestReturnedFalse => + match reference { + FloorExpectedPass => NativeDivergence { cause: NativeFalseExpectedPass } + FloorExpectedRed => NativeAgreementExpectedRed + FloorRouteGapRefusal => NativeDivergence { cause: NativeReachedRouteGap } + } + NativeTestReturnedOther => NativeDivergence { cause: NativeReturnedOtherVerdict } + NativeTestRefused { stage: stage, reason: reason } => + match reference { + FloorRouteGapRefusal => + match stage { + NativeTestStageEval => + if native_route_reason_member(reason: reason, roster: native_route_effect_boundary_reasons) { + NativeAgreementRouteGap + } else { + NativeExclusion { cause: classify_native_refusal(stage: stage, reason: reason) } + } + NativeTestStageContext => NativeExclusion { cause: classify_native_refusal(stage: stage, reason: reason) } + NativeTestStagePrepare => NativeExclusion { cause: classify_native_refusal(stage: stage, reason: reason) } + NativeTestStageEntry => NativeExclusion { cause: classify_native_refusal(stage: stage, reason: reason) } + } + FloorExpectedPass => NativeExclusion { cause: classify_native_refusal(stage: stage, reason: reason) } + FloorExpectedRed => NativeExclusion { cause: classify_native_refusal(stage: stage, reason: reason) } + } + } } -// THE COUNTED PARTITION the contract names ("every excluded member typed and counted"). The -// census is DERIVED from the rows, never accumulated beside them — a counter that disagrees -// with the population it summarizes has no constructor here. -type NativeRouteExclusionCensus { - accepted: Int +// THE COUNTED PARTITION the contract names ("every excluded member typed and counted"), extended +// with the agreement and divergence counts the pairing requires. The census is DERIVED from the +// rows, never accumulated beside them — a counter that disagrees with the population it +// summarizes has no constructor here. The rosters are folded in once (the caller derives them +// once) so the per-member reference question is a membership ask, not a re-derivation. +type NativeRouteCensus { + agreement_pass: Int + agreement_expected_red: Int + agreement_route_gap: Int excluded_compiler_frontier: Int - excluded_driver_limit: Int + excluded_entry_limit: Int + excluded_eval_boundary: Int excluded_unattributed: Int + divergences: Int } -fn native_route_exclusion_census(rows: List) -> NativeRouteExclusionCensus { +fn native_route_census( + rows: List, + expected_red: List, + route_gap: List +) -> NativeRouteCensus { fold_list( xs: rows, - empty: NativeRouteExclusionCensus { - accepted: 0, + empty: NativeRouteCensus { + agreement_pass: 0, + agreement_expected_red: 0, + agreement_route_gap: 0, excluded_compiler_frontier: 0, - excluded_driver_limit: 0, - excluded_unattributed: 0 + excluded_entry_limit: 0, + excluded_eval_boundary: 0, + excluded_unattributed: 0, + divergences: 0 }, cons: fn(acc, row) { - match row.verdict { - NativeAccepted => NativeRouteExclusionCensus { - accepted: acc.accepted + 1, + let reference = native_route_reference_for( + qualified: native_route_identity_qualified(identity: row.identity), + expected_red: expected_red, + route_gap: route_gap + ) + match native_route_disposition(verdict: row.verdict, reference: reference) { + NativeAgreementPass => NativeRouteCensus { + agreement_pass: acc.agreement_pass + 1, + agreement_expected_red: acc.agreement_expected_red, + agreement_route_gap: acc.agreement_route_gap, + excluded_compiler_frontier: acc.excluded_compiler_frontier, + excluded_entry_limit: acc.excluded_entry_limit, + excluded_eval_boundary: acc.excluded_eval_boundary, + excluded_unattributed: acc.excluded_unattributed, + divergences: acc.divergences + } + NativeAgreementExpectedRed => NativeRouteCensus { + agreement_pass: acc.agreement_pass, + agreement_expected_red: acc.agreement_expected_red + 1, + agreement_route_gap: acc.agreement_route_gap, + excluded_compiler_frontier: acc.excluded_compiler_frontier, + excluded_entry_limit: acc.excluded_entry_limit, + excluded_eval_boundary: acc.excluded_eval_boundary, + excluded_unattributed: acc.excluded_unattributed, + divergences: acc.divergences + } + NativeAgreementRouteGap => NativeRouteCensus { + agreement_pass: acc.agreement_pass, + agreement_expected_red: acc.agreement_expected_red, + agreement_route_gap: acc.agreement_route_gap + 1, excluded_compiler_frontier: acc.excluded_compiler_frontier, - excluded_driver_limit: acc.excluded_driver_limit, - excluded_unattributed: acc.excluded_unattributed + excluded_entry_limit: acc.excluded_entry_limit, + excluded_eval_boundary: acc.excluded_eval_boundary, + excluded_unattributed: acc.excluded_unattributed, + divergences: acc.divergences } - NativeRefused { head_reason: _, fatal_reason: r } => - match classify_native_refusal(reason: r) { - CompilerFrontierAttributed { reason: _ } => NativeRouteExclusionCensus { - accepted: acc.accepted, + NativeExclusion { cause: cause } => + match cause { + CompilerFrontierAttributed { reason: _ } => NativeRouteCensus { + agreement_pass: acc.agreement_pass, + agreement_expected_red: acc.agreement_expected_red, + agreement_route_gap: acc.agreement_route_gap, excluded_compiler_frontier: acc.excluded_compiler_frontier + 1, - excluded_driver_limit: acc.excluded_driver_limit, - excluded_unattributed: acc.excluded_unattributed + excluded_entry_limit: acc.excluded_entry_limit, + excluded_eval_boundary: acc.excluded_eval_boundary, + excluded_unattributed: acc.excluded_unattributed, + divergences: acc.divergences } - NativeDriverAdmissionLimit { reason: _ } => NativeRouteExclusionCensus { - accepted: acc.accepted, + NativeEntryResolutionLimit { reason: _ } => NativeRouteCensus { + agreement_pass: acc.agreement_pass, + agreement_expected_red: acc.agreement_expected_red, + agreement_route_gap: acc.agreement_route_gap, excluded_compiler_frontier: acc.excluded_compiler_frontier, - excluded_driver_limit: acc.excluded_driver_limit + 1, - excluded_unattributed: acc.excluded_unattributed + excluded_entry_limit: acc.excluded_entry_limit + 1, + excluded_eval_boundary: acc.excluded_eval_boundary, + excluded_unattributed: acc.excluded_unattributed, + divergences: acc.divergences } - UnattributedRefusal { reason: _ } => NativeRouteExclusionCensus { - accepted: acc.accepted, + NativeEvalBoundary { reason: _ } => NativeRouteCensus { + agreement_pass: acc.agreement_pass, + agreement_expected_red: acc.agreement_expected_red, + agreement_route_gap: acc.agreement_route_gap, excluded_compiler_frontier: acc.excluded_compiler_frontier, - excluded_driver_limit: acc.excluded_driver_limit, - excluded_unattributed: acc.excluded_unattributed + 1 + excluded_entry_limit: acc.excluded_entry_limit, + excluded_eval_boundary: acc.excluded_eval_boundary + 1, + excluded_unattributed: acc.excluded_unattributed, + divergences: acc.divergences + } + UnattributedRefusal { reason: _ } => NativeRouteCensus { + agreement_pass: acc.agreement_pass, + agreement_expected_red: acc.agreement_expected_red, + agreement_route_gap: acc.agreement_route_gap, + excluded_compiler_frontier: acc.excluded_compiler_frontier, + excluded_entry_limit: acc.excluded_entry_limit, + excluded_eval_boundary: acc.excluded_eval_boundary, + excluded_unattributed: acc.excluded_unattributed + 1, + divergences: acc.divergences } } + NativeDivergence { cause: _ } => NativeRouteCensus { + agreement_pass: acc.agreement_pass, + agreement_expected_red: acc.agreement_expected_red, + agreement_route_gap: acc.agreement_route_gap, + excluded_compiler_frontier: acc.excluded_compiler_frontier, + excluded_entry_limit: acc.excluded_entry_limit, + excluded_eval_boundary: acc.excluded_eval_boundary, + excluded_unattributed: acc.excluded_unattributed, + divergences: acc.divergences + 1 + } } } ) } -// "No silent remainder" as an executing check: the four counters close over the population. -fn native_route_census_is_total( - census: NativeRouteExclusionCensus, - rows: List -) -> Bool { - census.accepted + census.excluded_compiler_frontier - + census.excluded_driver_limit + census.excluded_unattributed == length(xs: rows) -} +// THE CONTROLS ARE OBSERVATIONS, NOT CONSTRUCTORS. Each control records what the emitted binary +// was OBSERVED to do; admission pattern-matches the observation, so a harness that cannot +// produce the observation cannot green the clause. +// +// THE FALSE AND TRUE CONTROLS PROVE THE VERDICT PATH IS LIVE. The lane's admission refuses +// unless the emitted binary observed the false control RETURN FALSE (a route that reports it +// Passed — or refuses it, or loses it — has a dead or diverted verdict arm, and every pass in +// the population is then worthless) and the true control RETURN TRUE (a route that falsifies or +// refuses everything cannot green either). Both controls live in v2.native_lane_fixture.control +// — outside the universe prefix, plain `fn` so floor discovery enrolls no rows for them — and +// the host names them to the binary explicitly, beside the derived universe. Absent is +// representable so a missing observation refuses by name rather than pattern-matching to a +// default. +// +// THE MALFORMED CONTROL PROVES THE FRONT-END REFUSAL ARM IS LIVE. The host runs the same binary +// with the poison fixture's directory added as a source root; an honest front-end collects a +// per-file refusal for it (the collecting fold refuses the FILE, never the whole context), and +// the receipt records the refusal it observed. MalformedSpecimenAccepted — no refusal row for +// the poison path — means the route "accepted" poisoned source and every refusal verdict in its +// census is worthless. +type NativeRouteMalformedControl + = MalformedSpecimenRefused { path: String, reason: Symbol } + | MalformedSpecimenAccepted // THE NO-FALLBACK CONTROL IS A RECEIVED FACT, NOT AN ASSUMPTION. After the emitted compiler -// exists, the old compiler/interpreter route is made unavailable — or an executing falsifier -// is armed that fails if the old route is touched — and the receipt records WHICH control -// held. OldRouteControlAbsent is representable so admission can refuse it by name: a receipt -// that cannot say how fallback was closed has not closed it. +// exists, the old compiler/interpreter route is made unavailable — or an executing falsifier is +// armed that fails if the old route is touched — and the receipt records WHICH control held and +// the identity of what was withdrawn or armed. OldRouteControlAbsent is representable so +// admission can refuse it by name: a receipt that cannot say how fallback was closed has not +// closed it. The empty-string arm is refused alongside the absent arm: a control whose identity +// names nothing is not an observation. type NativeRouteOldRouteControl = OldRouteWithdrawn { withdrawn_executable: String } | OldRouteFalsifierArmed { falsifier_identity: String } | OldRouteControlAbsent -// The deliberately broken specimen is the route's RED control: a source the compiler must -// refuse. If the emitted binary ACCEPTS it, the route's refusal arm is dead and every refusal -// verdict in the population is worthless — admission refuses. -type NativeRouteBrokenControl - = BrokenSpecimenRefused { reason: Symbol } - | BrokenSpecimenAccepted - // THE TERMINAL OBSERVATION. Every identity the contract names is a field: the tested tree; the // seed used ONLY to prepare the emitted compiler (recorded honestly — this job does not claim // native bootstrap); the emitted closure's identity; the executed binary's path AND content // identity (route identity is observed, never synthesized from the intended command); the -// per-member verdicts; and both controls. +// DERIVED universe; the OBSERVED population; the per-file front-end refusals the collecting +// fold gathered; and every control. type NativeRouteReceipt { job: String tested_tree: String @@ -225,37 +442,60 @@ type NativeRouteReceipt { emitted_closure_identity: String executable_path: String executable_identity: String + universe: List population: List - broken_control: NativeRouteBrokenControl + file_refusals: List + false_control: Optional + true_control: Optional + malformed_control: NativeRouteMalformedControl old_route_control: NativeRouteOldRouteControl } // ADMISSION IS THE CONTRACT, ONE AUTHORITY, ONE CLAUSE PER PREDICATE. The job greens only when -// all hold: the receipt is well-formed (right job, identities recorded, population actually -// derived — an empty population is a harness failure, not a result); the positive population -// is non-empty (NativeAccepted IS the positive population, so the load-bearing clause is -// non-emptiness: an all-refused census proves the route refused, not that it routed); the -// broken specimen still refused; every exclusion is classified — the lane does not green over -// unowned refusals; route identity is recorded; and the old-route control is present. The fold -// collects EVERY violated clause rather than stopping at the first, so one red run names -// everything that must change before the next run. +// all hold: the receipt is well-formed (right job, every identity recorded); the universe is +// derived and well-formed (non-empty, duplicate-free, prefix-closed); the observed population +// JOINS the universe exactly (no duplicates, no foreign rows, no omissions — "no silent +// remainder" as an executing check, not a comment); the positive population is non-empty (an +// all-refused census proves the route refused, not that it routed); no divergence is observed; +// every exclusion is classified; every file refusal is owned at both grains; every Context-stage +// refusal is backed by an observed file refusal; and all four controls hold. The fold collects +// EVERY violated clause rather than stopping at the first, so one red run names everything that +// must change before the next run. type NativeRouteAdmissionRefusalCause = JobNameMismatch | TestedTreeUnrecorded | PreparationSeedUnrecorded | EmittedClosureUnrecorded - | PopulationUnderived + | RouteIdentityUnrecorded + | UniverseUnderived + | UniverseDuplicatesPresent + | UniversePrefixViolationsPresent + | PopulationDuplicatesPresent + | PopulationForeignRowsPresent + | PopulationOmissionsPresent | PositivePopulationEmpty - | BrokenSpecimenAcceptedByRoute + | DivergencesPresent | UnattributedExclusionsPresent - | UnattributedHeadAdvisoryPresent - | RouteIdentityUnrecorded + | UnattributedFileRefusalsPresent + | UnattributedHeadAdvisoriesPresent + | ContextRefusalsUnbacked + | FalseControlNotObserved + | TrueControlNotObserved + | MalformedControlAcceptedByRoute | OldRouteControlNeverInstalled type NativeRouteAdmission = NativeRouteAdmitted | NativeRouteRefused { causes: List } +// ONE CLAUSE, ONE PREDICATE, ONE ROW. The check record couples a clause's verdict to the cause +// its failure appends, so the admission fold is one pass and the cause list can never drift from +// the predicate list. +type NativeRouteClauseCheck { + holds: Bool + cause: NativeRouteAdmissionRefusalCause +} + fn native_route_job_name_matches(receipt: NativeRouteReceipt) -> Bool { receipt.job == required_v2_native_job_name } @@ -276,60 +516,201 @@ fn native_route_emitted_closure_recorded(receipt: NativeRouteReceipt) -> Bool { receipt.emitted_closure_identity != "" } -fn native_route_population_derived(receipt: NativeRouteReceipt) -> Bool { - !is_empty(xs: receipt.population) +fn native_route_identity_recorded(receipt: NativeRouteReceipt) -> Bool { + receipt.executable_path != "" && receipt.executable_identity != "" } -fn native_route_positive_population_present(receipt: NativeRouteReceipt) -> Bool { - length(xs: filter(xs: receipt.population, predicate: fn(row) { +fn native_route_universe_derived(receipt: NativeRouteReceipt) -> Bool { + !is_empty(xs: receipt.universe) +} + +fn native_route_universe_unique(receipt: NativeRouteReceipt) -> Bool { + native_route_identities_unique(xs: receipt.universe) +} + +fn native_route_universe_prefix_closed(receipt: NativeRouteReceipt) -> Bool { + !any(xs: receipt.universe, predicate: fn(i) { + !native_route_universe_member(module_qn: i.module) + }) +} + +// The population's identity projection: the uniqueness and coverage questions range over +// identities, not rows, so the projection is named once beside the join that asks it. +fn native_route_row_identities(rows: List) -> List { + fold_list(xs: rows, empty: [], cons: fn(acc, row) { list_snoc_item(xs: acc, item: row.identity) }) +} + +fn native_route_population_unique(receipt: NativeRouteReceipt) -> Bool { + native_route_identities_unique( + xs: native_route_row_identities(rows: receipt.population) + ) +} + +// THE EXACT JOIN, BOTH DIRECTIONS. "No silent remainder" is two questions: every universe member +// carries exactly one observed row (no omissions), and every observed row names a universe +// member (no foreign rows — a cherry-picked or padded population refuses here). With uniqueness +// on both sides, the two directions together are set equality at identity grain. +fn native_route_population_covers_universe(receipt: NativeRouteReceipt) -> Bool { + !any(xs: receipt.universe, predicate: fn(member) { + !any(xs: receipt.population, predicate: fn(row) { + native_route_identity_eq(a: row.identity, b: member) + }) + }) +} + +fn native_route_population_no_foreign(receipt: NativeRouteReceipt) -> Bool { + !any(xs: receipt.population, predicate: fn(row) { + !native_route_identity_member(target: row.identity, xs: receipt.universe) + }) +} + +fn native_route_positive_population_present(census: NativeRouteCensus) -> Bool { + census.agreement_pass > 0 +} + +fn native_route_no_divergences(census: NativeRouteCensus) -> Bool { + census.divergences == 0 +} + +fn native_route_exclusions_all_classified(census: NativeRouteCensus) -> Bool { + census.excluded_unattributed == 0 +} + +// EVERY FILE REFUSAL IS OWNED AT FATAL GRAIN: the fatal reason is the cause that refused the +// file, so its ownership row is the FatalGrain one. An unowned fatal cause is a new front-end +// frontier fact surfacing where nobody counted it, and it blocks admission by its own name. +fn native_route_file_refusals_attributed(receipt: NativeRouteReceipt) -> Bool { + !any(xs: receipt.file_refusals, predicate: fn(fr) { + match cause_ownership_lookup(cause: fr.fatal_reason, grain: FatalGrain) { + Present { value: _ } => false + Absent => true + } + }) +} + +// THE HEAD REASON IS NOT DEAD RECEIPT DATA. rejected_with_pending prepends the non-fatal +// advisory, so the head reason is a real observation of the frontier at a different grain — the +// same observation the door ledger's own rows carry. Every file refusal's head reason must be +// owned at HEAD grain: an unowned advisory is a new frontier cause surfacing where nobody +// counted it, and it blocks admission by its own name rather than riding along unclassified. +fn native_route_head_advisories_attributed(receipt: NativeRouteReceipt) -> Bool { + !any(xs: receipt.file_refusals, predicate: fn(fr) { + match cause_ownership_lookup(cause: fr.head_reason, grain: HeadGrain) { + Present { value: _ } => false + Absent => true + } + }) +} + +// A CONTEXT-STAGE REFUSAL IS A DERIVED CLASSIFICATION, AND DERIVED CLASSIFICATIONS ARE FORGEABLE +// UNTIL BOUND TO THEIR OBSERVATION. The host reclassifies a member whose entry file never loaded +// as Context-stage with the file's fatal reason; the receipt therefore carries the file-refusal +// population, and every Context-stage row's reason must appear among the observed file refusals' +// fatal reasons. A Context-stage row whose reason no file refusal carries was minted without its +// evidence — admission refuses. +fn native_route_context_refusals_backed(receipt: NativeRouteReceipt) -> Bool { + !any(xs: receipt.population, predicate: fn(row) { match row.verdict { - NativeAccepted => true - NativeRefused { head_reason: _, fatal_reason: _ } => false + NativeTestRefused { stage: stage, reason: reason } => + match stage { + NativeTestStageContext => + !any(xs: receipt.file_refusals, predicate: fn(fr) { fr.fatal_reason == reason }) + NativeTestStagePrepare => false + NativeTestStageEntry => false + NativeTestStageEval => false + } + NativeTestPassed => false + NativeTestReturnedFalse => false + NativeTestReturnedOther => false } - })) > 0 + }) } -fn native_route_broken_control_holds(receipt: NativeRouteReceipt) -> Bool { - match receipt.broken_control { - BrokenSpecimenRefused { reason: _ } => true - BrokenSpecimenAccepted => false +fn native_route_false_control_holds(receipt: NativeRouteReceipt) -> Bool { + match receipt.false_control { + Present { value: v } => + match v { + NativeTestReturnedFalse => true + NativeTestPassed => false + NativeTestReturnedOther => false + NativeTestRefused { stage: _, reason: _ } => false + } + Absent => false } } -fn native_route_exclusions_all_classified(receipt: NativeRouteReceipt) -> Bool { - native_route_exclusion_census(rows: receipt.population).excluded_unattributed == 0 +fn native_route_true_control_holds(receipt: NativeRouteReceipt) -> Bool { + match receipt.true_control { + Present { value: v } => + match v { + NativeTestPassed => true + NativeTestReturnedFalse => false + NativeTestReturnedOther => false + NativeTestRefused { stage: _, reason: _ } => false + } + Absent => false + } } -fn native_route_identity_recorded(receipt: NativeRouteReceipt) -> Bool { - receipt.executable_path != "" && receipt.executable_identity != "" +fn native_route_malformed_control_holds(receipt: NativeRouteReceipt) -> Bool { + match receipt.malformed_control { + MalformedSpecimenRefused { path: path, reason: _ } => path != "" + MalformedSpecimenAccepted => false + } } fn native_route_old_route_closed(receipt: NativeRouteReceipt) -> Bool { match receipt.old_route_control { - OldRouteWithdrawn { withdrawn_executable: _ } => true - OldRouteFalsifierArmed { falsifier_identity: _ } => true + OldRouteWithdrawn { withdrawn_executable: p } => p != "" + OldRouteFalsifierArmed { falsifier_identity: f } => f != "" OldRouteControlAbsent => false } } fn native_route_admission(receipt: NativeRouteReceipt) -> NativeRouteAdmission { - let c1 = if native_route_job_name_matches(receipt: receipt) { [] } else { [JobNameMismatch] } - let c2 = if native_route_tested_tree_recorded(receipt: receipt) { c1 } else { concat(c1, [TestedTreeUnrecorded]) } - let c3 = if native_route_preparation_seed_recorded(receipt: receipt) { c2 } else { concat(c2, [PreparationSeedUnrecorded]) } - let c4 = if native_route_emitted_closure_recorded(receipt: receipt) { c3 } else { concat(c3, [EmittedClosureUnrecorded]) } - let c5 = if native_route_population_derived(receipt: receipt) { c4 } else { concat(c4, [PopulationUnderived]) } - let c6 = if native_route_positive_population_present(receipt: receipt) { c5 } else { concat(c5, [PositivePopulationEmpty]) } - let c7 = if native_route_broken_control_holds(receipt: receipt) { c6 } else { concat(c6, [BrokenSpecimenAcceptedByRoute]) } - let c8 = if native_route_exclusions_all_classified(receipt: receipt) { c7 } else { concat(c7, [UnattributedExclusionsPresent]) } - let c9 = if native_route_head_advisories_all_attributed(rows: receipt.population) { c8 } else { concat(c8, [UnattributedHeadAdvisoryPresent]) } - let c10 = if native_route_identity_recorded(receipt: receipt) { c9 } else { concat(c9, [RouteIdentityUnrecorded]) } - let causes = if native_route_old_route_closed(receipt: receipt) { c10 } else { concat(c10, [OldRouteControlNeverInstalled]) } + let expected_red = floor_expected_red_roster() + let route_gap = floor_route_gap_roster() + let census = native_route_census( + rows: receipt.population, + expected_red: expected_red, + route_gap: route_gap + ) + let checks: List = [ + NativeRouteClauseCheck { holds: native_route_job_name_matches(receipt: receipt), cause: JobNameMismatch }, + NativeRouteClauseCheck { holds: native_route_tested_tree_recorded(receipt: receipt), cause: TestedTreeUnrecorded }, + NativeRouteClauseCheck { holds: native_route_preparation_seed_recorded(receipt: receipt), cause: PreparationSeedUnrecorded }, + NativeRouteClauseCheck { holds: native_route_emitted_closure_recorded(receipt: receipt), cause: EmittedClosureUnrecorded }, + NativeRouteClauseCheck { holds: native_route_identity_recorded(receipt: receipt), cause: RouteIdentityUnrecorded }, + NativeRouteClauseCheck { holds: native_route_universe_derived(receipt: receipt), cause: UniverseUnderived }, + NativeRouteClauseCheck { holds: native_route_universe_unique(receipt: receipt), cause: UniverseDuplicatesPresent }, + NativeRouteClauseCheck { holds: native_route_universe_prefix_closed(receipt: receipt), cause: UniversePrefixViolationsPresent }, + NativeRouteClauseCheck { holds: native_route_population_unique(receipt: receipt), cause: PopulationDuplicatesPresent }, + NativeRouteClauseCheck { holds: native_route_population_no_foreign(receipt: receipt), cause: PopulationForeignRowsPresent }, + NativeRouteClauseCheck { holds: native_route_population_covers_universe(receipt: receipt), cause: PopulationOmissionsPresent }, + NativeRouteClauseCheck { holds: native_route_positive_population_present(census: census), cause: PositivePopulationEmpty }, + NativeRouteClauseCheck { holds: native_route_no_divergences(census: census), cause: DivergencesPresent }, + NativeRouteClauseCheck { holds: native_route_exclusions_all_classified(census: census), cause: UnattributedExclusionsPresent }, + NativeRouteClauseCheck { holds: native_route_file_refusals_attributed(receipt: receipt), cause: UnattributedFileRefusalsPresent }, + NativeRouteClauseCheck { holds: native_route_head_advisories_attributed(receipt: receipt), cause: UnattributedHeadAdvisoriesPresent }, + NativeRouteClauseCheck { holds: native_route_context_refusals_backed(receipt: receipt), cause: ContextRefusalsUnbacked }, + NativeRouteClauseCheck { holds: native_route_false_control_holds(receipt: receipt), cause: FalseControlNotObserved }, + NativeRouteClauseCheck { holds: native_route_true_control_holds(receipt: receipt), cause: TrueControlNotObserved }, + NativeRouteClauseCheck { holds: native_route_malformed_control_holds(receipt: receipt), cause: MalformedControlAcceptedByRoute }, + NativeRouteClauseCheck { holds: native_route_old_route_closed(receipt: receipt), cause: OldRouteControlNeverInstalled } + ] + let causes = fold_list( + xs: checks, + empty: [], + cons: fn(acc, check) { + if check.holds { acc } else { concat(acc, [check.cause]) } + } + ) if is_empty(xs: causes) { NativeRouteAdmitted } else { NativeRouteRefused { causes: causes } } } // The job's terminal print renders causes by name; the projection lives beside the coproduct, // so a new cause refuses to compile until it is named (exhaustive match). The membership probe -// is how a witness — and the enrolment gate's report — asks for one clause's verdict without +// is how a witness — and the job's own report — asks for one clause's verdict without // re-matching the whole coproduct. fn native_route_admission_refusal_cause_name(cause: NativeRouteAdmissionRefusalCause) -> String { match cause { @@ -337,12 +718,22 @@ fn native_route_admission_refusal_cause_name(cause: NativeRouteAdmissionRefusalC TestedTreeUnrecorded => "tested_tree_unrecorded" PreparationSeedUnrecorded => "preparation_seed_unrecorded" EmittedClosureUnrecorded => "emitted_closure_unrecorded" - PopulationUnderived => "population_underived" + RouteIdentityUnrecorded => "route_identity_unrecorded" + UniverseUnderived => "universe_underived" + UniverseDuplicatesPresent => "universe_duplicates_present" + UniversePrefixViolationsPresent => "universe_prefix_violations_present" + PopulationDuplicatesPresent => "population_duplicates_present" + PopulationForeignRowsPresent => "population_foreign_rows_present" + PopulationOmissionsPresent => "population_omissions_present" PositivePopulationEmpty => "positive_population_empty" - BrokenSpecimenAcceptedByRoute => "broken_specimen_accepted_by_route" + DivergencesPresent => "divergences_present" UnattributedExclusionsPresent => "unattributed_exclusions_present" - UnattributedHeadAdvisoryPresent => "unattributed_head_advisory_present" - RouteIdentityUnrecorded => "route_identity_unrecorded" + UnattributedFileRefusalsPresent => "unattributed_file_refusals_present" + UnattributedHeadAdvisoriesPresent => "unattributed_head_advisories_present" + ContextRefusalsUnbacked => "context_refusals_unbacked" + FalseControlNotObserved => "false_control_not_observed" + TrueControlNotObserved => "true_control_not_observed" + MalformedControlAcceptedByRoute => "malformed_control_accepted_by_route" OldRouteControlNeverInstalled => "old_route_control_never_installed" } } @@ -385,49 +776,3 @@ fn native_route_admission_summary(admission: NativeRouteAdmission) -> String { ) } } - -// ENROLMENT IS THE ADMISSION QUESTION ASKED OF THE REAL CENSUS. The gate derives the lane's -// enrolment state from a census receipt — the same predicate the enrolled job will gate on, -// asked before enrolment, so the enrolment decision and the standing verdict can never be two -// facts. -type NativeRouteEnrolment - = EnrolmentOpen - | EnrolmentBlocked { causes: List } - -fn native_route_enrolment(census: NativeRouteReceipt) -> NativeRouteEnrolment { - match native_route_admission(receipt: census) { - NativeRouteAdmitted => EnrolmentOpen - NativeRouteRefused { causes: causes } => EnrolmentBlocked { causes: causes } - } -} - -// TODAY'S STANDING, AS DATA. The lane is modelled in full and its enrolment is BLOCKED on the -// compile-door frontier. The trigger names the CAPABILITY that unblocks it (DESIGN 4b(3): a -// trigger names the capability, never an artifact that would contribute to one), and the edit -// row names the exact mechanical change that lands it, so the future lane author executes -// rather than re-derives. This row DELETES at enrolment: once the job stands in the required -// workflow, the standing it records here is the job's own receipt, not a row beside it. -type NativeRouteEnrolmentState - = EnrolmentOpenToday - | EnrolmentAwaitingDoorFrontier - -type NativeRouteEnrolmentStanding { - state: NativeRouteEnrolmentState - trigger: String - enrolment_edit: String - census_evidence: String -} - -data native_route_enrolment_standing: NativeRouteEnrolmentStanding = NativeRouteEnrolmentStanding { - state: EnrolmentAwaitingDoorFrontier, - trigger: "the emitted native compiler accepts at least one real v2.test.* module — the compile-door frontier (v2.workflow.compile_door_ledger known_frontier_causes burn-down) has closed far enough that the derived positive population is non-empty; re-derive by running the census recipe named in census_evidence and evaluating native_route_enrolment over its receipt", - enrolment_edit: "add V2NativeLane to gunbc.required_ci_phase_roster RequiredCiLane with its phase variant(s) enrolled in required_ci_phases and owned by the new lane in required_ci_phase_lane, mirror the variants in claim_executor (the per-run host join refuses a divergence in either direction), add WitnessFloorV2NativeLane to gunbc.witness_floor_workflow witness_floor_lane_roster with its job materialization (seed builds once, emits the compiler closure, cargo-builds it, withdraws the old route, runs the census and both controls, prints the receipt, gates on native_route_admission), add the lane to required_lanes_roster so the aggregate witnesses job needs it, regenerate .github/workflows/witnesses.yml", - census_evidence: "local census harness on the PR-#10692 head (tree f173aaa24860a0efe7b2142cefcedc69675ff66f): emit via `gunbc compile --entry src/v2/compiler/00_compile.dag --source-root dag --source-root src/v2 --target rust`, cargo build with the repo-pinned toolchain, withdraw the seed binary, invoke the emitted binary by explicit path over every derived universe member, classify each verdict with classify_native_refusal. Measured 2026-09-09 over the derived universe of 882 members: zero accepted, 882 refused with every fatal cause attributed (the five causes the door-ledger table lacked were added to it by this lane's measurement), the broken specimen refused, and native_route_admission over the real receipt executed to `refused: positive_population_empty`" -} - -fn native_route_enrolment_blocked_today(standing: NativeRouteEnrolmentStanding) -> Bool { - match standing.state { - EnrolmentAwaitingDoorFrontier => true - EnrolmentOpenToday => false - } -} diff --git a/src/v2/test/v2_native_route_test.dag b/src/v2/test/v2_native_route_test.dag index 93f864dbcda..a37f42c15a1 100644 --- a/src/v2/test/v2_native_route_test.dag +++ b/src/v2/test/v2_native_route_test.dag @@ -1,38 +1,88 @@ module v2.test.v2_native_route import gunbc.witness_v2_native_route { - NativeRouteVerdict, NativeAccepted, NativeRefused, + NativeRouteTestIdentity, native_route_identity_qualified, native_route_identities_unique, + native_route_universe_member, + NativeRouteReference, FloorExpectedPass, FloorExpectedRed, FloorRouteGapRefusal, + native_route_reference_for, NativeRouteMemberRow, - NativeRouteExclusionCause, CompilerFrontierAttributed, NativeDriverAdmissionLimit, UnattributedRefusal, + NativeRouteExclusionCause, CompilerFrontierAttributed, NativeEntryResolutionLimit, + NativeEvalBoundary, UnattributedRefusal, classify_native_refusal, + NativeRouteDisposition, NativeAgreementPass, NativeAgreementExpectedRed, + NativeAgreementRouteGap, NativeExclusion, NativeDivergence, + NativeRouteDivergenceCause, NativePassedExpectedRed, NativeFalseExpectedPass, + NativeReturnedOtherVerdict, NativeReachedRouteGap, + native_route_disposition, + NativeRouteCensus, native_route_census, + NativeRouteMalformedControl, MalformedSpecimenRefused, MalformedSpecimenAccepted, NativeRouteOldRouteControl, OldRouteWithdrawn, OldRouteFalsifierArmed, OldRouteControlAbsent, - NativeRouteBrokenControl, BrokenSpecimenRefused, BrokenSpecimenAccepted, NativeRouteReceipt, NativeRouteAdmission, NativeRouteAdmitted, NativeRouteRefused, - NativeRouteEnrolment, EnrolmentOpen, EnrolmentBlocked, required_v2_native_job_name, - native_route_universe_member, - native_route_universe, - classify_native_refusal, - native_route_exclusion_census, - native_route_census_is_total, - native_route_admission, - native_route_admitted, - native_route_admission_refused_with, - native_route_admission_summary, - native_route_enrolment, - native_route_enrolment_standing, - native_route_enrolment_blocked_today, -} -import std.algebra { Cons, Empty } -import v2.std.algebra { length } + native_route_admission, native_route_admitted, native_route_admission_refused_with, + native_route_admission_summary +} +import v2.compiler.compile { + NativeTestVerdict, NativeTestPassed, NativeTestReturnedFalse, NativeTestReturnedOther, + NativeTestRefused, + NativeTestStage, NativeTestStageContext, NativeTestStagePrepare, NativeTestStageEntry, + NativeTestStageEval, + NativeTestFileRefusal +} +import v2.std.optional { Absent, Optional, Present } import v2.std.collection { List } import v2.std.logic { Bool } import v2.std.node { Symbol } +import v2.std.integer { Int } + +// ── synthetic receipt constructors ───────────────────────────────────────────────────────── +// +// The admission fold reads the REAL floor rosters (floor_expected_red_roster, +// floor_route_gap_roster) from inside native_route_admission, so the synthetic identities here +// are chosen OFF every roster: "v2.test.*" names no real module carries. The disposition, +// reference and census functions take their rosters as arguments, so their tests are fully +// synthetic. + +fn test_identity(module: String, declaration: String) -> NativeRouteTestIdentity { + NativeRouteTestIdentity { module: module, declaration: declaration } +} + +fn passed_row(module: String, declaration: String) -> NativeRouteMemberRow { + NativeRouteMemberRow { + identity: test_identity(module: module, declaration: declaration), + verdict: NativeTestPassed + } +} + +fn false_row(module: String, declaration: String) -> NativeRouteMemberRow { + NativeRouteMemberRow { + identity: test_identity(module: module, declaration: declaration), + verdict: NativeTestReturnedFalse + } +} -fn receipt_with( +fn refused_row( + module: String, + declaration: String, + stage: NativeTestStage, + reason: Symbol +) -> NativeRouteMemberRow { + NativeRouteMemberRow { + identity: test_identity(module: module, declaration: declaration), + verdict: NativeTestRefused { stage: stage, reason: reason } + } +} + +fn file_refusal_row(path: Symbol, head: Symbol, fatal: Symbol) -> NativeTestFileRefusal { + NativeTestFileRefusal { path: path, head_reason: head, fatal_reason: fatal } +} + +// A receipt with every well-formedness field recorded and both live controls observed; the +// universe, population and file-refusal populations are the arguments under test. +fn receipt_over( + universe: List, population: List, - broken_control: NativeRouteBrokenControl, - old_route_control: NativeRouteOldRouteControl + file_refusals: List ) -> NativeRouteReceipt { NativeRouteReceipt { job: required_v2_native_job_name, @@ -41,325 +91,815 @@ fn receipt_with( emitted_closure_identity: "closure-sha", executable_path: "/emitted/compiler", executable_identity: "binary-sha", + universe: universe, population: population, - broken_control: broken_control, - old_route_control: old_route_control + file_refusals: file_refusals, + false_control: Present { value: NativeTestReturnedFalse }, + true_control: Present { value: NativeTestPassed }, + malformed_control: MalformedSpecimenRefused { + path: "fixtures/native_lane_malformed/poison.dag", + reason: ^tokenize_lex_e1_unterminated_string + }, + old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } } } -fn accepted_member(path: String) -> NativeRouteMemberRow { - NativeRouteMemberRow { module_path: path, verdict: NativeAccepted } +// The smallest green receipt: one passing member, universe and population equal. +fn green_receipt() -> NativeRouteReceipt { + receipt_over( + universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + population: [passed_row(module: "v2.test.alpha", declaration: "passes")], + file_refusals: [] + ) } -// A single-reason refusal: head and fatal are the same observation (no pending advisory rode -// along). Driver-limit refusals have this shape. -fn refused_member(path: String, reason: Symbol) -> NativeRouteMemberRow { - NativeRouteMemberRow { - module_path: path, - verdict: NativeRefused { head_reason: reason, fatal_reason: reason } +// ── the universe is a prefix ─────────────────────────────────────────────────────────────── + +test fn the_universe_is_the_prefix_and_nothing_else() -> Bool { + native_route_universe_member(module_qn: "v2.test.v2_native_route") + && native_route_universe_member(module_qn: "v2.test.") + && !native_route_universe_member(module_qn: "v2.compiler.compile") + && !native_route_universe_member(module_qn: "v2.test") + && !native_route_universe_member(module_qn: "dag.test.claim") +} + +test fn identity_qualification_derives_the_dotted_spelling() -> Bool { + native_route_identity_qualified( + identity: test_identity(module: "v2.test.alpha", declaration: "passes") + ) == "v2.test.alpha.passes" +} + +test fn duplicate_identities_are_detected_at_identity_grain() -> Bool { + let dup = test_identity(module: "v2.test.alpha", declaration: "passes") + !native_route_identities_unique(xs: [ + dup, + test_identity(module: "v2.test.beta", declaration: "passes"), + dup + ]) + && native_route_identities_unique(xs: [ + test_identity(module: "v2.test.alpha", declaration: "passes"), + test_identity(module: "v2.test.alpha", declaration: "fails"), + test_identity(module: "v2.test.beta", declaration: "passes") + ]) +} + +// ── the reference is the floor's rosters joined at the identity ──────────────────────────── + +test fn the_reference_is_the_floor_rosters_joined_at_the_identity() -> Bool { + match native_route_reference_for(qualified: "v2.test.alpha.red", expected_red: ["v2.test.alpha.red"], route_gap: []) { + FloorExpectedRed => true + FloorExpectedPass => false + FloorRouteGapRefusal => false } } -// The two-grain refusal shape the real census measures: rejected_with_pending prepends the -// non-fatal overlap advisory, so the HEAD is the advisory and the FATAL is what refused. -fn refused_member_at(path: String, head: Symbol, fatal: Symbol) -> NativeRouteMemberRow { - NativeRouteMemberRow { - module_path: path, - verdict: NativeRefused { head_reason: head, fatal_reason: fatal } +test fn the_reference_defaults_to_the_floors_standing_expectation() -> Bool { + match native_route_reference_for(qualified: "v2.test.alpha.green", expected_red: [], route_gap: []) { + FloorExpectedPass => true + FloorExpectedRed => false + FloorRouteGapRefusal => false } } -fn green_receipt() -> NativeRouteReceipt { - receipt_with( - population: [ - accepted_member(path: "v2.test.one"), - accepted_member(path: "v2.test.two"), - refused_member_at( - path: "v2.test.three", - head: ^parse_grammar_choice_overlap_residue, - fatal: ^parse_g0_tokens_remain - ) - ], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } - ) +test fn route_gap_membership_wins_over_expected_red() -> Bool { + match native_route_reference_for( + qualified: "v2.test.alpha.gapped", + expected_red: ["v2.test.alpha.gapped"], + route_gap: ["v2.test.alpha.gapped"] + ) { + FloorRouteGapRefusal => true + FloorExpectedPass => false + FloorExpectedRed => false + } } -// TODAY'S FRONTIER SHAPE: every universe member refused with an attributed cause, the broken -// specimen refused, both controls present — and admission still refuses, because an all-refused -// census proves the route refused, not that it routed. -fn frontier_receipt() -> NativeRouteReceipt { - receipt_with( - population: [ - refused_member_at( - path: "v2.test.one", - head: ^parse_grammar_choice_overlap_residue, - fatal: ^parse_g0_tokens_remain - ), - refused_member(path: "v2.test.two", reason: ^resolve_module_not_found) - ], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } - ) +// ── classification is stage- and provenance-keyed ────────────────────────────────────────── + +test fn an_entry_stage_refusal_is_a_driver_limit_only_at_a_known_reason() -> Bool { + match classify_native_refusal(stage: NativeTestStageEntry, reason: ^native_test_entry_declaration_not_arrow) { + NativeEntryResolutionLimit { reason: r } => r == ^native_test_entry_declaration_not_arrow + CompilerFrontierAttributed { reason: _ } => false + NativeEvalBoundary { reason: _ } => false + UnattributedRefusal { reason: _ } => false + } + && match classify_native_refusal(stage: NativeTestStageEntry, reason: ^parse_g0_tokens_remain) { + NativeEntryResolutionLimit { reason: _ } => false + CompilerFrontierAttributed { reason: _ } => false + NativeEvalBoundary { reason: _ } => false + UnattributedRefusal { reason: r } => r == ^parse_g0_tokens_remain + } } -test fn universe_membership_is_the_prefix_and_nothing_else() -> Bool { - native_route_universe_member(module_qn: "v2.test.foo") - && native_route_universe_member(module_qn: "v2.test.claim.long.bar") - && !native_route_universe_member(module_qn: "v2.test") - && !native_route_universe_member(module_qn: "v2.testlong.foo") - && !native_route_universe_member(module_qn: "test.claim.infer_x") - && !native_route_universe_member(module_qn: "v2.workflow.compile_door_ledger") +test fn an_eval_stage_refusal_is_the_boundary_only_at_a_boundary_reason() -> Bool { + match classify_native_refusal(stage: NativeTestStageEval, reason: ^eval_rejected_effect_io_unbound) { + NativeEvalBoundary { reason: r } => r == ^eval_rejected_effect_io_unbound + CompilerFrontierAttributed { reason: _ } => false + NativeEntryResolutionLimit { reason: _ } => false + UnattributedRefusal { reason: _ } => false + } + && match classify_native_refusal(stage: NativeTestStageEval, reason: ^eval_some_unowned_reason) { + NativeEvalBoundary { reason: _ } => false + CompilerFrontierAttributed { reason: _ } => false + NativeEntryResolutionLimit { reason: _ } => false + UnattributedRefusal { reason: r } => r == ^eval_some_unowned_reason + } } -test fn universe_derivation_selects_exactly_the_prefix_members() -> Bool { - let derived = native_route_universe(module_qns: [ - "v2.test.a", - "v2.workflow.compile_door_ledger", - "v2.test.claim.b", - "gunbc.witness_floor_workflow" - ]) - length(xs: derived) == 2 - && match derived { - Cons { head: first, tail: rest } => - match rest { - Cons { head: second, tail: Empty {} } => - first == "v2.test.a" && second == "v2.test.claim.b" - Cons { head: _, tail: _ } => false - Empty {} => false - } - Empty {} => false - } -} - -test fn refusal_classification_delegates_to_the_door_ledger_at_fatal_grain() -> Bool { - match classify_native_refusal(reason: ^parse_g0_tokens_remain) { +test fn a_context_stage_refusal_is_frontier_attributed_only_when_the_ledger_owns_it() -> Bool { + match classify_native_refusal(stage: NativeTestStageContext, reason: ^parse_g0_tokens_remain) { CompilerFrontierAttributed { reason: r } => r == ^parse_g0_tokens_remain - NativeDriverAdmissionLimit { reason: _ } => false + NativeEntryResolutionLimit { reason: _ } => false + NativeEvalBoundary { reason: _ } => false UnattributedRefusal { reason: _ } => false } + && match classify_native_refusal(stage: NativeTestStageContext, reason: ^some_unowned_cause) { + CompilerFrontierAttributed { reason: _ } => false + NativeEntryResolutionLimit { reason: _ } => false + NativeEvalBoundary { reason: _ } => false + UnattributedRefusal { reason: r } => r == ^some_unowned_cause + } } -// THE GRAINS ARE DIFFERENT OBSERVATIONS: fact_density_hollow_alias_locus is owned at HEAD grain -// (the door ledger's own rows) and has no FatalGrain row, so asking the fatal question of it is -// honestly unattributed — the lookup never confuses ownership at one grain for ownership at the -// other. -test fn a_head_owned_cause_is_not_thereby_fatal_owned() -> Bool { - match classify_native_refusal(reason: ^fact_density_hollow_alias_locus) { - UnattributedRefusal { reason: r } => r == ^fact_density_hollow_alias_locus +test fn a_prepare_stage_refusal_is_frontier_attributed_only_when_the_ledger_owns_it() -> Bool { + match classify_native_refusal(stage: NativeTestStagePrepare, reason: ^parse_g0_tokens_remain) { + CompilerFrontierAttributed { reason: r } => r == ^parse_g0_tokens_remain + NativeEntryResolutionLimit { reason: _ } => false + NativeEvalBoundary { reason: _ } => false + UnattributedRefusal { reason: _ } => false + } + && match classify_native_refusal(stage: NativeTestStagePrepare, reason: ^some_unowned_cause) { CompilerFrontierAttributed { reason: _ } => false - NativeDriverAdmissionLimit { reason: _ } => false + NativeEntryResolutionLimit { reason: _ } => false + NativeEvalBoundary { reason: _ } => false + UnattributedRefusal { reason: r } => r == ^some_unowned_cause } } -test fn refusal_classification_owns_the_driver_admission_limit() -> Bool { - match classify_native_refusal(reason: ^resolve_module_not_found) { - NativeDriverAdmissionLimit { reason: r } => r == ^resolve_module_not_found - CompilerFrontierAttributed { reason: _ } => false - UnattributedRefusal { reason: _ } => false +// ── the disposition matrix ───────────────────────────────────────────────────────────────── + +test fn a_pass_agrees_with_the_standing_expectation() -> Bool { + match native_route_disposition(verdict: NativeTestPassed, reference: FloorExpectedPass) { + NativeAgreementPass => true + NativeAgreementExpectedRed => false + NativeAgreementRouteGap => false + NativeExclusion { cause: _ } => false + NativeDivergence { cause: _ } => false } } -test fn refusal_classification_never_silently_buckets_an_unowned_cause() -> Bool { - match classify_native_refusal(reason: ^a_cause_nobody_owns) { - UnattributedRefusal { reason: r } => r == ^a_cause_nobody_owns - CompilerFrontierAttributed { reason: _ } => false - NativeDriverAdmissionLimit { reason: _ } => false +test fn a_pass_against_an_expected_red_identity_is_a_divergence() -> Bool { + match native_route_disposition(verdict: NativeTestPassed, reference: FloorExpectedRed) { + NativeDivergence { cause: c } => + match c { + NativePassedExpectedRed => true + NativeFalseExpectedPass => false + NativeReturnedOtherVerdict => false + NativeReachedRouteGap => false + } + NativeAgreementPass => false + NativeAgreementExpectedRed => false + NativeAgreementRouteGap => false + NativeExclusion { cause: _ } => false } } -test fn exclusion_census_counts_every_member_exactly_once() -> Bool { - let rows = [ - accepted_member(path: "v2.test.a"), - refused_member_at( - path: "v2.test.b", - head: ^parse_grammar_choice_overlap_residue, - fatal: ^parse_g0_tokens_remain - ), - refused_member(path: "v2.test.c", reason: ^resolve_module_not_found), - refused_member(path: "v2.test.d", reason: ^a_cause_nobody_owns) - ] - let census = native_route_exclusion_census(rows: rows) - census.accepted == 1 - && census.excluded_compiler_frontier == 1 - && census.excluded_driver_limit == 1 - && census.excluded_unattributed == 1 - && native_route_census_is_total(census: census, rows: rows) -} - -test fn a_fully_met_contract_admits() -> Bool { - native_route_admitted(admission: native_route_admission(receipt: green_receipt())) +test fn a_false_against_an_expected_red_identity_is_the_expected_red_observed() -> Bool { + match native_route_disposition(verdict: NativeTestReturnedFalse, reference: FloorExpectedRed) { + NativeAgreementExpectedRed => true + NativeAgreementPass => false + NativeAgreementRouteGap => false + NativeExclusion { cause: _ } => false + NativeDivergence { cause: _ } => false + } } -test fn an_empty_positive_population_refuses_even_with_everything_else_held() -> Bool { - let admission = native_route_admission(receipt: frontier_receipt()) - !native_route_admitted(admission: admission) - && native_route_admission_refused_with(admission: admission, cause_name: "positive_population_empty") +test fn a_false_against_the_standing_expectation_is_a_divergence() -> Bool { + match native_route_disposition(verdict: NativeTestReturnedFalse, reference: FloorExpectedPass) { + NativeDivergence { cause: c } => + match c { + NativeFalseExpectedPass => true + NativePassedExpectedRed => false + NativeReturnedOtherVerdict => false + NativeReachedRouteGap => false + } + NativeAgreementPass => false + NativeAgreementExpectedRed => false + NativeAgreementRouteGap => false + NativeExclusion { cause: _ } => false + } } -test fn a_broken_specimen_the_route_accepts_kills_the_whole_census() -> Bool { - let admission = native_route_admission(receipt: receipt_with( - population: [accepted_member(path: "v2.test.one")], - broken_control: BrokenSpecimenAccepted, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } - )) - native_route_admission_refused_with(admission: admission, cause_name: "broken_specimen_accepted_by_route") +test fn a_non_boolean_return_is_a_divergence_on_every_reference() -> Bool { + match native_route_disposition(verdict: NativeTestReturnedOther, reference: FloorExpectedPass) { + NativeDivergence { cause: NativeReturnedOtherVerdict } => true + _ => false + } + && match native_route_disposition(verdict: NativeTestReturnedOther, reference: FloorExpectedRed) { + NativeDivergence { cause: NativeReturnedOtherVerdict } => true + _ => false + } + && match native_route_disposition(verdict: NativeTestReturnedOther, reference: FloorRouteGapRefusal) { + NativeDivergence { cause: NativeReturnedOtherVerdict } => true + _ => false + } } -test fn an_unattributed_exclusion_blocks_admission_until_someone_owns_it() -> Bool { - let admission = native_route_admission(receipt: receipt_with( - population: [ - accepted_member(path: "v2.test.one"), - refused_member(path: "v2.test.two", reason: ^a_cause_nobody_owns) +test fn a_reached_verdict_on_a_route_gap_identity_is_a_divergence() -> Bool { + match native_route_disposition(verdict: NativeTestPassed, reference: FloorRouteGapRefusal) { + NativeDivergence { cause: NativeReachedRouteGap } => true + _ => false + } + && match native_route_disposition(verdict: NativeTestReturnedFalse, reference: FloorRouteGapRefusal) { + NativeDivergence { cause: NativeReachedRouteGap } => true + _ => false + } +} + +test fn the_route_gap_agrees_only_with_the_hermetic_boundary_refusal() -> Bool { + match native_route_disposition( + verdict: NativeTestRefused { stage: NativeTestStageEval, reason: ^eval_rejected_effect_io_unbound }, + reference: FloorRouteGapRefusal + ) { + NativeAgreementRouteGap => true + _ => false + } +} + +test fn a_route_gap_refusal_off_the_boundary_is_an_exclusion() -> Bool { + match native_route_disposition( + verdict: NativeTestRefused { stage: NativeTestStageEval, reason: ^some_unowned_cause }, + reference: FloorRouteGapRefusal + ) { + NativeExclusion { cause: UnattributedRefusal { reason: r } } => r == ^some_unowned_cause + _ => false + } + && match native_route_disposition( + verdict: NativeTestRefused { stage: NativeTestStagePrepare, reason: ^parse_g0_tokens_remain }, + reference: FloorRouteGapRefusal + ) { + NativeExclusion { cause: CompilerFrontierAttributed { reason: r } } => r == ^parse_g0_tokens_remain + _ => false + } +} + +// ── the census ───────────────────────────────────────────────────────────────────────────── + +test fn the_census_counts_every_disposition() -> Bool { + let census = native_route_census( + rows: [ + passed_row(module: "v2.test.a", declaration: "one"), + false_row(module: "v2.test.b", declaration: "two"), + refused_row(module: "v2.test.c", declaration: "three", stage: NativeTestStageEval, reason: ^eval_rejected_effect_io_unbound), + refused_row(module: "v2.test.d", declaration: "four", stage: NativeTestStagePrepare, reason: ^parse_g0_tokens_remain), + refused_row(module: "v2.test.e", declaration: "five", stage: NativeTestStageEntry, reason: ^native_test_entry_declaration_not_arrow), + refused_row(module: "v2.test.f", declaration: "six", stage: NativeTestStageEval, reason: ^eval_rejected_effect_io_unbound), + refused_row(module: "v2.test.g", declaration: "seven", stage: NativeTestStageContext, reason: ^some_unowned_cause), + passed_row(module: "v2.test.h", declaration: "eight") ], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } - )) - native_route_admission_refused_with(admission: admission, cause_name: "unattributed_exclusions_present") + expected_red: ["v2.test.b.two", "v2.test.h.eight"], + route_gap: ["v2.test.c.three"] + ) + census.agreement_pass == 1 + && census.agreement_expected_red == 1 + && census.agreement_route_gap == 1 + && census.excluded_compiler_frontier == 1 + && census.excluded_entry_limit == 1 + && census.excluded_eval_boundary == 1 + && census.excluded_unattributed == 1 + && census.divergences == 1 } -// THE HEAD ADVISORY IS LIVE RECEIPT DATA: a row whose FATAL cause is owned at fatal grain but -// whose HEAD advisory is owned nowhere refuses by the head clause's own name — and does NOT -// misreport as an exclusion-cause failure, because the grains are different questions. -test fn an_unattributed_head_advisory_blocks_admission_by_its_own_name() -> Bool { - let admission = native_route_admission(receipt: receipt_with( - population: [ - accepted_member(path: "v2.test.one"), - refused_member_at( - path: "v2.test.two", - head: ^an_advisory_nobody_owns, +// ── admission: the green shape ───────────────────────────────────────────────────────────── + +test fn a_green_receipt_is_admitted() -> Bool { + native_route_admitted(admission: native_route_admission(receipt: green_receipt())) +} + +// ── admission: well-formedness clauses ───────────────────────────────────────────────────── + +test fn the_wrong_job_name_is_refused() -> Bool { + let receipt = NativeRouteReceipt { + job: "required-v2-native-but-wrong", + tested_tree: green_receipt().tested_tree, + preparation_seed_identity: green_receipt().preparation_seed_identity, + emitted_closure_identity: green_receipt().emitted_closure_identity, + executable_path: green_receipt().executable_path, + executable_identity: green_receipt().executable_identity, + universe: green_receipt().universe, + population: green_receipt().population, + file_refusals: green_receipt().file_refusals, + false_control: green_receipt().false_control, + true_control: green_receipt().true_control, + malformed_control: green_receipt().malformed_control, + old_route_control: green_receipt().old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "job_name_mismatch" + ) +} + +test fn an_unrecorded_tested_tree_is_refused() -> Bool { + let receipt = NativeRouteReceipt { + job: green_receipt().job, + tested_tree: "", + preparation_seed_identity: green_receipt().preparation_seed_identity, + emitted_closure_identity: green_receipt().emitted_closure_identity, + executable_path: green_receipt().executable_path, + executable_identity: green_receipt().executable_identity, + universe: green_receipt().universe, + population: green_receipt().population, + file_refusals: green_receipt().file_refusals, + false_control: green_receipt().false_control, + true_control: green_receipt().true_control, + malformed_control: green_receipt().malformed_control, + old_route_control: green_receipt().old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "tested_tree_unrecorded" + ) +} + +test fn an_unrecorded_preparation_seed_is_refused() -> Bool { + let receipt = NativeRouteReceipt { + job: green_receipt().job, + tested_tree: green_receipt().tested_tree, + preparation_seed_identity: "", + emitted_closure_identity: green_receipt().emitted_closure_identity, + executable_path: green_receipt().executable_path, + executable_identity: green_receipt().executable_identity, + universe: green_receipt().universe, + population: green_receipt().population, + file_refusals: green_receipt().file_refusals, + false_control: green_receipt().false_control, + true_control: green_receipt().true_control, + malformed_control: green_receipt().malformed_control, + old_route_control: green_receipt().old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "preparation_seed_unrecorded" + ) +} + +test fn an_unrecorded_emitted_closure_is_refused() -> Bool { + let receipt = NativeRouteReceipt { + job: green_receipt().job, + tested_tree: green_receipt().tested_tree, + preparation_seed_identity: green_receipt().preparation_seed_identity, + emitted_closure_identity: "", + executable_path: green_receipt().executable_path, + executable_identity: green_receipt().executable_identity, + universe: green_receipt().universe, + population: green_receipt().population, + file_refusals: green_receipt().file_refusals, + false_control: green_receipt().false_control, + true_control: green_receipt().true_control, + malformed_control: green_receipt().malformed_control, + old_route_control: green_receipt().old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "emitted_closure_unrecorded" + ) +} + +test fn an_unrecorded_route_identity_is_refused() -> Bool { + let base = green_receipt() + let no_path = NativeRouteReceipt { + job: base.job, + tested_tree: base.tested_tree, + preparation_seed_identity: base.preparation_seed_identity, + emitted_closure_identity: base.emitted_closure_identity, + executable_path: "", + executable_identity: base.executable_identity, + universe: base.universe, + population: base.population, + file_refusals: base.file_refusals, + false_control: base.false_control, + true_control: base.true_control, + malformed_control: base.malformed_control, + old_route_control: base.old_route_control + } + let no_identity = NativeRouteReceipt { + job: base.job, + tested_tree: base.tested_tree, + preparation_seed_identity: base.preparation_seed_identity, + emitted_closure_identity: base.emitted_closure_identity, + executable_path: base.executable_path, + executable_identity: "", + universe: base.universe, + population: base.population, + file_refusals: base.file_refusals, + false_control: base.false_control, + true_control: base.true_control, + malformed_control: base.malformed_control, + old_route_control: base.old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: no_path), + cause_name: "route_identity_unrecorded" + ) + && native_route_admission_refused_with( + admission: native_route_admission(receipt: no_identity), + cause_name: "route_identity_unrecorded" + ) +} + +// ── admission: the universe shape ────────────────────────────────────────────────────────── + +test fn an_empty_universe_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over(universe: [], population: [], file_refusals: [])), + cause_name: "universe_underived" + ) +} + +test fn a_duplicated_universe_is_refused() -> Bool { + let member = test_identity(module: "v2.test.alpha", declaration: "passes") + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [member, member], + population: [passed_row(module: "v2.test.alpha", declaration: "passes")], + file_refusals: [] + )), + cause_name: "universe_duplicates_present" + ) +} + +test fn a_foreign_universe_member_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [test_identity(module: "v2.compiler.compile", declaration: "not_a_test")], + population: [passed_row(module: "v2.compiler.compile", declaration: "not_a_test")], + file_refusals: [] + )), + cause_name: "universe_prefix_violations_present" + ) +} + +// ── admission: the exact join ────────────────────────────────────────────────────────────── + +test fn a_duplicated_population_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + population: [ + passed_row(module: "v2.test.alpha", declaration: "passes"), + passed_row(module: "v2.test.alpha", declaration: "passes") + ], + file_refusals: [] + )), + cause_name: "population_duplicates_present" + ) +} + +test fn a_foreign_population_row_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + population: [ + passed_row(module: "v2.test.alpha", declaration: "passes"), + passed_row(module: "v2.test.beta", declaration: "extra") + ], + file_refusals: [] + )), + cause_name: "population_foreign_rows_present" + ) +} + +test fn an_omitted_population_row_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [ + test_identity(module: "v2.test.alpha", declaration: "passes"), + test_identity(module: "v2.test.beta", declaration: "missing") + ], + population: [passed_row(module: "v2.test.alpha", declaration: "passes")], + file_refusals: [] + )), + cause_name: "population_omissions_present" + ) +} + +// ── admission: the population must prove the route ───────────────────────────────────────── + +test fn an_all_refused_population_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [test_identity(module: "v2.test.alpha", declaration: "blocked")], + population: [refused_row( + module: "v2.test.alpha", + declaration: "blocked", + stage: NativeTestStagePrepare, + reason: ^parse_g0_tokens_remain + )], + file_refusals: [] + )), + cause_name: "positive_population_empty" + ) +} + +test fn a_divergence_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [test_identity(module: "v2.test.alpha", declaration: "lies")], + population: [false_row(module: "v2.test.alpha", declaration: "lies")], + file_refusals: [] + )), + cause_name: "divergences_present" + ) +} + +test fn an_unattributed_exclusion_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [ + test_identity(module: "v2.test.alpha", declaration: "passes"), + test_identity(module: "v2.test.beta", declaration: "blocked") + ], + population: [ + passed_row(module: "v2.test.alpha", declaration: "passes"), + refused_row( + module: "v2.test.beta", + declaration: "blocked", + stage: NativeTestStagePrepare, + reason: ^some_unowned_cause + ) + ], + file_refusals: [] + )), + cause_name: "unattributed_exclusions_present" + ) +} + +// ── admission: file-refusal ownership at both grains ─────────────────────────────────────── + +test fn an_unattributed_file_refusal_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + population: [passed_row(module: "v2.test.alpha", declaration: "passes")], + file_refusals: [file_refusal_row( + path: ^some_path, + head: ^parse_grammar_choice_overlap_residue, + fatal: ^some_unowned_cause + )] + )), + cause_name: "unattributed_file_refusals_present" + ) +} + +test fn an_unattributed_head_advisory_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + population: [passed_row(module: "v2.test.alpha", declaration: "passes")], + file_refusals: [file_refusal_row( + path: ^some_path, + head: ^some_unowned_advisory, fatal: ^parse_g0_tokens_remain + )] + )), + cause_name: "unattributed_head_advisories_present" + ) +} + +// ── admission: context refusals are bound to their observation ───────────────────────────── + +test fn a_context_refusal_without_its_file_refusal_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [ + test_identity(module: "v2.test.alpha", declaration: "passes"), + test_identity(module: "v2.test.beta", declaration: "blocked") + ], + population: [ + passed_row(module: "v2.test.alpha", declaration: "passes"), + refused_row( + module: "v2.test.beta", + declaration: "blocked", + stage: NativeTestStageContext, + reason: ^parse_g0_tokens_remain + ) + ], + file_refusals: [] + )), + cause_name: "context_refusals_unbacked" + ) +} + +test fn a_context_refusal_backed_by_its_file_refusal_is_not_refused_on_that_clause() -> Bool { + let admission = native_route_admission(receipt: receipt_over( + universe: [ + test_identity(module: "v2.test.alpha", declaration: "passes"), + test_identity(module: "v2.test.beta", declaration: "blocked") + ], + population: [ + passed_row(module: "v2.test.alpha", declaration: "passes"), + refused_row( + module: "v2.test.beta", + declaration: "blocked", + stage: NativeTestStageContext, + reason: ^parse_g0_tokens_remain ) ], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + file_refusals: [file_refusal_row( + path: ^some_path, + head: ^parse_grammar_choice_overlap_residue, + fatal: ^parse_g0_tokens_remain + )] )) - native_route_admission_refused_with(admission: admission, cause_name: "unattributed_head_advisory_present") - && !native_route_admission_refused_with(admission: admission, cause_name: "unattributed_exclusions_present") + !native_route_admission_refused_with(admission: admission, cause_name: "context_refusals_unbacked") + && native_route_admitted(admission: admission) } -// The driver-limit reason is owned by THIS lane at whichever grain it surfaces: a single- -// diagnostic driver refusal (head == fatal == resolve_module_not_found) passes the head join -// without a door-ledger row, because the door ledger never sees the driver's synthetic-subject -// refusal. -test fn a_driver_limit_head_advisory_is_owned_by_this_lane() -> Bool { - let admission = native_route_admission(receipt: frontier_receipt()) - !native_route_admission_refused_with(admission: admission, cause_name: "unattributed_head_advisory_present") +// ── admission: the controls ──────────────────────────────────────────────────────────────── + +test fn a_missing_false_control_is_refused() -> Bool { + let base = green_receipt() + let receipt = NativeRouteReceipt { + job: base.job, + tested_tree: base.tested_tree, + preparation_seed_identity: base.preparation_seed_identity, + emitted_closure_identity: base.emitted_closure_identity, + executable_path: base.executable_path, + executable_identity: base.executable_identity, + universe: base.universe, + population: base.population, + file_refusals: base.file_refusals, + false_control: Absent, + true_control: base.true_control, + malformed_control: base.malformed_control, + old_route_control: base.old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "false_control_not_observed" + ) } -test fn route_identity_is_observed_never_synthesized_from_the_intended_command() -> Bool { - let admission = native_route_admission(receipt: NativeRouteReceipt { - job: required_v2_native_job_name, - tested_tree: "tree-sha", - preparation_seed_identity: "seed-sha", - emitted_closure_identity: "closure-sha", - executable_path: "", - executable_identity: "", - population: [accepted_member(path: "v2.test.one")], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } - }) - native_route_admission_refused_with(admission: admission, cause_name: "route_identity_unrecorded") +test fn a_passed_false_control_is_refused() -> Bool { + let base = green_receipt() + let receipt = NativeRouteReceipt { + job: base.job, + tested_tree: base.tested_tree, + preparation_seed_identity: base.preparation_seed_identity, + emitted_closure_identity: base.emitted_closure_identity, + executable_path: base.executable_path, + executable_identity: base.executable_identity, + universe: base.universe, + population: base.population, + file_refusals: base.file_refusals, + false_control: Present { value: NativeTestPassed }, + true_control: base.true_control, + malformed_control: base.malformed_control, + old_route_control: base.old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "false_control_not_observed" + ) } -test fn an_uninstalled_old_route_control_refuses_by_name() -> Bool { - let admission = native_route_admission(receipt: receipt_with( - population: [accepted_member(path: "v2.test.one")], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteControlAbsent - )) - native_route_admission_refused_with(admission: admission, cause_name: "old_route_control_never_installed") +test fn a_missing_true_control_is_refused() -> Bool { + let base = green_receipt() + let receipt = NativeRouteReceipt { + job: base.job, + tested_tree: base.tested_tree, + preparation_seed_identity: base.preparation_seed_identity, + emitted_closure_identity: base.emitted_closure_identity, + executable_path: base.executable_path, + executable_identity: base.executable_identity, + universe: base.universe, + population: base.population, + file_refusals: base.file_refusals, + false_control: base.false_control, + true_control: Absent, + malformed_control: base.malformed_control, + old_route_control: base.old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "true_control_not_observed" + ) } -// The seed and the closure are separate observations with separate refusal names: a receipt -// recording the seed but not the closure must name emitted_closure_unrecorded, and one recording -// the closure but not the seed must name preparation_seed_unrecorded. Before the predicates were -// split, the first shape misreported as preparation_seed_unrecorded and the second cause had no -// reachable construction. -test fn an_unrecorded_closure_refuses_by_its_own_name() -> Bool { - let admission = native_route_admission(receipt: NativeRouteReceipt { - job: required_v2_native_job_name, - tested_tree: "tree-sha", - preparation_seed_identity: "seed-sha", - emitted_closure_identity: "", - executable_path: "/emitted/compiler", - executable_identity: "binary-sha", - population: [accepted_member(path: "v2.test.one")], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } - }) - native_route_admission_refused_with(admission: admission, cause_name: "emitted_closure_unrecorded") - && !native_route_admission_refused_with(admission: admission, cause_name: "preparation_seed_unrecorded") +test fn a_falsified_true_control_is_refused() -> Bool { + let base = green_receipt() + let receipt = NativeRouteReceipt { + job: base.job, + tested_tree: base.tested_tree, + preparation_seed_identity: base.preparation_seed_identity, + emitted_closure_identity: base.emitted_closure_identity, + executable_path: base.executable_path, + executable_identity: base.executable_identity, + universe: base.universe, + population: base.population, + file_refusals: base.file_refusals, + false_control: base.false_control, + true_control: Present { value: NativeTestReturnedFalse }, + malformed_control: base.malformed_control, + old_route_control: base.old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "true_control_not_observed" + ) } -test fn an_unrecorded_seed_refuses_by_its_own_name() -> Bool { - let admission = native_route_admission(receipt: NativeRouteReceipt { - job: required_v2_native_job_name, - tested_tree: "tree-sha", - preparation_seed_identity: "", - emitted_closure_identity: "closure-sha", - executable_path: "/emitted/compiler", - executable_identity: "binary-sha", - population: [accepted_member(path: "v2.test.one")], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } - }) - native_route_admission_refused_with(admission: admission, cause_name: "preparation_seed_unrecorded") - && !native_route_admission_refused_with(admission: admission, cause_name: "emitted_closure_unrecorded") +test fn an_accepted_malformed_specimen_is_refused() -> Bool { + let base = green_receipt() + let receipt = NativeRouteReceipt { + job: base.job, + tested_tree: base.tested_tree, + preparation_seed_identity: base.preparation_seed_identity, + emitted_closure_identity: base.emitted_closure_identity, + executable_path: base.executable_path, + executable_identity: base.executable_identity, + universe: base.universe, + population: base.population, + file_refusals: base.file_refusals, + false_control: base.false_control, + true_control: base.true_control, + malformed_control: MalformedSpecimenAccepted, + old_route_control: base.old_route_control + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "malformed_control_accepted_by_route" + ) } -test fn one_red_run_names_every_violated_clause() -> Bool { - let admission = native_route_admission(receipt: receipt_with( - population: [refused_member(path: "v2.test.one", reason: ^a_cause_nobody_owns)], - broken_control: BrokenSpecimenAccepted, +test fn an_absent_old_route_control_is_refused() -> Bool { + let base = green_receipt() + let receipt = NativeRouteReceipt { + job: base.job, + tested_tree: base.tested_tree, + preparation_seed_identity: base.preparation_seed_identity, + emitted_closure_identity: base.emitted_closure_identity, + executable_path: base.executable_path, + executable_identity: base.executable_identity, + universe: base.universe, + population: base.population, + file_refusals: base.file_refusals, + false_control: base.false_control, + true_control: base.true_control, + malformed_control: base.malformed_control, old_route_control: OldRouteControlAbsent - )) - native_route_admission_refused_with(admission: admission, cause_name: "positive_population_empty") - && native_route_admission_refused_with(admission: admission, cause_name: "broken_specimen_accepted_by_route") - && native_route_admission_refused_with(admission: admission, cause_name: "unattributed_exclusions_present") - && native_route_admission_refused_with(admission: admission, cause_name: "unattributed_head_advisory_present") - && native_route_admission_refused_with(admission: admission, cause_name: "old_route_control_never_installed") + } + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt), + cause_name: "old_route_control_never_installed" + ) } -test fn a_receipt_naming_another_job_is_not_this_lanes_evidence() -> Bool { +// ── admission: the fold collects every violated clause ───────────────────────────────────── + +test fn one_red_run_names_every_violated_clause() -> Bool { let admission = native_route_admission(receipt: NativeRouteReceipt { - job: "some-other-job", - tested_tree: "tree-sha", - preparation_seed_identity: "seed-sha", - emitted_closure_identity: "closure-sha", - executable_path: "/emitted/compiler", - executable_identity: "binary-sha", - population: [accepted_member(path: "v2.test.one")], - broken_control: BrokenSpecimenRefused { reason: ^parse_expected_declaration }, - old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } + job: "wrong-job", + tested_tree: "", + preparation_seed_identity: "", + emitted_closure_identity: "", + executable_path: "", + executable_identity: "", + universe: [], + population: [], + file_refusals: [], + false_control: Absent, + true_control: Absent, + malformed_control: MalformedSpecimenAccepted, + old_route_control: OldRouteControlAbsent }) native_route_admission_refused_with(admission: admission, cause_name: "job_name_mismatch") -} - -test fn enrolment_is_the_admission_question_asked_of_the_census() -> Bool { - match native_route_enrolment(census: green_receipt()) { - EnrolmentOpen => true - EnrolmentBlocked { causes: _ } => false - } - && match native_route_enrolment(census: frontier_receipt()) { - EnrolmentOpen => false - EnrolmentBlocked { causes: causes } => - native_route_admission_refused_with( - admission: NativeRouteRefused { causes: causes }, - cause_name: "positive_population_empty" - ) - } + && native_route_admission_refused_with(admission: admission, cause_name: "tested_tree_unrecorded") + && native_route_admission_refused_with(admission: admission, cause_name: "preparation_seed_unrecorded") + && native_route_admission_refused_with(admission: admission, cause_name: "emitted_closure_unrecorded") + && native_route_admission_refused_with(admission: admission, cause_name: "route_identity_unrecorded") + && native_route_admission_refused_with(admission: admission, cause_name: "universe_underived") + && native_route_admission_refused_with(admission: admission, cause_name: "positive_population_empty") + && native_route_admission_refused_with(admission: admission, cause_name: "false_control_not_observed") + && native_route_admission_refused_with(admission: admission, cause_name: "true_control_not_observed") + && native_route_admission_refused_with(admission: admission, cause_name: "malformed_control_accepted_by_route") + && native_route_admission_refused_with(admission: admission, cause_name: "old_route_control_never_installed") } test fn the_admission_summary_names_every_violated_clause() -> Bool { - native_route_admission_summary(admission: native_route_admission(receipt: frontier_receipt())) - == "refused: positive_population_empty" - && native_route_admission_summary(admission: native_route_admission(receipt: green_receipt())) == "admitted" -} - -test fn the_standing_row_records_enrolment_blocked_on_the_door_frontier() -> Bool { - native_route_enrolment_blocked_today(standing: native_route_enrolment_standing) - && native_route_enrolment_standing.trigger != "" - && native_route_enrolment_standing.enrolment_edit != "" - && native_route_enrolment_standing.census_evidence != "" + native_route_admission_summary(admission: native_route_admission(receipt: NativeRouteReceipt { + job: "wrong-job", + tested_tree: "", + preparation_seed_identity: "", + emitted_closure_identity: "", + executable_path: "", + executable_identity: "", + universe: [], + population: [], + file_refusals: [], + false_control: Absent, + true_control: Absent, + malformed_control: MalformedSpecimenAccepted, + old_route_control: OldRouteControlAbsent + })) == "refused: job_name_mismatch, tested_tree_unrecorded, preparation_seed_unrecorded, emitted_closure_unrecorded, route_identity_unrecorded, universe_underived, positive_population_empty, false_control_not_observed, true_control_not_observed, malformed_control_accepted_by_route, old_route_control_never_installed" } From b1bde4d723bb1af164338de073aca72b80d50592 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 09:09:24 +0000 Subject: [PATCH 53/89] Wire the required-v2-native lane into the roster, workflow, and aggregate Add V2NativeLane/V2NativePhase to the required-CI roster, the lane's claim_executor command to fabric_witness_run, and the required-v2-native job to the witness floor workflow with the aggregate witnesses job needing it in both verdict arms. Regenerate witnesses.yml. Co-authored-by: briansrls --- .github/workflows/witnesses.yml | 95 +++++++++++++++- dag/gunbc/fabric/fabric_witness_run.dag | 11 +- dag/gunbc/required_ci_phase_roster.dag | 6 + dag/gunbc/witness/witness_floor_workflow.dag | 111 ++++++++++++++++++- 4 files changed, 213 insertions(+), 10 deletions(-) diff --git a/.github/workflows/witnesses.yml b/.github/workflows/witnesses.yml index 552145a635a..96b994a01e7 100644 --- a/.github/workflows/witnesses.yml +++ b/.github/workflows/witnesses.yml @@ -403,9 +403,91 @@ jobs: retention-days: 14 include-hidden-files: true if: always() && steps.heal_commit_push.outputs.heal_author_commit_required == '1' + required-v2-native: + runs-on: [self-hosted, linux, arm64] + timeout-minutes: 90 + steps: + - name: Record runner filesystem at job start + run: | + # dissolve-on: toolchain_filesystem_probe -- delete the start/end runner-filesystem instrument after its joined readings identify and the fleet fixes the toolchain deleter, OR after per-job runner microVMs make the shared filesystem eviction class impossible + GUNBC_TOOLCHAIN_FS_PREFIX='GUNBC_TOOLCHAIN_FS phase=start' + echo "$GUNBC_TOOLCHAIN_FS_PREFIX time_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unavailable) host=$(hostname 2>/dev/null || echo unavailable) runner=${RUNNER_NAME:-unset}" + echo "$GUNBC_TOOLCHAIN_FS_PREFIX runner_temp=${RUNNER_TEMP:-unset} rustup_home=${RUSTUP_HOME:-unset} cargo_home=${CARGO_HOME:-unset} uptime_seconds=$(cut -d' ' -f1 /proc/uptime 2>/dev/null || echo unavailable)" + echo "$GUNBC_TOOLCHAIN_FS_PREFIX loadavg=$(tr ' ' ',' < /proc/loadavg 2>/dev/null || echo unavailable)" + df -Pk "${RUNNER_TEMP:-/path-that-does-not-exist}" 2>&1 | while IFS= read -r GUNBC_TOOLCHAIN_FS_LINE; do echo "$GUNBC_TOOLCHAIN_FS_PREFIX df_blocks=$GUNBC_TOOLCHAIN_FS_LINE"; done + df -Pik "${RUNNER_TEMP:-/path-that-does-not-exist}" 2>&1 | while IFS= read -r GUNBC_TOOLCHAIN_FS_LINE; do echo "$GUNBC_TOOLCHAIN_FS_PREFIX df_inodes=$GUNBC_TOOLCHAIN_FS_LINE"; done + for GUNBC_TOOLCHAIN_FS_PATH in "${RUNNER_TEMP:-/path-that-does-not-exist}"/rustup/toolchains/*/bin/rustc "${RUNNER_TEMP:-/path-that-does-not-exist}"/rustup/toolchains/*/bin/rustfmt "${RUNNER_TEMP:-/path-that-does-not-exist}"/cargo/bin/cargo; do if [ -e "$GUNBC_TOOLCHAIN_FS_PATH" ]; then GUNBC_TOOLCHAIN_FS_STAT=$(stat -c 'size=%s mtime_epoch=%Y mtime=%y inode=%i' "$GUNBC_TOOLCHAIN_FS_PATH" 2>&1 || echo stat-unavailable); echo "$GUNBC_TOOLCHAIN_FS_PREFIX kind=file path=$GUNBC_TOOLCHAIN_FS_PATH exists=yes $GUNBC_TOOLCHAIN_FS_STAT"; else echo "$GUNBC_TOOLCHAIN_FS_PREFIX kind=file path=$GUNBC_TOOLCHAIN_FS_PATH exists=no size=unavailable mtime_epoch=unavailable mtime=unavailable inode=unavailable"; fi; done + for GUNBC_TOOLCHAIN_FS_PATH in "${RUNNER_TEMP:-/path-that-does-not-exist}"/rustup/toolchains/*/bin "${RUNNER_TEMP:-/path-that-does-not-exist}"/rustup/toolchains "${RUNNER_TEMP:-/path-that-does-not-exist}"/cargo/bin; do if [ -d "$GUNBC_TOOLCHAIN_FS_PATH" ]; then GUNBC_TOOLCHAIN_FS_STAT=$(stat -c 'mtime_epoch=%Y mtime=%y inode=%i' "$GUNBC_TOOLCHAIN_FS_PATH" 2>&1 || echo stat-unavailable); echo "$GUNBC_TOOLCHAIN_FS_PREFIX kind=directory path=$GUNBC_TOOLCHAIN_FS_PATH exists=yes $GUNBC_TOOLCHAIN_FS_STAT"; else echo "$GUNBC_TOOLCHAIN_FS_PREFIX kind=directory path=$GUNBC_TOOLCHAIN_FS_PATH exists=no mtime_epoch=unavailable mtime=unavailable inode=unavailable"; fi; done + if: always() + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ inputs.expected_healed_sha || github.sha }} + - name: Refuse a heal revalidation whose run subject or checkout does not name the expected healed SHA + run: | + EXPECTED_HEALED_SHA="${{ inputs.expected_healed_sha }}" + RUN_SUBJECT_HEAD="${{ github.sha }}" + if [ -n "$EXPECTED_HEALED_SHA" ]; then + ACTUAL_HEAD="$(git rev-parse HEAD)" + if ! [ "$RUN_SUBJECT_HEAD" = "$EXPECTED_HEALED_SHA" ]; then + echo "::error::heal revalidation refused: workflow run subject $RUN_SUBJECT_HEAD does not equal expected healed head $EXPECTED_HEALED_SHA" + exit 1 + fi + if ! [ "$ACTUAL_HEAD" = "$EXPECTED_HEALED_SHA" ]; then + echo "::error::heal revalidation refused: checkout head $ACTUAL_HEAD does not equal expected healed head $EXPECTED_HEALED_SHA" + exit 1 + fi + echo "heal revalidation preflight: checkout names expected healed head $EXPECTED_HEALED_SHA" + fi + - name: Isolate toolchain homes + run: | + # dissolve-on: ci_toolchain_home_isolation_script -- orch-emitted foreign-executor prelude step wiping and setting HOME/CARGO_HOME/RUSTUP_HOME under RUNNER_TEMP so concurrent runner slots stop sharing one toolchain; leaf rm/echo strings remain until a typed per-job filesystem-and-environment effect lands on host_effect_apply (shell-to-intent Phase 2). This obligation covers THIS carrier and ci_isolate_toolchain_script, which share that terminal construction; ci_pin_rustup_default_script carries its own obligation because it does not + rm -rf "$RUNNER_TEMP/rustup" "$RUNNER_TEMP/cargo" + echo "HOME=$RUNNER_TEMP" >> "$GITHUB_ENV" + echo "CARGO_HOME=$RUNNER_TEMP/cargo" >> "$GITHUB_ENV" + echo "RUSTUP_HOME=$RUNNER_TEMP/rustup" >> "$GITHUB_ENV" + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1.16.0 + with: + components: rustfmt + cache: false + - name: Pin rustup default (isolated RUSTUP_HOME has no default toolchain) + run: | + # dissolve-on: ci_pin_rustup_default_script -- orch-emitted foreign-executor step selecting a rustup default toolchain inside an isolated RUSTUP_HOME, which starts with none, and resolving the cargo binary that selection implies. The leaf rustup/command/echo strings remain until a typed TOOLCHAIN-SELECTION effect lands on host_effect_apply -- NOT the filesystem-and-environment effect ci_toolchain_home_isolation_script waits on, which is why this is a separate obligation: that effect landing alone would leave this carrier standing + rustup default "$(rustup show active-toolchain | awk '{print $1; exit}')" + if [ -x "$CARGO_HOME/bin/cargo" ]; then CARGO_BIN="$CARGO_HOME/bin/cargo"; else CARGO_BIN="$(command -v cargo || true)"; fi + if [ -z "$CARGO_BIN" ]; then echo "::error::no cargo binary: neither the isolated $CARGO_HOME/bin/cargo shim nor PATH carries one"; exit 1; fi + echo "CARGO_BIN=$CARGO_BIN" >> "$GITHUB_ENV" + - name: Build the witness fold + id: build_witness_fold + run: | + cargo build --release -p v1-compiler --bin claim_executor --bin gunbc + - name: Require isolated toolchain homes + run: | + if [ -z "${CARGO_HOME:-}" ] || [ -z "${RUSTUP_HOME:-}" ]; then echo "::error::ToolchainHomesNotIsolated CARGO_HOME=${CARGO_HOME:-unset} RUSTUP_HOME=${RUSTUP_HOME:-unset} -- the isolation step did not run, so this job shares a toolchain with every other runner slot on this host and a concurrent install can replace a binary mid-run" >&2; exit 1; fi + if: "!cancelled()" + - name: "Required CI: v2-native lane (emitted-native compiler executes the derived v2.test.* universe)" + run: | + ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) + cd "$ROOT" + 'target/release/claim_executor' '--required-ci' '--source-root' 'dag' '--source-root' 'src/v2' '--required-lane' 'v2-native' + if: "!cancelled() && steps.build_witness_fold.outcome == 'success'" + - name: Record runner filesystem at job end + run: | + # dissolve-on: toolchain_filesystem_probe -- delete the start/end runner-filesystem instrument after its joined readings identify and the fleet fixes the toolchain deleter, OR after per-job runner microVMs make the shared filesystem eviction class impossible + GUNBC_TOOLCHAIN_FS_PREFIX='GUNBC_TOOLCHAIN_FS phase=end' + echo "$GUNBC_TOOLCHAIN_FS_PREFIX time_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unavailable) host=$(hostname 2>/dev/null || echo unavailable) runner=${RUNNER_NAME:-unset}" + echo "$GUNBC_TOOLCHAIN_FS_PREFIX runner_temp=${RUNNER_TEMP:-unset} rustup_home=${RUSTUP_HOME:-unset} cargo_home=${CARGO_HOME:-unset} uptime_seconds=$(cut -d' ' -f1 /proc/uptime 2>/dev/null || echo unavailable)" + echo "$GUNBC_TOOLCHAIN_FS_PREFIX loadavg=$(tr ' ' ',' < /proc/loadavg 2>/dev/null || echo unavailable)" + df -Pk "${RUNNER_TEMP:-/path-that-does-not-exist}" 2>&1 | while IFS= read -r GUNBC_TOOLCHAIN_FS_LINE; do echo "$GUNBC_TOOLCHAIN_FS_PREFIX df_blocks=$GUNBC_TOOLCHAIN_FS_LINE"; done + df -Pik "${RUNNER_TEMP:-/path-that-does-not-exist}" 2>&1 | while IFS= read -r GUNBC_TOOLCHAIN_FS_LINE; do echo "$GUNBC_TOOLCHAIN_FS_PREFIX df_inodes=$GUNBC_TOOLCHAIN_FS_LINE"; done + for GUNBC_TOOLCHAIN_FS_PATH in "${RUNNER_TEMP:-/path-that-does-not-exist}"/rustup/toolchains/*/bin/rustc "${RUNNER_TEMP:-/path-that-does-not-exist}"/rustup/toolchains/*/bin/rustfmt "${RUNNER_TEMP:-/path-that-does-not-exist}"/cargo/bin/cargo; do if [ -e "$GUNBC_TOOLCHAIN_FS_PATH" ]; then GUNBC_TOOLCHAIN_FS_STAT=$(stat -c 'size=%s mtime_epoch=%Y mtime=%y inode=%i' "$GUNBC_TOOLCHAIN_FS_PATH" 2>&1 || echo stat-unavailable); echo "$GUNBC_TOOLCHAIN_FS_PREFIX kind=file path=$GUNBC_TOOLCHAIN_FS_PATH exists=yes $GUNBC_TOOLCHAIN_FS_STAT"; else echo "$GUNBC_TOOLCHAIN_FS_PREFIX kind=file path=$GUNBC_TOOLCHAIN_FS_PATH exists=no size=unavailable mtime_epoch=unavailable mtime=unavailable inode=unavailable"; fi; done + for GUNBC_TOOLCHAIN_FS_PATH in "${RUNNER_TEMP:-/path-that-does-not-exist}"/rustup/toolchains/*/bin "${RUNNER_TEMP:-/path-that-does-not-exist}"/rustup/toolchains "${RUNNER_TEMP:-/path-that-does-not-exist}"/cargo/bin; do if [ -d "$GUNBC_TOOLCHAIN_FS_PATH" ]; then GUNBC_TOOLCHAIN_FS_STAT=$(stat -c 'mtime_epoch=%Y mtime=%y inode=%i' "$GUNBC_TOOLCHAIN_FS_PATH" 2>&1 || echo stat-unavailable); echo "$GUNBC_TOOLCHAIN_FS_PREFIX kind=directory path=$GUNBC_TOOLCHAIN_FS_PATH exists=yes $GUNBC_TOOLCHAIN_FS_STAT"; else echo "$GUNBC_TOOLCHAIN_FS_PREFIX kind=directory path=$GUNBC_TOOLCHAIN_FS_PATH exists=no mtime_epoch=unavailable mtime=unavailable inode=unavailable"; fi; done + if: always() witnesses: runs-on: [self-hosted, linux, arm64] - needs: [required-witnesses-build, required-witnesses-floor] + needs: [required-witnesses-build, required-witnesses-floor, required-v2-native] timeout-minutes: 5 if: always() steps: @@ -415,16 +497,17 @@ jobs: if '[' "$EVENT_NAME" '=' 'pull_request' ']'; then HEAD_STANDING='unobserved'; fi 'echo' 'required floor attempt: pull_request='"$PULL_REQUEST"' measured_head='"$MEASURED_HEAD"' run_id='"$RUN_ID"' head_standing='"$HEAD_STANDING" VERDICT='stands-green' - if '[' "$BUILD" '!=' 'success' ']' || '[' "$FLOOR" '!=' 'success' ']'; then VERDICT='stands-unestablished'; fi - if '[' "$BUILD" '=' 'failure' ']' || '[' "$FLOOR" '=' 'failure' ']'; then VERDICT='stands-red'; fi + if '[' "$BUILD" '!=' 'success' ']' || '[' "$FLOOR" '!=' 'success' ']' || '[' "$V2_NATIVE" '!=' 'success' ']'; then VERDICT='stands-unestablished'; fi + if '[' "$BUILD" '=' 'failure' ']' || '[' "$FLOOR" '=' 'failure' ']' || '[' "$V2_NATIVE" '=' 'failure' ']'; then VERDICT='stands-red'; fi MECHANISM='unestablished' ATTRIBUTION='unestablished' - 'echo' 'required lanes: build='"$BUILD"' floor='"$FLOOR"' verdict='"$VERDICT"' mechanism='"$MECHANISM"' attribution='"$ATTRIBUTION" - if '[' "$VERDICT" '=' 'stands-red' ']'; then 'echo' '::error::a required lane concluded failure; that alone does not establish whether its subject was evaluated, so read the log before assuming a defect in the diff (build='"$BUILD"' floor='"$FLOOR"' mechanism='"$MECHANISM"' attribution='"$ATTRIBUTION"') - open that job'\''s log' >&2; exit 1; fi - if '[' "$VERDICT" '=' 'stands-unestablished' ']'; then 'echo' '::error::a required lane produced no conclusion of its own, so this head'\''s floor verdict is unobservable rather than failed; rerun it (build='"$BUILD"' floor='"$FLOOR"' mechanism='"$MECHANISM"' attribution='"$ATTRIBUTION"') - open that job'\''s log' >&2; exit 1; fi + 'echo' 'required lanes: build='"$BUILD"' floor='"$FLOOR"' v2-native='"$V2_NATIVE"' verdict='"$VERDICT"' mechanism='"$MECHANISM"' attribution='"$ATTRIBUTION" + if '[' "$VERDICT" '=' 'stands-red' ']'; then 'echo' '::error::a required lane concluded failure; that alone does not establish whether its subject was evaluated, so read the log before assuming a defect in the diff (build='"$BUILD"' floor='"$FLOOR"' v2-native='"$V2_NATIVE"' mechanism='"$MECHANISM"' attribution='"$ATTRIBUTION"') - open that job'\''s log' >&2; exit 1; fi + if '[' "$VERDICT" '=' 'stands-unestablished' ']'; then 'echo' '::error::a required lane produced no conclusion of its own, so this head'\''s floor verdict is unobservable rather than failed; rerun it (build='"$BUILD"' floor='"$FLOOR"' v2-native='"$V2_NATIVE"' mechanism='"$MECHANISM"' attribution='"$ATTRIBUTION"') - open that job'\''s log' >&2; exit 1; fi env: BUILD: ${{ needs['required-witnesses-build'].result }} FLOOR: ${{ needs['required-witnesses-floor'].result }} + V2_NATIVE: ${{ needs['required-v2-native'].result }} PULL_REQUEST: ${{ github.event.pull_request.number }} EVENT_NAME: ${{ github.event_name }} MEASURED_HEAD: ${{ github.event.pull_request.head.sha }} diff --git a/dag/gunbc/fabric/fabric_witness_run.dag b/dag/gunbc/fabric/fabric_witness_run.dag index 7d75ec6e013..46af6e1d325 100644 --- a/dag/gunbc/fabric/fabric_witness_run.dag +++ b/dag/gunbc/fabric/fabric_witness_run.dag @@ -20,7 +20,7 @@ import product.fabric.work { work_for, } import std.content_hash { ContentHash, content_hash_of_value, serialize_content_hash } -import gunbc.required_ci_phase_roster { BuildLane, WitnessesLane, required_ci_lane_name } +import gunbc.required_ci_phase_roster { BuildLane, WitnessesLane, V2NativeLane, required_ci_lane_name } import extdeps.git.object_store { GitObjectId, git_object_id_wire_hex, git_object_id_content_hash, git_sha1_object_id } import product.fabric.supply { SupplierOffer, OfferFungibility, DemandOfferAffordability, GrantDuration, EstimatedGrantDuration, @@ -555,6 +555,15 @@ fn build_lane_run_command() -> ArgvCommand { required_ci_lane_command(lane: required_ci_lane_build, measurement_receipt: "") } +data required_ci_lane_v2_native: String = required_ci_lane_name(l: V2NativeLane) + +// The v2-native lane: the emitted-native compiler executes the derived v2.test.* universe +// through its own whole-source-root Eval driver. The job contract and admission authority are +// gunbc.witness_v2_native_route; this command is only the argv the workflow step runs. +fn v2_native_lane_run_command() -> ArgvCommand { + required_ci_lane_command(lane: required_ci_lane_v2_native, measurement_receipt: "") +} + // THE FLOOR IS MODELED AS AN ORDINARY PRICED DEMAND. NO PRICE IS CLEARED TODAY. // // The intent is unchanged: gunbc's own CI enters the market on the same terms as any other demand, diff --git a/dag/gunbc/required_ci_phase_roster.dag b/dag/gunbc/required_ci_phase_roster.dag index e6e086be083..f15cb6bc0fa 100644 --- a/dag/gunbc/required_ci_phase_roster.dag +++ b/dag/gunbc/required_ci_phase_roster.dag @@ -28,11 +28,13 @@ import std.types { String, Bool, List } type RequiredCiLane = BuildLane | WitnessesLane + | V2NativeLane fn required_ci_lane_name(l: RequiredCiLane) -> String { match l { BuildLane => "build" WitnessesLane => "witnesses" + V2NativeLane => "v2-native" } } @@ -61,6 +63,7 @@ type RequiredCiPhase | GeneratedArtifactPhase | RegenFixedPointPhase | FloorPhase + | V2NativePhase // TWO NAMED ARMS, NEVER A WIDENED PATH PREDICATE. A path that neither authority owns is in // neither arm; inability to produce either arm refuses GeneratedArtifactPhase. The registry arm @@ -106,6 +109,7 @@ fn required_ci_phase_name(p: RequiredCiPhase) -> String { GeneratedArtifactPhase => "generated-artifact" RegenFixedPointPhase => "regen-fixed-point" FloorPhase => "floor" + V2NativePhase => "v2-native" } } @@ -118,6 +122,7 @@ fn required_ci_phase_lane(p: RequiredCiPhase) -> RequiredCiLane { GeneratedArtifactPhase => BuildLane RegenFixedPointPhase => BuildLane FloorPhase => WitnessesLane + V2NativePhase => V2NativeLane } } @@ -131,6 +136,7 @@ data required_ci_phases: List = [ GeneratedArtifactPhase, RegenFixedPointPhase, FloorPhase, + V2NativePhase, ] fn required_ci_phase_is(a: RequiredCiPhase, b: RequiredCiPhase) -> Bool { diff --git a/dag/gunbc/witness/witness_floor_workflow.dag b/dag/gunbc/witness/witness_floor_workflow.dag index a10403476f8..a12c9f1ee53 100644 --- a/dag/gunbc/witness/witness_floor_workflow.dag +++ b/dag/gunbc/witness/witness_floor_workflow.dag @@ -69,7 +69,8 @@ import gunbc.ci_spec { import gunbc.repo_identity { gunbc_default_branch_name } import gunbc.merge_admission_produce { ci_repo_root_shell } import gunbc.ci_layer_roots { witness_layer_roots } -import gunbc.fabric_witness_run { witnesses_lane_run_command, witnesses_lane_measurement_command, build_lane_run_command } +import gunbc.fabric_witness_run { witnesses_lane_run_command, witnesses_lane_measurement_command, build_lane_run_command, v2_native_lane_run_command } +import gunbc.witness_v2_native_route { required_v2_native_job_name } import extdeps.exec.command { ArgvCommand, shell_command_render } import gunbc.required_lanes_gate { RequiredLane, required_lanes_gate_stmts } import extdeps.github.expressions { ContextAccess, Github, Needs, interpolate } @@ -1675,6 +1676,102 @@ fn witness_floor_job() -> Job { } } +// THE NATIVE LANE JOB (operator authorization 2026-09-09: ONE additional required job, wired — +// not an enrolment promise). The three questions a new job must answer before it claims a paid +// runner on every push: +// +// THE MEASURED WALL. The lane pays: the shared prepared steps (toolchain + two-bin seed build), +// the v2 closure emission, one cargo build of the emitted compiler crate, the universe +// derivation fold (the floor's own discovery producer over the full module inventory), and the +// native run itself — one process over the derived v2.test.* universe plus the three controls. +// The interpreter measured the admission fold's quadratic shape over the full derived population +// at tens of seconds; the cargo build of the emitted crate is the dominant term. The envelope is +// 90 minutes against the floor lane's 180. +// +// WHAT RED DISCRIMINATES. The admission authority is gunbc.witness_v2_native_route: the observed +// population must join the derived universe exactly (no duplicates, no foreign rows, no +// omissions), the positive population must be non-empty, every refusal must be classified from +// stage and provenance, every native verdict must agree with the floor-derived reference at the +// same identity, and all four controls must hold — the false control observed false, the true +// control observed passed, the malformed specimen refused, the old route withdrawn. A step +// folded into an existing lane could not carry this: its red would land on that lane's receipt, +// conflating two routes' subjects. +// +// WHY A JOB AND NOT A STEP. The no-fallback control withdraws the old compiler/interpreter route +// after the emitted compiler exists; the floor lane's own execution IS the old route, so the +// control and the floor cannot share a job. The lane also builds a second crate (the emitted +// compiler), a capability envelope the existing lanes do not declare. A job is the smallest +// boundary that keeps both facts honest. +data v2_native_lane_timeout: Minute = minute(count: 90) + +fn v2_native_lane_build_script() -> String { + repo_self_build_command(bins: witness_floor_required_bins) +} + +fn v2_native_lane_run_script() -> String { + required_lane_run_script(command: v2_native_lane_run_command()) +} + +fn v2_native_lane_run_step() -> Step { + RunStep { + name: Present { value: "Required CI: v2-native lane (emitted-native compiler executes the derived v2.test.* universe)" }, + id: none, + run: v2_native_lane_run_script(), + shell: none, + env: none, + working_directory: none, + if_condition: Present { value: witness_floor_precondition() }, + continue_on_error: none, + timeout_minutes: none + } +} + +fn v2_native_lane_bound_steps() -> List { + append( + prepared_bound_steps(build_script: v2_native_lane_build_script()), + items: [ + WitnessFloorBoundStep { + step: witness_toolchain_homes_isolated_guard_step(), + role: capability_neutral, + step_name: "Require isolated toolchain homes", + }, + WitnessFloorBoundStep { + step: v2_native_lane_run_step(), + role: consumes_only(capabilities: [CargoCapability, RustcCapability, RustfmtCapability]), + step_name: "Required CI: v2-native lane (emitted-native compiler executes the derived v2.test.* universe)", + }, + WitnessFloorBoundStep { + step: witness_toolchain_filesystem_end_probe_step(), + role: capability_neutral, + step_name: "Record runner filesystem at job end", + }, + ], + ) +} + +fn v2_native_lane_steps() -> List { + list_map(xs: v2_native_lane_bound_steps(), f: fn(b) { b.step }) +} + +fn v2_native_lane_needs() -> List { [] } + +fn v2_native_lane_job() -> Job { + Job { + id: required_v2_native_job_name, + name: none, + runner: gunbc_ci_selected_runner_spec(), + steps: v2_native_lane_steps(), + needs: v2_native_lane_needs(), + env: none, + outputs: none, + if_condition: none, + timeout_minutes: minute_count(v2_native_lane_timeout), + continue_on_error: none, + concurrency: none, + permissions: none + } +} + // THE LANE'S EXECUTION ENVELOPE, NAMED BECAUSE A SECOND CONSUMER NOW READS IT. The value did not // change; it stopped being a literal. gunbc.repo_ruleset derives the merge queue's // check_response_timeout from this row, because a queue whose response timeout is BELOW the @@ -2279,6 +2376,7 @@ fn required_lanes_run_id_expression() -> String { // all. A `Rejected` fold can therefore never reach a published step. data required_lanes_gate_build_var: String = "BUILD" data required_lanes_gate_floor_var: String = "FLOOR" +data required_lanes_gate_v2_native_var: String = "V2_NATIVE" // THE REQUIRED LANE ROSTER, AND IT IS ONE ROSTER READ TWICE RATHER THAN TWO LISTS THAT AGREE. // @@ -2303,6 +2401,11 @@ fn required_lanes_roster() -> FreeSemigroup { job_id: floor_lane_job_id, label: "floor", var_name: required_lanes_gate_floor_var + }, + RequiredLane { + job_id: required_v2_native_job_name, + label: "v2-native", + var_name: required_lanes_gate_v2_native_var } ] } @@ -2536,12 +2639,12 @@ fn witness_floor_triggers() -> List { // THE AGGREGATE IS DELIBERATELY NOT A MEMBER. It checks out nothing and runs no witness: it reads // the other lanes' results. Every consumer of this list is asking about lanes that carry a subject, // and the aggregate joined that population only as something to filter back out. -type WitnessFloorLane = WitnessFloorBuildLane | WitnessFloorFloorLane | WitnessFloorHealLane +type WitnessFloorLane = WitnessFloorBuildLane | WitnessFloorFloorLane | WitnessFloorHealLane | WitnessFloorV2NativeLane fn witness_floor_lane_roster() -> FreeSemigroup { FreeSemigroup { head: WitnessFloorBuildLane, - tail: [WitnessFloorFloorLane, WitnessFloorHealLane] + tail: [WitnessFloorFloorLane, WitnessFloorHealLane, WitnessFloorV2NativeLane] } } @@ -2550,6 +2653,7 @@ fn witness_floor_lane_job(lane: WitnessFloorLane) -> Job { WitnessFloorBuildLane => build_lane_job() WitnessFloorFloorLane => witness_floor_job() WitnessFloorHealLane => heal_generated_artifacts_job() + WitnessFloorV2NativeLane => v2_native_lane_job() } } @@ -2568,6 +2672,7 @@ fn witness_floor_lane_job_id(lane: WitnessFloorLane) -> String { WitnessFloorBuildLane => build_lane_job_id WitnessFloorFloorLane => floor_lane_job_id WitnessFloorHealLane => heal_generated_artifacts_job_id + WitnessFloorV2NativeLane => required_v2_native_job_name } } From ff503a49c840c291dfa6cf6c6f5c1f0dc726fa99 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 09:09:30 +0000 Subject: [PATCH 54/89] Add the required-v2-native host harness and phase dispatch The lane's one phase derives the v2.test.* universe with the floor's own discovery producer over the full module inventory, prepares the emitted-native compiler through the emit-compile phase's crate writer and cargo invocation, withdraws the old-route gunbc binary for the spawn window, runs the emitted binary by explicit path over the universe plus the named controls, reclassifies context-stage refusals against the observed file refusals, mints the NativeRouteReceipt as the authority's own types, and hands it to native_route_admission for the verdict. claim_executor gains the V2Native lane and phase with the roster sizes moved to six. Co-authored-by: briansrls --- src/v1/stage0/src/bin/claim_executor.rs | 43 +- src/v1/stage0/src/cli_run.rs | 2 + .../stage0/src/cli_run/native_lane_runner.rs | 888 ++++++++++++++++++ .../src/emitted_closure_compile_host.rs | 14 +- 4 files changed, 939 insertions(+), 8 deletions(-) create mode 100644 src/v1/stage0/src/cli_run/native_lane_runner.rs diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index 72ee804b9d8..0644ece7454 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -1054,6 +1054,25 @@ fn run() -> Result { ran.push("floor"); } + // THE NATIVE ROUTE PHASE. The harness derives the v2.test.* universe with the floor's + // own discovery producer, prepares the emitted-native compiler through the emit-compile + // machinery, runs the universe plus controls through that binary by explicit path with + // the old route withdrawn, and hands the minted receipt to the admission authority — + // the phase greens exactly when gunbc.witness_v2_native_route admits it. + if required_ci_phase_selected(RequiredCiPhase::V2Native, required_ci_lane) { + eprintln!( + "required-ci: phase v2-native (emitted-native compiler executes the derived v2.test.* universe)" + ); + match v1_compiler::cli_run::run_required_v2_native(&source_roots) { + Ok(()) => {} + Err(e) => { + eprintln!("required-ci: v2-native refused: {e}"); + phase_failures.push(format!("v2-native refused: {e}")); + } + } + ran.push("v2-native"); + } + // COUNTER-KEY CENSUS (dashboard node adhoc-af8a3fe8-13d): this is the // phase population. The required-floor aggregate below counts claim outcomes, so the // formerly shared `failed` key gave one spelling two meanings in one process's output. @@ -1549,6 +1568,7 @@ fn emit_worker_terminal_before_return(code: ExitCode) -> ExitCode { enum RequiredCiLane { Build, Witnesses, + V2Native, } impl RequiredCiLane { @@ -1556,6 +1576,7 @@ impl RequiredCiLane { match self { RequiredCiLane::Build => "build", RequiredCiLane::Witnesses => "witnesses", + RequiredCiLane::V2Native => "v2-native", } } @@ -1566,8 +1587,9 @@ impl RequiredCiLane { match value { "build" => Ok(RequiredCiLane::Build), "witnesses" => Ok(RequiredCiLane::Witnesses), + "v2-native" => Ok(RequiredCiLane::V2Native), other => Err(format!( - "unknown --required-lane: {other} (expected build | witnesses)" + "unknown --required-lane: {other} (expected build | witnesses | v2-native)" )), } } @@ -1580,6 +1602,7 @@ enum RequiredCiPhase { GeneratedArtifact, RegenFixedPoint, Floor, + V2Native, } impl RequiredCiPhase { @@ -1590,6 +1613,7 @@ impl RequiredCiPhase { RequiredCiPhase::RegenFixedPoint => "regen-fixed-point", RequiredCiPhase::GeneratedArtifact => "generated-artifact", RequiredCiPhase::Floor => "floor", + RequiredCiPhase::V2Native => "v2-native", } } @@ -1615,21 +1639,29 @@ impl RequiredCiPhase { // measurement to reference and nothing to compare. RequiredCiPhase::RegenFixedPoint => RequiredCiLane::Build, RequiredCiPhase::Floor => RequiredCiLane::Witnesses, + // THE NATIVE ROUTE IS ITS OWN LANE, NOT A FLOOR RIDER: the lane's no-fallback + // control withdraws the old compiler/interpreter route's CLI for the spawn window, + // and the floor lane's own execution IS that route -- the two cannot share a job + // (lane authority: gunbc.witness_v2_native_route). + RequiredCiPhase::V2Native => RequiredCiLane::V2Native, } } } -// THE REQUIRED GATE IS FIVE PHASES. Four are the 2026-08-29 compiler-floor bankruptcy roster; +// THE REQUIRED GATE IS SIX PHASES. Four are the 2026-08-29 compiler-floor bankruptcy roster; // generated-artifact returned after its declared exposure produced a real stale projection on // main, and now also owns the former regen phase's stage0-mirror population. Keeping two phase // identities would preserve the independently-green outcomes this composition removes. The other -// three removed phases remain outside required CI and inside the declared drop. -const REQUIRED_CI_PHASES: [RequiredCiPhase; 5] = [ +// three removed phases remain outside required CI and inside the declared drop. The sixth, +// v2-native, is the 2026-09-09 operator-authorized native-route lane: the emitted-native +// compiler executes the derived v2.test.* universe (gunbc.witness_v2_native_route). +const REQUIRED_CI_PHASES: [RequiredCiPhase; 6] = [ RequiredCiPhase::Parse, RequiredCiPhase::NamespaceWaveAdmission, RequiredCiPhase::GeneratedArtifact, RequiredCiPhase::RegenFixedPoint, RequiredCiPhase::Floor, + RequiredCiPhase::V2Native, ]; /// The `.dag` coproduct this enum realizes, and the declaration whose variants it must equal. @@ -1641,12 +1673,13 @@ const PHASE_ROSTER_AUTHORITY_MODULE: &str = "gunbc.required_ci_phase_roster"; const PHASE_ROSTER_AUTHORITY_DECL: &str = "RequiredCiPhase"; /// Every phase this binary realizes, in the authority's own variant spelling. -const PHASE_ROSTER_VARIANT_LABELS: [&str; 5] = [ +const PHASE_ROSTER_VARIANT_LABELS: [&str; 6] = [ "ParsePhase", "NamespaceWaveAdmissionPhase", "GeneratedArtifactPhase", "RegenFixedPointPhase", "FloorPhase", + "V2NativePhase", ]; /// Refuse if the host phase enum and the `.dag` phase roster disagree — the same both-directions diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 7ba4c1bbb30..887df037e48 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -90,6 +90,7 @@ mod census_heads; pub mod declaration_index; pub mod derived_row_roster; mod required_floor_runner; +mod native_lane_runner; pub mod rostered_row_join; mod serve_budget_refusal; pub(crate) use required_floor_runner::*; @@ -97,6 +98,7 @@ pub use required_floor_runner::{ floor_discovery_path_excluded, make_eval_context, make_eval_context_with_runtime_options, run_claim_measured, run_required_floor, }; +pub use native_lane_runner::run_required_v2_native; mod entry_resolve; pub(crate) use active_workset::*; pub(crate) use entry_resolve::*; diff --git a/src/v1/stage0/src/cli_run/native_lane_runner.rs b/src/v1/stage0/src/cli_run/native_lane_runner.rs new file mode 100644 index 00000000000..2741f19f59d --- /dev/null +++ b/src/v1/stage0/src/cli_run/native_lane_runner.rs @@ -0,0 +1,888 @@ +//! The required-v2-native lane's host harness (lane authority: +//! `gunbc.witness_v2_native_route`; roster row: `gunbc.required_ci_phase_roster` +//! `V2NativePhase`). +//! +//! THE SUBJECT IS THE ROUTE, NOT THE MODEL. The `.dag` authority owns the admission contract; +//! this module is the harness that MINTS the receipt the contract admits or refuses: it derives +//! the `v2.test.*` universe with the floor's own discovery producer, prepares the emitted-native +//! compiler through the emit-compile phase's one crate writer, invokes that binary by explicit +//! path over the universe plus the controls, withdraws the old-route CLI for the duration of +//! every native spawn, and binds the observed verdicts into a `NativeRouteReceipt` that is then +//! admitted — or refused — by evaluating `native_route_admission` itself. Nothing here decides +//! admission in Rust: the host observes, the substrate judges. +//! +//! WHAT THE OLD-ROUTE CONTROL PROVES HERE, AND WHAT IT DOES NOT. The lane's verdicts are parsed +//! from the spawned emitted binary's stdout, so the execution route is a process boundary, not a +//! call convention. Withdrawing the seed's `gunbc` binary for the spawn window proves no +//! subprocess fallback to the seed CLI occurred; it does not prove this process never +//! interpreted a test — that discipline is the code's own (the interpreter contexts below +//! evaluate ONLY the discovery producer and the admission authority, never a universe test), +//! and the receipt's route identity (binary path + content hash) is what admission checks. + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::rc::Rc; + +use crate::v1_interpreter::{self, str_value, Value}; + +/// The compiler entry whose closure becomes the lane's emitted-native compiler. Its +/// `compiler_pipeline_entry` is `SourceRootEvalDriver`, so the emitted crate's `main.rs` is the +/// whole-source-root Eval driver this lane exists to route through. +const NATIVE_COMPILE_ENTRY: &str = "src/v2/compiler/00_compile.dag"; + +/// The universe prefix — the same `v2.test.` the required floor gates on. The derivation below +/// filters the floor discovery producer's rows to it; the admission authority independently +/// checks prefix closure, so a drift between the two is a red, not a silent widening. +const UNIVERSE_PREFIX: &str = "v2.test."; + +/// The live-verdict control pair (v2.native_lane_fixture.control): plain `fn` declarations +/// outside the universe prefix, so floor discovery enrolls no rows for them and this harness +/// names them to the binary explicitly, beside the derived universe. +const CONTROL_MODULE: &str = "v2.native_lane_fixture.control"; +const FALSE_CONTROL_DECL: &str = "native_lane_false_control"; +const TRUE_CONTROL_DECL: &str = "native_lane_true_control"; + +/// The malformed control: a deliberately unterminating string any honest front-end must refuse +/// at tokenize. It lives outside every source root and enters only the control run's ingest. +const MALFORMED_FIXTURE_DIR: &str = "fixtures/native_lane_malformed"; +const MALFORMED_FIXTURE_PATH: &str = "fixtures/native_lane_malformed/poison.dag"; + +/// The admission authority's entry file, for the interpreter context that judges the receipt. +const NATIVE_ROUTE_AUTHORITY_ENTRY: &str = "dag/gunbc/witness/v2_native_route.dag"; + +/// One observed verdict row, decoded from the emitted binary's stdout at the same grain the +/// receipt carries: qualified identity plus the driver's own verdict vocabulary. +struct NativeObservation { + module: String, + declaration: String, + verdict: NativeVerdictObserved, +} + +enum NativeVerdictObserved { + Passed, + ReturnedFalse, + ReturnedOther, + Refused { stage: String, reason: String }, +} + +/// One per-file front-end refusal the emitted binary's collecting context fold observed. +struct NativeFileRefusalObserved { + path: String, + head_reason: String, + fatal_reason: String, +} + +/// The emitted compiler, prepared: where the binary is, what its bytes are, and the identity of +/// the closure it was emitted from. +struct EmittedPreparation { + binary_path: PathBuf, + binary_identity: String, + closure_identity: String, + seed_identity: String, +} + +fn sha256_file(path: &Path) -> Result { + use sha2::Digest; + let bytes = std::fs::read(path) + .map_err(|e| format!("could not read {} for its content identity: {e}", path.display()))?; + Ok(format!("{:x}", sha2::Sha256::digest(&bytes))) +} + +/// The emitted closure's identity: one digest over the crate's emitted sources, path and +/// content in sorted order, so the receipt names WHAT was compiled, not merely that something +/// was. +fn emitted_closure_identity(crate_dir: &Path) -> Result { + use sha2::Digest; + let src_dir = crate_dir.join("src"); + let mut files: Vec = std::fs::read_dir(&src_dir) + .map_err(|e| format!("could not list {}: {e}", src_dir.display()))? + .filter_map(|entry| entry.ok().map(|e| e.path())) + .filter(|path| path.extension().map(|ext| ext == "rs").unwrap_or(false)) + .collect(); + files.sort(); + let mut hasher = sha2::Sha256::new(); + for path in &files { + let bytes = std::fs::read(path) + .map_err(|e| format!("could not read {}: {e}", path.display()))?; + hasher.update(path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default().as_bytes()); + hasher.update(&bytes); + } + Ok(format!("{:x}", hasher.finalize())) +} + +/// THE UNIVERSE IS DERIVED BY THE FLOOR'S OWN PRODUCER, NOT BY A SECOND SCAN. The production +/// required floor folds `discover_floor_rows_for_source` over the full module inventory and +/// finalizes with `floor_discovery_finalize_source_outcomes`; this derivation makes the same +/// two calls over the same inventory and keeps the rows whose AUTHORED module (read off the +/// `module` header by the index, never path-derived) carries the universe prefix. A second +/// discovery beside the producer would be free to disagree with the floor about what a test is. +fn derive_native_universe( + source_roots: &[String], +) -> Result<(Vec<(String, String)>, HashMap), String> { + let (graph, indices) = + super::resolve_workspace_entry(source_roots, super::FLOOR_DISCOVERY_PRODUCER_ENTRY)?; + let ctx = super::make_eval_context(&graph, indices, v1_interpreter::ExecutionMode::Wet); + let index = super::try_build_module_index(source_roots)?; + let mut inventory: Vec<(String, String, String)> = index + .iter() + .map(|(module_path, source)| { + ( + source.path.replace('\\', "/"), + module_path.clone(), + source.content.clone(), + ) + }) + .collect(); + inventory.sort(); + let module_for_path: HashMap = inventory + .iter() + .map(|(path, module, _)| (path.clone(), module.clone())) + .collect(); + eprintln!( + "required-ci: v2-native deriving the universe — {} sources through the floor discovery producer", + inventory.len() + ); + let mut outcomes: Vec = Vec::with_capacity(inventory.len()); + for (path, _, content) in &inventory { + let args = [ + (Some("repo_path".to_string()), str_value(path)), + (Some("content".to_string()), str_value(content)), + ]; + let outcome = v1_interpreter::run_in_context_with_args( + &ctx, + "v2.workflow.floor_discovery_producer.discover_floor_rows_for_source", + &args, + false, + ) + .map_err(|e| { + format!( + "V2-NATIVE REFUSAL cause=UniverseDerivationUnevaluable source={path} — \ + discover_floor_rows_for_source: {e}" + ) + })?; + outcomes.push(outcome); + } + let finalized = v1_interpreter::run_in_context_with_args( + &ctx, + "v2.workflow.floor_discovery_producer.floor_discovery_finalize_source_outcomes", + &[( + Some("outcomes".to_string()), + super::list_value_from_vec(outcomes), + )], + false, + ) + .map_err(|e| { + format!( + "V2-NATIVE REFUSAL cause=UniverseDerivationUnevaluable — \ + floor_discovery_finalize_source_outcomes: {e}" + ) + })?; + let rows = super::parse_floor_discovery_producer_result(&ctx, &finalized) + .map_err(|reason| format!("V2-NATIVE REFUSAL cause=UniverseDerivationRefused — {reason}"))?; + let mut universe: Vec<(String, String)> = Vec::new(); + for row in &rows { + let Some(module) = module_for_path.get(row.entry.as_str()) else { + return Err(format!( + "V2-NATIVE REFUSAL cause=UniverseEntryOutsideSubject entry={} — the discovery \ + authority enrolled an entry the module index does not hold", + row.entry + )); + }; + if module.starts_with(UNIVERSE_PREFIX) { + universe.push((module.clone(), row.function.clone())); + } + } + universe.sort(); + universe.dedup(); + if universe.is_empty() { + return Err( + "V2-NATIVE REFUSAL cause=UniverseUnderived — the floor discovery producer enrolled \ + no v2.test.* rows over the full module inventory" + .to_string(), + ); + } + Ok((universe, module_for_path)) +} + +/// PREPARATION IS THE EMIT-COMPILE PHASE'S OWN MACHINERY, REUSED. The same emission entry +/// point, the same crate writer, the same cargo invocation the required emit-compile probes use +/// — a second emit-or-build path beside them would be free to disagree about what "the emitted +/// compiler" is. The seed is used exactly once here, in-process, to emit; the receipt records +/// the seed's identity honestly, and this job claims no native bootstrap. +fn prepare_emitted_compiler(source_roots: &[String]) -> Result { + let workspace = super::process_workspace_root(); + let probe_root = super::local_emit_compile_probe_root(); + eprintln!("required-ci: v2-native emitting {NATIVE_COMPILE_ENTRY} (seed, in-process)"); + let run = super::compile_entry_emission( + source_roots, + NATIVE_COMPILE_ENTRY, + true, + crate::v1_compiler_artifact::RenderTarget::Rust, + ); + match &run.disposition { + super::CompileDisposition::Completed { .. } => {} + super::CompileDisposition::Refused { phase, cause } => { + return Err(format!( + "V2-NATIVE REFUSAL cause=EmissionRefused stage={phase} — {cause}" + )) + } + super::CompileDisposition::NotExecuted { + earlier_phase, + cause, + } => { + return Err(format!( + "V2-NATIVE REFUSAL cause=EmissionRefused stage={earlier_phase} — {cause}" + )) + } + } + let (crate_dir, written) = + super::emitted_closure_compile_host::write_probe_crate(&run, &probe_root, NATIVE_COMPILE_ENTRY) + .map_err(|cause| format!("V2-NATIVE REFUSAL cause=EmittedCrateNotWritten — {cause}"))?; + let closure_identity = emitted_closure_identity(&crate_dir)?; + eprintln!( + "required-ci: v2-native emitted {written} files into {} (closure {closure_identity}); cargo build", + crate_dir.display() + ); + let verdict = super::emitted_closure_compile_host::run_cargo( + &crate_dir, + &workspace, + "v2_native_lane_carries_no_mutation_probe", + ); + if !super::emitted_closure_compile_host::cargo_verdict_compiled(&verdict) { + return Err(format!( + "V2-NATIVE REFUSAL cause=EmittedCompilerBuildFailed — {}", + super::emitted_closure_compile_host::cargo_verdict_summary(&verdict) + )); + } + let binary_path = workspace + .join("target") + .join("release") + .join(super::emitted_closure_compile_host::probe_package_name(NATIVE_COMPILE_ENTRY)); + if !binary_path.is_file() { + return Err(format!( + "V2-NATIVE REFUSAL cause=EmittedCompilerAbsent — cargo reported success but {} is \ + not a file", + binary_path.display() + )); + } + let binary_identity = sha256_file(&binary_path)?; + let seed_identity = sha256_file(&std::env::current_exe().map_err(|e| { + format!("V2-NATIVE REFUSAL cause=SeedIdentityUnreadable — current_exe: {e}") + })?)?; + eprintln!( + "required-ci: v2-native emitted compiler at {} (sha256 {binary_identity})", + binary_path.display() + ); + Ok(EmittedPreparation { + binary_path, + binary_identity, + closure_identity, + seed_identity, + }) +} + +/// THE OLD-ROUTE WITHDRAWAL, AS A GUARD SO A REFUSAL PATH CANNOT SKIP THE RESTORE. The seed's +/// `gunbc` binary — the old compiler/interpreter route's CLI — is renamed out of the way before +/// the first native spawn and restored when the guard drops, on every exit path. The receipt +/// records the withdrawn executable's path; the window is what admission's control clause names. +struct OldRouteWithdrawalGuard { + original: PathBuf, + withdrawn: PathBuf, +} + +fn withdraw_old_route(workspace: &Path) -> Result { + let original = workspace.join("target").join("release").join("gunbc"); + let withdrawn = workspace + .join("target") + .join("release") + .join("gunbc.withdrawn-v2-native"); + if !original.is_file() { + return Err(format!( + "V2-NATIVE REFUSAL cause=OldRouteWithdrawalImpossible — {} is not a file; the \ + control cannot withdraw what is absent", + original.display() + )); + } + std::fs::rename(&original, &withdrawn).map_err(|e| { + format!( + "V2-NATIVE REFUSAL cause=OldRouteWithdrawalImpossible — renaming {}: {e}", + original.display() + ) + })?; + eprintln!( + "required-ci: v2-native old route withdrawn ({} moved aside for the native spawns)", + original.display() + ); + Ok(OldRouteWithdrawalGuard { original, withdrawn }) +} + +impl Drop for OldRouteWithdrawalGuard { + fn drop(&mut self) { + if let Err(e) = std::fs::rename(&self.withdrawn, &self.original) { + eprintln!( + "required-ci: v2-native WARNING — restoring the old route failed ({}): {e}", + self.original.display() + ); + } + } +} + +/// The universe file the emitted binary reads: `moduledeclaration` per line, the derived +/// universe followed by the two live-verdict controls (which the harness names explicitly — +/// they are controls, never universe members, and the receipt's population join would refuse +/// them as foreign rows if they reached it). +fn write_universe_file( + path: &Path, + universe: &[(String, String)], +) -> Result<(), String> { + let mut text = String::new(); + for (module, declaration) in universe { + text.push_str(module); + text.push('\t'); + text.push_str(declaration); + text.push('\n'); + } + for declaration in [FALSE_CONTROL_DECL, TRUE_CONTROL_DECL] { + text.push_str(CONTROL_MODULE); + text.push('\t'); + text.push_str(declaration); + text.push('\n'); + } + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("creating {}: {e}", parent.display()))?; + } + std::fs::write(path, text).map_err(|e| format!("writing {}: {e}", path.display())) +} + +/// One controls-only universe file for the malformed-specimen run: the poisoned source root is +/// the subject, and the controls give the run a live verdict path to print beside the refusal. +fn write_control_universe_file(path: &Path) -> Result<(), String> { + write_universe_file(path, &[]) +} + +/// The spawned run's decoded observations: one row per printed verdict, the per-file refusals +/// the context fold collected, and the terminal marker's row count. A line that is neither an +/// observation, nor a file refusal, nor the terminal marker refuses the parse — the binary's +/// stdout is a receipt surface, and an unrecognized line on it is a harness defect, not noise. +struct NativeRunOutput { + observations: Vec, + file_refusals: Vec, + terminal_rows: u64, +} + +fn decode_verdict_json(value: &serde_json::Value) -> Result { + let variant = value + .get("_variant") + .and_then(|v| v.as_str()) + .ok_or_else(|| format!("verdict carries no _variant tag: {value}"))?; + match variant { + "NativeTestPassed" => Ok(NativeVerdictObserved::Passed), + "NativeTestReturnedFalse" => Ok(NativeVerdictObserved::ReturnedFalse), + "NativeTestReturnedOther" => Ok(NativeVerdictObserved::ReturnedOther), + "NativeTestRefused" => { + let stage = value + .get("stage") + .and_then(|s| s.get("_variant")) + .and_then(|v| v.as_str()) + .ok_or_else(|| format!("refusal verdict carries no stage tag: {value}"))?; + let reason = value + .get("reason") + .and_then(|r| r.as_str()) + .ok_or_else(|| format!("refusal verdict carries no reason: {value}"))?; + Ok(NativeVerdictObserved::Refused { + stage: stage.to_string(), + reason: reason.to_string(), + }) + } + other => Err(format!("unknown verdict variant: {other}")), + } +} + +fn parse_native_run_output(stdout: &str) -> Result { + let mut observations = Vec::new(); + let mut file_refusals = Vec::new(); + let mut terminal_rows = None; + for line in stdout.lines() { + if line.trim().is_empty() { + continue; + } + let value: serde_json::Value = serde_json::from_str(line) + .map_err(|e| format!("unparseable line on the native run's stdout: {e}: {line}"))?; + if let Some(terminal) = value.get("_terminal") { + if terminal.as_str() != Some("complete") { + return Err(format!("terminal marker is not complete: {line}")); + } + terminal_rows = value.get("rows").and_then(|r| r.as_u64()); + continue; + } + if let Some(refusal) = value.get("file_refusal") { + let path = refusal + .get("path") + .and_then(|v| v.as_str()) + .ok_or_else(|| format!("file refusal carries no path: {line}"))?; + let head_reason = refusal + .get("head_reason") + .and_then(|v| v.as_str()) + .ok_or_else(|| format!("file refusal carries no head_reason: {line}"))?; + let fatal_reason = refusal + .get("fatal_reason") + .and_then(|v| v.as_str()) + .ok_or_else(|| format!("file refusal carries no fatal_reason: {line}"))?; + file_refusals.push(NativeFileRefusalObserved { + path: path.to_string(), + head_reason: head_reason.to_string(), + fatal_reason: fatal_reason.to_string(), + }); + continue; + } + let module: Vec = value + .get("module") + .and_then(|m| serde_json::from_value(m.clone()).ok()) + .ok_or_else(|| format!("observation carries no module segments: {line}"))?; + let declaration = value + .get("declaration") + .and_then(|d| d.as_str()) + .ok_or_else(|| format!("observation carries no declaration: {line}"))?; + let verdict = decode_verdict_json( + value + .get("verdict") + .ok_or_else(|| format!("observation carries no verdict: {line}"))?, + )?; + observations.push(NativeObservation { + module: module.join("."), + declaration: declaration.to_string(), + verdict, + }); + } + let terminal_rows = terminal_rows + .ok_or_else(|| "the native run printed no terminal marker".to_string())?; + Ok(NativeRunOutput { + observations, + file_refusals, + terminal_rows, + }) +} + +/// Spawn the emitted binary once, by explicit path, and decode its stdout. A nonzero exit or an +/// unparseable stdout refuses the lane — the run's output is the receipt's evidence, and a +/// partial or garbled evidence stream is a red, never a truncated green. +fn run_native_binary( + binary: &Path, + universe_file: &Path, + source_roots: &[String], +) -> Result { + let output = Command::new(binary) + .arg(universe_file) + .args(source_roots) + .output() + .map_err(|e| { + format!( + "V2-NATIVE REFUSAL cause=NativeRunSpawnFailed — spawning {}: {e}", + binary.display() + ) + })?; + let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + if !output.status.success() { + return Err(format!( + "V2-NATIVE REFUSAL cause=NativeRunFailed status={:?} — {}\n{}", + output.status.code(), + binary.display(), + String::from_utf8_lossy(&output.stderr) + .lines() + .rev() + .take(20) + .collect::>() + .into_iter() + .rev() + .collect::>() + .join("\n") + )); + } + parse_native_run_output(&stdout) +} + +/// A CONTEXT-STAGE ROW IS A DERIVED CLASSIFICATION, AND IT IS MINTED ONLY FROM ITS OBSERVATION. +/// A module whose file the context fold refused never reached preparation, so the binary's row +/// for it is a prepare-stage resolver refusal; the harness rewrites exactly those rows to the +/// Context stage carrying the FILE'S fatal reason — the cause that actually refused the file — +/// and admission's context_refusals_backed clause refuses any Context row whose reason no +/// observed file refusal carries. A prepare refusal whose module's file loaded stays a prepare +/// refusal with the resolver's own reason. +fn reclassify_context_refusals( + observations: &mut [NativeObservation], + file_refusals: &[NativeFileRefusalObserved], + module_for_path: &HashMap, +) { + let refused_by_path: HashMap<&str, &NativeFileRefusalObserved> = file_refusals + .iter() + .map(|fr| (fr.path.as_str(), fr)) + .collect(); + for observation in observations.iter_mut() { + let Some(path) = module_for_path.get(observation.module.as_str()) else { + continue; + }; + let Some(file_refusal) = refused_by_path.get(path.as_str()) else { + continue; + }; + if matches!(observation.verdict, NativeVerdictObserved::Refused { .. }) { + observation.verdict = NativeVerdictObserved::Refused { + stage: "NativeTestStageContext".to_string(), + reason: file_refusal.fatal_reason.clone(), + }; + } + } +} + +// ── the receipt, as interpreter Values ────────────────────────────────────────────────────── +// +// The receipt is built as the `.dag` authority's own types — Symbol fields enter as Str (the +// interpreter's symbol literal value), records and variants name their types, and the Optional +// controls use the Present/Absent encoding every host bridge in this crate shares. + +fn identity_value( + ctx: &v1_interpreter::InterpContext, + module: &str, + declaration: &str, +) -> Value { + Value::Record { + type_name: ctx.sym("NativeRouteTestIdentity"), + fields: Rc::new(vec![ + (ctx.sym("module"), str_value(module)), + (ctx.sym("declaration"), str_value(declaration)), + ]), + } +} + +fn verdict_value(ctx: &v1_interpreter::InterpContext, verdict: &NativeVerdictObserved) -> Value { + match verdict { + NativeVerdictObserved::Passed => Value::Variant { + type_name: ctx.sym("NativeTestVerdict"), + variant_name: ctx.sym("NativeTestPassed"), + fields: Rc::new(vec![]), + }, + NativeVerdictObserved::ReturnedFalse => Value::Variant { + type_name: ctx.sym("NativeTestVerdict"), + variant_name: ctx.sym("NativeTestReturnedFalse"), + fields: Rc::new(vec![]), + }, + NativeVerdictObserved::ReturnedOther => Value::Variant { + type_name: ctx.sym("NativeTestVerdict"), + variant_name: ctx.sym("NativeTestReturnedOther"), + fields: Rc::new(vec![]), + }, + NativeVerdictObserved::Refused { stage, reason } => Value::Variant { + type_name: ctx.sym("NativeTestVerdict"), + variant_name: ctx.sym("NativeTestRefused"), + fields: Rc::new(vec![ + ( + ctx.sym("stage"), + Value::Variant { + type_name: ctx.sym("NativeTestStage"), + variant_name: ctx.sym(stage), + fields: Rc::new(vec![]), + }, + ), + (ctx.sym("reason"), str_value(reason)), + ]), + }, + } +} + +fn optional_verdict_value( + ctx: &v1_interpreter::InterpContext, + verdict: Option<&NativeVerdictObserved>, +) -> Value { + match verdict { + Some(v) => Value::Variant { + type_name: ctx.sym("Optional"), + variant_name: ctx.sym("Present"), + fields: Rc::new(vec![(ctx.sym("value"), verdict_value(ctx, v))]), + }, + None => Value::Variant { + type_name: ctx.sym("Optional"), + variant_name: ctx.sym("Absent"), + fields: Rc::new(vec![]), + }, + } +} + +#[allow(clippy::too_many_arguments)] +fn receipt_value( + ctx: &v1_interpreter::InterpContext, + tested_tree: &str, + preparation: &EmittedPreparation, + universe: &[(String, String)], + population: &[NativeObservation], + file_refusals: &[NativeFileRefusalObserved], + false_control: Option<&NativeVerdictObserved>, + true_control: Option<&NativeVerdictObserved>, + malformed_control: Value, + withdrawn_executable: &str, +) -> Value { + let universe_values: Vec = universe + .iter() + .map(|(module, declaration)| identity_value(ctx, module, declaration)) + .collect(); + let population_values: Vec = population + .iter() + .map(|row| Value::Record { + type_name: ctx.sym("NativeRouteMemberRow"), + fields: Rc::new(vec![ + ( + ctx.sym("identity"), + identity_value(ctx, &row.module, &row.declaration), + ), + (ctx.sym("verdict"), verdict_value(ctx, &row.verdict)), + ]), + }) + .collect(); + let file_refusal_values: Vec = file_refusals + .iter() + .map(|fr| Value::Record { + type_name: ctx.sym("NativeTestFileRefusal"), + fields: Rc::new(vec![ + (ctx.sym("path"), str_value(&fr.path)), + (ctx.sym("head_reason"), str_value(&fr.head_reason)), + (ctx.sym("fatal_reason"), str_value(&fr.fatal_reason)), + ]), + }) + .collect(); + Value::Record { + type_name: ctx.sym("NativeRouteReceipt"), + fields: Rc::new(vec![ + (ctx.sym("job"), str_value("required-v2-native")), + (ctx.sym("tested_tree"), str_value(tested_tree)), + ( + ctx.sym("preparation_seed_identity"), + str_value(&preparation.seed_identity), + ), + ( + ctx.sym("emitted_closure_identity"), + str_value(&preparation.closure_identity), + ), + ( + ctx.sym("executable_path"), + str_value(preparation.binary_path.display().to_string()), + ), + ( + ctx.sym("executable_identity"), + str_value(&preparation.binary_identity), + ), + (ctx.sym("universe"), super::list_value_from_vec(universe_values)), + ( + ctx.sym("population"), + super::list_value_from_vec(population_values), + ), + ( + ctx.sym("file_refusals"), + super::list_value_from_vec(file_refusal_values), + ), + ( + ctx.sym("false_control"), + optional_verdict_value(ctx, false_control), + ), + ( + ctx.sym("true_control"), + optional_verdict_value(ctx, true_control), + ), + (ctx.sym("malformed_control"), malformed_control), + ( + ctx.sym("old_route_control"), + Value::Variant { + type_name: ctx.sym("NativeRouteOldRouteControl"), + variant_name: ctx.sym("OldRouteWithdrawn"), + fields: Rc::new(vec![( + ctx.sym("withdrawn_executable"), + str_value(withdrawn_executable), + )]), + }, + ), + ]), + } +} + +/// The lane's one phase. Green exactly when the admission authority admits the minted receipt; +/// every earlier failure is a located refusal that stops the line. THE VERDICT IS THE +/// AUTHORITY'S OWN, EVALUATED — never re-decided in Rust: the receipt Value is handed to +/// `native_route_admission` in the authority's closure, and the summary the lane prints is +/// `native_route_admission_summary` of that same value, so the terminal line and the admission +/// fold cannot disagree about what was decided. +pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { + let workspace = super::process_workspace_root(); + let tested_tree = std::env::var("GITHUB_SHA").unwrap_or_else(|_| "local".to_string()); + + // 1. THE UNIVERSE, DERIVED. The floor's producer over the full module inventory, filtered + // to the universe prefix, plus the module->path join the context reclassification needs. + let (universe, module_for_path) = derive_native_universe(source_roots)?; + eprintln!( + "required-ci: v2-native universe derived — {} v2.test.* identities", + universe.len() + ); + + // 2. PREPARATION. The seed emits the compiler closure once, in-process; cargo builds the + // emitted crate; the receipt records all three identities. + let preparation = prepare_emitted_compiler(source_roots)?; + + // 3. THE UNIVERSE FILE — the derived universe plus the two named controls. + let universe_file = workspace + .join("target") + .join("v2-native-lane") + .join("universe.tsv"); + write_universe_file(&universe_file, &universe)?; + + // 4. THE OLD ROUTE IS WITHDRAWN for the whole spawn window (universe run and malformed + // control alike); the guard restores it on every exit path. + let withdrawal = withdraw_old_route(&workspace)?; + let withdrawn_executable = withdrawal.original.display().to_string(); + + // 5. THE NATIVE RUN. The emitted binary, by explicit path, over the derived universe. + eprintln!("required-ci: v2-native running the emitted compiler over the universe"); + let main_output = run_native_binary(&preparation.binary_path, &universe_file, source_roots)?; + eprintln!( + "required-ci: v2-native native run complete — {} verdict rows, {} file refusals", + main_output.terminal_rows, + main_output.file_refusals.len() + ); + + // 6. THE MALFORMED CONTROL. The same binary with the poison fixture's directory added as a + // source root; an honest front-end collects a per-file refusal for it. + let control_universe_file = workspace + .join("target") + .join("v2-native-lane") + .join("controls-universe.tsv"); + write_control_universe_file(&control_universe_file)?; + let mut control_roots = source_roots.to_vec(); + control_roots.push(MALFORMED_FIXTURE_DIR.to_string()); + let control_output = run_native_binary( + &preparation.binary_path, + &control_universe_file, + &control_roots, + )?; + drop(withdrawal); + eprintln!("required-ci: v2-native old route restored"); + + // 7. THE RECEIPT. Controls are lifted out of the observed population (they are not universe + // members and the exact join would refuse them as foreign), context refusals are reclassified + // against the observed file refusals, and the malformed control records what the poisoned + // run observed. + let mut population: Vec = Vec::new(); + let mut false_control: Option = None; + let mut true_control: Option = None; + for observation in main_output.observations { + if observation.module == CONTROL_MODULE && observation.declaration == FALSE_CONTROL_DECL { + false_control = Some(observation.verdict); + } else if observation.module == CONTROL_MODULE + && observation.declaration == TRUE_CONTROL_DECL + { + true_control = Some(observation.verdict); + } else { + population.push(observation); + } + } + reclassify_context_refusals( + &mut population, + &main_output.file_refusals, + &module_for_path, + ); + let malformed_control = match control_output + .file_refusals + .iter() + .find(|fr| fr.path.contains(MALFORMED_FIXTURE_PATH)) + { + Some(fr) => { + let reason = fr.fatal_reason.clone(); + let path = fr.path.clone(); + // Built late because it needs the admission context's symbols; carried as data here. + (path, reason) + } + None => (String::new(), String::new()), + }; + + let (graph, indices) = super::resolve_workspace_entry( + source_roots, + super::WorkspaceRootRelativeEntry(NATIVE_ROUTE_AUTHORITY_ENTRY), + )?; + let route_ctx = super::make_eval_context(&graph, indices, v1_interpreter::ExecutionMode::Wet); + let malformed_value = if malformed_control.0.is_empty() { + Value::Variant { + type_name: route_ctx.sym("NativeRouteMalformedControl"), + variant_name: route_ctx.sym("MalformedSpecimenAccepted"), + fields: Rc::new(vec![]), + } + } else { + Value::Variant { + type_name: route_ctx.sym("NativeRouteMalformedControl"), + variant_name: route_ctx.sym("MalformedSpecimenRefused"), + fields: Rc::new(vec![ + ( + route_ctx.sym("path"), + str_value(&malformed_control.0), + ), + ( + route_ctx.sym("reason"), + str_value(&malformed_control.1), + ), + ]), + } + }; + let receipt = receipt_value( + &route_ctx, + &tested_tree, + &preparation, + &universe, + &population, + &main_output.file_refusals, + false_control.as_ref(), + true_control.as_ref(), + malformed_value, + &withdrawn_executable, + ); + + // 8. ADMISSION, BY THE AUTHORITY. The lane prints the authority's own summary either way. + let admission = v1_interpreter::run_in_context_with_args( + &route_ctx, + "gunbc.witness_v2_native_route.native_route_admission", + &[(Some("receipt".to_string()), receipt)], + false, + ) + .map_err(|e| format!("V2-NATIVE REFUSAL cause=AdmissionUnevaluable — {e}"))?; + let summary = v1_interpreter::run_in_context_with_args( + &route_ctx, + "gunbc.witness_v2_native_route.native_route_admission_summary", + &[(Some("admission".to_string()), admission.clone())], + false, + ) + .map_err(|e| format!("V2-NATIVE REFUSAL cause=AdmissionUnevaluable — summary: {e}"))?; + let summary_text = match &summary { + Value::Str(s) => s.to_string(), + other => { + return Err(format!( + "V2-NATIVE REFUSAL cause=AdmissionUnevaluable — the summary returned {}, not a \ + String", + super::output_policy_value_shape(other) + )) + } + }; + let admitted = match &admission { + Value::Variant { variant_name, .. } => { + route_ctx.sym_eq(*variant_name, "NativeRouteAdmitted") + } + other => { + return Err(format!( + "V2-NATIVE REFUSAL cause=AdmissionUnevaluable — native_route_admission returned \ + {}, not a NativeRouteAdmission variant", + super::output_policy_value_shape(other) + )) + } + }; + eprintln!("required-ci: v2-native admission {summary_text}"); + if admitted { + Ok(()) + } else { + Err(format!( + "V2-NATIVE REFUSAL cause=AdmissionRefused — {summary_text}" + )) + } +} diff --git a/src/v1/stage0/src/emitted_closure_compile_host.rs b/src/v1/stage0/src/emitted_closure_compile_host.rs index 4134685e412..068aa30f639 100644 --- a/src/v1/stage0/src/emitted_closure_compile_host.rs +++ b/src/v1/stage0/src/emitted_closure_compile_host.rs @@ -88,7 +88,9 @@ const PROBE_ROOT_DIR_NAME: &str = "gunbc-emit-compile"; /// /// Deriving the name per entry makes each probe crate its own package, so fingerprints cannot /// alias; dependencies are separate packages and stay warm. -fn probe_package_name(entry: &str) -> String { +/// `pub(crate)` so the v2-native lane's harness names the built binary by the same package name +/// the manifest writer gave the crate — one derivation, never a second slug beside it. +pub(crate) fn probe_package_name(entry: &str) -> String { let slug: String = entry .chars() .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' }) @@ -584,7 +586,11 @@ fn probe_crate_dir(probe_root: &Path, entry: &str) -> PathBuf { } /// Write the emitted Rust files plus a manifest, and return the crate directory. -fn write_probe_crate( +/// +/// `pub(crate)` for the required-v2-native lane's harness (cli_run::native_lane_runner), which +/// prepares the emitted-native compiler through this same writer rather than growing a second +/// one beside it (DESIGN §2 — the note on `write_probe_crate_files` is the argument). +pub(crate) fn write_probe_crate( run: &CompileRun, probe_root: &Path, entry: &str, @@ -697,7 +703,9 @@ fn attributed_diagnostic( /// /// Phases within one required run are sequential in one process, so nothing else holds cargo's /// lock on that directory. -fn run_cargo(crate_dir: &Path, workspace: &Path, attribution_symbol: &str) -> CargoVerdict { +/// `pub(crate)` for the same consumer as `write_probe_crate`: the v2-native lane builds the +/// emitted compiler crate through this same cargo invocation. +pub(crate) fn run_cargo(crate_dir: &Path, workspace: &Path, attribution_symbol: &str) -> CargoVerdict { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); let mut command = std::process::Command::new(&cargo); command From 66f757e6fef722fcd195e6bbb6121d992e017ad4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 09:29:02 +0000 Subject: [PATCH 55/89] Regenerate stage0 mirrors for the SourceRootEvalDriver emitter arm std_compiler_entry.rs gains the SourceRootEvalDriver variant and v1_compiler_emit_rust.rs the emit_source_root_eval_driver_main_rs template with its dispatch arm, emitted by the regenerated seed and verified at the fixed point (first_generation_equal=true over the whole 155-module population). Co-authored-by: briansrls --- src/v1/stage0/src/std_compiler_entry.rs | 3 ++ src/v1/stage0/src/v1_compiler_emit_rust.rs | 43 +++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/std_compiler_entry.rs b/src/v1/stage0/src/std_compiler_entry.rs index b3b96ec2c84..b22a21ba75a 100644 --- a/src/v1/stage0/src/std_compiler_entry.rs +++ b/src/v1/stage0/src/std_compiler_entry.rs @@ -16,9 +16,12 @@ use std::rc::Rc; pub enum CompilerEntryDriver { RetainedHostCliKernel, DirectIngestDriver, + SourceRootEvalDriver, } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct RetainedHostCliKernel; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct DirectIngestDriver; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct SourceRootEvalDriver; diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 503740cf544..5ac701489c3 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -38627,6 +38627,15 @@ pub fn compiler_entry_retained_host_driver() -> String { CACHED.with(|c: &String| c.clone()) } +pub fn compiler_entry_source_root_eval_driver() -> String { + thread_local! { + static CACHED: String = { + "SourceRootEvalDriver".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct CompilerPipelineEntryDecl { pub module_name: String, @@ -38738,6 +38747,13 @@ pub fn compiler_pipeline_entry_is_retained_host(modules: Rc> } } +pub fn compiler_pipeline_entry_is_source_root_eval(modules: Rc>>) -> bool { + match compiler_pipeline_entry_decl(modules.clone()) { + Some(d) => (d.driver.clone() == compiler_entry_source_root_eval_driver()), + std::option::Option::None => false, + } +} + pub fn compiler_pipeline_entry_is_ambiguous(modules: Rc>>) -> bool { ((compiler_pipeline_entry_decls(modules.clone()).len() as i64) > 1) } @@ -38927,7 +38943,18 @@ pub fn emit_main_rs( if (corpus_has_compiler_pipeline(modules.clone()) && (retained_host_pipeline.clone() == false)) { - return emit_direct_ingest_driver_main_rs(crate_name.clone(), pipeline_module.clone()); + { + if compiler_pipeline_entry_is_source_root_eval(modules.clone()) { + return emit_source_root_eval_driver_main_rs( + crate_name.clone(), + pipeline_module.clone(), + ); + } + return emit_direct_ingest_driver_main_rs( + crate_name.clone(), + pipeline_module.clone(), + ); + } } if (((workflow_funcs.clone().len() as i64) == 0) && (retained_host_pipeline.clone() == false)) @@ -39082,6 +39109,20 @@ pub fn emit_direct_ingest_driver_main_rs( } } +pub fn emit_source_root_eval_driver_main_rs( + crate_name: String, + pipeline_module: String, +) -> Rc { + { + let pipeline_mod = + crate::v1_compiler_emit_core_support::module_to_filename(pipeline_module.clone()); + Rc::new(TextFile { + path: v1_rt::concat(v1_rt::concat(rust_source_root(), "main".to_string()), rust_source_ext()), + content: v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("// Generated by v1 compiler -- do not edit.\n\n".to_string(), "#![allow(unused_parens, clippy::all, clippy::disallowed_macros)]\n\n".to_string()), "use std::rc::Rc;\n\n".to_string()), "use ".to_string()), crate_name.clone()), "::extdeps_communication_medium::{DecodeFidelity, Medium};\n".to_string()), "use ".to_string()), crate_name.clone()), "::".to_string()), pipeline_mod.clone()), "::{\n".to_string()), " native_test_context_from_ingest, native_test_eval_one, native_test_prepare_module,\n".to_string()), " NativeTestObservation, NativeTestStage, NativeTestVerdict,\n".to_string()), "};\n".to_string()), "use ".to_string()), crate_name.clone()), "::v2_compiler_source_authority::{source_root_for_storage_path, DagSourceReadWitness};\n".to_string()), "use ".to_string()), crate_name.clone()), "::v2_std_artifact::{Artifact, ArtifactKind};\n".to_string()), "use ".to_string()), crate_name.clone()), "::v2_std_diagnostic::Outcome;\n".to_string()), "\n".to_string()), "fn collect_dag_paths(dir: &std::path::Path, out: &mut Vec) -> Result<(), String> {\n".to_string()), " let entries = match std::fs::read_dir(dir) {\n".to_string()), " Ok(entries) => entries,\n".to_string()), " Err(cause) => return Err(format!(\"could not walk source root {}: {cause}\", dir.display())),\n".to_string()), " };\n".to_string()), " for entry in entries {\n".to_string()), " let entry = match entry {\n".to_string()), " Ok(entry) => entry,\n".to_string()), " Err(cause) => return Err(format!(\"could not read entry under {}: {cause}\", dir.display())),\n".to_string()), " };\n".to_string()), " let path = entry.path();\n".to_string()), " if path.is_dir() {\n".to_string()), " collect_dag_paths(&path, out)?;\n".to_string()), " } else if path.extension().map(|ext| ext == \"dag\").unwrap_or(false) {\n".to_string()), " out.push(path.to_string_lossy().into_owned());\n".to_string()), " }\n".to_string()), " }\n".to_string()), " Ok(())\n".to_string()), "}\n".to_string()), "\n".to_string()), "fn main() {\n".to_string()), " let mut args = std::env::args().skip(1);\n".to_string()), " let universe_path = match args.next() {\n".to_string()), " Some(path) => path,\n".to_string()), " None => {\n".to_string()), " eprintln!(\"REFUSED: no universe file given -- usage: ".to_string()), crate_name.clone()), " ...\");\n".to_string()), " std::process::exit(2);\n".to_string()), " }\n".to_string()), " };\n".to_string()), " let source_roots: Vec = args.collect();\n".to_string()), " if source_roots.is_empty() {\n".to_string()), " eprintln!(\"REFUSED: no source roots given -- usage: ".to_string()), crate_name.clone()), " ...\");\n".to_string()), " std::process::exit(2);\n".to_string()), " }\n".to_string()), " let universe_text = match std::fs::read_to_string(&universe_path) {\n".to_string()), " Ok(text) => text,\n".to_string()), " Err(cause) => {\n".to_string()), " eprintln!(\"REFUSED: could not read universe file {universe_path}: {cause}\");\n".to_string()), " std::process::exit(2);\n".to_string()), " }\n".to_string()), " };\n".to_string()), " let mut universe: Vec<(Rc>, String)> = Vec::new();\n".to_string()), " let mut seen: std::collections::HashSet = std::collections::HashSet::new();\n".to_string()), " for line in universe_text.lines() {\n".to_string()), " if line.is_empty() || line.starts_with('#') {\n".to_string()), " continue;\n".to_string()), " }\n".to_string()), " match line.split_once('\\t') {\n".to_string()), " Some((module, declaration)) if !module.is_empty() && !declaration.is_empty() => {\n".to_string()), " if !seen.insert(line.to_string()) {\n".to_string()), " eprintln!(\"REFUSED: duplicate universe line: {line}\");\n".to_string()), " std::process::exit(2);\n".to_string()), " }\n".to_string()), " universe.push((\n".to_string()), " Rc::new(module.split('.').map(|segment| segment.to_string()).collect::>()),\n".to_string()), " declaration.to_string(),\n".to_string()), " ));\n".to_string()), " }\n".to_string()), " _ => {\n".to_string()), " eprintln!(\"REFUSED: malformed universe line (expected moduledeclaration): {line}\");\n".to_string()), " std::process::exit(2);\n".to_string()), " }\n".to_string()), " }\n".to_string()), " }\n".to_string()), " if universe.is_empty() {\n".to_string()), " eprintln!(\"REFUSED: universe file {universe_path} carried no test identities\");\n".to_string()), " std::process::exit(2);\n".to_string()), " }\n".to_string()), " let mut paths: Vec = Vec::new();\n".to_string()), " for root in &source_roots {\n".to_string()), " if let Err(cause) = collect_dag_paths(std::path::Path::new(root), &mut paths) {\n".to_string()), " eprintln!(\"REFUSED: {cause}\");\n".to_string()), " std::process::exit(2);\n".to_string()), " }\n".to_string()), " }\n".to_string()), " paths.sort();\n".to_string()), " let mut reads: Vec> = Vec::new();\n".to_string()), " for path in &paths {\n".to_string()), " let text = match std::fs::read_to_string(path) {\n".to_string()), " Ok(text) => text,\n".to_string()), " Err(cause) => {\n".to_string()), " eprintln!(\"REFUSED: could not read {path}: {cause}\");\n".to_string()), " std::process::exit(2);\n".to_string()), " }\n".to_string()), " };\n".to_string()), " reads.push(Rc::new(DagSourceReadWitness {\n".to_string()), " source: Rc::new(Medium {\n".to_string()), " carried: text,\n".to_string()), " fidelity: DecodeFidelity::Lossless,\n".to_string()), " _phantom: std::marker::PhantomData,\n".to_string()), " }),\n".to_string()), " artifact: Rc::new(Artifact {\n".to_string()), " kind: ArtifactKind::SourceFile,\n".to_string()), " id: path.clone(),\n".to_string()), " file_path: path.clone(),\n".to_string()), " }),\n".to_string()), " compilation_unit: path.clone(),\n".to_string()), " source_root: source_root_for_storage_path(path.clone()),\n".to_string()), " }));\n".to_string()), " }\n".to_string()), " let context = match &*native_test_context_from_ingest(Rc::new(reads.into())) {\n".to_string()), " Outcome::Accepted { value, .. } => value.clone(),\n".to_string()), " Outcome::Rejected { diagnostics } => {\n".to_string()), " eprintln!(\"CONTEXT-REFUSED: {diagnostics:#?}\");\n".to_string()), " std::process::exit(1);\n".to_string()), " }\n".to_string()), " };\n".to_string()), " for refusal in context.file_refusals.iter() {\n".to_string()), " println!(\"{}\", serde_json::json!({ \"file_refusal\": refusal }));\n".to_string()), " }\n".to_string()), " let mut module_order: Vec<(String, Rc>)> = Vec::new();\n".to_string()), " let mut module_decls: std::collections::HashMap> = std::collections::HashMap::new();\n".to_string()), " for (qn, declaration) in &universe {\n".to_string()), " let dotted = qn.iter().cloned().collect::>().join(\".\");\n".to_string()), " if !module_decls.contains_key(&dotted) {\n".to_string()), " module_order.push((dotted.clone(), qn.clone()));\n".to_string()), " module_decls.insert(dotted.clone(), Vec::new());\n".to_string()), " }\n".to_string()), " module_decls.get_mut(&dotted).unwrap().push(declaration.clone());\n".to_string()), " }\n".to_string()), " let mut rows: u64 = 0;\n".to_string()), " for (dotted, qn) in &module_order {\n".to_string()), " let declarations = &module_decls[dotted];\n".to_string()), " match &*native_test_prepare_module(context.clone(), qn.clone()) {\n".to_string()), " Outcome::Accepted { value: prepared, .. } => {\n".to_string()), " for declaration in declarations {\n".to_string()), " let observation = native_test_eval_one(prepared.clone(), declaration.clone());\n".to_string()), " println!(\"{}\", serde_json::to_string(&observation).unwrap());\n".to_string()), " rows += 1;\n".to_string()), " }\n".to_string()), " }\n".to_string()), " Outcome::Rejected { diagnostics } => {\n".to_string()), " let reason = diagnostics.tail.last().map(|d| d.reason.clone()).unwrap_or_else(|| diagnostics.head.reason.clone());\n".to_string()), " for declaration in declarations {\n".to_string()), " let observation = NativeTestObservation {\n".to_string()), " module: qn.clone(),\n".to_string()), " declaration: declaration.clone(),\n".to_string()), " verdict: Rc::new(NativeTestVerdict::NativeTestRefused {\n".to_string()), " stage: NativeTestStage::NativeTestStagePrepare,\n".to_string()), " reason: reason.clone(),\n".to_string()), " }),\n".to_string()), " };\n".to_string()), " println!(\"{}\", serde_json::to_string(&observation).unwrap());\n".to_string()), " rows += 1;\n".to_string()), " }\n".to_string()), " }\n".to_string()), " }\n".to_string()), " }\n".to_string()), " println!(\"{}\", serde_json::json!({ \"_terminal\": \"complete\", \"rows\": rows, \"file_refusals\": context.file_refusals.len() }));\n".to_string()), "}\n".to_string()), +}) + } +} + pub fn emit_entry_point_absent_main_rs(crate_name: String) -> Rc { Rc::new(TextFile { path: v1_rt::concat(v1_rt::concat(rust_source_root(), "main".to_string()), rust_source_ext()), From 3e0344003db7d4e12f0d8448dd9142ebdf851288 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 09:51:21 +0000 Subject: [PATCH 56/89] Name the probe crate's lib target v1_compiled, the emitter's self-name contract emit_rust_selected binds the self-emitted crate's name to v1_compiled for every non-retained-host pipeline entry, and the SourceRootEvalDriver and DirectIngestDriver mains reach the closure through use v1_compiled::. The probe manifest's per-entry package name left the lib target named after the package, so a pipeline entry's driver main failed E0433 in the probe build -- unreachable while every probe entry was pipeline-free, and measured on the required-v2-native lane's first preparation. The lib path stays cargo's default; only the name is stated. Co-authored-by: briansrls --- .../src/emitted_closure_compile_host.rs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/v1/stage0/src/emitted_closure_compile_host.rs b/src/v1/stage0/src/emitted_closure_compile_host.rs index 068aa30f639..7603d35889d 100644 --- a/src/v1/stage0/src/emitted_closure_compile_host.rs +++ b/src/v1/stage0/src/emitted_closure_compile_host.rs @@ -485,8 +485,18 @@ pub fn emit_compile_outcome_summary(outcome: &EmitCompileOutcome) -> String { /// Package header from `extdeps.rust.version` `render_cargo_package_header_prefix`; dependency /// rows from `v1.compiler.stage0_crates` `stage0_foundation_runtime_dependencies` (the seed's /// runtime dependency set, which emitted code links against), each rendered by that module's -/// `render_stage0_crate_dep`. No `[lib]` section: `src/lib.rs` is cargo's own default, so naming -/// it would be a second spelling. +/// `render_stage0_crate_dep`. +/// +/// THE `[lib]` NAME IS THE EMITTER'S CONTRACT, NOT A SPELLING OF THE PATH. `src/lib.rs` stays +/// cargo's default path; what must be named is the LIB TARGET's crate name, because the emitted +/// `main.rs` reaches the closure through it: `v1.compiler.emit_rust` `emit_rust_selected` binds +/// the self-emitted crate's name (`v1_compiled` for every non-retained-host pipeline entry), and +/// the SourceRootEvalDriver and DirectIngestDriver mains both `use v1_compiled::…`. The package +/// name is per-entry (one slug per probe, sharing one target dir), so without this section the +/// lib takes the package's name and the driver main's self-references fail E0433 — measured on +/// the required-v2-native lane's first preparation. Pipeline-free probe entries never named +/// their crate in a `use`, so the gap was unreachable until a pipeline entry became a probe +/// subject. /// /// The corpus's hand-authored TOML string (`tools.self_host_curated_seed_linked_harness` /// `cssl_v1_compiled_probe_lib_cargo_toml`) is deliberately not used: it is marked scaffold debt @@ -530,8 +540,11 @@ fn probe_manifest(workspace: &Path, entry: &str) -> String { let features = render_stage0_crate_features_section(stage0_features_for_crate_kind( GeneratedPartitionCrateKind::GeneratedFoundationCrate, )); + // `v1_compiled` is the emitter's own literal (`emit_rust_selected`), mirrored here the way + // every seed mirror in this file is: the host cannot reach into the emission for it, and the + // emitted main.rs's self-references fail to link under any other lib name. format!( - "{}\nedition = \"2021\"\n{features}\n[dependencies]\n{rendered}", + "{}\nedition = \"2021\"\n\n[lib]\nname = \"v1_compiled\"\n{features}\n[dependencies]\n{rendered}", render_cargo_package_header_prefix(probe_package_name(entry)) ) } @@ -1947,8 +1960,12 @@ mod tests { assert!(manifest.contains(name), "missing dependency row {name}"); } assert!(manifest.contains("/repo/src/v1/stage0")); - // `src/lib.rs` is cargo's own default, so restating it would be a second spelling. - assert!(!manifest.contains("[lib]")); + // THE LIB NAME IS THE EMITTER'S SELF-NAME CONTRACT: the emitted driver mains reach the + // closure through `use v1_compiled::…` (`v1.compiler.emit_rust` `emit_rust_selected` + // binds the name), so the probe crate's lib target must carry it even though the package + // name is per-entry. The PATH stays cargo's default and is not restated. + assert!(manifest.contains("[lib]\nname = \"v1_compiled\"\n")); + assert!(!manifest.contains("path = \"src/lib.rs\"")); } /// One emitted crate on disk, authored by the caller, so each test states the shape it means. From 2663e466dda9192d13de02d0786c5575aeeb9266 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 10:08:02 +0000 Subject: [PATCH 57/89] Release retained emission arena before the native cargo build The lane's first run held ~15GiB RSS from the emission's resolved graph into the cargo build of the emitted compiler and was SIGKILLed (rc=137) with no diagnostic. Drop the emission run and malloc_trim the retained arena at both derivation-to-emission and emission-to-build handoffs, reporting the reclaimed KB so a trim that cannot release live memory shows in the lane log. Co-authored-by: briansrls --- .../stage0/src/cli_run/native_lane_runner.rs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/cli_run/native_lane_runner.rs b/src/v1/stage0/src/cli_run/native_lane_runner.rs index 2741f19f59d..9969b0fc56a 100644 --- a/src/v1/stage0/src/cli_run/native_lane_runner.rs +++ b/src/v1/stage0/src/cli_run/native_lane_runner.rs @@ -240,8 +240,21 @@ fn prepare_emitted_compiler(source_roots: &[String]) -> Result Result<(), String> { "required-ci: v2-native universe derived — {} v2.test.* identities", universe.len() ); + // The derivation's interpreter context and the full inventory's source bytes died with the + // call above; return the retained arena before the emission peaks beside it (same release + // discipline as the emission-to-build handoff below). + let derivation_trim_kb = super::trim_retained_heap(); + eprintln!("required-ci: v2-native derivation arena released (trim_reclaimed_kb={derivation_trim_kb:?})"); // 2. PREPARATION. The seed emits the compiler closure once, in-process; cargo builds the // emitted crate; the receipt records all three identities. From fc1549333b791337b676e7901bd68bd72ab7edf8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 10:23:53 +0000 Subject: [PATCH 58/89] Apply rustfmt to the v2-native lane host changes Co-authored-by: briansrls --- src/v1/stage0/src/cli_run.rs | 4 +- .../stage0/src/cli_run/native_lane_runner.rs | 76 ++++++++++--------- .../src/emitted_closure_compile_host.rs | 6 +- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 887df037e48..066be0ffef6 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -89,16 +89,16 @@ mod census_heads; #[path = "declaration_index.rs"] pub mod declaration_index; pub mod derived_row_roster; -mod required_floor_runner; mod native_lane_runner; +mod required_floor_runner; pub mod rostered_row_join; mod serve_budget_refusal; +pub use native_lane_runner::run_required_v2_native; pub(crate) use required_floor_runner::*; pub use required_floor_runner::{ floor_discovery_path_excluded, make_eval_context, make_eval_context_with_runtime_options, run_claim_measured, run_required_floor, }; -pub use native_lane_runner::run_required_v2_native; mod entry_resolve; pub(crate) use active_workset::*; pub(crate) use entry_resolve::*; diff --git a/src/v1/stage0/src/cli_run/native_lane_runner.rs b/src/v1/stage0/src/cli_run/native_lane_runner.rs index 9969b0fc56a..7e44419bbb6 100644 --- a/src/v1/stage0/src/cli_run/native_lane_runner.rs +++ b/src/v1/stage0/src/cli_run/native_lane_runner.rs @@ -84,8 +84,12 @@ struct EmittedPreparation { fn sha256_file(path: &Path) -> Result { use sha2::Digest; - let bytes = std::fs::read(path) - .map_err(|e| format!("could not read {} for its content identity: {e}", path.display()))?; + let bytes = std::fs::read(path).map_err(|e| { + format!( + "could not read {} for its content identity: {e}", + path.display() + ) + })?; Ok(format!("{:x}", sha2::Sha256::digest(&bytes))) } @@ -103,9 +107,14 @@ fn emitted_closure_identity(crate_dir: &Path) -> Result { files.sort(); let mut hasher = sha2::Sha256::new(); for path in &files { - let bytes = std::fs::read(path) - .map_err(|e| format!("could not read {}: {e}", path.display()))?; - hasher.update(path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default().as_bytes()); + let bytes = + std::fs::read(path).map_err(|e| format!("could not read {}: {e}", path.display()))?; + hasher.update( + path.file_name() + .map(|n| n.to_string_lossy().into_owned()) + .unwrap_or_default() + .as_bytes(), + ); hasher.update(&bytes); } Ok(format!("{:x}", hasher.finalize())) @@ -178,8 +187,10 @@ fn derive_native_universe( floor_discovery_finalize_source_outcomes: {e}" ) })?; - let rows = super::parse_floor_discovery_producer_result(&ctx, &finalized) - .map_err(|reason| format!("V2-NATIVE REFUSAL cause=UniverseDerivationRefused — {reason}"))?; + let rows = + super::parse_floor_discovery_producer_result(&ctx, &finalized).map_err(|reason| { + format!("V2-NATIVE REFUSAL cause=UniverseDerivationRefused — {reason}") + })?; let mut universe: Vec<(String, String)> = Vec::new(); for row in &rows { let Some(module) = module_for_path.get(row.entry.as_str()) else { @@ -236,9 +247,12 @@ fn prepare_emitted_compiler(source_roots: &[String]) -> Result Result Result Result<(), String> { +fn write_universe_file(path: &Path, universe: &[(String, String)]) -> Result<(), String> { let mut text = String::new(); for (module, declaration) in universe { text.push_str(module); @@ -469,8 +482,8 @@ fn parse_native_run_output(stdout: &str) -> Result { verdict, }); } - let terminal_rows = terminal_rows - .ok_or_else(|| "the native run printed no terminal marker".to_string())?; + let terminal_rows = + terminal_rows.ok_or_else(|| "the native run printed no terminal marker".to_string())?; Ok(NativeRunOutput { observations, file_refusals, @@ -554,11 +567,7 @@ fn reclassify_context_refusals( // interpreter's symbol literal value), records and variants name their types, and the Optional // controls use the Present/Absent encoding every host bridge in this crate shares. -fn identity_value( - ctx: &v1_interpreter::InterpContext, - module: &str, - declaration: &str, -) -> Value { +fn identity_value(ctx: &v1_interpreter::InterpContext, module: &str, declaration: &str) -> Value { Value::Record { type_name: ctx.sym("NativeRouteTestIdentity"), fields: Rc::new(vec![ @@ -683,7 +692,10 @@ fn receipt_value( ctx.sym("executable_identity"), str_value(&preparation.binary_identity), ), - (ctx.sym("universe"), super::list_value_from_vec(universe_values)), + ( + ctx.sym("universe"), + super::list_value_from_vec(universe_values), + ), ( ctx.sym("population"), super::list_value_from_vec(population_values), @@ -834,14 +846,8 @@ pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { type_name: route_ctx.sym("NativeRouteMalformedControl"), variant_name: route_ctx.sym("MalformedSpecimenRefused"), fields: Rc::new(vec![ - ( - route_ctx.sym("path"), - str_value(&malformed_control.0), - ), - ( - route_ctx.sym("reason"), - str_value(&malformed_control.1), - ), + (route_ctx.sym("path"), str_value(&malformed_control.0)), + (route_ctx.sym("reason"), str_value(&malformed_control.1)), ]), } }; diff --git a/src/v1/stage0/src/emitted_closure_compile_host.rs b/src/v1/stage0/src/emitted_closure_compile_host.rs index 7603d35889d..baceb8e7a32 100644 --- a/src/v1/stage0/src/emitted_closure_compile_host.rs +++ b/src/v1/stage0/src/emitted_closure_compile_host.rs @@ -718,7 +718,11 @@ fn attributed_diagnostic( /// lock on that directory. /// `pub(crate)` for the same consumer as `write_probe_crate`: the v2-native lane builds the /// emitted compiler crate through this same cargo invocation. -pub(crate) fn run_cargo(crate_dir: &Path, workspace: &Path, attribution_symbol: &str) -> CargoVerdict { +pub(crate) fn run_cargo( + crate_dir: &Path, + workspace: &Path, + attribution_symbol: &str, +) -> CargoVerdict { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); let mut command = std::process::Command::new(&cargo); command From c8173eee5cd00bd56de03fa9f75619feafb9dbb4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 15:10:28 +0000 Subject: [PATCH 59/89] =?UTF-8?q?Lower=20FreeMonoid=20tail=20to=20im::Vect?= =?UTF-8?q?or::skip=20=E2=80=94=20O(log=20n)=20share,=20not=20O(n)=20copy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The emitter lowered every cons-match tail on a FreeMonoid to iter().skip(1).cloned().collect(), materializing the whole tail per step: every fold over a FreeMonoid was quadratic. Measured on the required-v2-native lane's first native run (2026-09-09): the self-hosted lexer, which tails the remaining source per character and per rule attempt, tokenized a 22KB file in 23.3s against 70ms for 899B, projecting a multi-hour whole-corpus context fold — the lane's dominant term. im::Vector::skip shares the RRB tree in O(log n). Two mirrors carry the only cons-tail sites in the stage0 corpus: v1_compiler_emit_rust.rs (the emitter itself) and std_occurrence_binding_candidates.rs. The e0599 emitter-decision census and its witness tests move to the new (skip, __fm) site with the measured rationale. Fixed-point regen green: the rebuilt seed regenerates both mirrors byte-identically. Co-authored-by: briansrls --- .../instruments/e0599_emitter_decision_census.dag | 8 ++++---- .../e0599_emitter_decision_census_witness_test.dag | 6 +++--- src/v1/05_emit_rust.dag | 10 +++++++++- src/v1/stage0/src/std_occurrence_binding_candidates.rs | 3 +-- src/v1/stage0/src/v1_compiler_emit_rust.rs | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/dag/gunbc/instruments/e0599_emitter_decision_census.dag b/dag/gunbc/instruments/e0599_emitter_decision_census.dag index 7bb2d0f7fae..f36a6ed888b 100644 --- a/dag/gunbc/instruments/e0599_emitter_decision_census.dag +++ b/dag/gunbc/instruments/e0599_emitter_decision_census.dag @@ -133,7 +133,7 @@ data e0599_im_vector_inherent_impl: E0599ExternalAuthority = E0599ExternalAuthor crate_version: "15.1.0", impl_site: "im::vector impl Vector (src/vector/mod.rs:180)", impl_bound: "impl Vector", - member_site: "im::vector Vector::is_empty | Vector::iter", + member_site: "im::vector Vector::is_empty | Vector::skip", cited_upstream: Absent, } @@ -181,11 +181,11 @@ data e0599_lowering_rows: List = [ ownership_alternative: "NotApplicable - a read; no copy is requested and no ownership predicate gates it", source_construct: "match over the FreeMonoid coproduct — the Cons { tail } binding", emitter_authority: "v1.compiler.emit_rust freemonoid_tail_let_from_fm", - emitted_literal: "let : Rc> = Rc::new((*__fm).iter().skip(1).cloned().collect()); ", + emitted_literal: "let : Rc> = Rc::new(__fm.skip(1)); ", cause: TargetApiRequirement, required_trait: "Clone", authority: e0599_im_vector_inherent_impl, - rationale: "iter() is in the same impl block as is_empty. rustc reports the failure at .iter() and never reaches the .cloned() that follows it, so this site MASKS a co-located OwnedDeconstructionRequirement on the same expression; both discharge on the same A: Clone, but B1 must not read this row as evidence that the tail extract needs no owned element.", + rationale: "skip() is in the same impl block as is_empty (it is self.clone().split_off(count)), so the bound survives the 2026-09-09 relowering; what that relowering removed was the O(n) tail MATERIALIZATION the old iter().skip(1).cloned().collect() site paid per cons-match — measured quadratic on the required-v2-native lane's whole-corpus lex fold (22KB file: 23.3s; 899B file: 70ms). The structural skip is O(log n) and shares the RRB tree, so the tail binding is now the read the source construct always was.", }, E0599LoweringRow { operation: FreeMonoidHeadExtract, @@ -284,7 +284,7 @@ fn e0599_receiver_is_field_access(receiver_expr: String) -> Bool { fn e0599_lowering_operation_for(method: String, receiver_expr: String) -> E0599LoweringOperation { if method == "is_empty" && receiver_expr == "__fm" { FreeMonoidEmptyTest - } else if method == "iter" && receiver_expr == "(*__fm)" { + } else if method == "skip" && receiver_expr == "__fm" { FreeMonoidTailIterate } else if method == "clone" && receiver_expr == "(*__fm)[0]" { FreeMonoidHeadExtract diff --git a/dag/test/claim/e0599_emitter_decision_census_witness_test.dag b/dag/test/claim/e0599_emitter_decision_census_witness_test.dag index 6f5c7e980e6..21f9fb9bdd9 100644 --- a/dag/test/claim/e0599_emitter_decision_census_witness_test.dag +++ b/dag/test/claim/e0599_emitter_decision_census_witness_test.dag @@ -46,8 +46,8 @@ test fn e0599_b0_freemonoid_empty_test_is_target_api() -> Bool { } test fn e0599_b0_freemonoid_tail_iterate_is_target_api() -> Bool { - e0599_lowering_operation_label(operation: e0599_lowering_operation_for(method: "iter", receiver_expr: "(*__fm)")) == "FreeMonoidTailIterate" - && e0599_requirement_cause_label(cause: e0599_cause_for(method: "iter", receiver_expr: "(*__fm)")) == "TargetApiRequirement" + e0599_lowering_operation_label(operation: e0599_lowering_operation_for(method: "skip", receiver_expr: "__fm")) == "FreeMonoidTailIterate" + && e0599_requirement_cause_label(cause: e0599_cause_for(method: "skip", receiver_expr: "__fm")) == "TargetApiRequirement" } test fn e0599_b0_freemonoid_head_extract_is_owned_deconstruction() -> Bool { @@ -81,7 +81,7 @@ test fn e0599_b0_head_extract_and_empty_test_are_different_causes() -> Bool { test fn e0599_b0_target_api_is_not_ownership_removable() -> Bool { e0599_requirement_cause_label(cause: e0599_cause_for(method: "is_empty", receiver_expr: "__fm")) != "CloneSharedRequirement" - && e0599_requirement_cause_label(cause: e0599_cause_for(method: "iter", receiver_expr: "(*__fm)")) != "CloneSharedRequirement" + && e0599_requirement_cause_label(cause: e0599_cause_for(method: "skip", receiver_expr: "__fm")) != "CloneSharedRequirement" } test fn e0599_b0_owned_deconstruction_is_not_ownership_removable() -> Bool { diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index fa983548408..2e0d0a0ef2d 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -12552,8 +12552,16 @@ fn arms_are_freemonoid_coproduct(arms: List, scrut_type: String, type_summ is_fm && empty_fillable && cons_fillable } +// THE TAIL IS A STRUCTURAL SKIP, NEVER A COPY. `im::Vector::skip` is O(log n) — it shares the +// RRB tree — while `iter().skip(1).cloned().collect()` materializes the whole tail: every +// cons-match in every emitted program paid O(n) per step, so every fold over a FreeMonoid was +// quadratic. Measured on the required-v2-native lane's first run (2026-09-09): the self-hosted +// lexer, which tails the remaining source per character and per rule attempt, parsed a 22KB +// file in 23.3s against 70ms for 899B (333x time for 25x size), projecting a multi-hour +// whole-corpus context fold — the lane's dominant term, ahead of the cargo build the envelope +// had priced as dominant. DESIGN section 6 prices a copied accumulator as always-fixed. fn freemonoid_tail_let_from_fm(tail_bind: String) -> String { - if tail_bind == "_" { "" } else { concat("let ", tail_bind, ": Rc> = Rc::new((*__fm).iter().skip(1).cloned().collect()); ") } + if tail_bind == "_" { "" } else { concat("let ", tail_bind, ": Rc> = Rc::new(__fm.skip(1)); ") } } fn freemonoid_empty_branch_body(empty_arm: Node?, catchall: Node?, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { diff --git a/src/v1/stage0/src/std_occurrence_binding_candidates.rs b/src/v1/stage0/src/std_occurrence_binding_candidates.rs index a65a2ec673c..5cbb4886280 100644 --- a/src/v1/stage0/src/std_occurrence_binding_candidates.rs +++ b/src/v1/stage0/src/std_occurrence_binding_candidates.rs @@ -313,8 +313,7 @@ pub fn occurrence_containment_parent_scope( std::option::Option::None } else { let parent_terminal = (*__fm)[0].clone(); - let parent_ancestors_reversed: Rc> = - Rc::new((*__fm).iter().skip(1).cloned().collect()); + let parent_ancestors_reversed: Rc> = Rc::new(__fm.skip(1)); Some(Rc::new(OccurrenceContainmentPath { ancestors: v1_rt::reverse(parent_ancestors_reversed.clone()), terminal: parent_terminal.clone(), diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 5ac701489c3..d32c07262c6 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -28248,7 +28248,7 @@ pub fn freemonoid_tail_let_from_fm(tail_bind: String) -> String { } else { v1_rt::concat( v1_rt::concat("let ".to_string(), tail_bind.clone()), - ": Rc> = Rc::new((*__fm).iter().skip(1).cloned().collect()); ".to_string(), + ": Rc> = Rc::new(__fm.skip(1)); ".to_string(), ) } } From 65ae51a9b29e6be4af01950a79ced7eaf569b857 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 17:56:51 +0000 Subject: [PATCH 60/89] Route FreeMonoid length/snoc through the count/list_push primitives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit length folded the whole carrier per call (O(n)); the parse repeat loop calls it on the remaining-token list twice per element — an O(elements x tokens) quadratic measured at 40% of self-hosted parse self-time on the required-v2-native lane's first native run (2026-09-09). list_snoc_item routed through list_append, paying a full O(n) right-fold per snoc and making every build-by-appending accumulator quadratic (measured on the first-set union fold). count is O(1) and list_push amortized O(log n) on the persistent-vector realization. Probe-measured on the emitted crate: 25s -> 6.6s parse on a 4k-element synthetic, 209s -> 33s on a 315KB table module. Co-authored-by: briansrls --- src/v2/std/algebra.dag | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/v2/std/algebra.dag b/src/v2/std/algebra.dag index 30de5cf40c4..29b94d87b6c 100644 --- a/src/v2/std/algebra.dag +++ b/src/v2/std/algebra.dag @@ -623,8 +623,12 @@ fn non_empty(xs: FreeMonoid) -> Bool { } } +// `count` is the primitive authority for carrier size (O(1) on the persistent-vector +// realization); the fold this replaced was O(n) per call, and the parse repeat loop calls +// length on the remaining-token list twice per element — an O(elements x tokens) quadratic +// measured at 40% of self-hosted parse self-time (2026-09-09, required-v2-native lane work). fn length(xs: FreeMonoid) -> Int { - fold_list(xs: xs, empty: 0, cons: fn(count, _) { count + 1 }) + xs |> count } fn for_all(xs: FreeMonoid, predicate: fn(T) -> Bool) -> Bool { @@ -690,8 +694,12 @@ fn list_append(left: FreeMonoid, right: FreeMonoid) -> FreeMonoid { }) } +// `list_push` is the primitive single-item append (amortized O(log n) on the persistent +// carrier); routing through list_append paid a full O(n) right-fold per snoc, which made +// every build-by-appending accumulator quadratic — measured on the first-set union fold in +// the self-hosted parser (2026-09-09, required-v2-native lane work). fn list_snoc_item(xs: FreeMonoid, item: T) -> FreeMonoid { - list_append(left: xs, right: Cons { head: item, tail: Empty }) + xs |> list_push(item) } type ListPrefixState { From a3a04bb960683ec7657ef2f379a8ce0135bc60f0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 17:56:58 +0000 Subject: [PATCH 61/89] Hoist first-fold knowledge into prepared grammar expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parse choice dispatch recomputed expr_first_fold on both branches at every Choice node at every token position: right-nested choice chains made that O(k^2) per position with O(t^2) union constants — the dominant self-hosted parse cost once the algebra carriers were fixed. The grammar is fixed for a whole parse, so each node's first fold (and each Choice's ambiguity verdict) is a pure function of the grammar: compute it once at preparation, bottom-up, and carry it on a PreparedGrammarExpr tree hung off GrammarFirstAnalysis / ParseTableRealization. parse_expr keeps its GrammarExpr signature as a compat wrapper that prepares on the fly; parse_nonterminal_memoized_core reads the prepared map. Forecast by a pointer-keyed memo probe on the emitted crate: 33s -> 14s on the 315KB table module. parse_minted_id_list also moves off list_append-per-node (O(n^2) per captured repeat) onto a snoc fold over the list_push primitive. Verified by execution: 29-test battery over parse_table_claims, grammar_validation (left-recursion suite), parse_token_first_empty_ semantics, parse_table_content_key and parse_table_memo_governed_witness all green through the seed interpreter. Co-authored-by: briansrls --- src/v2/compiler/02_parse.dag | 188 ++++++++++++++---- ...parse_table_memo_governed_witness_test.dag | 9 +- .../parse/parse_table_content_key_test.dag | 3 +- 3 files changed, 161 insertions(+), 39 deletions(-) diff --git a/src/v2/compiler/02_parse.dag b/src/v2/compiler/02_parse.dag index 8d20fcb0f27..e99141bda53 100644 --- a/src/v2/compiler/02_parse.dag +++ b/src/v2/compiler/02_parse.dag @@ -275,6 +275,7 @@ type ParseTableRealization { nullable_set: PointwisePower first_rows: Map> productions_by_name: Map + prepared_exprs: Map } type ParseTable = ParseTableRealization @@ -535,7 +536,8 @@ fn parse_table_carry_analysis(table: ParseTable, entries: Map ParseTable { memo_lookup_calls: 0, nullable_set: PointwisePower { member: fn(_) { false } }, first_rows: empty_map(), - productions_by_name: empty_map() + productions_by_name: empty_map(), + prepared_exprs: empty_map() } } @@ -756,11 +760,13 @@ fn parse_mint_from_source( // list whose consumers read as "parse stamped this", which is the fabricated answer. Empty is the // answer to a different question than OccurrenceSynthetic's -- nothing was minted at all there -- // and both are correctly empty for this one. -fn parse_minted_id_from_node(n: Node) -> List { +// Snoc-built, never append-per-node: list_append(acc, [id]) re-folds the accumulator per +// element (O(n^2) per captured repeat), list_snoc_item is the primitive push (O(log n)). +fn parse_minted_id_snoc(acc: List, n: Node) -> List { match n.occurrence_id { - OccurrenceMinted { id: id } => Cons { head: id, tail: std.algebra.Empty } - OccurrenceSynthetic => std.algebra.Empty - OccurrenceProjected { id: _, caused_by: _ } => std.algebra.Empty + OccurrenceMinted { id: id } => list_snoc_item(xs: acc, item: id) + OccurrenceSynthetic => acc + OccurrenceProjected { id: _, caused_by: _ } => acc } } @@ -768,9 +774,7 @@ fn parse_minted_id_list(nodes: List) -> List { fold_list( xs: nodes, empty: std.algebra.Empty, - cons: fn(acc, n) { - list_append(left: acc, right: parse_minted_id_from_node(n: n)) - } + cons: fn(acc, n) { parse_minted_id_snoc(acc: acc, n: n) } ) } @@ -1171,8 +1175,8 @@ fn parse_nonterminal_memoized_core( } Violates { diagnostic: _ } => let table_miss = parse_table_record_miss(table: table_at_lookup) - let inner = parse_expr( - expr: production.expression, + let inner = parse_expr_with_first( + expr: parse_table_prepared_expr(table: table_miss, production: production), tokens: tokens, all_tokens: all_tokens, root: root, @@ -1305,6 +1309,7 @@ type GrammarExprFirstFold { type GrammarFirstAnalysis { nullable_set: PointwisePower first_rows: Map> + prepared_exprs: Map } fn grammar_empty_terminal_list() -> List { @@ -1503,6 +1508,100 @@ fn choice_branches_ambiguous(left: GrammarExprFirstFold, right: GrammarExprFirst } } +// A prepared grammar expression is the parse-time carrier for FIRST knowledge: the grammar is +// fixed for a whole parse, so each Choice node's branch folds (and its ambiguity verdict) are +// pure functions of the grammar and are computed ONCE here, at preparation, bottom-up — every +// node's fold combines its children's already-computed folds in O(union) instead of re-walking +// the subtree. The pre-hoisting dispatch recomputed expr_first_fold on both branches at every +// Choice node at every token position: right-nested choice chains made that O(k^2) per position +// with O(t^2) union constants, measured as the dominant self-hosted parse cost (2026-09-09, +// required-v2-native lane work: 209s -> 33s on a 315KB table module from the algebra carrier +// fixes, then 33s -> 14s from this hoist, forecast by a pointer-keyed memo probe). +type PreparedGrammarExpr + = PreparedTerminal { token_class: Symbol, stamp: TerminalStampMode } + | PreparedNonterminal { production: Symbol } + | PreparedSequence { left: PreparedGrammarExpr, right: PreparedGrammarExpr } + | PreparedChoice { + left: PreparedGrammarExpr, + right: PreparedGrammarExpr, + left_first: GrammarExprFirstFold, + right_first: GrammarExprFirstFold, + ambiguous: Bool + } + | PreparedOptional { element: PreparedGrammarExpr } + | PreparedRepeat { element: PreparedGrammarExpr } + +type PreparedExprFold { + tree: PreparedGrammarExpr + first: GrammarExprFirstFold +} + +fn prepare_grammar_expr( + expr: GrammarExpr, + nullable_set: PointwisePower, + first_rows: Map> +) -> PreparedExprFold { + match expr { + Terminal { token_class: token_class, stamp: stamp } => + PreparedExprFold { + tree: PreparedTerminal { token_class: token_class, stamp: stamp }, + first: expr_first_fold_terminal(token_class: token_class) + } + Nonterminal { production: production } => + PreparedExprFold { + tree: PreparedNonterminal { production: production }, + first: expr_first_fold_nonterminal(name: production, nullable_set: nullable_set, first_rows: first_rows) + } + Sequence { left: left, right: right } => + let left_prepared = prepare_grammar_expr(expr: left, nullable_set: nullable_set, first_rows: first_rows) + let right_prepared = prepare_grammar_expr(expr: right, nullable_set: nullable_set, first_rows: first_rows) + PreparedExprFold { + tree: PreparedSequence { left: left_prepared.tree, right: right_prepared.tree }, + first: expr_first_fold_sequence(left: left_prepared.first, right: right_prepared.first) + } + Choice { left: left, right: right } => + let left_prepared = prepare_grammar_expr(expr: left, nullable_set: nullable_set, first_rows: first_rows) + let right_prepared = prepare_grammar_expr(expr: right, nullable_set: nullable_set, first_rows: first_rows) + PreparedExprFold { + tree: PreparedChoice { + left: left_prepared.tree, + right: right_prepared.tree, + left_first: left_prepared.first, + right_first: right_prepared.first, + ambiguous: choice_branches_ambiguous(left: left_prepared.first, right: right_prepared.first) + }, + first: expr_first_fold_choice(left: left_prepared.first, right: right_prepared.first) + } + Optional { element: element } => + let element_prepared = prepare_grammar_expr(expr: element, nullable_set: nullable_set, first_rows: first_rows) + PreparedExprFold { + tree: PreparedOptional { element: element_prepared.tree }, + first: expr_first_fold_optional(element: element_prepared.first) + } + Repeat { element: element } => + let element_prepared = prepare_grammar_expr(expr: element, nullable_set: nullable_set, first_rows: first_rows) + PreparedExprFold { + tree: PreparedRepeat { element: element_prepared.tree }, + first: expr_first_fold_repeat(element: element_prepared.first) + } + } +} + +// The on-the-fly arm for tables whose analysis did not carry prepared trees (parse_table_empty +// and test-built tables): identical folds, computed at call time. Tables built by +// parse_table_for_production_analyzed always resolve Present here — both maps are folded from +// the same root.productions, so Absent-after-Holds is unreachable by construction, and this arm +// computes the same value rather than widening anything. +fn parse_table_prepared_expr(table: ParseTable, production: GrammarProduction) -> PreparedGrammarExpr { + match v2.std.collection.map_get(m: table.prepared_exprs, key: production.name) { + Accepted { value: Present { value: prepared }, diagnostics: _ } => prepared + Accepted { value: Absent, diagnostics: _ } => + prepare_grammar_expr(expr: production.expression, nullable_set: table.nullable_set, first_rows: table.first_rows).tree + Rejected { diagnostics: _ } => + prepare_grammar_expr(expr: production.expression, nullable_set: table.nullable_set, first_rows: table.first_rows).tree + } +} + fn append_choice_ambiguity_row( acc: List, production: Symbol, @@ -1595,9 +1694,21 @@ fn compute_grammar_first_analysis_given( root: GrammarRoot, nullable_set: PointwisePower ) -> GrammarFirstAnalysis { + let first_rows = compute_production_first_rows(root: root, nullable_set: nullable_set) GrammarFirstAnalysis { nullable_set: nullable_set, - first_rows: compute_production_first_rows(root: root, nullable_set: nullable_set) + first_rows: first_rows, + prepared_exprs: fold_list( + xs: root.productions, + empty: empty_map(), + cons: fn(acc, p) { + map_insert( + m: acc, + key: p.name, + value: prepare_grammar_expr(expr: p.expression, nullable_set: nullable_set, first_rows: first_rows).tree + ) + } + ) } } @@ -2141,8 +2252,8 @@ fn parse_expr_terminal( } fn parse_expr_sequence( - left: GrammarExpr, - right: GrammarExpr, + left: PreparedGrammarExpr, + right: PreparedGrammarExpr, tokens: List, all_tokens: List, root: GrammarRoot, @@ -2184,8 +2295,8 @@ fn parse_expr_sequence( } fn parse_choice_residue_backtrack( - left: GrammarExpr, - right: GrammarExpr, + left: PreparedGrammarExpr, + right: PreparedGrammarExpr, tokens: List, all_tokens: List, root: GrammarRoot, @@ -2215,8 +2326,8 @@ fn parse_choice_residue_backtrack( } fn parse_choice_on_present_token( - left: GrammarExpr, - right: GrammarExpr, + left: PreparedGrammarExpr, + right: PreparedGrammarExpr, tok: Token, tokens: List, all_tokens: List, @@ -2250,17 +2361,18 @@ fn parse_choice_on_present_token( } fn parse_choice_first_dispatch( - left: GrammarExpr, - right: GrammarExpr, + left: PreparedGrammarExpr, + right: PreparedGrammarExpr, + left_first: GrammarExprFirstFold, + right_first: GrammarExprFirstFold, + ambiguous: Bool, tokens: List, all_tokens: List, root: GrammarRoot, table: ParseTable, prov: ParseProvenanceState ) -> ParseExprResult { - let left_first = expr_first_fold(expr: left, nullable_set: table.nullable_set, first_rows: table.first_rows) - let right_first = expr_first_fold(expr: right, nullable_set: table.nullable_set, first_rows: table.first_rows) - if choice_branches_ambiguous(left: left_first, right: right_first) { + if ambiguous { parse_choice_residue_backtrack( left: left, right: right, @@ -2298,7 +2410,7 @@ fn parse_choice_first_dispatch( } fn parse_expr_optional( - element: GrammarExpr, + element: PreparedGrammarExpr, tokens: List, all_tokens: List, root: GrammarRoot, @@ -2327,7 +2439,7 @@ fn parse_expr_optional( } fn parse_expr_repeat_step( - element: GrammarExpr, + element: PreparedGrammarExpr, all_tokens: List, root: GrammarRoot, acc: ParseRepeatAcc @@ -2383,7 +2495,7 @@ fn parse_expr_repeat_step( } fn parse_expr_repeat( - element: GrammarExpr, + element: PreparedGrammarExpr, tokens: List, all_tokens: List, root: GrammarRoot, @@ -2410,7 +2522,7 @@ fn parse_expr_repeat( } fn parse_expr_with_first( - expr: GrammarExpr, + expr: PreparedGrammarExpr, tokens: List, all_tokens: List, root: GrammarRoot, @@ -2418,7 +2530,7 @@ fn parse_expr_with_first( prov: ParseProvenanceState ) -> ParseExprResult { match expr { - Terminal { token_class: token_class, stamp: stamp } => + PreparedTerminal { token_class: token_class, stamp: stamp } => parse_expr_terminal( token_class: token_class, stamp: stamp, @@ -2426,7 +2538,7 @@ fn parse_expr_with_first( table: table, prov: prov ) - Nonterminal { production: prod_name } => + PreparedNonterminal { production: prod_name } => parse_nonterminal_memoized( all_tokens: all_tokens, tokens: tokens, @@ -2435,7 +2547,7 @@ fn parse_expr_with_first( prod_name: prod_name, prov: prov ) - Sequence { left: left, right: right } => + PreparedSequence { left: left, right: right } => parse_expr_sequence( left: left, right: right, @@ -2445,17 +2557,20 @@ fn parse_expr_with_first( table: table, prov: prov ) - Choice { left: left, right: right } => + PreparedChoice { left: left, right: right, left_first: left_first, right_first: right_first, ambiguous: ambiguous } => parse_choice_first_dispatch( left: left, right: right, + left_first: left_first, + right_first: right_first, + ambiguous: ambiguous, tokens: tokens, all_tokens: all_tokens, root: root, table: table, prov: prov ) - Optional { element: element } => + PreparedOptional { element: element } => parse_expr_optional( element: element, tokens: tokens, @@ -2464,7 +2579,7 @@ fn parse_expr_with_first( table: table, prov: prov ) - Repeat { element: element } => + PreparedRepeat { element: element } => parse_expr_repeat( element: element, tokens: tokens, @@ -2476,6 +2591,9 @@ fn parse_expr_with_first( } } +// Compat entry for callers holding a raw GrammarExpr (grammar tests, stall probes): prepares +// the expression on the fly from the table's carried analysis — the same folds the production +// path reads from its prepared map — then dispatches on the prepared tree. fn parse_expr( expr: GrammarExpr, tokens: List, @@ -2485,7 +2603,7 @@ fn parse_expr( prov: ParseProvenanceState ) -> ParseExprResult { parse_expr_with_first( - expr: expr, + expr: prepare_grammar_expr(expr: expr, nullable_set: table.nullable_set, first_rows: table.first_rows).tree, tokens: tokens, all_tokens: all_tokens, root: root, diff --git a/src/v2/test/claim/long/parse_table_memo_governed_witness_test.dag b/src/v2/test/claim/long/parse_table_memo_governed_witness_test.dag index 030da0b4291..cfe912717a8 100644 --- a/src/v2/test/claim/long/parse_table_memo_governed_witness_test.dag +++ b/src/v2/test/claim/long/parse_table_memo_governed_witness_test.dag @@ -25,7 +25,8 @@ fn parse_table_recompute_fixture() -> ParseTable { memo_lookup_calls: base.memo_lookup_calls, nullable_set: base.nullable_set, first_rows: base.first_rows, - productions_by_name: base.productions_by_name + productions_by_name: base.productions_by_name, + prepared_exprs: base.prepared_exprs } } @@ -43,7 +44,8 @@ fn parse_table_memoize_fixture() -> ParseTable { memo_lookup_calls: base.memo_lookup_calls, nullable_set: base.nullable_set, first_rows: base.first_rows, - productions_by_name: base.productions_by_name + productions_by_name: base.productions_by_name, + prepared_exprs: base.prepared_exprs } } @@ -119,7 +121,8 @@ fn witness_bypass_store_reds_non_memoize_carrier() -> Bool { memo_lookup_calls: base.memo_lookup_calls, nullable_set: base.nullable_set, first_rows: base.first_rows, - productions_by_name: base.productions_by_name + productions_by_name: base.productions_by_name, + prepared_exprs: base.prepared_exprs } !parse_table_memo_store_well_formed_at(table: bypass, key: key) } diff --git a/src/v2/test/claim/parse/parse_table_content_key_test.dag b/src/v2/test/claim/parse/parse_table_content_key_test.dag index c1da1af71ba..533779f20ca 100644 --- a/src/v2/test/claim/parse/parse_table_content_key_test.dag +++ b/src/v2/test/claim/parse/parse_table_content_key_test.dag @@ -16,7 +16,8 @@ fn mk_table(grammar: Hash, tokens: Hash) -> ParseTable { memo_lookup_calls: base.memo_lookup_calls, nullable_set: base.nullable_set, first_rows: base.first_rows, - productions_by_name: base.productions_by_name + productions_by_name: base.productions_by_name, + prepared_exprs: base.prepared_exprs } } From 88ba14080b9eb92fcc774f54c8b45797366517e5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 21:39:34 +0000 Subject: [PATCH 62/89] Deref boxed variant fields in enum shared accessors The storage side boxes a variant record field whose type rides a recursion cycle (rust_field_carrier_final_type via needs_box_wrapping), but the synthesized shared accessor cloned the &Box binding while declaring to return T -- a latent mismatch that compiled only because no enum with a shared accessor had a boxed field until ParseExprResult.table (ParseTable became downstream-of-cycle when ParseTableRealization gained the prepared_exprs map of self-recursive PreparedGrammarExpr). The struct-field projection already carried this deref (field_access_field_is_boxed); the enum arm now reads the same predicate and emits (**__val).clone(). Co-authored-by: briansrls --- src/v1/05_emit_rust.dag | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 2e0d0a0ef2d..007c2cdc2e4 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -8195,6 +8195,9 @@ fn find_shared_enum_fields(children: List, env: TypeEnv) -> List { // THE ACCESSOR RETURNS T BY VALUE FROM &self, so `__val.clone()` must be T::clone, not the // reference's; without `T: Clone` on the impl rustc clones the `&T` (E0308 expected `T`, found // `&T`, six generic carriers). The bound is on the impl the accessor lives in, not on the enum. +// A field whose type rides a recursion cycle is stored Box (rust_field_carrier_final_type, same +// needs_box_wrapping predicate), so the match binds &Box while the accessor owes the declared T +// -- the struct-field projection already carries this deref, and the enum arm must carry it too. fn emit_enum_shared_accessors(name: String, type_params: String, generic_param_names: List, children: List, recursive_types: Set, shared_types: Set, env: TypeEnv, emit_info: EmitGraphInfo) -> String { let all_shared = find_shared_enum_fields(children: children, env: env) if all_shared |> count == 0 { return "" } @@ -8232,7 +8235,16 @@ fn emit_enum_shared_accessors(name: String, type_params: String, generic_param_n } else if variant_is_synthetic_positional_payload(fields: child.children, source_indices: env.source_indices) { concat(" ", variant_path, "(_) => panic!(\"no ", fname, " on positional-payload variant\"),") } else { - concat(" ", variant_path, " { ", emit_ident(name: fname, target: Rust), ": __val, .. } => ", apply_type_template1(template: sharing.clone_value, arg0: "__val"), ",") + let arm_field_boxed = match child.children |> filter(f => authored_name(env: env, node: f) == fname) |> first { + Present { value: f } => needs_box_wrapping(n: resolved_type(n: f), recursive_types: recursive_types, shared_types: shared_types, source_indices: env.source_indices) + Absent => false + } + let clone_expr = if arm_field_boxed { + apply_type_template1(template: sharing.deref_clone, arg0: "*__val") + } else { + apply_type_template1(template: sharing.clone_value, arg0: "__val") + } + concat(" ", variant_path, " { ", emit_ident(name: fname, target: Rust), ": __val, .. } => ", clone_expr, ",") } ) let arms_str = arms |> join(separator: "\n") From bdf71650495dfa6473d63b7540a0c16b47f3d482 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 22:04:59 +0000 Subject: [PATCH 63/89] Regenerate stage0 mirror for the boxed-accessor deref The fixed-point pass reports first_generation_equal=true: no v1-corpus enum carries a boxed shared variant field, so the emitter change moves only the emitter's own mirror. Co-authored-by: briansrls --- src/v1/stage0/src/v1_compiler_emit_rust.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index d32c07262c6..76e3c975a21 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -17515,7 +17515,18 @@ if ((child.children.clone().len() as i64) == 0) { if variant_is_synthetic_positional_payload(child.children.clone(), env.source_indices.clone()) { v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(" ".to_string(), variant_path.clone()), "(_) => panic!(\"no ".to_string()), fname.clone()), " on positional-payload variant\"),".to_string()) } else { - v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(" ".to_string(), variant_path.clone()), " { ".to_string()), crate::v1_compiler_emit::emit_ident(fname.clone(), RenderTarget::Rust)), ": __val, .. } => ".to_string()), crate::v1_compiler_emit_core_support::apply_type_template1(sharing.clone_value.clone(), "__val".to_string())), ",".to_string()) + { + let arm_field_boxed = match Rc::new({ let mut __result = Vec::new(); for f in child.children.clone().iter().cloned() { if (crate::v1_compiler_infer_env::authored_name(env.clone(), f.clone()) == fname.clone()) { __result.push(f); } } __result }).first().cloned() { + Some(f) => needs_box_wrapping(crate::v1_compiler_infer_types::resolved_type(f.clone()), recursive_types.clone(), shared_types.clone(), env.source_indices.clone()), + std::option::Option::None => false, +}; +let clone_expr = if arm_field_boxed.clone() { + crate::v1_compiler_emit_core_support::apply_type_template1(sharing.deref_clone.clone(), "*__val".to_string()) + } else { + crate::v1_compiler_emit_core_support::apply_type_template1(sharing.clone_value.clone(), "__val".to_string()) + }; +v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(" ".to_string(), variant_path.clone()), " { ".to_string()), crate::v1_compiler_emit::emit_ident(fname.clone(), RenderTarget::Rust)), ": __val, .. } => ".to_string()), clone_expr.clone()), ",".to_string()) +} } } }); } __result }); From 4bb152e016956eb90b3d3a3c77ab87963cf2338a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 00:08:39 +0000 Subject: [PATCH 64/89] Align prepare_grammar_expr with the List nullable-set carrier Main's host-carrier migration moved the first-fold family from PointwisePower (a closure carrier, never cache-portable) to List; the merge kept this branch's prepared-grammar-expr builder on the old spelling. The interpreter runs it untyped so the battery stayed green, but the emitted crate's rustc refused the mismatch (E0308) -- the native route catching at its own wall what the v2 infer frontier currently accepts silently. Co-authored-by: briansrls --- src/v2/compiler/02_parse.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/compiler/02_parse.dag b/src/v2/compiler/02_parse.dag index 2e29a541d8a..8eb7159e7e8 100644 --- a/src/v2/compiler/02_parse.dag +++ b/src/v2/compiler/02_parse.dag @@ -1538,7 +1538,7 @@ type PreparedExprFold { fn prepare_grammar_expr( expr: GrammarExpr, - nullable_set: PointwisePower, + nullable_set: List, first_rows: Map> ) -> PreparedExprFold { match expr { From 1e775ac3ef28cc69c431bbe0d53f8ae9b4f1407f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 00:32:33 +0000 Subject: [PATCH 65/89] Hoist dag_language_model out of the native-test context fold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cons closure rebuilt the full language model (grammar, lex rules, canonical-symbol set) once per corpus file — measured ~0.3s/file of pure recompute, ~26 min over the 5286-file universe on the required-v2-native lane. The value has no per-file dependency; hoist it beside prepare_grammar, matching the three existing hoist sites in this module. Co-authored-by: briansrls --- src/v2/compiler/00_compile.dag | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/v2/compiler/00_compile.dag b/src/v2/compiler/00_compile.dag index 3a571ec8c80..ef9e93f5bfc 100644 --- a/src/v2/compiler/00_compile.dag +++ b/src/v2/compiler/00_compile.dag @@ -773,8 +773,13 @@ fn native_test_context_fold_step( } } +// One language model for the whole fold: dag_language_model() rebuilds the grammar, lex rules +// and canonical-symbol set on every call, and calling it inside the cons closure paid that +// once PER FILE — measured at ~0.3s/file of the context fold's wall time (2026-09-09, +// required-v2-native lane work), a pure recompute with no per-file dependency. fn native_test_context_from_ingest(ingest: SourceRootIngest) -> Outcome { - match prepare_grammar(grammar: dag_language_model().grammar) { + let lm = dag_language_model() + match prepare_grammar(grammar: lm.grammar) { Rejected { diagnostics: d } => Rejected { diagnostics: d } Accepted { value: prepared, diagnostics: residue } => { let folded = fold_list( @@ -788,7 +793,7 @@ fn native_test_context_from_ingest(ingest: SourceRootIngest) -> Outcome Date: Thu, 10 Sep 2026 05:31:51 +0000 Subject: [PATCH 66/89] Grow lexer lexemes by snoc, not append, killing the quadratic literal scan Co-authored-by: briansrls --- src/v2/compiler/01_tokenize.dag | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/v2/compiler/01_tokenize.dag b/src/v2/compiler/01_tokenize.dag index 84bab6dee0a..ad131673d6a 100644 --- a/src/v2/compiler/01_tokenize.dag +++ b/src/v2/compiler/01_tokenize.dag @@ -121,6 +121,15 @@ fn lex_match_prefix(prefix: v2.std.text.String, source: v2.std.text.String) -> L } } +// Lexeme growth must push items (amortized O(log n) on the persistent carrier, per +// list_snoc_item): list_append right-folds over its LEFT side, which is the accumulator +// at these call sites, so per-character accumulation paid O(n) a character - quadratic +// in literal length, measured at 18+ minutes on the 132KB string literal in +// gunbc.live_deploy.srv1_residue_rehearsal (2026-09-09, required-v2-native lane). +fn lexeme_grow(acc: v2.std.text.String, chunk: v2.std.text.String) -> v2.std.text.String { + fold_list(xs: chunk, empty: acc, cons: fn(grown, c) { list_snoc_item(xs: grown, item: c) }) +} + type RepeatState { remaining: v2.std.text.String lexeme: v2.std.text.String @@ -178,7 +187,7 @@ fn lex_repeat_step(elem_r: LexMatchThunk, state: RepeatState) -> RepeatState { elem_r: elem_r, state: RepeatState { remaining: rest, - lexeme: list_append(left: state.lexeme, right: consumed), + lexeme: lexeme_grow(acc: state.lexeme, chunk: consumed), done: false } ) @@ -199,7 +208,7 @@ fn lex_delimited_step(close_r: LexMatchThunk, body_r: LexMatchThunk, state: Deli LexMatchAccepted { remaining: after_close, lexeme: close_lex } => DelimitedState { remaining: after_close, - lexeme: list_append(left: state.lexeme, right: close_lex), + lexeme: lexeme_grow(acc: state.lexeme, chunk: close_lex), done: true, failed: false } @@ -217,7 +226,7 @@ fn lex_delimited_step(close_r: LexMatchThunk, body_r: LexMatchThunk, state: Deli body_r: body_r, state: DelimitedState { remaining: after_body, - lexeme: list_append(left: state.lexeme, right: body_lex), + lexeme: lexeme_grow(acc: state.lexeme, chunk: body_lex), done: false, failed: false } From eb41dfabe379d1f443d8c1e68da2fb75ebc77d0e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 05:31:51 +0000 Subject: [PATCH 67/89] Root the malformed-control run at the fixture directory alone Co-authored-by: briansrls --- dag/gunbc/witness/v2_native_route.dag | 11 ++++++----- src/v1/stage0/src/cli_run/native_lane_runner.rs | 15 ++++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/dag/gunbc/witness/v2_native_route.dag b/dag/gunbc/witness/v2_native_route.dag index ae2faae77f8..271597b002e 100644 --- a/dag/gunbc/witness/v2_native_route.dag +++ b/dag/gunbc/witness/v2_native_route.dag @@ -408,11 +408,12 @@ fn native_route_census( // default. // // THE MALFORMED CONTROL PROVES THE FRONT-END REFUSAL ARM IS LIVE. The host runs the same binary -// with the poison fixture's directory added as a source root; an honest front-end collects a -// per-file refusal for it (the collecting fold refuses the FILE, never the whole context), and -// the receipt records the refusal it observed. MalformedSpecimenAccepted — no refusal row for -// the poison path — means the route "accepted" poisoned source and every refusal verdict in its -// census is worthless. +// over the poison fixture's directory as the run's only source root — the run's single consumed +// output is the poison path's refusal, so rooting it at the full corpus would pay a second +// whole-corpus context fold for one observation. An honest front-end collects a per-file refusal +// for it (the collecting fold refuses the FILE, never the whole context), and the receipt records +// the refusal it observed. MalformedSpecimenAccepted — no refusal row for the poison path — means +// the route "accepted" poisoned source and every refusal verdict in its census is worthless. type NativeRouteMalformedControl = MalformedSpecimenRefused { path: String, reason: Symbol } | MalformedSpecimenAccepted diff --git a/src/v1/stage0/src/cli_run/native_lane_runner.rs b/src/v1/stage0/src/cli_run/native_lane_runner.rs index 7e44419bbb6..d3025bf5748 100644 --- a/src/v1/stage0/src/cli_run/native_lane_runner.rs +++ b/src/v1/stage0/src/cli_run/native_lane_runner.rs @@ -383,7 +383,10 @@ fn write_universe_file(path: &Path, universe: &[(String, String)]) -> Result<(), } /// One controls-only universe file for the malformed-specimen run: the poisoned source root is -/// the subject, and the controls give the run a live verdict path to print beside the refusal. +/// the subject. The control rows stay named because the driver refuses an empty universe file; +/// under the control run's fixture-only roots they resolve to nothing, and their prepare-stage +/// refusal observations are not consumed — the run's only consumed output is the poison path's +/// per-file refusal. fn write_control_universe_file(path: &Path) -> Result<(), String> { write_universe_file(path, &[]) } @@ -776,15 +779,17 @@ pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { main_output.file_refusals.len() ); - // 6. THE MALFORMED CONTROL. The same binary with the poison fixture's directory added as a - // source root; an honest front-end collects a per-file refusal for it. + // 6. THE MALFORMED CONTROL. The same binary over the poison fixture's directory ALONE. + // The run's only consumed output is the poison path's per-file refusal, and rooting the + // control at the full corpus paid a second whole-corpus context fold — the lane's dominant + // cost — to produce it. Tokenization is per-file and deterministic, so the observed refusal + // is identical under the restricted roots. let control_universe_file = workspace .join("target") .join("v2-native-lane") .join("controls-universe.tsv"); write_control_universe_file(&control_universe_file)?; - let mut control_roots = source_roots.to_vec(); - control_roots.push(MALFORMED_FIXTURE_DIR.to_string()); + let control_roots = vec![MALFORMED_FIXTURE_DIR.to_string()]; let control_output = run_native_binary( &preparation.binary_path, &control_universe_file, From 3b40193a040813e8775c1108736a52c6a794aa12 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 05:31:51 +0000 Subject: [PATCH 68/89] Own the native census's surviving fatal-grain exclusion causes Co-authored-by: briansrls --- src/v2/workflow/compile_door_ledger.dag | 61 ++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/src/v2/workflow/compile_door_ledger.dag b/src/v2/workflow/compile_door_ledger.dag index a93e77605a7..5771814cd8b 100644 --- a/src/v2/workflow/compile_door_ledger.dag +++ b/src/v2/workflow/compile_door_ledger.dag @@ -355,12 +355,13 @@ type CauseOwnership { // (gunbc.witness_v2_native_route joins this table at head grain): the lexer-coverage cause // surfaces at HEAD on the native route when it is the first diagnostic, even where a later fatal // follows — owned here so the native head join has no unowned advisory; the fatal-grain row of -// the same cause is the position that refuses. The six FATAL-grain rows were measured 2026-09-09 -// by the gunbc.witness_v2_native_route census as the LAST diagnostic — the cause that actually -// refused — on real v2.test.* modules through the emitted native binary. rejected_with_pending -// prepends the non-fatal overlap residue, so those six never surface at the head grain the door -// ledger's own rows count; their ownership lives here at the grain they are observed at, and the -// native-route census classifies against exactly those rows. +// the same cause is the position that refuses. The FATAL-grain rows were measured 2026-09-09 and +// 2026-09-10 by the gunbc.witness_v2_native_route census as the LAST diagnostic — the cause that +// actually refused — through the emitted native binary, at the per-row scope each flip_trigger +// states. rejected_with_pending prepends the non-fatal overlap residue, so the fatal-grain causes +// never surface at the head grain the door ledger's own rows count; their ownership lives here at +// the grain they are observed at, and the native-route census classifies against exactly those +// rows. data known_frontier_causes: List = [ CauseOwnership { @@ -428,6 +429,54 @@ data known_frontier_causes: List = [ grain: FatalGrain, lane: SharedSelfHostCriticalPath, flip_trigger: "body-lowering stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-09 at fatal grain by the gunbc.witness_v2_native_route census (2 modules)" + }, + CauseOwnership { + cause: ^body_lowering_reason_statement_precedes_without_binding, + grain: FatalGrain, + lane: SharedSelfHostCriticalPath, + flip_trigger: "body-lowering stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-10 at fatal grain by the gunbc.witness_v2_native_route census over the lane's source roots dag + src/v2 (71 files: 40 under v2.test.* homes, 31 in dag/extdeps and dag/gunbc)" + }, + CauseOwnership { + cause: ^body_lowering_reason_if_navigation_refused, + grain: FatalGrain, + lane: SharedSelfHostCriticalPath, + flip_trigger: "body-lowering stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-10 at fatal grain by the gunbc.witness_v2_native_route census over the lane's source roots dag + src/v2 (5 files, all under v2.test.* homes)" + }, + CauseOwnership { + cause: ^body_lowering_reason_let_body_not_reachable_from_node, + grain: FatalGrain, + lane: SharedSelfHostCriticalPath, + flip_trigger: "body-lowering stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-10 at fatal grain by the gunbc.witness_v2_native_route census over the lane's source roots dag + src/v2 (3 files, all under v2.test.* homes)" + }, + CauseOwnership { + cause: ^body_lowering_reason_statement_spine_headless, + grain: FatalGrain, + lane: SharedSelfHostCriticalPath, + flip_trigger: "body-lowering stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-10 at fatal grain by the gunbc.witness_v2_native_route census over the lane's source roots dag + src/v2 (1 file: dag/gunbc/discovery_census.dag)" + }, + CauseOwnership { + cause: ^resolve_reason_unbound_symbol, + grain: FatalGrain, + lane: SharedSelfHostCriticalPath, + flip_trigger: "resolve stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-10 at fatal grain by the gunbc.witness_v2_native_route census over the lane's source roots dag + src/v2 (2059 v2.test.* members across 417 modules, dominantly the parse frontier's import cascade: the member's own file parses while a file in its import closure refuses, so the imported names resolve to nothing — zeroing rides the parse-frontier rows beside resolve's own binding gaps, the head-grain row's thread)" + }, + CauseOwnership { + cause: ^resolve_reason_ambiguous_symbol, + grain: FatalGrain, + lane: SharedSelfHostCriticalPath, + flip_trigger: "resolve stage frontier on the self-host critical path; flips to Blocking when the count zeroes. Measured 2026-09-10 at fatal grain by the gunbc.witness_v2_native_route census over the lane's source roots dag + src/v2 (29 v2.test.* members across 6 modules)" + }, + CauseOwnership { + cause: ^infer_grounding_not_derived, + grain: FatalGrain, + lane: SharedSelfHostCriticalPath, + flip_trigger: "the node grounding frontier (v2.compiler.infer node_grounding_frontier_note) observed at prepare, where the carried diagnostic is fatal rather than eval-gated; flips to Blocking when the count zeroes, which the frontier note's dissolve-on conditions own. Measured 2026-09-10 at fatal grain by the gunbc.witness_v2_native_route census over the lane's source roots dag + src/v2 (2 v2.test.* members, both in v2.test.manual.recompute_trace_probe)" + }, + CauseOwnership { + cause: ^eval_rejected_grounding_not_derived, + grain: FatalGrain, + lane: SharedSelfHostCriticalPath, + flip_trigger: "eval's grounding gate refuses a body whose inferred facts carry no DerivedGrounding — the node grounding frontier (v2.compiler.infer node_grounding_frontier_note); flips to Blocking when the count zeroes, which the frontier note's dissolve-on conditions own. Measured 2026-09-10 at fatal grain by the gunbc.witness_v2_native_route census over the lane's source roots dag + src/v2 (261 v2.test.* members across 110 modules)" } ] From 989009133c38cfc48a5f488435404eaa8a283a86 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 05:31:52 +0000 Subject: [PATCH 69/89] Add the native lane's positive-population smoke member Co-authored-by: briansrls --- src/v2/test/native_lane_smoke_test.dag | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/v2/test/native_lane_smoke_test.dag diff --git a/src/v2/test/native_lane_smoke_test.dag b/src/v2/test/native_lane_smoke_test.dag new file mode 100644 index 00000000000..7c72ec98e1d --- /dev/null +++ b/src/v2/test/native_lane_smoke_test.dag @@ -0,0 +1,23 @@ +module v2.test.native_lane_smoke + +import v2.std.logic { Bool } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +// THE required-v2-native LANE'S POSITIVE-POPULATION FLOOR MEMBER (authority: +// gunbc.witness_v2_native_route, clause native_route_positive_population_present). A universe +// in which nothing passes proves the route dead, and exclusion attribution alone cannot see +// that — a route that refuses every member can still be fully owned. So the census requires +// one member to reach NativeTestPassed where the floor expects pass. The body is deliberately +// the grounded fragment only: a Bool literal, the shape the lane's live-verdict control pair +// (v2.native_lane_fixture.control) already proves reaches a verdict — every richer body today +// meets the node grounding frontier (v2.compiler.infer node_grounding_frontier_note) at the +// eval gate, measured 2026-09-10 at 261 members across 110 modules. This member is the +// route's canary, not a stand-in for the frontier: the census's typed exclusion counts carry +// the frontier's size, and as grounding derivation covers more node kinds the positive +// population grows into real tests. It does not retire — at every frontier depth, a route +// that stops reaching verdicts reddens here first. +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +test fn native_route_positive_population_smoke() -> Bool { + true +} From 9a4b9e5798502b92c6f39c50eafbede06b9b6bb3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 06:09:50 +0000 Subject: [PATCH 70/89] Raise the v2-native lane timeout to the measured post-lexer-fix envelope Co-authored-by: briansrls --- .github/workflows/witnesses.yml | 2 +- dag/gunbc/witness/witness_floor_workflow.dag | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/witnesses.yml b/.github/workflows/witnesses.yml index 833e6026b8f..dfd94a9e3d1 100644 --- a/.github/workflows/witnesses.yml +++ b/.github/workflows/witnesses.yml @@ -406,7 +406,7 @@ jobs: if: always() && steps.heal_commit_push.outputs.heal_author_commit_required == '1' required-v2-native: runs-on: [self-hosted, linux, arm64] - timeout-minutes: 90 + timeout-minutes: 180 steps: - name: Record runner filesystem at job start run: | diff --git a/dag/gunbc/witness/witness_floor_workflow.dag b/dag/gunbc/witness/witness_floor_workflow.dag index a12c9f1ee53..fea864cf13f 100644 --- a/dag/gunbc/witness/witness_floor_workflow.dag +++ b/dag/gunbc/witness/witness_floor_workflow.dag @@ -1684,9 +1684,12 @@ fn witness_floor_job() -> Job { // the v2 closure emission, one cargo build of the emitted compiler crate, the universe // derivation fold (the floor's own discovery producer over the full module inventory), and the // native run itself — one process over the derived v2.test.* universe plus the three controls. -// The interpreter measured the admission fold's quadratic shape over the full derived population -// at tens of seconds; the cargo build of the emitted crate is the dominant term. The envelope is -// 90 minutes against the floor lane's 180. +// Measured 2026-09-10 on the 4-core cloud VM, after the lexer accumulation fix +// (v2.compiler.tokenize lexeme_grow): the native run dominates the envelope — ~70 minutes for +// the whole-corpus context fold plus the 3467-row eval, observed by the +// gunbc.witness_v2_native_route census — beside ~10 minutes of emission and ~10 minutes of +// cargo build. The envelope is 180 minutes, the floor lane's own value, so the merge queue's +// check_response_timeout (witness_floor_lane_timeout plus margin) still bounds it. // // WHAT RED DISCRIMINATES. The admission authority is gunbc.witness_v2_native_route: the observed // population must join the derived universe exactly (no duplicates, no foreign rows, no @@ -1702,7 +1705,7 @@ fn witness_floor_job() -> Job { // control and the floor cannot share a job. The lane also builds a second crate (the emitted // compiler), a capability envelope the existing lanes do not declare. A job is the smallest // boundary that keeps both facts honest. -data v2_native_lane_timeout: Minute = minute(count: 90) +data v2_native_lane_timeout: Minute = minute(count: 180) fn v2_native_lane_build_script() -> String { repo_self_build_command(bins: witness_floor_required_bins) From 8f5c76faa5f5f81e0a0b3c38191a0d3f8636c6c0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 10:07:48 +0000 Subject: [PATCH 71/89] Hoist megarac_media_attach in-body annotations to module-item grain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file landed on main with three // blocks inside declaration bodies, which the source-annotation grain (DESIGN 4c: standalone leading blocks attached to module-scope declarations only) refuses — main's own floor push run 34437200195 is red on exactly this file. Each block moves, verbatim, to join its enclosing fn's existing leading annotation block, separated by a // paragraph line; no declaration bytes change. --- .../machine_intake/megarac_media_attach.dag | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/dag/gunbc/machine_intake/megarac_media_attach.dag b/dag/gunbc/machine_intake/megarac_media_attach.dag index 710559ebebe..8b5298a800d 100644 --- a/dag/gunbc/machine_intake/megarac_media_attach.dag +++ b/dag/gunbc/machine_intake/megarac_media_attach.dag @@ -178,6 +178,14 @@ fn start_media_body(image_name: NonEmptyStr, image_index: String) -> NonEmptyStr // no credential at all because the cookie is the bearer. The ipmi operations beside this take a // password FILE for the same threat and can, because -f exists there. Naming the asymmetry is the // point; hiding it behind a wrapper would make one call's exposure look like none. +// +// AN OPENED SESSION WHOSE TOKEN CANNOT BE READ IS STILL AN ALLOCATED SESSION. An earlier +// revision returned SessionReleaseNotAttempted here, which is the leak that saturated this +// controller: OpenSession succeeded, the cookie was written, and CloseSession never ran because +// the renderer treated the arm as "never opened". The DELETE still needs an X-CSRFTOKEN header +// by the upstream contract; the cookie is the bearer, so the header is supplied as a sentinel +// rather than inventing a second CloseSession shape. A refused or uncertain close remains +// SessionReleaseRefused, never NotAttempted. func megarac_attach_remote_image( bmc_host: NonEmptyStr, username: NonEmptyStr, @@ -209,13 +217,6 @@ func megarac_attach_remote_image( Present { value: doc } => json_string_member(doc: doc, key: "CSRFToken") } - // AN OPENED SESSION WHOSE TOKEN CANNOT BE READ IS STILL AN ALLOCATED SESSION. An earlier - // revision returned SessionReleaseNotAttempted here, which is the leak that saturated this - // controller: OpenSession succeeded, the cookie was written, and CloseSession never ran because - // the renderer treated the arm as "never opened". The DELETE still needs an X-CSRFTOKEN header - // by the upstream contract; the cookie is the bearer, so the header is supplied as a sentinel - // rather than inventing a second CloseSession shape. A refused or uncertain close remains - // SessionReleaseRefused, never NotAttempted. return match csrf { Absent => megarac_release_unreadable_session( @@ -284,6 +285,11 @@ func megarac_attach_then_release( // CONFIGURATIONS IS CONSULTED BEFORE ANYTHING IS WRITTEN, so the converged case performs no write // at all. This is not an optimisation: start-media against an already-presented image is a write // whose effect on a booted host is unmodelled, and the point of reading first is to not make it. +// +// MALFORMED PRESENTED STATE IS UNKNOWN, NOT "NOT ATTACHED". An earlier revision collapsed a +// JSON parse failure or a non-array document to false, then walked the available-images list +// and could StartMedia over a controller whose current attachment was unreadable. That is the +// absorbing-fallback DESIGN §5 forbids: ignorance rendered as the empty case. func megarac_attach_with_token( bmc_host: NonEmptyStr, cookie_jar: NonEmptyStr, @@ -300,10 +306,6 @@ func megarac_attach_with_token( if presented.success == false { return MegaRacConfigurationsUnreadable { detail: presented.body } } - // MALFORMED PRESENTED STATE IS UNKNOWN, NOT "NOT ATTACHED". An earlier revision collapsed a - // JSON parse failure or a non-array document to false, then walked the available-images list - // and could StartMedia over a controller whose current attachment was unreadable. That is the - // absorbing-fallback DESIGN §5 forbids: ignorance rendered as the empty case. match presented_media_standing(body: presented.body, image_name: image_name) { PresentedMediaUnreadable { detail: d } => return MegaRacConfigurationsUnreadable { detail: d } @@ -413,6 +415,13 @@ fn presented_media_standing(body: String, image_name: NonEmptyStr) -> PresentedM // asynchronous. Reporting MegaRacStartRefused on a status code would refuse an attach that is // merely still mounting. The bounded read-back decides; the status code only distinguishes which // unconfirmed state is reported. +// +// THE READ-BACK DECIDES, INCLUDING WHEN START REPORTED FAILURE. This firmware answers HTTP 500 +// on writes that take effect; mapping start.success == false to MegaRacStartRefused refused an +// attach that was merely still mounting, and contradicted the comment this function already +// carried. A missing confirmation after any start response is pending (Unconfirmed). StartRefused +// is reserved for a start that failed AND whose configurations document is itself unreadable +// as JSON — that is, when we cannot even ask the read-back. func megarac_start_and_confirm( bmc_host: NonEmptyStr, cookie_jar: NonEmptyStr, @@ -438,12 +447,6 @@ func megarac_start_and_confirm( return MegaRacAttachUnconfirmed { image_name: image_name, configurations: configurations.body } } - // THE READ-BACK DECIDES, INCLUDING WHEN START REPORTED FAILURE. This firmware answers HTTP 500 - // on writes that take effect; mapping start.success == false to MegaRacStartRefused refused an - // attach that was merely still mounting, and contradicted the comment this function already - // carried. A missing confirmation after any start response is pending (Unconfirmed). StartRefused - // is reserved for a start that failed AND whose configurations document is itself unreadable - // as JSON — that is, when we cannot even ask the read-back. return match presented_media_standing(body: configurations.body, image_name: image_name) { PresentedMediaAttached => MegaRacAttached { image_name: image_name, image_index: image_index } From 6a597c3767301e269a0d0cf8e3a345965461edfb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 11:21:34 +0000 Subject: [PATCH 72/89] native lane: exact provenance joins and the identity-grain ratchet (review round 2, points 1-3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Point 1: derive_native_universe now carries BOTH directions of the module index's path relation; reclassify_context_refusals queries module->path (was: path->module queried by module, an unmatchable lookup that left context refusals misclassified as prepare-stage). Two unit tests pin the exact-identity join and the off-key miss. Point 2: context backing is a triple join, not a reason match. The receipt carries module_source_index (module <-> source path for every universe member, built at derivation; a miss refuses UniverseEntryOutsideSubject), and native_route_context_refusals_backed requires identity.module == index.module AND file_refusal.path == index.path AND the exact fatal reason. New reds: backing by another module's file, and the right path with the wrong reason. Point 3: the ratchet is identity-grain. native_route_required_pass_identities rosters every identity admitted as a native pass (the smoke member alone today); native_route_required_passes_hold refuses RequiredNativePassRegressed when a rostered identity returns any other disposition, so native capability cannot shrink while the lane stays green. Secondary: universe.sort() without dedup() — duplicates now reach the authority's UniverseDuplicatesPresent clause instead of being erased before it can judge them. Co-authored-by: briansrls --- dag/gunbc/witness/v2_native_route.dag | 96 +++++++- .../stage0/src/cli_run/native_lane_runner.rs | 207 ++++++++++++++++-- src/v2/test/v2_native_route_test.dag | 198 ++++++++++++++++- 3 files changed, 470 insertions(+), 31 deletions(-) diff --git a/dag/gunbc/witness/v2_native_route.dag b/dag/gunbc/witness/v2_native_route.dag index 271597b002e..22da7c59f38 100644 --- a/dag/gunbc/witness/v2_native_route.dag +++ b/dag/gunbc/witness/v2_native_route.dag @@ -129,6 +129,18 @@ type NativeRouteMemberRow { verdict: NativeTestVerdict } +// THE MODULE → SOURCE-PATH RELATION, OVER THE UNIVERSE'S OWN MODULES. A Context-stage row is a +// derived classification minted by the host from a file refusal, and the derivation's join key +// is this relation: the observation names a module, the file refusal names a path, and only the +// module → path row binds the two. The receipt carries it (restricted to the universe's modules +// — the whole domain the classification can touch) so admission can require the backing file +// refusal to belong to the ROW'S OWN module, not to some file anywhere with the same reason. +// The path is a Symbol because the join partner — NativeTestFileRefusal.path — is one. +type NativeRouteModuleSource { + module: String + path: Symbol +} + // THE EXCLUSION TAXONOMY, CLASSIFIED FROM STAGE AND PROVENANCE — never from the reason symbol // alone (the review's standing instruction: the same symbol is a driver limit on one route and a // resolver defect on another). A refused member is excluded for exactly one of these causes: @@ -434,8 +446,10 @@ type NativeRouteOldRouteControl // seed used ONLY to prepare the emitted compiler (recorded honestly — this job does not claim // native bootstrap); the emitted closure's identity; the executed binary's path AND content // identity (route identity is observed, never synthesized from the intended command); the -// DERIVED universe; the OBSERVED population; the per-file front-end refusals the collecting -// fold gathered; and every control. +// DERIVED universe; the module → source-path relation over the universe's modules (the context +// reclassification's join key, carried so admission can re-ask the join, not trust it); the +// OBSERVED population; the per-file front-end refusals the collecting fold gathered; and every +// control. type NativeRouteReceipt { job: String tested_tree: String @@ -444,6 +458,7 @@ type NativeRouteReceipt { executable_path: String executable_identity: String universe: List + module_source_index: List population: List file_refusals: List false_control: Optional @@ -457,9 +472,11 @@ type NativeRouteReceipt { // derived and well-formed (non-empty, duplicate-free, prefix-closed); the observed population // JOINS the universe exactly (no duplicates, no foreign rows, no omissions — "no silent // remainder" as an executing check, not a comment); the positive population is non-empty (an -// all-refused census proves the route refused, not that it routed); no divergence is observed; +// all-refused census proves the route refused, not that it routed); every identity on the +// required-native ratchet roster is observed passing natively again (a regression of an already +// admitted native pass blocks, however its refusal would classify); no divergence is observed; // every exclusion is classified; every file refusal is owned at both grains; every Context-stage -// refusal is backed by an observed file refusal; and all four controls hold. The fold collects +// refusal is backed by its own module's observed file refusal; and all four controls hold. The fold collects // EVERY violated clause rather than stopping at the first, so one red run names everything that // must change before the next run. type NativeRouteAdmissionRefusalCause @@ -475,6 +492,7 @@ type NativeRouteAdmissionRefusalCause | PopulationForeignRowsPresent | PopulationOmissionsPresent | PositivePopulationEmpty + | RequiredNativePassRegressed | DivergencesPresent | UnattributedExclusionsPresent | UnattributedFileRefusalsPresent @@ -569,6 +587,55 @@ fn native_route_positive_population_present(census: NativeRouteCensus) -> Bool { census.agreement_pass > 0 } +// THE REQUIRED-NATIVE RATCHET. The positive-population clause alone is an existential canary: +// a test that passes natively today could regress tomorrow to an already-known compiler refusal +// and disappear into the attributed-exclusion population while the lane stays green — coverage +// could collapse from hundreds of identities to one. This roster is the convergence ratchet +// that forbids the collapse: every identity ADMITTED as a native agreement-pass is enrolled +// here, and every subsequent head must observe it passing natively again. The frontier census +// still types every not-yet-native test as an exclusion; the distinction the roster draws is +// not yet admitted natively → typed exclusion allowed +// already admitted natively → a refusal is a regression and blocks +// Membership changes are explicit authority edits — a removal or redisposition is a reviewed +// row change beside its reason, never a silent shrink. A count floor would not do: one identity +// can replace another while capability is lost, so the join is at identity grain. +data native_route_required_pass_identities: List = [ + NativeRouteTestIdentity { + module: "v2.test.native_lane_smoke", + declaration: "native_route_positive_population_smoke" + } +] + +// One clause, asked of the observed population with the floor's rosters already derived: every +// rostered identity must be present with disposition NativeAgreementPass. The disposition fold +// is reused whole rather than re-asked as a raw verdict match, so a rostered identity whose +// reference expectation ever changes is judged by the same pairing every other member gets. +fn native_route_required_passes_hold( + rows: List, + expected_red: List, + route_gap: List +) -> Bool { + !any(xs: native_route_required_pass_identities, predicate: fn(required) { + !any(xs: rows, predicate: fn(row) { + native_route_identity_eq(a: row.identity, b: required) + && match native_route_disposition( + verdict: row.verdict, + reference: native_route_reference_for( + qualified: native_route_identity_qualified(identity: row.identity), + expected_red: expected_red, + route_gap: route_gap + ) + ) { + NativeAgreementPass => true + NativeAgreementExpectedRed => false + NativeAgreementRouteGap => false + NativeExclusion { cause: _ } => false + NativeDivergence { cause: _ } => false + } + }) + }) +} + fn native_route_no_divergences(census: NativeRouteCensus) -> Bool { census.divergences == 0 } @@ -604,18 +671,25 @@ fn native_route_head_advisories_attributed(receipt: NativeRouteReceipt) -> Bool } // A CONTEXT-STAGE REFUSAL IS A DERIVED CLASSIFICATION, AND DERIVED CLASSIFICATIONS ARE FORGEABLE -// UNTIL BOUND TO THEIR OBSERVATION. The host reclassifies a member whose entry file never loaded -// as Context-stage with the file's fatal reason; the receipt therefore carries the file-refusal -// population, and every Context-stage row's reason must appear among the observed file refusals' -// fatal reasons. A Context-stage row whose reason no file refusal carries was minted without its -// evidence — admission refuses. +// UNTIL BOUND TO THEIR OBSERVATION — AT SUBJECT GRAIN. The host reclassifies a member whose +// entry file never loaded as Context-stage with the file's fatal reason; the receipt therefore +// carries the module → source-path relation beside the file-refusal population, and every +// Context-stage row must join BOTH: its module's own source path (through module_source_index) +// AND an observed file refusal at exactly that path carrying exactly that fatal reason. A join +// on the reason alone is forgeable by any unrelated file sharing a common parser reason; a join +// on the module alone says nothing about which refusal backs it. The triple is the observation. fn native_route_context_refusals_backed(receipt: NativeRouteReceipt) -> Bool { !any(xs: receipt.population, predicate: fn(row) { match row.verdict { NativeTestRefused { stage: stage, reason: reason } => match stage { NativeTestStageContext => - !any(xs: receipt.file_refusals, predicate: fn(fr) { fr.fatal_reason == reason }) + !any(xs: receipt.module_source_index, predicate: fn(ms) { + ms.module == row.identity.module + && any(xs: receipt.file_refusals, predicate: fn(fr) { + fr.path == ms.path && fr.fatal_reason == reason + }) + }) NativeTestStagePrepare => false NativeTestStageEntry => false NativeTestStageEval => false @@ -689,6 +763,7 @@ fn native_route_admission(receipt: NativeRouteReceipt) -> NativeRouteAdmission { NativeRouteClauseCheck { holds: native_route_population_no_foreign(receipt: receipt), cause: PopulationForeignRowsPresent }, NativeRouteClauseCheck { holds: native_route_population_covers_universe(receipt: receipt), cause: PopulationOmissionsPresent }, NativeRouteClauseCheck { holds: native_route_positive_population_present(census: census), cause: PositivePopulationEmpty }, + NativeRouteClauseCheck { holds: native_route_required_passes_hold(rows: receipt.population, expected_red: expected_red, route_gap: route_gap), cause: RequiredNativePassRegressed }, NativeRouteClauseCheck { holds: native_route_no_divergences(census: census), cause: DivergencesPresent }, NativeRouteClauseCheck { holds: native_route_exclusions_all_classified(census: census), cause: UnattributedExclusionsPresent }, NativeRouteClauseCheck { holds: native_route_file_refusals_attributed(receipt: receipt), cause: UnattributedFileRefusalsPresent }, @@ -727,6 +802,7 @@ fn native_route_admission_refusal_cause_name(cause: NativeRouteAdmissionRefusalC PopulationForeignRowsPresent => "population_foreign_rows_present" PopulationOmissionsPresent => "population_omissions_present" PositivePopulationEmpty => "positive_population_empty" + RequiredNativePassRegressed => "required_native_pass_regressed" DivergencesPresent => "divergences_present" UnattributedExclusionsPresent => "unattributed_exclusions_present" UnattributedFileRefusalsPresent => "unattributed_file_refusals_present" diff --git a/src/v1/stage0/src/cli_run/native_lane_runner.rs b/src/v1/stage0/src/cli_run/native_lane_runner.rs index d3025bf5748..9f4e42ad2f2 100644 --- a/src/v1/stage0/src/cli_run/native_lane_runner.rs +++ b/src/v1/stage0/src/cli_run/native_lane_runner.rs @@ -120,15 +120,25 @@ fn emitted_closure_identity(crate_dir: &Path) -> Result { Ok(format!("{:x}", hasher.finalize())) } +/// The derived universe plus BOTH directions of the module index's path relation. The +/// discovery-row join needs path → module (producer rows key on the entry path); the context +/// reclassification needs module → path (observations key on the module). The module → path +/// direction carries no collision refusal of its own: `ModuleSourceIndex` is keyed by module +/// and its construction already refuses two files declaring one module, so the relation is a +/// function by construction — a second check here would have an unauthorable RED. +struct NativeUniverseDerivation { + universe: Vec<(String, String)>, + module_for_path: HashMap, + path_for_module: HashMap, +} + /// THE UNIVERSE IS DERIVED BY THE FLOOR'S OWN PRODUCER, NOT BY A SECOND SCAN. The production /// required floor folds `discover_floor_rows_for_source` over the full module inventory and /// finalizes with `floor_discovery_finalize_source_outcomes`; this derivation makes the same /// two calls over the same inventory and keeps the rows whose AUTHORED module (read off the /// `module` header by the index, never path-derived) carries the universe prefix. A second /// discovery beside the producer would be free to disagree with the floor about what a test is. -fn derive_native_universe( - source_roots: &[String], -) -> Result<(Vec<(String, String)>, HashMap), String> { +fn derive_native_universe(source_roots: &[String]) -> Result { let (graph, indices) = super::resolve_workspace_entry(source_roots, super::FLOOR_DISCOVERY_PRODUCER_ENTRY)?; let ctx = super::make_eval_context(&graph, indices, v1_interpreter::ExecutionMode::Wet); @@ -148,6 +158,10 @@ fn derive_native_universe( .iter() .map(|(path, module, _)| (path.clone(), module.clone())) .collect(); + let path_for_module: HashMap = inventory + .iter() + .map(|(path, module, _)| (module.clone(), path.clone())) + .collect(); eprintln!( "required-ci: v2-native deriving the universe — {} sources through the floor discovery producer", inventory.len() @@ -204,8 +218,10 @@ fn derive_native_universe( universe.push((module.clone(), row.function.clone())); } } + // SORTED FOR CANONICAL TRANSPORT, NEVER DEDUPLICATED. A duplicated identity is a harness + // defect the admission authority refuses by name (UniverseDuplicatesPresent); normalizing + // it away here would erase the evidence before the authority can judge it. universe.sort(); - universe.dedup(); if universe.is_empty() { return Err( "V2-NATIVE REFUSAL cause=UniverseUnderived — the floor discovery producer enrolled \ @@ -213,7 +229,11 @@ fn derive_native_universe( .to_string(), ); } - Ok((universe, module_for_path)) + Ok(NativeUniverseDerivation { + universe, + module_for_path, + path_for_module, + }) } /// PREPARATION IS THE EMIT-COMPILE PHASE'S OWN MACHINERY, REUSED. The same emission entry @@ -536,20 +556,26 @@ fn run_native_binary( /// A module whose file the context fold refused never reached preparation, so the binary's row /// for it is a prepare-stage resolver refusal; the harness rewrites exactly those rows to the /// Context stage carrying the FILE'S fatal reason — the cause that actually refused the file — -/// and admission's context_refusals_backed clause refuses any Context row whose reason no -/// observed file refusal carries. A prepare refusal whose module's file loaded stays a prepare -/// refusal with the resolver's own reason. +/// and admission's context_refusals_backed clause refuses any Context row whose (module, path, +/// reason) triple no observed file refusal backs through the receipt's module-source index. A +/// prepare refusal whose module's file loaded stays a prepare refusal with the resolver's own +/// reason. +/// +/// THE JOIN KEY IS module → path, NEVER path → module: the observation names its module, and +/// the file refusal names its path, so the relation is queried in the module → path direction. +/// (An earlier draft queried the path-keyed map with the module, which type-checks at +/// `HashMap` and matches nothing — the reclassification silently never fired.) fn reclassify_context_refusals( observations: &mut [NativeObservation], file_refusals: &[NativeFileRefusalObserved], - module_for_path: &HashMap, + path_for_module: &HashMap, ) { let refused_by_path: HashMap<&str, &NativeFileRefusalObserved> = file_refusals .iter() .map(|fr| (fr.path.as_str(), fr)) .collect(); for observation in observations.iter_mut() { - let Some(path) = module_for_path.get(observation.module.as_str()) else { + let Some(path) = path_for_module.get(observation.module.as_str()) else { continue; }; let Some(file_refusal) = refused_by_path.get(path.as_str()) else { @@ -639,6 +665,7 @@ fn receipt_value( tested_tree: &str, preparation: &EmittedPreparation, universe: &[(String, String)], + module_source_index: &[(String, String)], population: &[NativeObservation], file_refusals: &[NativeFileRefusalObserved], false_control: Option<&NativeVerdictObserved>, @@ -674,6 +701,16 @@ fn receipt_value( ]), }) .collect(); + let module_source_values: Vec = module_source_index + .iter() + .map(|(module, path)| Value::Record { + type_name: ctx.sym("NativeRouteModuleSource"), + fields: Rc::new(vec![ + (ctx.sym("module"), str_value(module)), + (ctx.sym("path"), str_value(path)), + ]), + }) + .collect(); Value::Record { type_name: ctx.sym("NativeRouteReceipt"), fields: Rc::new(vec![ @@ -699,6 +736,10 @@ fn receipt_value( ctx.sym("universe"), super::list_value_from_vec(universe_values), ), + ( + ctx.sym("module_source_index"), + super::list_value_from_vec(module_source_values), + ), ( ctx.sym("population"), super::list_value_from_vec(population_values), @@ -742,12 +783,32 @@ pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { let tested_tree = std::env::var("GITHUB_SHA").unwrap_or_else(|_| "local".to_string()); // 1. THE UNIVERSE, DERIVED. The floor's producer over the full module inventory, filtered - // to the universe prefix, plus the module->path join the context reclassification needs. - let (universe, module_for_path) = derive_native_universe(source_roots)?; + // to the universe prefix, plus both directions of the module/path relation: the entry join + // consumed path → module during derivation, and the context reclassification consumes + // module → path below. + let derivation = derive_native_universe(source_roots)?; + let universe = derivation.universe; eprintln!( "required-ci: v2-native universe derived — {} v2.test.* identities", universe.len() ); + // THE MODULE-SOURCE INDEX THE RECEIPT CARRIES, restricted to the universe's own modules — + // the domain the context reclassification can touch and the admission join reasons over. + // Built here, where a missing row is a located refusal, rather than inside receipt minting + // where it could only panic or default. + let mut universe_modules: Vec = + universe.iter().map(|(module, _)| module.clone()).collect(); + universe_modules.dedup(); + let mut module_source_index: Vec<(String, String)> = Vec::with_capacity(universe_modules.len()); + for module in &universe_modules { + let Some(path) = derivation.path_for_module.get(module) else { + return Err(format!( + "V2-NATIVE REFUSAL cause=UniverseEntryOutsideSubject module={module} — the \ + derived universe names a module the module index does not hold" + )); + }; + module_source_index.push((module.clone(), path.clone())); + } // The derivation's interpreter context and the full inventory's source bytes died with the // call above; return the retained arena before the emission peaks beside it (same release // discipline as the emission-to-build handoff below). @@ -819,7 +880,7 @@ pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { reclassify_context_refusals( &mut population, &main_output.file_refusals, - &module_for_path, + &derivation.path_for_module, ); let malformed_control = match control_output .file_refusals @@ -861,6 +922,7 @@ pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { &tested_tree, &preparation, &universe, + &module_source_index, &population, &main_output.file_refusals, false_control.as_ref(), @@ -915,3 +977,122 @@ pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { )) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn refused_observation( + module: &str, + declaration: &str, + stage: &str, + reason: &str, + ) -> NativeObservation { + NativeObservation { + module: module.to_string(), + declaration: declaration.to_string(), + verdict: NativeVerdictObserved::Refused { + stage: stage.to_string(), + reason: reason.to_string(), + }, + } + } + + fn refusal_shape(observation: &NativeObservation) -> Option<(&str, &str)> { + match &observation.verdict { + NativeVerdictObserved::Refused { stage, reason } => Some((stage, reason)), + _ => None, + } + } + + /// THE PRODUCTION-PATH RECLASSIFICATION, AT ITS EXACT JOIN GRAIN. A test module whose own + /// file the context fold refused (here: an unterminated string at tokenize, the poison + /// class the malformed control carries) surfaces in the binary's rows as a Prepare-stage + /// resolver refusal; the reclassification must rewrite THAT identity — keyed by its module, + /// through the module → path direction of the index relation — to the Context stage + /// carrying the file's fatal reason. A second module whose file loaded keeps its Prepare + /// refusal untouched: the join is per-subject, never "some file anywhere". + #[test] + fn context_reclassification_joins_module_to_its_own_files_refusal() { + let mut observations = vec![ + refused_observation( + "v2.test.poisoned", + "never_ran", + "NativeTestStagePrepare", + "resolve_module_not_found", + ), + refused_observation( + "v2.test.healthy", + "ran_and_refused", + "NativeTestStagePrepare", + "resolve_reason_unbound_symbol", + ), + ]; + let file_refusals = vec![NativeFileRefusalObserved { + path: "src/v2/test/poisoned_test.dag".to_string(), + head_reason: "parse_g0_tokens_remain".to_string(), + fatal_reason: "tokenize_lex_e1_unterminated_string".to_string(), + }]; + let path_for_module: HashMap = [ + ( + "v2.test.poisoned".to_string(), + "src/v2/test/poisoned_test.dag".to_string(), + ), + ( + "v2.test.healthy".to_string(), + "src/v2/test/healthy_test.dag".to_string(), + ), + ] + .into_iter() + .collect(); + + reclassify_context_refusals(&mut observations, &file_refusals, &path_for_module); + + assert_eq!( + refusal_shape(&observations[0]), + Some(( + "NativeTestStageContext", + "tokenize_lex_e1_unterminated_string" + )), + "the poisoned module's exact identity must receive its own file's Context-stage \ + fatal reason" + ); + assert_eq!( + refusal_shape(&observations[1]), + Some(("NativeTestStagePrepare", "resolve_reason_unbound_symbol")), + "a module whose file loaded keeps the resolver's own Prepare-stage reason" + ); + } + + /// THE DIRECTION IS LOAD-BEARING. Handed the relation in its REVERSED shape — paths as + /// keys, the exact pre-repair defect — the lookup by module must miss and leave the row a + /// Prepare refusal: no module → path row means no reclassification, never a guessed join. + #[test] + fn context_reclassification_misses_when_queried_off_key() { + let mut observations = vec![refused_observation( + "v2.test.poisoned", + "never_ran", + "NativeTestStagePrepare", + "resolve_module_not_found", + )]; + let file_refusals = vec![NativeFileRefusalObserved { + path: "src/v2/test/poisoned_test.dag".to_string(), + head_reason: "parse_g0_tokens_remain".to_string(), + fatal_reason: "tokenize_lex_e1_unterminated_string".to_string(), + }]; + let reversed_relation: HashMap = [( + "src/v2/test/poisoned_test.dag".to_string(), + "v2.test.poisoned".to_string(), + )] + .into_iter() + .collect(); + + reclassify_context_refusals(&mut observations, &file_refusals, &reversed_relation); + + assert_eq!( + refusal_shape(&observations[0]), + Some(("NativeTestStagePrepare", "resolve_module_not_found")), + "a path-keyed map cannot answer the module-keyed question" + ); + } +} diff --git a/src/v2/test/v2_native_route_test.dag b/src/v2/test/v2_native_route_test.dag index a37f42c15a1..5aaf0415548 100644 --- a/src/v2/test/v2_native_route_test.dag +++ b/src/v2/test/v2_native_route_test.dag @@ -2,10 +2,13 @@ module v2.test.v2_native_route import gunbc.witness_v2_native_route { NativeRouteTestIdentity, native_route_identity_qualified, native_route_identities_unique, + native_route_identity_member, native_route_universe_member, NativeRouteReference, FloorExpectedPass, FloorExpectedRed, FloorRouteGapRefusal, native_route_reference_for, NativeRouteMemberRow, + NativeRouteModuleSource, + native_route_required_pass_identities, NativeRouteExclusionCause, CompilerFrontierAttributed, NativeEntryResolutionLimit, NativeEvalBoundary, UnattributedRefusal, classify_native_refusal, NativeRouteDisposition, NativeAgreementPass, NativeAgreementExpectedRed, @@ -31,6 +34,7 @@ import v2.compiler.compile { } import v2.std.optional { Absent, Optional, Present } import v2.std.collection { List } +import v2.std.algebra { length } import v2.std.logic { Bool } import v2.std.node { Symbol } import v2.std.integer { Int } @@ -78,9 +82,11 @@ fn file_refusal_row(path: Symbol, head: Symbol, fatal: Symbol) -> NativeTestFile } // A receipt with every well-formedness field recorded and both live controls observed; the -// universe, population and file-refusal populations are the arguments under test. +// universe, module-source index, population and file-refusal populations are the arguments +// under test. fn receipt_over( universe: List, + module_source_index: List, population: List, file_refusals: List ) -> NativeRouteReceipt { @@ -92,6 +98,7 @@ fn receipt_over( executable_path: "/emitted/compiler", executable_identity: "binary-sha", universe: universe, + module_source_index: module_source_index, population: population, file_refusals: file_refusals, false_control: Present { value: NativeTestReturnedFalse }, @@ -104,11 +111,42 @@ fn receipt_over( } } -// The smallest green receipt: one passing member, universe and population equal. +// THE RATCHET'S ONE ROSTERED IDENTITY, AS A SYNTHETIC ROW. The required-pass roster is real +// authority data, so every synthetic receipt that aims at admission must carry the smoke +// member passing — the clause reads the roster, not the test's intent. +fn smoke_identity() -> NativeRouteTestIdentity { + test_identity( + module: "v2.test.native_lane_smoke", + declaration: "native_route_positive_population_smoke" + ) +} + +fn smoke_source_row() -> NativeRouteModuleSource { + NativeRouteModuleSource { + module: "v2.test.native_lane_smoke", + path: ^src_v2_test_native_lane_smoke_test_dag + } +} + +// The smallest green receipt: one passing member beside the rostered smoke member, universe +// and population equal, and the module-source index covering both universe modules. fn green_receipt() -> NativeRouteReceipt { receipt_over( - universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], - population: [passed_row(module: "v2.test.alpha", declaration: "passes")], + universe: [ + test_identity(module: "v2.test.alpha", declaration: "passes"), + smoke_identity() + ], + module_source_index: [ + NativeRouteModuleSource { module: "v2.test.alpha", path: ^alpha_path }, + smoke_source_row() + ], + population: [ + passed_row(module: "v2.test.alpha", declaration: "passes"), + passed_row( + module: "v2.test.native_lane_smoke", + declaration: "native_route_positive_population_smoke" + ) + ], file_refusals: [] ) } @@ -386,6 +424,7 @@ test fn the_wrong_job_name_is_refused() -> Bool { executable_path: green_receipt().executable_path, executable_identity: green_receipt().executable_identity, universe: green_receipt().universe, + module_source_index: green_receipt().module_source_index, population: green_receipt().population, file_refusals: green_receipt().file_refusals, false_control: green_receipt().false_control, @@ -408,6 +447,7 @@ test fn an_unrecorded_tested_tree_is_refused() -> Bool { executable_path: green_receipt().executable_path, executable_identity: green_receipt().executable_identity, universe: green_receipt().universe, + module_source_index: green_receipt().module_source_index, population: green_receipt().population, file_refusals: green_receipt().file_refusals, false_control: green_receipt().false_control, @@ -430,6 +470,7 @@ test fn an_unrecorded_preparation_seed_is_refused() -> Bool { executable_path: green_receipt().executable_path, executable_identity: green_receipt().executable_identity, universe: green_receipt().universe, + module_source_index: green_receipt().module_source_index, population: green_receipt().population, file_refusals: green_receipt().file_refusals, false_control: green_receipt().false_control, @@ -452,6 +493,7 @@ test fn an_unrecorded_emitted_closure_is_refused() -> Bool { executable_path: green_receipt().executable_path, executable_identity: green_receipt().executable_identity, universe: green_receipt().universe, + module_source_index: green_receipt().module_source_index, population: green_receipt().population, file_refusals: green_receipt().file_refusals, false_control: green_receipt().false_control, @@ -475,6 +517,7 @@ test fn an_unrecorded_route_identity_is_refused() -> Bool { executable_path: "", executable_identity: base.executable_identity, universe: base.universe, + module_source_index: base.module_source_index, population: base.population, file_refusals: base.file_refusals, false_control: base.false_control, @@ -490,6 +533,7 @@ test fn an_unrecorded_route_identity_is_refused() -> Bool { executable_path: base.executable_path, executable_identity: "", universe: base.universe, + module_source_index: base.module_source_index, population: base.population, file_refusals: base.file_refusals, false_control: base.false_control, @@ -511,7 +555,7 @@ test fn an_unrecorded_route_identity_is_refused() -> Bool { test fn an_empty_universe_is_refused() -> Bool { native_route_admission_refused_with( - admission: native_route_admission(receipt: receipt_over(universe: [], population: [], file_refusals: [])), + admission: native_route_admission(receipt: receipt_over(universe: [], module_source_index: [], population: [], file_refusals: [])), cause_name: "universe_underived" ) } @@ -521,6 +565,7 @@ test fn a_duplicated_universe_is_refused() -> Bool { native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [member, member], + module_source_index: [], population: [passed_row(module: "v2.test.alpha", declaration: "passes")], file_refusals: [] )), @@ -532,6 +577,7 @@ test fn a_foreign_universe_member_is_refused() -> Bool { native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [test_identity(module: "v2.compiler.compile", declaration: "not_a_test")], + module_source_index: [], population: [passed_row(module: "v2.compiler.compile", declaration: "not_a_test")], file_refusals: [] )), @@ -545,6 +591,7 @@ test fn a_duplicated_population_is_refused() -> Bool { native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + module_source_index: [], population: [ passed_row(module: "v2.test.alpha", declaration: "passes"), passed_row(module: "v2.test.alpha", declaration: "passes") @@ -559,6 +606,7 @@ test fn a_foreign_population_row_is_refused() -> Bool { native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + module_source_index: [], population: [ passed_row(module: "v2.test.alpha", declaration: "passes"), passed_row(module: "v2.test.beta", declaration: "extra") @@ -576,6 +624,7 @@ test fn an_omitted_population_row_is_refused() -> Bool { test_identity(module: "v2.test.alpha", declaration: "passes"), test_identity(module: "v2.test.beta", declaration: "missing") ], + module_source_index: [], population: [passed_row(module: "v2.test.alpha", declaration: "passes")], file_refusals: [] )), @@ -589,6 +638,7 @@ test fn an_all_refused_population_is_refused() -> Bool { native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [test_identity(module: "v2.test.alpha", declaration: "blocked")], + module_source_index: [], population: [refused_row( module: "v2.test.alpha", declaration: "blocked", @@ -601,10 +651,53 @@ test fn an_all_refused_population_is_refused() -> Bool { ) } +// ── admission: the required-native ratchet ──────────────────────────────────────────────── + +test fn a_regressed_required_native_pass_is_refused() -> Bool { + // The rostered smoke identity, observed as an ATTRIBUTED exclusion — the exact collapse the + // ratchet exists to block: every clause about classification greens, and coverage still lost + // the identity. + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [smoke_identity()], + module_source_index: [smoke_source_row()], + population: [refused_row( + module: "v2.test.native_lane_smoke", + declaration: "native_route_positive_population_smoke", + stage: NativeTestStagePrepare, + reason: ^parse_g0_tokens_remain + )], + file_refusals: [] + )), + cause_name: "required_native_pass_regressed" + ) +} + +test fn an_omitted_required_native_pass_is_refused() -> Bool { + // The smoke identity is not in the universe at all — coverage shrank silently. The omission + // clause cannot fire (the universe itself no longer names the member), so the ratchet is the + // only wall, and it reads the roster against the OBSERVED population. + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + module_source_index: [], + population: [passed_row(module: "v2.test.alpha", declaration: "passes")], + file_refusals: [] + )), + cause_name: "required_native_pass_regressed" + ) +} + +test fn the_ratchet_roster_is_the_smoke_member_alone_today() -> Bool { + length(xs: native_route_required_pass_identities) == 1 + && native_route_identity_member(target: smoke_identity(), xs: native_route_required_pass_identities) +} + test fn a_divergence_is_refused() -> Bool { native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [test_identity(module: "v2.test.alpha", declaration: "lies")], + module_source_index: [], population: [false_row(module: "v2.test.alpha", declaration: "lies")], file_refusals: [] )), @@ -619,6 +712,7 @@ test fn an_unattributed_exclusion_is_refused() -> Bool { test_identity(module: "v2.test.alpha", declaration: "passes"), test_identity(module: "v2.test.beta", declaration: "blocked") ], + module_source_index: [], population: [ passed_row(module: "v2.test.alpha", declaration: "passes"), refused_row( @@ -640,6 +734,7 @@ test fn an_unattributed_file_refusal_is_refused() -> Bool { native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + module_source_index: [], population: [passed_row(module: "v2.test.alpha", declaration: "passes")], file_refusals: [file_refusal_row( path: ^some_path, @@ -655,6 +750,7 @@ test fn an_unattributed_head_advisory_is_refused() -> Bool { native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], + module_source_index: [], population: [passed_row(module: "v2.test.alpha", declaration: "passes")], file_refusals: [file_refusal_row( path: ^some_path, @@ -675,6 +771,9 @@ test fn a_context_refusal_without_its_file_refusal_is_refused() -> Bool { test_identity(module: "v2.test.alpha", declaration: "passes"), test_identity(module: "v2.test.beta", declaration: "blocked") ], + module_source_index: [ + NativeRouteModuleSource { module: "v2.test.beta", path: ^beta_path } + ], population: [ passed_row(module: "v2.test.alpha", declaration: "passes"), refused_row( @@ -690,11 +789,81 @@ test fn a_context_refusal_without_its_file_refusal_is_refused() -> Bool { ) } +// THE REASON-ONLY JOIN IS THE HOLE THIS CLAUSE CLOSED. The file refusal below carries the +// Context row's exact fatal reason, but at ANOTHER MODULE'S path — under the pre-repair clause +// (any file anywhere with the reason) this receipt was admitted. +test fn a_context_refusal_backed_by_another_modules_file_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [ + test_identity(module: "v2.test.alpha", declaration: "passes"), + test_identity(module: "v2.test.beta", declaration: "blocked") + ], + module_source_index: [ + NativeRouteModuleSource { module: "v2.test.alpha", path: ^alpha_path }, + NativeRouteModuleSource { module: "v2.test.beta", path: ^beta_path } + ], + population: [ + passed_row(module: "v2.test.alpha", declaration: "passes"), + refused_row( + module: "v2.test.beta", + declaration: "blocked", + stage: NativeTestStageContext, + reason: ^parse_g0_tokens_remain + ) + ], + file_refusals: [file_refusal_row( + path: ^alpha_path, + head: ^parse_grammar_choice_overlap_residue, + fatal: ^parse_g0_tokens_remain + )] + )), + cause_name: "context_refusals_unbacked" + ) +} + +// THE PATH WITHOUT THE REASON IS ALSO NOT THE OBSERVATION: the row's module resolves to the +// refusal's path, but the file refused for a different cause than the row carries. +test fn a_context_refusal_at_its_own_path_with_the_wrong_reason_is_refused() -> Bool { + native_route_admission_refused_with( + admission: native_route_admission(receipt: receipt_over( + universe: [ + test_identity(module: "v2.test.alpha", declaration: "passes"), + test_identity(module: "v2.test.beta", declaration: "blocked") + ], + module_source_index: [ + NativeRouteModuleSource { module: "v2.test.beta", path: ^beta_path } + ], + population: [ + passed_row(module: "v2.test.alpha", declaration: "passes"), + refused_row( + module: "v2.test.beta", + declaration: "blocked", + stage: NativeTestStageContext, + reason: ^parse_g0_tokens_remain + ) + ], + file_refusals: [file_refusal_row( + path: ^beta_path, + head: ^parse_grammar_choice_overlap_residue, + fatal: ^resolve_module_not_found + )] + )), + cause_name: "context_refusals_unbacked" + ) +} + test fn a_context_refusal_backed_by_its_file_refusal_is_not_refused_on_that_clause() -> Bool { let admission = native_route_admission(receipt: receipt_over( universe: [ test_identity(module: "v2.test.alpha", declaration: "passes"), - test_identity(module: "v2.test.beta", declaration: "blocked") + test_identity(module: "v2.test.beta", declaration: "blocked"), + smoke_identity() + ], + module_source_index: [ + NativeRouteModuleSource { module: "v2.test.alpha", path: ^alpha_path }, + NativeRouteModuleSource { module: "v2.test.beta", path: ^beta_path }, + smoke_source_row() ], population: [ passed_row(module: "v2.test.alpha", declaration: "passes"), @@ -703,10 +872,14 @@ test fn a_context_refusal_backed_by_its_file_refusal_is_not_refused_on_that_clau declaration: "blocked", stage: NativeTestStageContext, reason: ^parse_g0_tokens_remain + ), + passed_row( + module: "v2.test.native_lane_smoke", + declaration: "native_route_positive_population_smoke" ) ], file_refusals: [file_refusal_row( - path: ^some_path, + path: ^beta_path, head: ^parse_grammar_choice_overlap_residue, fatal: ^parse_g0_tokens_remain )] @@ -727,6 +900,7 @@ test fn a_missing_false_control_is_refused() -> Bool { executable_path: base.executable_path, executable_identity: base.executable_identity, universe: base.universe, + module_source_index: base.module_source_index, population: base.population, file_refusals: base.file_refusals, false_control: Absent, @@ -750,6 +924,7 @@ test fn a_passed_false_control_is_refused() -> Bool { executable_path: base.executable_path, executable_identity: base.executable_identity, universe: base.universe, + module_source_index: base.module_source_index, population: base.population, file_refusals: base.file_refusals, false_control: Present { value: NativeTestPassed }, @@ -773,6 +948,7 @@ test fn a_missing_true_control_is_refused() -> Bool { executable_path: base.executable_path, executable_identity: base.executable_identity, universe: base.universe, + module_source_index: base.module_source_index, population: base.population, file_refusals: base.file_refusals, false_control: base.false_control, @@ -796,6 +972,7 @@ test fn a_falsified_true_control_is_refused() -> Bool { executable_path: base.executable_path, executable_identity: base.executable_identity, universe: base.universe, + module_source_index: base.module_source_index, population: base.population, file_refusals: base.file_refusals, false_control: base.false_control, @@ -819,6 +996,7 @@ test fn an_accepted_malformed_specimen_is_refused() -> Bool { executable_path: base.executable_path, executable_identity: base.executable_identity, universe: base.universe, + module_source_index: base.module_source_index, population: base.population, file_refusals: base.file_refusals, false_control: base.false_control, @@ -842,6 +1020,7 @@ test fn an_absent_old_route_control_is_refused() -> Bool { executable_path: base.executable_path, executable_identity: base.executable_identity, universe: base.universe, + module_source_index: base.module_source_index, population: base.population, file_refusals: base.file_refusals, false_control: base.false_control, @@ -866,6 +1045,7 @@ test fn one_red_run_names_every_violated_clause() -> Bool { executable_path: "", executable_identity: "", universe: [], + module_source_index: [], population: [], file_refusals: [], false_control: Absent, @@ -880,6 +1060,7 @@ test fn one_red_run_names_every_violated_clause() -> Bool { && native_route_admission_refused_with(admission: admission, cause_name: "route_identity_unrecorded") && native_route_admission_refused_with(admission: admission, cause_name: "universe_underived") && native_route_admission_refused_with(admission: admission, cause_name: "positive_population_empty") + && native_route_admission_refused_with(admission: admission, cause_name: "required_native_pass_regressed") && native_route_admission_refused_with(admission: admission, cause_name: "false_control_not_observed") && native_route_admission_refused_with(admission: admission, cause_name: "true_control_not_observed") && native_route_admission_refused_with(admission: admission, cause_name: "malformed_control_accepted_by_route") @@ -895,11 +1076,12 @@ test fn the_admission_summary_names_every_violated_clause() -> Bool { executable_path: "", executable_identity: "", universe: [], + module_source_index: [], population: [], file_refusals: [], false_control: Absent, true_control: Absent, malformed_control: MalformedSpecimenAccepted, old_route_control: OldRouteControlAbsent - })) == "refused: job_name_mismatch, tested_tree_unrecorded, preparation_seed_unrecorded, emitted_closure_unrecorded, route_identity_unrecorded, universe_underived, positive_population_empty, false_control_not_observed, true_control_not_observed, malformed_control_accepted_by_route, old_route_control_never_installed" + })) == "refused: job_name_mismatch, tested_tree_unrecorded, preparation_seed_unrecorded, emitted_closure_unrecorded, route_identity_unrecorded, universe_underived, positive_population_empty, required_native_pass_regressed, false_control_not_observed, true_control_not_observed, malformed_control_accepted_by_route, old_route_control_never_installed" } From 911944d9b1ec94ff6a60f1ae20fb3f0566073cb2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 11:21:45 +0000 Subject: [PATCH 73/89] native driver: select declarations by exact module scope, never by nested descent (review round 2, point 4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit native_test_find_decl_node recursively walked every nested Named edge, so a same-named graft spine segment (or any nested edge) could be selected and evaluated under the requested test identity — the receipt's module-qualified identity names the request, not the selected node. The search is now exact: unwrap the module production wrapper through the namespace-graft authority, walk one Named spine edge per qualified-name segment (find_named_child, refusing ambiguity), and match direct children of the module scope only. native_test_decl_deeper is deleted. A failed scope walk is its own entry reason (native_test_entry_module_scope_unresolved), deliberately absent from native_route_entry_limit_reasons so a graft-shape surprise reds the lane instead of excluding tests. The committed control (v2.test.native_decl_selection) prepares a synthetic module with no top-level declaration named collision and asserts the nested spine segment is never selected: requesting collision returns declaration_not_found (the recursive walk returned not_arrow), the real declaration still evaluates to NativeTestPassed, and a sibling module's declaration is unreachable. Co-authored-by: briansrls --- src/v2/compiler/00_compile.dag | 104 ++++++++++-------- src/v2/test/native_decl_selection_test.dag | 120 +++++++++++++++++++++ 2 files changed, 180 insertions(+), 44 deletions(-) create mode 100644 src/v2/test/native_decl_selection_test.dag diff --git a/src/v2/compiler/00_compile.dag b/src/v2/compiler/00_compile.dag index ef9e93f5bfc..70abb1a1ad0 100644 --- a/src/v2/compiler/00_compile.dag +++ b/src/v2/compiler/00_compile.dag @@ -41,6 +41,7 @@ import v2.std.compilers.target_model { TargetModel } import v2.extdeps.languages.dag { dag_language_model, dag_target_model, + namespace_graft_unwrap_module_working_root, qualified_name_from_module_node } import v2.lens.complexity_accumulator_copy.compile_gate { accumulator_copy_compile_gate } @@ -74,7 +75,7 @@ import v2.std.diagnostic { outcome_with_diagnostics } import v2.std.node { Arrow, Edge, Named, Node, NodeFold, Positional, Symbol, TypeNode, fold_node } -import v2.std.node_query { find_arrow_body_child } +import v2.std.node_query { find_arrow_body_child, find_named_child } import v2.std.optional { Absent, Optional, Present, optional_absent, optional_present } import v2.std.qualified_name { QualifiedName } import v2.std.cross_tree.import_model { SourceRootRef } @@ -838,11 +839,32 @@ fn native_test_prepare_module( ) } -// THE DECLARATION SEARCH IS SCOPED TO ONE PREPARED MODULE TREE, so a name match anywhere in it -// names this module's declaration — module scope is the only place named declaration edges live, -// and a duplicate declaration name is a resolve refusal upstream of here. Direct children are -// preferred at every level so the module-scope edge wins over any same-named edge nested inside -// a declaration body. +// THE DECLARATION SEARCH IS EXACT: a test identity names a top-level declaration of one prepared +// module, so the search first resolves that module's scope node — unwrapping the module +// production wrapper through the namespace-graft authority, then walking one Named spine edge per +// qualified-name segment — and then matches DIRECT CHILDREN of the scope only. Descending into +// nested Named edges could select a same-named graft segment or an edge nested inside a +// declaration body and evaluate it under the requested identity. A scope walk that fails is a +// graft-shape surprise and surfaces as its own entry reason, deliberately absent from +// native_route_entry_limit_reasons so the surprise reds the lane instead of excluding tests. + +fn native_test_module_scope_go(at: Node, segments: QualifiedName) -> Outcome { + match segments { + Empty => outcome_accepted(value: at) + Cons { head: segment, tail: rest } => + bind_outcome( + o: find_named_child(root: at, name: segment), + f: fn(child) { native_test_module_scope_go(at: child, segments: rest) } + ) + } +} + +fn native_test_module_scope_node(root: Node, module: QualifiedName) -> Outcome { + native_test_module_scope_go( + at: namespace_graft_unwrap_module_working_root(root: root), + segments: module + ) +} fn native_test_decl_in_children(children: List, name: Symbol) -> Optional { fold(children, init: optional_absent(), f: fn(acc, e) { @@ -862,22 +884,6 @@ fn native_test_decl_in_children(children: List, name: Symbol) -> Optional< }) } -fn native_test_decl_deeper(children: List, name: Symbol) -> Optional { - fold(children, init: optional_absent(), f: fn(acc, e) { - match acc { - Present { value: v } => optional_present(value: v) - Absent => native_test_find_decl_node(node: e.target, name: name) - } - }) -} - -fn native_test_find_decl_node(node: Node, name: Symbol) -> Optional { - match native_test_decl_in_children(children: node.children, name: name) { - Present { value: v } => optional_present(value: v) - Absent => native_test_decl_deeper(children: node.children, name: name) - } -} - fn native_test_observation_refused( module: QualifiedName, declaration: Symbol, @@ -935,39 +941,49 @@ fn native_test_eval_one( prepared: NativeTestPreparedModule, declaration: Symbol ) -> NativeTestObservation { - match native_test_find_decl_node(node: prepared.inferred.root, name: declaration) { - Absent => + match native_test_module_scope_node(root: prepared.inferred.root, module: prepared.module) { + Rejected { diagnostics: _ } => native_test_observation_refused( module: prepared.module, declaration: declaration, stage: NativeTestStageEntry, - reason: ^native_test_entry_declaration_not_found + reason: ^native_test_entry_module_scope_unresolved ) - Present { value: target } => - match target.kind { - TypeNode { connective: Arrow } => - match find_arrow_body_child(root: target) { - Rejected { diagnostics: d } => + Accepted { value: scope, diagnostics: _ } => + match native_test_decl_in_children(children: scope.children, name: declaration) { + Absent => + native_test_observation_refused( + module: prepared.module, + declaration: declaration, + stage: NativeTestStageEntry, + reason: ^native_test_entry_declaration_not_found + ) + Present { value: target } => + match target.kind { + TypeNode { connective: Arrow } => + match find_arrow_body_child(root: target) { + Rejected { diagnostics: d } => + native_test_observation_refused( + module: prepared.module, + declaration: declaration, + stage: NativeTestStageEntry, + reason: native_test_fatal_reason(d: d) + ) + Accepted { value: body, diagnostics: _ } => + native_test_eval_body( + prepared: prepared, + declaration: declaration, + body: body + ) + } + _ => native_test_observation_refused( module: prepared.module, declaration: declaration, stage: NativeTestStageEntry, - reason: native_test_fatal_reason(d: d) - ) - Accepted { value: body, diagnostics: _ } => - native_test_eval_body( - prepared: prepared, - declaration: declaration, - body: body + reason: ^native_test_entry_declaration_not_arrow ) } - _ => - native_test_observation_refused( - module: prepared.module, - declaration: declaration, - stage: NativeTestStageEntry, - reason: ^native_test_entry_declaration_not_arrow - ) } } } diff --git a/src/v2/test/native_decl_selection_test.dag b/src/v2/test/native_decl_selection_test.dag new file mode 100644 index 00000000000..084ebd52f5b --- /dev/null +++ b/src/v2/test/native_decl_selection_test.dag @@ -0,0 +1,120 @@ +module v2.test.native_decl_selection + +import v2.compiler.compile { + native_test_context_from_ingest, + native_test_eval_one, + native_test_prepare_module, + NativeTestObservation, + NativeTestPassed, + NativeTestPreparedModule, + NativeTestRefused, + NativeTestStageEntry +} +import v2.compiler.source_authority { DagSourceReadWitness } +import v2.std.artifact { Artifact, SourceFile } +import v2.std.cross_tree.import_model { DagTree } +import v2.std.diagnostic { Accepted, Rejected } +import v2.std.logic { Bool } +import v2.std.node { Symbol } +import v2.std.optional { Absent, Optional, Present, optional_absent, optional_present } +import extdeps.communication.medium { Lossless, Medium } + +// Selection controls for the native driver's exact scope walk: a test identity names a top-level +// declaration of the prepared module, so a same-named NESTED edge — the graft spine segment +// v2.test.collision itself — must never be selected and evaluated under the requested identity. +// The synthetic module declares no top-level collision; its only collision-named edge is the +// spine segment (plus a record field, which infer erases). + +data collision_module_source: String = "module v2.test.collision\n\nimport v2.std.logic { Bool }\n\nimport v2.std.integer { Int }\n\ntest fn real_test() -> Bool { true }\n\ntype Probe { collision: Int }\n" + +data collision_sibling_source: String = "module v2.test.collision_sibling\n\nimport v2.std.logic { Bool }\n\ntest fn shared_name() -> Bool { true }\n" + +fn collision_read() -> DagSourceReadWitness { + DagSourceReadWitness { + source: Medium { carried: collision_module_source, fidelity: Lossless }, + artifact: Artifact { + kind: SourceFile, + id: ^native_decl_selection_collision_read, + file_path: "src/v2/test/fixture/native_decl_selection/collision_test.dag" + }, + compilation_unit: ^native_decl_selection_collision_cu, + source_root: DagTree + } +} + +fn collision_sibling_read() -> DagSourceReadWitness { + DagSourceReadWitness { + source: Medium { carried: collision_sibling_source, fidelity: Lossless }, + artifact: Artifact { + kind: SourceFile, + id: ^native_decl_selection_sibling_read, + file_path: "src/v2/test/fixture/native_decl_selection/collision_sibling_test.dag" + }, + compilation_unit: ^native_decl_selection_sibling_cu, + source_root: DagTree + } +} + +fn collision_prepared() -> Optional { + match native_test_context_from_ingest(ingest: [collision_read(), collision_sibling_read()]) { + Rejected { diagnostics: _ } => optional_absent() + Accepted { value: context, diagnostics: _ } => + match native_test_prepare_module(context: context, module: [^v2, ^test, ^collision]) { + Rejected { diagnostics: _ } => optional_absent() + Accepted { value: prepared, diagnostics: _ } => optional_present(value: prepared) + } + } +} + +fn observation_is_entry_not_found(observation: NativeTestObservation) -> Bool { + match observation.verdict { + NativeTestRefused { stage: stage, reason: reason } => + match stage { + NativeTestStageEntry => reason == ^native_test_entry_declaration_not_found + _ => false + } + _ => false + } +} + +test fn the_requested_declaration_is_selected_and_evaluated() -> Bool { + match collision_prepared() { + Absent => false + Present { value: prepared } => + let observation = native_test_eval_one(prepared: prepared, declaration: ^real_test) + match observation.verdict { + NativeTestPassed => true + _ => false + } + } +} + +test fn a_nested_same_named_graft_edge_is_not_selected() -> Bool { + match collision_prepared() { + Absent => false + Present { value: prepared } => + observation_is_entry_not_found( + observation: native_test_eval_one(prepared: prepared, declaration: ^collision) + ) + } +} + +test fn an_absent_declaration_is_not_found() -> Bool { + match collision_prepared() { + Absent => false + Present { value: prepared } => + observation_is_entry_not_found( + observation: native_test_eval_one(prepared: prepared, declaration: ^missing) + ) + } +} + +test fn a_sibling_modules_declaration_is_not_selected() -> Bool { + match collision_prepared() { + Absent => false + Present { value: prepared } => + observation_is_entry_not_found( + observation: native_test_eval_one(prepared: prepared, declaration: ^shared_name) + ) + } +} From 4e5c564f3a5fc426f8ee5ae5702b79789cd7f37e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 11:21:45 +0000 Subject: [PATCH 74/89] required-ci: join (phase, lane) pairs in both directions, in every lane (review round 2, point 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The roster join covered only phase EXISTENCE (variant names off the declaration index); lane OWNERSHIP stayed a parallel Rust match. With three independently selected lanes and the native route's isolation load-bearing, a host edit mapping V2NativePhase to another lane would leave the native job selecting zero phases — phases_run=0, no failure, a green required job that executed no native test. The authority now exposes required_ci_lane_phase_rows (one nullary call, every rostered phase with its owning lane in CLI spelling). Every --required-ci run evaluates it at start, in every lane, and refuses: any (phase, lane) pair present on exactly one side; a selected lane whose authority-expected phase set is empty; and, at run end, an observed ran set that differs from the expected set. Live RED probe: with the host match arm moved to build, the v2-native lane named both divergent pairs and refused the empty ran set instead of greening over zero phases. Co-authored-by: briansrls --- dag/gunbc/required_ci_phase_roster.dag | 42 ++-- src/v1/stage0/src/bin/claim_executor.rs | 186 +++++++++++++++++- src/v1/stage0/src/cli_run.rs | 2 + .../src/cli_run/required_lane_roster.rs | 79 ++++++++ 4 files changed, 294 insertions(+), 15 deletions(-) create mode 100644 src/v1/stage0/src/cli_run/required_lane_roster.rs diff --git a/dag/gunbc/required_ci_phase_roster.dag b/dag/gunbc/required_ci_phase_roster.dag index f15cb6bc0fa..c4f5c6f58bd 100644 --- a/dag/gunbc/required_ci_phase_roster.dag +++ b/dag/gunbc/required_ci_phase_roster.dag @@ -14,17 +14,18 @@ import std.types { String, Bool, List } // // THE HOST REALIZATION IS A DECLARED PARALLEL REPRESENTATION WITH AN EXECUTING JOIN, NOT A TWIN // LEFT TO DRIFT. claim_executor's RequiredCiPhase enum realizes this coproduct, and every -// --required-ci run joins the two BEFORE running any phase: set equality over this declaration's -// variant names, both directions, so "declared here and not realized" and "realized and not -// declared" are separate refusals that stop the line (the join shape -// v1_compiler.namespace_wave_admission vocabulary_findings executes against its disposition -// authority). The join deliberately does NOT cover lane ownership -- the host lane match is not -// readable from the declaration index -- and that residue is honest for enrolment truth: both lanes -// execute in every required run, so a lane divergence can move a phase between two jobs that both -// run; it cannot un-enrol one. The terminal state is the census's dissolution row: the workflow -// emission consumes this roster whole and the host enum, lane match and REQUIRED_CI_PHASES list -// delete atomically. Until then the parallel representation is this module's declared debt and the -// per-run join is its wall. +// --required-ci run joins the two BEFORE running any phase, at two grains: set equality over this +// declaration's variant names (existence, read off the declaration index on the parse phase's +// ingestion), and set equality over required_ci_lane_phase_rows' (lane, phase) pairs (ownership, +// evaluated in EVERY lane at run start, because a match arm is not a declaration and the index +// cannot read one). The pair join carries two further refusals: a selected lane whose +// authority-expected phase set is empty stops rather than greening over zero phases, and the +// observed ran set must equal the authority-expected set exactly. Lane ownership is load-bearing +// now that the workflow is three independently selected lanes with the native route isolated in +// its own. The terminal state is the census's dissolution row: the workflow emission consumes +// this roster whole and the host enum, lane match and REQUIRED_CI_PHASES list delete atomically. +// Until then the parallel representation is this module's declared debt and the per-run joins are +// its wall. type RequiredCiLane = BuildLane | WitnessesLane @@ -155,3 +156,22 @@ fn lane_phase_names(lane: RequiredCiLane, phases: List) -> List |> filter(p => required_ci_lane_name(l: required_ci_phase_lane(p: p)) == required_ci_lane_name(l: lane)) |> map(p => required_ci_phase_name(p: p)) } + +// THE (LANE, PHASE) ROWS THE HOST'S OWNERSHIP JOIN EVALUATES, AS ONE NULLARY CALL. A match arm +// is not a declaration, so lane ownership cannot leave this module through the declaration index; +// it leaves through this function, in the CLI spelling the host matches on. The host joins these +// pairs against its own enum-plus-lane-match in both directions at every required run's start, +// refuses a selected lane whose expected set here is empty, and requires the observed ran set to +// equal the expected set exactly. +type RequiredCiLanePhaseRow { + lane: String + phase: String +} + +fn required_ci_lane_phase_rows() -> List { + required_ci_phases + |> map(p => RequiredCiLanePhaseRow { + lane: required_ci_lane_name(l: required_ci_phase_lane(p: p)), + phase: required_ci_phase_name(p: p) + }) +} diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index 0644ece7454..2ef3095421a 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -565,6 +565,33 @@ fn run() -> Result { } } + // THE (PHASE, LANE) PAIR JOIN RUNS IN EVERY LANE, BEFORE ANY PHASE. The variant-set + // join rides the parse phase's index, but a match arm is not a declaration, so lane + // ownership is joined by evaluating the roster authority's + // `required_ci_lane_phase_rows`. Three lanes are independently selected and the native + // route's isolation is load-bearing: a host edit mapping a phase to the wrong lane + // (leaving the native job selecting zero phases), or a selected lane owning zero + // phases in the authority, stops the line here rather than greening over an + // unmeasured population. + let authority_lane_rows = + match v1_compiler::cli_run::authority_lane_phase_rows(&source_roots) { + Ok(rows) => Some(rows), + Err(e) => { + eprintln!("required-ci: lane-roster FAIL {e}"); + phase_failures.push(format!("lane-roster: {e}")); + None + } + }; + if let Some(rows) = &authority_lane_rows { + let findings = lane_roster_findings(rows, required_ci_lane); + for finding in &findings { + eprintln!("required-ci: lane-roster FAIL {finding}"); + } + if !findings.is_empty() { + phase_failures.push(format!("lane-roster ({} finding(s))", findings.len())); + } + } + // PHASE 1 — the .dag parse sweep, over every authored root (src/v1, dag, src/v2). // Independent of everything below it. The roster is // `cli_run::DAG_PARSE_SWEEP_ROOTS`, shared with the standalone bin so the cheapest @@ -1073,6 +1100,25 @@ fn run() -> Result { ran.push("v2-native"); } + // THE OBSERVED RAN SET IS THE AUTHORITY-EXPECTED SET, EXACTLY. The census below prints + // phases_run; this refusal covers the state where that number is legible but wrong — a + // phase silently never reached, or one executed that the authority does not expect in + // this lane. Equality over an empty expected set cannot mask a zero-phase run: the + // empty-expectation case already refused at run start. + if let Some(rows) = &authority_lane_rows { + let expected = expected_lane_phases(rows, required_ci_lane); + let expected_refs: std::collections::BTreeSet<&str> = + expected.iter().map(String::as_str).collect(); + let observed: std::collections::BTreeSet<&str> = ran.iter().copied().collect(); + if observed != expected_refs { + phase_failures.push(format!( + "lane-roster ran-set: the authority expects {:?} in this lane but the run \ + executed {:?}", + expected_refs, observed + )); + } + } + // COUNTER-KEY CENSUS (dashboard node adhoc-af8a3fe8-13d): this is the // phase population. The required-floor aggregate below counts claim outcomes, so the // formerly shared `failed` key gave one spelling two meanings in one process's output. @@ -1688,10 +1734,11 @@ const PHASE_ROSTER_VARIANT_LABELS: [&str; 6] = [ /// nothing else joins these two. An absent authority module refuses too — that is the state in /// which nothing is checking the roster, not permission to proceed. /// -/// WHAT THIS JOIN DELIBERATELY DOES NOT COVER: lane ownership. The host `lane` match is not -/// readable from the declaration index, and a lane divergence cannot un-enrol a phase — both -/// lanes execute in every required run — so the residue is bounded to which job carries a phase, -/// and its terminal is the atomic deletion the roster's census row names. +/// WHAT THIS JOIN DOES NOT COVER: lane ownership. A match arm is not a declaration, so the +/// index this join reads cannot see which lane owns a phase. That half is joined by evaluation +/// instead — `authority_lane_phase_rows` against this host's enum-plus-lane-match, executed at +/// run start in EVERY lane (three lanes are independently selected now, and the native route's +/// isolation is load-bearing), with the empty-expectation and exact-ran-set refusals beside it. fn phase_roster_findings( index: &v1_compiler::cli_run::declaration_index::DeclarationIndex, ) -> Vec { @@ -1729,6 +1776,61 @@ fn phase_roster_findings( findings } +/// The authority-expected phase names for the selected lane — the whole roster when no lane was +/// selected (a lane-less run owns every phase). +fn expected_lane_phases( + rows: &[v1_compiler::cli_run::LanePhaseRow], + selected: Option, +) -> std::collections::BTreeSet { + rows.iter() + .filter(|r| selected.is_none_or(|l| r.lane == l.name())) + .map(|r| r.phase.clone()) + .collect() +} + +/// The (phase, lane) pair join, both directions, plus the empty-expectation refusal. The pair +/// join is what makes a host lane-match edit red: moving a phase between lanes changes the host +/// pair set while the authority's stays, and the difference names the divergence. The empty +/// refusal is what makes a zero-phase selected lane stop: `phases_run=0` can no longer render +/// as a successful required job. +fn lane_roster_findings( + rows: &[v1_compiler::cli_run::LanePhaseRow], + selected: Option, +) -> Vec { + use std::collections::BTreeSet; + let host: BTreeSet<(String, String)> = REQUIRED_CI_PHASES + .iter() + .map(|p| (p.lane().name().to_string(), p.name().to_string())) + .collect(); + let authority: BTreeSet<(String, String)> = rows + .iter() + .map(|r| (r.lane.clone(), r.phase.clone())) + .collect(); + let mut findings = Vec::new(); + for (lane, phase) in authority.difference(&host) { + findings.push(format!( + "the authority rosters phase `{phase}` in lane `{lane}` and this host does not \ + realize that pair — lane ownership diverged" + )); + } + for (lane, phase) in host.difference(&authority) { + findings.push(format!( + "this host runs phase `{phase}` in lane `{lane}` and the authority does not roster \ + that pair — lane ownership diverged" + )); + } + if expected_lane_phases(rows, selected).is_empty() { + findings.push(format!( + "the selected lane `{}` owns no phases in the authority roster — the run would \ + report success over zero phases", + selected + .map(|l| l.name()) + .unwrap_or("all (no --required-lane given)") + )); + } + findings +} + /// EVERY PHASE IS ACCOUNTED FOR IN EVERY RUN, whether or not this lane owns it. /// /// A lane that simply omitted the other lane's phases would produce a ledger in which "this @@ -2199,6 +2301,82 @@ fn main() -> ExitCode { mod tests { use super::*; + fn lane_phase_row(lane: &str, phase: &str) -> v1_compiler::cli_run::LanePhaseRow { + v1_compiler::cli_run::LanePhaseRow { + lane: lane.to_string(), + phase: phase.to_string(), + } + } + + /// The pairs `required_ci_lane_phase_rows` derives from the standing roster. + fn standing_authority_rows() -> Vec { + vec![ + lane_phase_row("witnesses", "parse"), + lane_phase_row("witnesses", "namespace-wave-admission"), + lane_phase_row("build", "generated-artifact"), + lane_phase_row("build", "regen-fixed-point"), + lane_phase_row("witnesses", "floor"), + lane_phase_row("v2-native", "v2-native"), + ] + } + + #[test] + fn the_standing_pairs_join_cleanly_in_every_lane() { + for lane in [ + RequiredCiLane::Build, + RequiredCiLane::Witnesses, + RequiredCiLane::V2Native, + ] { + assert!( + lane_roster_findings(&standing_authority_rows(), Some(lane)).is_empty(), + "lane {} must join cleanly", + lane.name() + ); + } + assert!(lane_roster_findings(&standing_authority_rows(), None).is_empty()); + } + + #[test] + fn a_lane_ownership_divergence_is_red_and_names_the_pair() { + // The review's scenario — the host mapping and the authority mapping disagree about + // which lane owns v2-native — simulated by moving the authority row; the pair + // difference is symmetric, so either side's edit fires both directions. + let mut rows = standing_authority_rows(); + rows.retain(|r| r.phase != "v2-native"); + rows.push(lane_phase_row("build", "v2-native")); + let findings = lane_roster_findings(&rows, Some(RequiredCiLane::V2Native)); + assert!( + findings + .iter() + .any(|f| f.contains("v2-native") && f.contains("build")), + "the divergence must name the pair: {findings:?}" + ); + assert!( + findings.iter().any(|f| f.contains("owns no phases")), + "the native lane's expectation is now empty and must refuse: {findings:?}" + ); + } + + #[test] + fn a_selected_lane_with_no_expected_phases_is_refused() { + let rows: Vec<_> = standing_authority_rows() + .into_iter() + .filter(|r| r.phase != "v2-native") + .collect(); + let findings = lane_roster_findings(&rows, Some(RequiredCiLane::V2Native)); + assert!(findings.iter().any(|f| f.contains("owns no phases"))); + } + + #[test] + fn the_expected_set_tracks_the_selected_lane() { + let rows = standing_authority_rows(); + assert_eq!( + expected_lane_phases(&rows, Some(RequiredCiLane::V2Native)), + ["v2-native".to_string()].into_iter().collect() + ); + assert_eq!(expected_lane_phases(&rows, None).len(), 6); + } + #[test] fn a_returned_subject_refusal_is_a_completed_measurement() { let blocker = RequiredCiBlocker { diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 066be0ffef6..f6b4ade4bd5 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -91,6 +91,7 @@ pub mod declaration_index; pub mod derived_row_roster; mod native_lane_runner; mod required_floor_runner; +mod required_lane_roster; pub mod rostered_row_join; mod serve_budget_refusal; pub use native_lane_runner::run_required_v2_native; @@ -99,6 +100,7 @@ pub use required_floor_runner::{ floor_discovery_path_excluded, make_eval_context, make_eval_context_with_runtime_options, run_claim_measured, run_required_floor, }; +pub use required_lane_roster::{authority_lane_phase_rows, LanePhaseRow}; mod entry_resolve; pub(crate) use active_workset::*; pub(crate) use entry_resolve::*; diff --git a/src/v1/stage0/src/cli_run/required_lane_roster.rs b/src/v1/stage0/src/cli_run/required_lane_roster.rs new file mode 100644 index 00000000000..8365dde86df --- /dev/null +++ b/src/v1/stage0/src/cli_run/required_lane_roster.rs @@ -0,0 +1,79 @@ +//! THE (PHASE, LANE) PAIR JOIN — the lane-ownership half of the phase-roster wall. +//! +//! The variant-set join (`phase_roster_findings` in the claim_executor bin) reads variant names +//! off the declaration index, so it can join phase EXISTENCE but not lane OWNERSHIP: a match arm +//! is not a declaration. Lane ownership leaves the authority only by evaluation, through +//! `gunbc.required_ci_phase_roster` `required_ci_lane_phase_rows`. +//! +//! Why this half exists: the required workflow is three independently selected lanes, and the +//! v2-native lane's isolation is load-bearing. A host edit that mapped `V2NativePhase` to another +//! lane while the authority still rosters it in `v2-native` would leave the native job selecting +//! zero phases — `phases_run=0`, no phase failure, a green required job that executed no native +//! test. Joining (phase, lane) pairs in both directions makes that edit red at run start in +//! every lane; the empty-expectation refusal and the exact ran-set check (both in the bin, over +//! the rows this module decodes) make a zero-phase selected lane refuse rather than report +//! success over nothing. + +use crate::v1_interpreter::{self, Value}; + +/// The roster authority's entry file — the same module the variant-set join reads off the +/// declaration index, here consumed by evaluation instead. +const LANE_ROSTER_AUTHORITY_ENTRY: super::WorkspaceRootRelativeEntry = + super::WorkspaceRootRelativeEntry("dag/gunbc/required_ci_phase_roster.dag"); + +/// One authority-rostered (lane, phase) pair, both names in the CLI spelling the host matches on. +pub struct LanePhaseRow { + pub lane: String, + pub phase: String, +} + +fn lane_phase_row_from_value( + ctx: &v1_interpreter::InterpContext, + val: &Value, +) -> Result { + let Value::Record { fields, .. } = val else { + return Err(format!( + "expected RequiredCiLanePhaseRow record, got `{}`", + ctx.format_value(val) + )); + }; + let lane = match ctx.field(fields, "lane") { + Some(Value::Str(s)) => s.to_string(), + _ => return Err("RequiredCiLanePhaseRow missing `lane`".to_string()), + }; + let phase = match ctx.field(fields, "phase") { + Some(Value::Str(s)) => s.to_string(), + _ => return Err("RequiredCiLanePhaseRow missing `phase`".to_string()), + }; + Ok(LanePhaseRow { lane, phase }) +} + +/// Evaluate the authority's `required_ci_lane_phase_rows` and decode every row. Any failure — +/// the module absent, the function unevaluable, a row of the wrong shape — is a refusal, never +/// an empty roster: an unreadable authority is the state in which nothing is checking lane +/// ownership, not permission to proceed. +pub fn authority_lane_phase_rows(source_roots: &[String]) -> Result, String> { + let (graph, indices) = + super::resolve_workspace_entry(source_roots, LANE_ROSTER_AUTHORITY_ENTRY)?; + let ctx = super::make_eval_context(&graph, indices, v1_interpreter::ExecutionMode::Hermetic); + let value = v1_interpreter::run_in_context_with_args( + &ctx, + "gunbc.required_ci_phase_roster.required_ci_lane_phase_rows", + &[], + false, + ) + .map_err(|e| format!("lane-roster authority evaluation refused: {e}"))?; + match &value { + Value::List(items) => { + let mut out = Vec::with_capacity(items.len()); + for item in items.iter() { + out.push(lane_phase_row_from_value(&ctx, item)?); + } + Ok(out) + } + other => Err(format!( + "required_ci_lane_phase_rows returned `{}`, expected a List", + ctx.format_value(other) + )), + } +} From 2b24cef04398f2b1f82fa6ca0729c00dfc49d533 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 11:28:01 +0000 Subject: [PATCH 75/89] Hoist v2_native_route_test ratchet annotations to module-item grain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The emit stage refuses in-body annotations (§4c); the two ratchet RED fixtures carried their rationale inside the fn bodies. Same class as the megarac repair, caught by the lane's own emission step. Co-authored-by: briansrls --- src/v2/test/v2_native_route_test.dag | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/v2/test/v2_native_route_test.dag b/src/v2/test/v2_native_route_test.dag index 5aaf0415548..185d3b9d92f 100644 --- a/src/v2/test/v2_native_route_test.dag +++ b/src/v2/test/v2_native_route_test.dag @@ -653,10 +653,10 @@ test fn an_all_refused_population_is_refused() -> Bool { // ── admission: the required-native ratchet ──────────────────────────────────────────────── +// The rostered smoke identity, observed as an ATTRIBUTED exclusion — the exact collapse the +// ratchet exists to block: every clause about classification greens, and coverage still lost +// the identity. test fn a_regressed_required_native_pass_is_refused() -> Bool { - // The rostered smoke identity, observed as an ATTRIBUTED exclusion — the exact collapse the - // ratchet exists to block: every clause about classification greens, and coverage still lost - // the identity. native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [smoke_identity()], @@ -673,10 +673,10 @@ test fn a_regressed_required_native_pass_is_refused() -> Bool { ) } +// The smoke identity is not in the universe at all — coverage shrank silently. The omission +// clause cannot fire (the universe itself no longer names the member), so the ratchet is the +// only wall, and it reads the roster against the OBSERVED population. test fn an_omitted_required_native_pass_is_refused() -> Bool { - // The smoke identity is not in the universe at all — coverage shrank silently. The omission - // clause cannot fire (the universe itself no longer names the member), so the ratchet is the - // only wall, and it reads the roster against the OBSERVED population. native_route_admission_refused_with( admission: native_route_admission(receipt: receipt_over( universe: [test_identity(module: "v2.test.alpha", declaration: "passes")], From 2fb67b411aecfccfce1c2642ebc724df0a755585 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 14:03:57 +0000 Subject: [PATCH 76/89] v2-native lane: root the probe crate in the declared per-job temp; carry the poison specimen off .dag Two CI refusals on run 34471447387, both environment/fixer-carrier defects in the lane's own machinery, neither in the route under test: 1. required-v2-native refused EmittedCrateNotWritten (EACCES): the lane harness rooted the emitted crate at the host-shared /tmp/gunbc-emit-compile, which on the persistent self-hosted fleet collides across jobs, runs and euids. The lane now selects the declared RUNNER_TEMP per-job root when one exists (lane_emit_compile_probe_root, beside the required phase's stricter refusal policy, over one shared env read) and keeps the host temp for the local route. Selection pinned by a unit test over both arms. 2. required-witnesses-floor refused ChangedWitnessObservationFailed: the lane's deliberately unparseable control fixture carried a .dag extension, so the changed-witness observation parsed it and the deliberate tokenize failure refused the whole observation. The committed bytes are not a dag program and no longer claim to be: renamed to poison.dag.poisoned, and the harness materializes them as poison.dag under the lane's scratch root (rebuilt fresh each run) for the control invocation. The observation's refusal arm stays absolute - no exception was added to it. Co-authored-by: briansrls --- dag/gunbc/witness/v2_native_route.dag | 7 +- fixtures/native_lane_malformed/poison.dag | 13 ---- .../native_lane_malformed/poison.dag.poisoned | 18 +++++ src/v1/stage0/src/cli_run.rs | 8 +-- .../stage0/src/cli_run/native_lane_runner.rs | 66 +++++++++++++++---- .../src/emitted_closure_compile_host.rs | 55 +++++++++++++++- src/v2/test/v2_native_route_test.dag | 2 +- 7 files changed, 136 insertions(+), 33 deletions(-) delete mode 100644 fixtures/native_lane_malformed/poison.dag create mode 100644 fixtures/native_lane_malformed/poison.dag.poisoned diff --git a/dag/gunbc/witness/v2_native_route.dag b/dag/gunbc/witness/v2_native_route.dag index 22da7c59f38..b0336d076e9 100644 --- a/dag/gunbc/witness/v2_native_route.dag +++ b/dag/gunbc/witness/v2_native_route.dag @@ -419,8 +419,11 @@ fn native_route_census( // representable so a missing observation refuses by name rather than pattern-matching to a // default. // -// THE MALFORMED CONTROL PROVES THE FRONT-END REFUSAL ARM IS LIVE. The host runs the same binary -// over the poison fixture's directory as the run's only source root — the run's single consumed +// THE MALFORMED CONTROL PROVES THE FRONT-END REFUSAL ARM IS LIVE. The host materializes the +// committed poison specimen as a `.dag` under the lane's scratch root — the committed carrier +// is not a `.dag` file, because the changed-witness observation parses every changed `.dag` +// path in a diff and the deliberate tokenize failure would refuse it — and runs the same +// binary over that scratch root as the run's only source root. The run's single consumed // output is the poison path's refusal, so rooting it at the full corpus would pay a second // whole-corpus context fold for one observation. An honest front-end collects a per-file refusal // for it (the collecting fold refuses the FILE, never the whole context), and the receipt records diff --git a/fixtures/native_lane_malformed/poison.dag b/fixtures/native_lane_malformed/poison.dag deleted file mode 100644 index 8eb13c1fb21..00000000000 --- a/fixtures/native_lane_malformed/poison.dag +++ /dev/null @@ -1,13 +0,0 @@ -module v2.native_lane_malformed.poison - -// THE required-v2-native LANE'S MALFORMED-SOURCE CONTROL (authority: -// gunbc.witness_v2_native_route). This file is deliberately NOT well-formed: the string below -// never terminates, so any honest front-end refuses it at tokenize. The lane's host harness -// runs the emitted native binary with this file's directory added as a source root and REQUIRES -// a whole-context refusal (exit 1) — a native route that "accepts" a poisoned context has a -// dead front-end refusal arm, and every refusal verdict in its census is then worthless. -// -// It lives outside dag/ and src/v2/ so the broken bytes never enter an honest ingest: the -// required parse sweep walks only the declared source roots, and this directory joins a source -// root set only in the lane's own control invocation. -data broken: String = "this string never terminates diff --git a/fixtures/native_lane_malformed/poison.dag.poisoned b/fixtures/native_lane_malformed/poison.dag.poisoned new file mode 100644 index 00000000000..1a1dae2b9fc --- /dev/null +++ b/fixtures/native_lane_malformed/poison.dag.poisoned @@ -0,0 +1,18 @@ +module v2.native_lane_malformed.poison + +// THE required-v2-native LANE'S MALFORMED-SOURCE CONTROL (authority: +// gunbc.witness_v2_native_route). This file is deliberately NOT well-formed: the string below +// never terminates, so any honest front-end refuses it at tokenize. The lane's host harness +// materializes these bytes as `poison.dag` under the lane's scratch root, runs the emitted +// native binary with that scratch directory as the only source root, and REQUIRES a per-file +// refusal — a native route that "accepts" a poisoned context has a dead front-end refusal arm, +// and every refusal verdict in its census is then worthless. +// +// THE COMMITTED CARRIER DOES NOT CARRY THE PLAIN `.dag` EXTENSION, because the bytes are not a +// dag program and the extension would claim otherwise to every repo-wide reader: the floor +// lane's changed-witness observation parses every changed `.dag` path in a diff, and this +// file's deliberate tokenize failure refused the whole observation (run 34471447387). It also +// lives outside dag/ and src/v2/ so the broken bytes never enter an honest ingest: the required +// parse sweep walks only the declared source roots, and the materialized copy joins a source +// root set only in the lane's own control invocation. +data broken: String = "this string never terminates diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index f6b4ade4bd5..dee07d28064 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -42359,10 +42359,10 @@ pub use emitted_closure_compile_host::{ cargo_verdict_stderr_tail, emit_compile_modules_reached, emit_compile_outcome_passed, emit_compile_outcome_summary, emit_compile_report, emit_compile_selection, emit_compile_selection_not_selected_digest, emit_compile_selection_selected_digest, - emit_compile_selection_universe_digest, local_emit_compile_probe_root, - required_ci_emit_compile_probe_root, required_emit_compile_entries, - retain_not_selected_identities, run_required_emit_compile, CargoVerdict, EmitCompileOutcome, - EmitCompileSelection, MutationVerdict, + emit_compile_selection_universe_digest, lane_emit_compile_probe_root, + local_emit_compile_probe_root, required_ci_emit_compile_probe_root, + required_emit_compile_entries, retain_not_selected_identities, run_required_emit_compile, + CargoVerdict, EmitCompileOutcome, EmitCompileSelection, MutationVerdict, }; /// THE FIXTURE ROUTE IS TEST-FACING ONLY, AND THAT IS WHY IT HAS ITS OWN `use` RATHER THAN A LINE diff --git a/src/v1/stage0/src/cli_run/native_lane_runner.rs b/src/v1/stage0/src/cli_run/native_lane_runner.rs index 9f4e42ad2f2..ed2a0dc7de9 100644 --- a/src/v1/stage0/src/cli_run/native_lane_runner.rs +++ b/src/v1/stage0/src/cli_run/native_lane_runner.rs @@ -43,10 +43,19 @@ const CONTROL_MODULE: &str = "v2.native_lane_fixture.control"; const FALSE_CONTROL_DECL: &str = "native_lane_false_control"; const TRUE_CONTROL_DECL: &str = "native_lane_true_control"; -/// The malformed control: a deliberately unterminating string any honest front-end must refuse -/// at tokenize. It lives outside every source root and enters only the control run's ingest. -const MALFORMED_FIXTURE_DIR: &str = "fixtures/native_lane_malformed"; -const MALFORMED_FIXTURE_PATH: &str = "fixtures/native_lane_malformed/poison.dag"; +/// The malformed control: deliberately unterminating bytes any honest front-end must refuse at +/// tokenize. THE COMMITTED CARRIER IS NOT A `.dag` FILE — the bytes are not a dag program, and +/// carrying the extension put them in the changed-witness observation's parse path, where the +/// deliberate tokenize failure refused the whole floor lane (run 34471447387). The harness +/// materializes the specimen as `poison.dag` under the lane's scratch root, and only that +/// materialized copy ever enters an ingest — the control run's own source root. +const MALFORMED_SPECIMEN_COMMITTED: &str = "fixtures/native_lane_malformed/poison.dag.poisoned"; +/// The control run's scratch source root and the materialized specimen's path under it, +/// workspace-relative like every path this harness hands the binary (its working directory is +/// the workspace root). The receipt records the observed refusal path; the admission authority +/// requires it non-empty, and this harness requires it to name the materialized specimen. +const MALFORMED_CONTROL_ROOT: &str = "target/v2-native-lane/malformed-control-root"; +const MALFORMED_MATERIALIZED_PATH: &str = "target/v2-native-lane/malformed-control-root/poison.dag"; /// The admission authority's entry file, for the interpreter context that judges the receipt. const NATIVE_ROUTE_AUTHORITY_ENTRY: &str = "dag/gunbc/witness/v2_native_route.dag"; @@ -243,7 +252,10 @@ fn derive_native_universe(source_roots: &[String]) -> Result Result { let workspace = super::process_workspace_root(); - let probe_root = super::local_emit_compile_probe_root(); + // The probe root follows the declared execution environment (per-job runner temp in CI, + // host temp locally) — the selection's authority and its receipt live beside the required + // phase's own root policy in `emitted_closure_compile_host`. + let probe_root = super::lane_emit_compile_probe_root(); eprintln!("required-ci: v2-native emitting {NATIVE_COMPILE_ENTRY} (seed, in-process)"); let run = super::compile_entry_emission( source_roots, @@ -411,6 +423,33 @@ fn write_control_universe_file(path: &Path) -> Result<(), String> { write_universe_file(path, &[]) } +/// Build the malformed control's scratch source root: exactly the committed specimen, +/// materialized under its `.dag` name. The root is REMOVED first — a scratch root that +/// accumulates whatever earlier runs left behind would let a stale second file into the +/// control's ingest, and the control's whole value is that its root holds exactly one poisoned +/// file. Returns the workspace-relative root the binary is spawned with. +fn materialize_malformed_specimen(workspace: &Path) -> Result { + let specimen = workspace.join(MALFORMED_SPECIMEN_COMMITTED); + let bytes = std::fs::read(&specimen).map_err(|e| { + format!( + "V2-NATIVE REFUSAL cause=MalformedSpecimenUnreadable — reading {}: {e}", + specimen.display() + ) + })?; + let root = workspace.join(MALFORMED_CONTROL_ROOT); + if root.exists() { + std::fs::remove_dir_all(&root).map_err(|e| format!("clearing {}: {e}", root.display()))?; + } + let materialized = workspace.join(MALFORMED_MATERIALIZED_PATH); + let parent = materialized + .parent() + .expect("the materialized path names a file under the control root"); + std::fs::create_dir_all(parent).map_err(|e| format!("creating {}: {e}", parent.display()))?; + std::fs::write(&materialized, bytes) + .map_err(|e| format!("writing {}: {e}", materialized.display()))?; + Ok(MALFORMED_CONTROL_ROOT.to_string()) +} + /// The spawned run's decoded observations: one row per printed verdict, the per-file refusals /// the context fold collected, and the terminal marker's row count. A line that is neither an /// observation, nor a file refusal, nor the terminal marker refuses the parse — the binary's @@ -840,17 +879,20 @@ pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { main_output.file_refusals.len() ); - // 6. THE MALFORMED CONTROL. The same binary over the poison fixture's directory ALONE. - // The run's only consumed output is the poison path's per-file refusal, and rooting the - // control at the full corpus paid a second whole-corpus context fold — the lane's dominant - // cost — to produce it. Tokenization is per-file and deterministic, so the observed refusal - // is identical under the restricted roots. + // 6. THE MALFORMED CONTROL. The same binary over the materialized specimen's scratch root + // ALONE. The run's only consumed output is the poison path's per-file refusal, and rooting + // the control at the full corpus paid a second whole-corpus context fold — the lane's + // dominant cost — to produce it. Tokenization is per-file and deterministic, so the + // observed refusal is identical under the restricted roots. The committed specimen is not a + // `.dag` file (its constant carries the reason), so the control's source root is built here + // — exactly the specimen, rebuilt fresh, never whatever a previous run left behind. let control_universe_file = workspace .join("target") .join("v2-native-lane") .join("controls-universe.tsv"); write_control_universe_file(&control_universe_file)?; - let control_roots = vec![MALFORMED_FIXTURE_DIR.to_string()]; + let control_root = materialize_malformed_specimen(&workspace)?; + let control_roots = vec![control_root]; let control_output = run_native_binary( &preparation.binary_path, &control_universe_file, @@ -885,7 +927,7 @@ pub fn run_required_v2_native(source_roots: &[String]) -> Result<(), String> { let malformed_control = match control_output .file_refusals .iter() - .find(|fr| fr.path.contains(MALFORMED_FIXTURE_PATH)) + .find(|fr| fr.path.contains(MALFORMED_MATERIALIZED_PATH)) { Some(fr) => { let reason = fr.fatal_reason.clone(); diff --git a/src/v1/stage0/src/emitted_closure_compile_host.rs b/src/v1/stage0/src/emitted_closure_compile_host.rs index baceb8e7a32..55b6d756591 100644 --- a/src/v1/stage0/src/emitted_closure_compile_host.rs +++ b/src/v1/stage0/src/emitted_closure_compile_host.rs @@ -553,6 +553,13 @@ fn probe_manifest(workspace: &Path, entry: &str) -> String { /// root is inferred into the workspace and would need its own `[workspace]` to escape — a manifest /// fact invented to work around its location. /// +/// The per-job temp a CI executor declares, when it declares one — the ONE read of that +/// declaration, so the required phase's refusal and the lane's selection cannot drift into two +/// spellings of the same fact. +fn declared_runner_temp() -> Option { + std::env::var_os("RUNNER_TEMP").filter(|value| !value.is_empty()) +} + /// RUNNER-SCOPED, NOT HOST-SHARED, AND THIS WAS MEASURED THE HARD WAY. A fixed path in the host's /// `/tmp` is shared by every tenant of a SELF-HOSTED runner and persists across runs, slots and /// jobs. On the first required run the directory existed owned by another uid, so creating the @@ -583,13 +590,34 @@ fn required_ci_probe_root_from_runner_temp( } pub fn required_ci_emit_compile_probe_root() -> Result { - required_ci_probe_root_from_runner_temp(std::env::var_os("RUNNER_TEMP").as_deref()) + required_ci_probe_root_from_runner_temp(declared_runner_temp().as_deref()) } pub fn local_emit_compile_probe_root() -> PathBuf { std::env::temp_dir().join(PROBE_ROOT_DIR_NAME) } +fn lane_probe_root_from_runner_temp(runner_temp: Option<&std::ffi::OsStr>) -> PathBuf { + match runner_temp.filter(|value| !value.is_empty()) { + Some(base) => PathBuf::from(base).join(PROBE_ROOT_DIR_NAME), + None => local_emit_compile_probe_root(), + } +} + +/// THE V2-NATIVE LANE'S PROBE ROOT FOLLOWS THE DECLARED EXECUTION ENVIRONMENT, SELECTED ONCE. +/// +/// The lane runs in two environments and each has its own root authority. In required CI the +/// executor declares a per-job temp: the self-hosted fleet's host-shared temp persists across +/// jobs, runs and euids, and a stale or concurrent `gunbc-emit-compile` there is an EACCES at +/// best and two runs writing one crate dir at worst (receipt: run 34471447387, +/// `EmittedCrateNotWritten — … Permission denied`). Locally no runner temp exists and the host +/// temp is the local route's authority. This is environment SELECTION, not a failure arm: both +/// roots are declared, nothing is widened, and the required phase's own stricter policy +/// (refuse without the declaration) is untouched beside it. +pub fn lane_emit_compile_probe_root() -> PathBuf { + lane_probe_root_from_runner_temp(declared_runner_temp().as_deref()) +} + fn probe_crate_dir(probe_root: &Path, entry: &str) -> PathBuf { let slug: String = entry .chars() @@ -2149,6 +2177,31 @@ mod tests { ); } + /// THE LANE SELECTS THE DECLARED PER-JOB ROOT WHEN ONE EXISTS, the host temp otherwise. + /// + /// Both arms are pinned because both are load-bearing: the first keeps a required-CI lane + /// off the self-hosted fleet's host-shared temp (the EACCES of run 34471447387), the second + /// keeps the local route runnable where no executor declares a temp. The expected values + /// name the authorities, never the spelled dir name — the single-spelling test above owns + /// that needle. + #[test] + fn the_lane_probe_root_follows_the_declared_environment() { + assert_eq!( + lane_probe_root_from_runner_temp(Some(std::ffi::OsStr::new("/runner/job"))), + required_ci_probe_root_from_runner_temp(Some(std::ffi::OsStr::new("/runner/job"))) + .expect("the same declared temp"), + "a declared per-job runner temp owns the lane's probe root, exactly as it owns the \ + required phase's" + ); + for absent in [None, Some(std::ffi::OsStr::new(""))] { + assert_eq!( + lane_probe_root_from_runner_temp(absent), + local_emit_compile_probe_root(), + "with no declared runner temp the lane takes the local route's root" + ); + } + } + /// A FAILED RESTORE MUST WIN OVER EVERY NON-TERMINAL FAULT VERDICT. /// /// The two conditions are independent and co-occur: the fault arm can be green, incomplete diff --git a/src/v2/test/v2_native_route_test.dag b/src/v2/test/v2_native_route_test.dag index 185d3b9d92f..d4b5cbe7bdd 100644 --- a/src/v2/test/v2_native_route_test.dag +++ b/src/v2/test/v2_native_route_test.dag @@ -104,7 +104,7 @@ fn receipt_over( false_control: Present { value: NativeTestReturnedFalse }, true_control: Present { value: NativeTestPassed }, malformed_control: MalformedSpecimenRefused { - path: "fixtures/native_lane_malformed/poison.dag", + path: "target/v2-native-lane/malformed-control-root/poison.dag", reason: ^tokenize_lex_e1_unterminated_string }, old_route_control: OldRouteWithdrawn { withdrawn_executable: "/seed/gunbc" } From 4adf735ca423a1697433d0fa04b6a7767283861b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 10 Sep 2026 14:23:11 +0000 Subject: [PATCH 77/89] Render rustc output in cargo_verdict_summary on a non-zero cargo The native lane interpolates only this summary on EmittedCompilerBuildFailed, so a status-only Completed arm swallowed the diagnostic the verdict already held. Co-authored-by: Cursor --- .../src/emitted_closure_compile_host.rs | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/emitted_closure_compile_host.rs b/src/v1/stage0/src/emitted_closure_compile_host.rs index 55b6d756591..610d7d611d2 100644 --- a/src/v1/stage0/src/emitted_closure_compile_host.rs +++ b/src/v1/stage0/src/emitted_closure_compile_host.rs @@ -169,11 +169,32 @@ pub fn cargo_verdict_compiled(verdict: &CargoVerdict) -> bool { matches!(verdict, CargoVerdict::Completed { status: 0, .. }) } +/// THE SUMMARY IS THE REFUSAL'S DIAGNOSTIC SURFACE. Callers that cannot compile print this string +/// and stop (`EmittedCompilerBuildFailed`, restore-failed mutation, a red baseline). Rendering +/// only `status` swallows the rustc header and stderr the verdict already holds — a completed +/// non-zero cargo then looks like a bare exit code, which is how a native-lane build refusal +/// reached the log with no error class. A zero-status run stays compact: there is nothing to +/// attribute. A non-zero run names the attributed diagnostic and line when the scan found them, +/// and always carries `stderr_tail` so an unattributed refusal (the native lane's dummy +/// attribution symbol, a kill-adjacent cargo, a fault in another module) still prints WHAT rustc +/// said. pub fn cargo_verdict_summary(verdict: &CargoVerdict) -> String { match verdict { CargoVerdict::NotAttempted { reason } => format!("NotAttempted reason={reason}"), CargoVerdict::DidNotComplete { detail } => format!("DidNotComplete detail={detail}"), - CargoVerdict::Completed { status, .. } => format!("Completed status={status}"), + CargoVerdict::Completed { status, .. } if *status == 0 => { + format!("Completed status={status}") + } + CargoVerdict::Completed { + status, + stderr_tail, + probe_line, + probe_diagnostic, + } => format!( + "Completed status={status} diagnostic={} line={} stderr_tail={stderr_tail}", + probe_diagnostic.as_deref().unwrap_or("unattributed"), + probe_line.as_deref().unwrap_or("unattributed"), + ), } } @@ -1970,6 +1991,47 @@ pub fn run_required_emit_compile( mod tests { use super::*; + /// A NON-ZERO COMPLETED VERDICT MUST NAME WHAT RUSTC SAID. The native lane's refusal + /// interpolates only this summary; a status-only render is how a cargo failure reached CI + /// as `Completed status=101` with the diagnostic sitting unread in the same verdict. + #[test] + fn cargo_verdict_summary_renders_the_diagnostic_a_non_zero_run_already_holds() { + let attributed = CargoVerdict::Completed { + status: 101, + stderr_tail: "error: could not compile `probe`".to_string(), + probe_line: Some("--> src/fixture.rs:1:1".to_string()), + probe_diagnostic: Some("error[E0308]: mismatched types".to_string()), + }; + let summary = cargo_verdict_summary(&attributed); + assert!( + summary.contains("error[E0308]: mismatched types"), + "attributed header is the class the caller is adjudicating: {summary}" + ); + assert!( + summary.contains("--> src/fixture.rs:1:1"), + "attributed location rides with the header: {summary}" + ); + let unattributed = CargoVerdict::Completed { + status: 101, + stderr_tail: "error[E0433]: failed to resolve".to_string(), + probe_line: None, + probe_diagnostic: None, + }; + let summary = cargo_verdict_summary(&unattributed); + assert!( + summary.contains("error[E0433]: failed to resolve"), + "an unattributed refusal still prints stderr_tail, which is the native lane's case: \ + {summary}" + ); + let green = CargoVerdict::Completed { + status: 0, + stderr_tail: String::new(), + probe_line: None, + probe_diagnostic: None, + }; + assert_eq!(cargo_verdict_summary(&green), "Completed status=0"); + } + /// The manifest is DERIVED, so this asserts the derivation reached the modeled rows, not a /// golden string: version-authority package header, the seed's runtime dependency set, and /// the path dependency the emitted closure links against. From 7e4dc48131c4d406ff19c08612c51760ca8b2a93 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 10 Sep 2026 14:52:44 +0000 Subject: [PATCH 78/89] Do not emit irrefutable let-else on Rc as_ref, and pass grain to cause_ownership_lookup The native cargo refusal (now rendered by cargo_verdict_summary) was irrefutable_let_patterns on v2_compiler_source_authority; the needs_rc_pattern prelude always appended else. The floor red was the door-ledger ownership witness still calling the one-argument lookup after grain became required. Co-authored-by: Cursor --- src/v1/05_emit_rust.dag | 7 ++++++- src/v1/stage0/src/v1_compiler_emit_rust.rs | 7 ++++++- .../claim/compile_door_ledger_ownership_test.dag | 12 +++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 007c2cdc2e4..68375f5c400 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -10196,7 +10196,12 @@ fn rc_pattern_preludes(pattern: MatchPattern, rc_analysis: RcPatternAnalysis, sh let inner_analysis = analyze_rc_pattern(pattern: fb_pat_here, scrut_type: "", shared_types: shared_types, emit_info: emit_info, source_indices: source_indices) if inner_analysis.needs_rc_pattern { let aware = emit_pattern_rc_aware(pattern: fb_pat_here, path_prefix: list_push(list_push([], bare_n), fb_name), rc_analysis: inner_analysis, shared_types: shared_types, scrut_type: "", source_indices: source_indices, emit_info: emit_info) - let head = concat("let ", aware, " = ", emit_ident(name: fb_name, target: Rust), ".as_ref() else { unreachable!() };") + let tail = if match_pattern_is_irrefutable(pattern: fb_pat_here) { + ";" + } else { + " else { unreachable!() };" + } + let head = concat("let ", aware, " = ", emit_ident(name: fb_name, target: Rust), ".as_ref()", tail) let inner_preludes = rc_pattern_preludes(pattern: fb_pat_here, rc_analysis: inner_analysis, shared_types: shared_types, source_indices: source_indices, emit_info: emit_info) if inner_preludes == "" { [head] } else { [concat(head, " ", inner_preludes)] } } else { diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 76e3c975a21..5dab0c9dd0d 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -21786,7 +21786,12 @@ let inner_analysis = analyze_rc_pattern(fb_pat_here.clone(), "".to_string(), sha if inner_analysis.needs_rc_pattern.clone() { { let aware = emit_pattern_rc_aware(fb_pat_here.clone(), v1_rt::rc_list_push(v1_rt::rc_list_push(Rc::new(vec![]), bare_n.clone()), fb_name.clone()), inner_analysis.clone(), shared_types.clone(), "".to_string(), source_indices.clone(), emit_info.clone()); -let head = v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("let ".to_string(), aware.clone()), " = ".to_string()), crate::v1_compiler_emit::emit_ident(fb_name.clone(), RenderTarget::Rust)), ".as_ref() else { unreachable!() };".to_string()); +let tail = if crate::v1_std_core::match_pattern_is_irrefutable(fb_pat_here.clone()) { + ";".to_string() + } else { + " else { unreachable!() };".to_string() + }; +let head = v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("let ".to_string(), aware.clone()), " = ".to_string()), crate::v1_compiler_emit::emit_ident(fb_name.clone(), RenderTarget::Rust)), ".as_ref()".to_string()), tail.clone()); let inner_preludes = rc_pattern_preludes(fb_pat_here.clone(), inner_analysis.clone(), shared_types.clone(), source_indices.clone(), emit_info.clone()); if (inner_preludes.clone() == "".to_string()) { Rc::new(vec![head.clone()]) diff --git a/src/v2/test/claim/compile_door_ledger_ownership_test.dag b/src/v2/test/claim/compile_door_ledger_ownership_test.dag index 35b3fce85a8..85aad1ddf77 100644 --- a/src/v2/test/claim/compile_door_ledger_ownership_test.dag +++ b/src/v2/test/claim/compile_door_ledger_ownership_test.dag @@ -1,5 +1,11 @@ module v2.test.compile_door_ledger_ownership +import v2.workflow.compile_door_ledger { + cause_ownership_lookup, HeadGrain, ThisLaneCalibration, SharedSelfHostCriticalPath, MigrationOwned, + affected_ledger_from_diff, AffectedDiffRefused, AffectedLedgered, ledger_refused_count +} +import v2.workflow.floor_diff_observe { NameStatusDiffFail, NameStatusDiffOk } + // Fast pure receipt (no door, no live tree) for the flip-trigger table: every frontier cause the // histogram receipts pin is attributed to its owning lane, and an unknown cause is fail-closed // UNATTRIBUTED (not silently bucketed). This is the DESIGN 5 tell that the burn-down table stays @@ -8,7 +14,7 @@ module v2.test.compile_door_ledger_ownership // witness in v2.test.long.compile_door_ledger, which pins the counts; this pins the ATTRIBUTION. fn lane_ok_this_lane(cause: Symbol) -> Bool { - match cause_ownership_lookup(cause: cause) { + match cause_ownership_lookup(cause: cause, grain: HeadGrain) { Present { value: row } => match row.lane { ThisLaneCalibration => true @@ -20,7 +26,7 @@ fn lane_ok_this_lane(cause: Symbol) -> Bool { } fn lane_ok_shared(cause: Symbol) -> Bool { - match cause_ownership_lookup(cause: cause) { + match cause_ownership_lookup(cause: cause, grain: HeadGrain) { Present { value: row } => match row.lane { ThisLaneCalibration => false @@ -32,7 +38,7 @@ fn lane_ok_shared(cause: Symbol) -> Bool { } fn lane_ok_migration(cause: Symbol) -> Bool { - match cause_ownership_lookup(cause: cause) { + match cause_ownership_lookup(cause: cause, grain: HeadGrain) { Present { value: row } => match row.lane { ThisLaneCalibration => false From e158a8c5074ddaa0a057ca5bb788e3a0eaff2d69 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 10 Sep 2026 15:18:45 +0000 Subject: [PATCH 79/89] Treat a sole-variant coproduct as irrefutable for as_ref let-else match_pattern_is_irrefutable is false for every VariantPattern, so a one-variant cause still emitted else { unreachable } and rustc refused under -D irrefutable-let-patterns. Co-authored-by: Cursor --- src/v1/05_emit_rust.dag | 30 +++++++++++++++-- src/v1/stage0/src/v1_compiler_emit_rust.rs | 38 ++++++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 68375f5c400..99865b64e63 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -10168,6 +10168,32 @@ fn collect_pattern_rc_variant_guards(pattern: MatchPattern, rc_analysis: RcPatte } } +// A let-else over `.as_ref()` is irrefutable when the pattern is structurally irrefutable +// (wildcard/bind) OR when the variant is the enum's only inhabitant. Rust treats a +// single-variant coproduct match as irrefutable (`-D irrefutable-let-patterns`); the +// dag-level `match_pattern_is_irrefutable` is false for every VariantPattern, which is how +// `DiscoveredSourceRefsDigestUnavailableCause` (one variant) emitted +// `let Cause::NoRefList {..} = cause.as_ref() else { unreachable!() }` and refused the native +// cargo under RUSTFLAGS=-D warnings. +fn rust_as_ref_let_is_irrefutable(pattern: MatchPattern, emit_info: EmitGraphInfo) -> Bool { + if match_pattern_is_irrefutable(pattern: pattern) { true } + else { + match pattern { + VariantPattern { name: n, parent_enum: parent, field_bindings: _ } => + let resolved = pattern_parent_enum(name: qualified_last_segment(name: n), parent_enum: parent, scrut_type: "", type_summaries: emit_info.type_summaries) + match resolved { + Present { value: enum_name } => + match map_get(emit_info.type_summaries, enum_name) { + Present { value: summary } => (sorted_map_keys(summary.variant_name_set) |> count) <= 1 + Absent => false + } + Absent => false + } + _ => false + } + } +} + fn rc_pattern_preludes(pattern: MatchPattern, rc_analysis: RcPatternAnalysis, shared_types: Set, source_indices: Map, emit_info: EmitGraphInfo) -> String { match pattern { VariantPattern { name: n, parent_enum: parent_enum, field_bindings: fbs } => @@ -10196,7 +10222,7 @@ fn rc_pattern_preludes(pattern: MatchPattern, rc_analysis: RcPatternAnalysis, sh let inner_analysis = analyze_rc_pattern(pattern: fb_pat_here, scrut_type: "", shared_types: shared_types, emit_info: emit_info, source_indices: source_indices) if inner_analysis.needs_rc_pattern { let aware = emit_pattern_rc_aware(pattern: fb_pat_here, path_prefix: list_push(list_push([], bare_n), fb_name), rc_analysis: inner_analysis, shared_types: shared_types, scrut_type: "", source_indices: source_indices, emit_info: emit_info) - let tail = if match_pattern_is_irrefutable(pattern: fb_pat_here) { + let tail = if rust_as_ref_let_is_irrefutable(pattern: fb_pat_here, emit_info: emit_info) { ";" } else { " else { unreachable!() };" @@ -10206,7 +10232,7 @@ fn rc_pattern_preludes(pattern: MatchPattern, rc_analysis: RcPatternAnalysis, sh if inner_preludes == "" { [head] } else { [concat(head, " ", inner_preludes)] } } else { let bound_str = emit_pattern(pattern: fb_pat_here, path_prefix: list_push(list_push([], bare_n), fb_name), shared_types: shared_types, scrut_type: "", source_indices: source_indices, emit_info: emit_info) - let tail = if match_pattern_is_irrefutable(pattern: fb_pat_here) { + let tail = if rust_as_ref_let_is_irrefutable(pattern: fb_pat_here, emit_info: emit_info) { ";" } else { " else { unreachable!() };" diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 5dab0c9dd0d..454bda51ced 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -21711,6 +21711,40 @@ Rc::new(vec![v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::conc }) } +pub fn rust_as_ref_let_is_irrefutable( + pattern: Rc, + emit_info: Rc, +) -> bool { + if crate::v1_std_core::match_pattern_is_irrefutable(pattern.clone()) { + true + } else { + match (*pattern.clone()).clone() { + MatchPattern::VariantPattern { + name: n, + parent_enum: parent, + .. + } => { + let resolved = pattern_parent_enum( + crate::v1_std_core::qualified_last_segment(n.clone()), + parent.clone(), + "".to_string(), + emit_info.type_summaries.clone(), + ); + match resolved { + Some(enum_name) => match emit_info.type_summaries.get(&enum_name).cloned() { + Some(summary) => { + v1_rt::sorted_map_keys(&summary.variant_name_set.clone()).len() <= 1 + } + None => false, + }, + None => false, + } + } + _ => false, + } + } +} + pub fn rc_pattern_preludes( pattern: Rc, rc_analysis: Rc, @@ -21786,7 +21820,7 @@ let inner_analysis = analyze_rc_pattern(fb_pat_here.clone(), "".to_string(), sha if inner_analysis.needs_rc_pattern.clone() { { let aware = emit_pattern_rc_aware(fb_pat_here.clone(), v1_rt::rc_list_push(v1_rt::rc_list_push(Rc::new(vec![]), bare_n.clone()), fb_name.clone()), inner_analysis.clone(), shared_types.clone(), "".to_string(), source_indices.clone(), emit_info.clone()); -let tail = if crate::v1_std_core::match_pattern_is_irrefutable(fb_pat_here.clone()) { +let tail = if rust_as_ref_let_is_irrefutable(fb_pat_here.clone(), emit_info.clone()) { ";".to_string() } else { " else { unreachable!() };".to_string() @@ -21802,7 +21836,7 @@ if (inner_preludes.clone() == "".to_string()) { } else { { let bound_str = emit_pattern(fb_pat_here.clone(), v1_rt::rc_list_push(v1_rt::rc_list_push(Rc::new(vec![]), bare_n.clone()), fb_name.clone()), shared_types.clone(), "".to_string(), source_indices.clone(), emit_info.clone()); -let tail = if crate::v1_std_core::match_pattern_is_irrefutable(fb_pat_here.clone()) { +let tail = if rust_as_ref_let_is_irrefutable(fb_pat_here.clone(), emit_info.clone()) { ";".to_string() } else { " else { unreachable!() };".to_string() From 0e25a0b5e9ea1ea9f99837a0eff5cfb34d82bf9d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 10 Sep 2026 15:19:52 +0000 Subject: [PATCH 80/89] Treat a sole-variant coproduct as irrefutable for as_ref let-else match_pattern_is_irrefutable is false for every VariantPattern, so a one-variant cause still emitted else { unreachable } and rustc refused under -D irrefutable-let-patterns. Co-authored-by: Cursor --- src/v1/stage0/src/v1_compiler_emit_rust.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 454bda51ced..78bbeead340 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -21733,7 +21733,7 @@ pub fn rust_as_ref_let_is_irrefutable( match resolved { Some(enum_name) => match emit_info.type_summaries.get(&enum_name).cloned() { Some(summary) => { - v1_rt::sorted_map_keys(&summary.variant_name_set.clone()).len() <= 1 + v1_rt::sorted_map_keys(summary.variant_name_set.as_ref()).len() <= 1 } None => false, }, From e292c78ab79a66046a42b94e2d9405495471e764 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 16:21:32 +0000 Subject: [PATCH 81/89] parse_lexeme_digest: fold the lexeme directly, not through chars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The branch's Char-regrounding edit routed the digest fold through chars(s:), which admits only the native compact-string realization; the lexer's repeat/delimited growth produces modeled Cons-chain strings, so every repeat-lexed token refused with "chars expects a string argument, got Variant" — ten floor witnesses BLOCKING (ingest_bridge, cross_language_add_python_to_typescript, inhabitant_neutralization_03, parse_stamp_span_index). fold_list's free-monoid read accepts every string realization and yields the same Int codepoints, so the memo content key is unchanged: witness_subject_token_stream_scope_sensitive stays green, and all ten previously-BLOCKING identities return true. Co-authored-by: Cursor --- src/v2/compiler/02_parse.dag | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/v2/compiler/02_parse.dag b/src/v2/compiler/02_parse.dag index 3b325f87da8..56e1a5060fb 100644 --- a/src/v2/compiler/02_parse.dag +++ b/src/v2/compiler/02_parse.dag @@ -591,8 +591,14 @@ fn parse_grammar_digest(validated: ValidatedGrammarRoot) -> Hash { } fn parse_lexeme_digest(lexeme: Lexeme) -> Hash { + // Fold the lexeme directly: fold_list's free-monoid read accepts every string realization + // (native compact string and Cons-chain alike) and yields the same Int codepoints either + // way, so the digest is realization-stable. Routing through chars(s:) instead refused + // Cons-chain lexemes outright — the lexer's repeat/delimited growth produces modeled + // (Cons-chain) strings, and chars admits only the native realization (measured: the + // ingest-bridge floor witnesses red with "chars expects a string argument, got Variant"). fold_list( - xs: chars(s: lexeme), + xs: lexeme, empty: symbol_identity_digest(sym: ^parse_lexeme_digest_nil), cons: fn(acc, ch) { combine_hash(a: acc, b: byte_limb_hash_peano_digest(limb: ch)) From d7162074cc96f41614499be69e2ff8fe98479900 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 10 Sep 2026 16:28:22 +0000 Subject: [PATCH 82/89] Do not Rc-wrap a diverging panic or compile_error expression The keyed-container inhabitant arm emits panic!; wrapping it in Rc::new is unreachable_code under the native lane's -D warnings. Co-authored-by: Cursor --- src/v1/05_emit_rust.dag | 13 ++++++++++++- src/v1/stage0/src/v1_compiler_emit_rust.rs | 15 +++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 99865b64e63..cacf8a49bb1 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -490,7 +490,18 @@ fn rust_type_is_rc_wrapped(type_name: String) -> Bool { } fn rust_shared_wrap_ctor(inner_expr: String) -> String { - sharing_wrap_ctor_for_target(target: Rust, inner_expr: inner_expr) + // Rc::new(panic!(...)) is unreachable_code under -D warnings: panic diverges, so the + // constructor call never returns. Native cargo of the 00_compile closure refused on + // v2_std_collection wrapping the keyed-container inhabitant panic. A diverging inner + // is already a value of every type; wrapping it is not a sharing realization. + if starts_with(s: inner_expr, prefix: "panic!(") + || starts_with(s: inner_expr, prefix: "compile_error!(") + || starts_with(s: inner_expr, prefix: "unreachable!(") + { + inner_expr + } else { + sharing_wrap_ctor_for_target(target: Rust, inner_expr: inner_expr) + } } fn rust_shared_wrap_ctor_prefix() -> String { diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 78bbeead340..a7b82d31ee6 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -817,10 +817,17 @@ pub fn rust_type_is_rc_wrapped(type_name: String) -> bool { } pub fn rust_shared_wrap_ctor(inner_expr: String) -> String { - crate::v1_compiler_languages::sharing_wrap_ctor_for_target( - RenderTarget::Rust, - inner_expr.clone(), - ) + if inner_expr.starts_with("panic!(") + || inner_expr.starts_with("compile_error!(") + || inner_expr.starts_with("unreachable!(") + { + inner_expr + } else { + crate::v1_compiler_languages::sharing_wrap_ctor_for_target( + RenderTarget::Rust, + inner_expr.clone(), + ) + } } pub fn rust_shared_wrap_ctor_prefix() -> String { From d43c69f5769af057a20795e1b0fc445a1bfbf721 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 16:42:53 +0000 Subject: [PATCH 83/89] Hoist the parse_lexeme_digest rationale to module-item grain The in-body comment block refused at emission: only module-item grain annotations are modeled (DESIGN 4c), and the v2-native lane's compile of the 00_compile closure produced six hard diagnostics, one per in-body line. Co-authored-by: Cursor --- src/v2/compiler/02_parse.dag | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/v2/compiler/02_parse.dag b/src/v2/compiler/02_parse.dag index 56e1a5060fb..f3da7bce6e7 100644 --- a/src/v2/compiler/02_parse.dag +++ b/src/v2/compiler/02_parse.dag @@ -590,13 +590,13 @@ fn parse_grammar_digest(validated: ValidatedGrammarRoot) -> Hash { ) } +// Fold the lexeme directly: fold_list's free-monoid read accepts every string realization +// (native compact string and Cons-chain alike) and yields the same Int codepoints either +// way, so the digest is realization-stable. Routing through chars(s:) instead refused +// Cons-chain lexemes outright — the lexer's repeat/delimited growth produces modeled +// (Cons-chain) strings, and chars admits only the native realization (measured: the +// ingest-bridge floor witnesses red with "chars expects a string argument, got Variant"). fn parse_lexeme_digest(lexeme: Lexeme) -> Hash { - // Fold the lexeme directly: fold_list's free-monoid read accepts every string realization - // (native compact string and Cons-chain alike) and yields the same Int codepoints either - // way, so the digest is realization-stable. Routing through chars(s:) instead refused - // Cons-chain lexemes outright — the lexer's repeat/delimited growth produces modeled - // (Cons-chain) strings, and chars admits only the native realization (measured: the - // ingest-bridge floor witnesses red with "chars expects a string argument, got Variant"). fold_list( xs: lexeme, empty: symbol_identity_digest(sym: ^parse_lexeme_digest_nil), From c451735356e19d3ea7a6aa0c1ea558e50ce5f47d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 10 Sep 2026 17:02:32 +0000 Subject: [PATCH 84/89] Hoist parse_lexeme_digest annotation to module-item grain The in-body // block is six source annotations; native emit of 00_compile refused each as unmodeled grain. Co-authored-by: Cursor --- src/v2/compiler/02_parse.dag | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/v2/compiler/02_parse.dag b/src/v2/compiler/02_parse.dag index 56e1a5060fb..f3da7bce6e7 100644 --- a/src/v2/compiler/02_parse.dag +++ b/src/v2/compiler/02_parse.dag @@ -590,13 +590,13 @@ fn parse_grammar_digest(validated: ValidatedGrammarRoot) -> Hash { ) } +// Fold the lexeme directly: fold_list's free-monoid read accepts every string realization +// (native compact string and Cons-chain alike) and yields the same Int codepoints either +// way, so the digest is realization-stable. Routing through chars(s:) instead refused +// Cons-chain lexemes outright — the lexer's repeat/delimited growth produces modeled +// (Cons-chain) strings, and chars admits only the native realization (measured: the +// ingest-bridge floor witnesses red with "chars expects a string argument, got Variant"). fn parse_lexeme_digest(lexeme: Lexeme) -> Hash { - // Fold the lexeme directly: fold_list's free-monoid read accepts every string realization - // (native compact string and Cons-chain alike) and yields the same Int codepoints either - // way, so the digest is realization-stable. Routing through chars(s:) instead refused - // Cons-chain lexemes outright — the lexer's repeat/delimited growth produces modeled - // (Cons-chain) strings, and chars admits only the native realization (measured: the - // ingest-bridge floor witnesses red with "chars expects a string argument, got Variant"). fold_list( xs: lexeme, empty: symbol_identity_digest(sym: ^parse_lexeme_digest_nil), From 25d1c7973bcf386ca96d9e9eab722417dc7bc7da Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 17:28:07 +0000 Subject: [PATCH 85/89] Ground the chars builtin across string realizations; restore chars(s:) at parse_lexeme_digest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The interpreted chars builtin admitted only the native Value::Str realization and refused modeled Empty/Cons-chain Strings, so the ten floor witnesses whose fixtures construct Token { lexeme: Empty } went red with "chars expects a string argument, got Variant". The emitted route has the converse need: fold_list cannot infer T from a host-carrier lexeme (E0282), so parse_lexeme_digest must route through the chars(s:) boundary conversion. Widen the interpreted builtin through free_monoid_to_string — the same grounding length/fold_list/chars_to_string already use — so every string realization chars() to the same Int codepoints, while a generic List, a non-codepoint chain, or an unrelated variant keeps expect_str's typed refusal (pinned by new chars_receiver_tests). With the builtin realization-agnostic, restore chars(s:) at parse_lexeme_digest: the digest is realization-stable on both routes. Verified: chars_receiver_tests green; ingest_bridge_realized and witness_subject_token_stream_scope_sensitive green interpreted (digest unchanged); v2-native lane emission green and the emitted crate builds under RUSTFLAGS="-D warnings" (E0282 cured); clippy --all-targets -D warnings and fmt --check green. Co-authored-by: briansrls --- src/v1/stage0/src/v1_interpreter.rs | 116 +++++++++++++++++++++++++++- src/v2/compiler/02_parse.dag | 13 ++-- 2 files changed, 121 insertions(+), 8 deletions(-) diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 16b54dd786c..b1905c2bd08 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -10370,7 +10370,7 @@ macro_rules! v1_algebra_method_arms { // String-straddle wall (`string_realization_straddle_detail`'s `Value::List` // exemption). Closed by regrounding `Char`/codepoint-sequence so the realization // is distinguishable (grounding root, sibling #5428). - let s = expect_str(Some(&$receiver), "chars")?; + let s = expect_str_free_monoid(Some(&$receiver), "chars")?; let items: Vec = s.chars().map(|c| Value::Int(c as i64)).collect(); Ok(list_value(items)) }, @@ -19654,6 +19654,107 @@ mod native_len_tests { } } +#[cfg(test)] +mod chars_receiver_tests { + use std::rc::Rc; + + use im::{vector as im_vec, HashMap}; + + use crate::v1_compiler_infer_emit_info::empty_emit_graph_info; + use crate::v1_compiler_infer_items::ResolvedGraph; + + use super::{ + expect_str_free_monoid, list_value, str_value, with_active_context, ExecutionMode, + InterpContext, InterpError, Value, + }; + + fn fresh_ctx() -> InterpContext { + let graph = ResolvedGraph { + modules: Rc::new(im_vec![]), + item_registry: Rc::new(HashMap::new()), + diagnostics: Rc::new(im_vec![]), + emit_graph_info: empty_emit_graph_info(), + }; + InterpContext::new(&graph, Rc::new(HashMap::new()), ExecutionMode::Hermetic) + } + + fn empty_string(ctx: &InterpContext) -> Value { + Value::Variant { + type_name: ctx.sym("String"), + variant_name: ctx.sym("Empty"), + fields: Rc::new(vec![]), + } + } + + fn cons_string(ctx: &InterpContext, text: &str) -> Value { + let mut cur = empty_string(ctx); + for cp in text.chars().rev() { + cur = Value::Variant { + type_name: ctx.sym("String"), + variant_name: ctx.sym("Cons"), + fields: Rc::new(vec![ + (ctx.sym("head"), Value::Int(cp as i64)), + (ctx.sym("tail"), cur), + ]), + }; + } + cur + } + + #[test] + fn chars_receiver_grounds_every_string_realization() { + let ctx = fresh_ctx(); + with_active_context(&ctx, || { + assert_eq!( + expect_str_free_monoid(Some(&str_value("ab")), "chars").unwrap(), + "ab" + ); + assert_eq!( + expect_str_free_monoid(Some(&empty_string(&ctx)), "chars").unwrap(), + "" + ); + assert_eq!( + expect_str_free_monoid(Some(&cons_string(&ctx, "hé")), "chars").unwrap(), + "hé" + ); + }); + } + + #[test] + fn chars_receiver_still_refuses_non_strings() { + let ctx = fresh_ctx(); + // A Cons chain whose head is not a codepoint is not a String. + let non_codepoint_chain = Value::Variant { + type_name: ctx.sym("String"), + variant_name: ctx.sym("Cons"), + fields: Rc::new(vec![ + (ctx.sym("head"), str_value("x")), + (ctx.sym("tail"), empty_string(&ctx)), + ]), + }; + // An unrelated variant is not an Empty/Cons chain at all. + let optional_some = Value::Variant { + type_name: ctx.sym("Optional"), + variant_name: ctx.sym("Some"), + fields: Rc::new(vec![(ctx.sym("value"), Value::Int(65))]), + }; + with_active_context(&ctx, || { + for v in [ + list_value(vec![Value::Int(97)]), + non_codepoint_chain, + optional_some, + Value::Int(97), + ] { + let err = expect_str_free_monoid(Some(&v), "chars").unwrap_err(); + assert!( + matches!(err, InterpError::TypeError { .. }), + "expected TypeError, got {err:?}" + ); + } + }); + } +} + // The well-known free-monoid encoding symbols. Pre-interned at context construction // (`over_scope_indexes`) so a lookup for any of them can never miss -- see // `free_monoid_ctx_syms` for why a miss must not be possible, only detected. @@ -19946,6 +20047,19 @@ fn expect_str(val: Option<&Value>, context: &str) -> InterpResult { } } +/// Like `expect_str`, but grounds a modeled String first: a String reaches a consumer in any +/// of its realizations — native `Str`, `Empty`/`Cons` codepoint chain, or a compacted mix — +/// and `free_monoid_to_string` already decides which values ARE strings (a generic +/// `Value::List` and a non-codepoint chain are not, and keep `expect_str`'s typed refusal). +/// `length`/`fold_list` already read every realization through `free_monoid_to_vec`; this is +/// the same realization-agnosticism for a consumer that needs the codepoints as one string. +fn expect_str_free_monoid(val: Option<&Value>, context: &str) -> InterpResult { + match val.and_then(free_monoid_to_string) { + Some(s) => Ok(s), + None => expect_str(val, context), + } +} + fn expect_byte_vec(val: Option<&Value>, context: &str) -> InterpResult> { match val { Some(Value::List(items)) => { diff --git a/src/v2/compiler/02_parse.dag b/src/v2/compiler/02_parse.dag index f3da7bce6e7..202ce29f2f8 100644 --- a/src/v2/compiler/02_parse.dag +++ b/src/v2/compiler/02_parse.dag @@ -590,15 +590,14 @@ fn parse_grammar_digest(validated: ValidatedGrammarRoot) -> Hash { ) } -// Fold the lexeme directly: fold_list's free-monoid read accepts every string realization -// (native compact string and Cons-chain alike) and yields the same Int codepoints either -// way, so the digest is realization-stable. Routing through chars(s:) instead refused -// Cons-chain lexemes outright — the lexer's repeat/delimited growth produces modeled -// (Cons-chain) strings, and chars admits only the native realization (measured: the -// ingest-bridge floor witnesses red with "chars expects a string argument, got Variant"). +// Route through chars(s:): the emitted realization needs the boundary conversion — emitted +// fold_list cannot infer T from a host-carrier lexeme (E0282) without it — and the +// interpreted chars builtin grounds every string realization (native compact string and +// Empty/Cons chain alike, via free_monoid_to_string) to the same Int codepoints fold_list +// itself would read, so the digest is realization-stable on both routes. fn parse_lexeme_digest(lexeme: Lexeme) -> Hash { fold_list( - xs: lexeme, + xs: chars(s: lexeme), empty: symbol_identity_digest(sym: ^parse_lexeme_digest_nil), cons: fn(acc, ch) { combine_hash(a: acc, b: byte_limb_hash_peano_digest(limb: ch)) From b674a0028294f7bcddf8806cc7eb65bd7141044b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 19:13:52 +0000 Subject: [PATCH 86/89] Hoist the rust_shared_wrap_ctor rationale to module-item grain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit d7162074cc landed its (correct) diverging-panic repair with the rationale as a four-line in-body // block, which the required-CI parse phase refuses at 05_emit_rust.dag:493-496 — source annotations are admitted at module-item grain only. The refusal cascaded: no head index, so namespace-wave-admission did not run, the floor ran degraded, and the build lane's generated-artifact phase refused with the same 4 hard diagnostics from the v2 self-compile. The comment text is unchanged, only moved above the declaration it describes. Verified with v1_src_dag_parse (the same run_dag_parse_sweep the parse phase executes): a planted unattached-annotation probe refuses naming exactly its file, and the repaired tree sweeps 5403 files parse-clean, exit 0. Co-authored-by: briansrls --- src/v1/05_emit_rust.dag | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index cacf8a49bb1..70a317abaf4 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -489,11 +489,11 @@ fn rust_type_is_rc_wrapped(type_name: String) -> Bool { sharing_type_is_wrapped_for_target(target: Rust, type_name: type_name) } +// Rc::new(panic!(...)) is unreachable_code under -D warnings: panic diverges, so the +// constructor call never returns. Native cargo of the 00_compile closure refused on +// v2_std_collection wrapping the keyed-container inhabitant panic. A diverging inner +// is already a value of every type; wrapping it is not a sharing realization. fn rust_shared_wrap_ctor(inner_expr: String) -> String { - // Rc::new(panic!(...)) is unreachable_code under -D warnings: panic diverges, so the - // constructor call never returns. Native cargo of the 00_compile closure refused on - // v2_std_collection wrapping the keyed-container inhabitant panic. A diverging inner - // is already a value of every type; wrapping it is not a sharing realization. if starts_with(s: inner_expr, prefix: "panic!(") || starts_with(s: inner_expr, prefix: "compile_error!(") || starts_with(s: inner_expr, prefix: "unreachable!(") From f188f470d153882289107d1a4418e088766bd541 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 10 Sep 2026 19:25:02 +0000 Subject: [PATCH 87/89] Hoist rust_shared_wrap_ctor rationale out of the function body Four in-body // lines refused v2 self-compile of the stage0 mirrors under the annotation grain wall. Co-authored-by: Cursor --- src/v1/05_emit_rust.dag | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index cacf8a49bb1..70a317abaf4 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -489,11 +489,11 @@ fn rust_type_is_rc_wrapped(type_name: String) -> Bool { sharing_type_is_wrapped_for_target(target: Rust, type_name: type_name) } +// Rc::new(panic!(...)) is unreachable_code under -D warnings: panic diverges, so the +// constructor call never returns. Native cargo of the 00_compile closure refused on +// v2_std_collection wrapping the keyed-container inhabitant panic. A diverging inner +// is already a value of every type; wrapping it is not a sharing realization. fn rust_shared_wrap_ctor(inner_expr: String) -> String { - // Rc::new(panic!(...)) is unreachable_code under -D warnings: panic diverges, so the - // constructor call never returns. Native cargo of the 00_compile closure refused on - // v2_std_collection wrapping the keyed-container inhabitant panic. A diverging inner - // is already a value of every type; wrapping it is not a sharing realization. if starts_with(s: inner_expr, prefix: "panic!(") || starts_with(s: inner_expr, prefix: "compile_error!(") || starts_with(s: inner_expr, prefix: "unreachable!(") From 1b302c50625f1cc4d301f92a5b6fcc34f77d7c58 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 19:25:48 +0000 Subject: [PATCH 88/89] Pin the forward behavior row count at 7 for the test_fn_decl row e8ed17102c added body_producer_forward_row_test_fn_decl (the native lane's driver evaluates test fn declarations, so they must lower through the forward producer) but did not move this receipt, which still pinned the closed row table at main's six. The table is the controlled subject universe and the sibling forward/fold surface-identity parity witness covers membership at identity grain; this row pins the count. Verified by execution: wave1_gate1_a1_forward_behavior_row_count_witness_holds returns true. Co-authored-by: briansrls --- .../wave1_gate1_a1_symbol_index_body_producer_witness_test.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/test/claim/self_host/wave1_gate1_a1_symbol_index_body_producer_witness_test.dag b/src/v2/test/claim/self_host/wave1_gate1_a1_symbol_index_body_producer_witness_test.dag index 3ce289703a9..431927c5e14 100644 --- a/src/v2/test/claim/self_host/wave1_gate1_a1_symbol_index_body_producer_witness_test.dag +++ b/src/v2/test/claim/self_host/wave1_gate1_a1_symbol_index_body_producer_witness_test.dag @@ -29,7 +29,7 @@ fn wave1_gate1_a1_domain_construction_frontier_holds() -> Bool { } fn wave1_gate1_a1_forward_behavior_row_count_holds() -> Bool { - body_lowering_forward_behavior_row_count() == 6 + body_lowering_forward_behavior_row_count() == 7 } fn wave1_gate1_a1_forward_fold_surface_identities_agree_holds() -> Bool { From f2e20168238f78063bc5771729ed857dcc596f33 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 10 Sep 2026 19:25:54 +0000 Subject: [PATCH 89/89] Enrol the v2-native lane in the workflow consolidation witnesses required_lanes_roster has carried three lanes since the v2-native lane enrolled, but w_RED_neither_lane_waits_on_the_other still pinned the two-lane split, so the floor red-carded the branch's own lane addition (returned Bool(false) on every required run since the roster grew). The witness now asserts the three-lane roster: each lane job carries no needs, and the aggregate's roster carries each of build/floor/v2-native exactly once. w_RED_lane_contexts_collide_with_no_other _emitted_workflow gains the same lane's clauses (no fleet-converge job id collision; pairwise distinct from build, floor, and the aggregate). Verified by execution: both witnesses return true; v1_src_dag_parse sweeps 5403 files clean. Co-authored-by: briansrls --- ...or_workflow_consolidation_witness_test.dag | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag b/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag index 3fc72261ca3..37df9718e0f 100644 --- a/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag +++ b/dag/test/claim/witness_floor_workflow_consolidation_witness_test.dag @@ -3,7 +3,8 @@ module test.claim.witness_floor_workflow_consolidation_witness_test import std.types { Bool, List, String } import v2.std.algebra { count_where, length, non_empty_to_list } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } -import gunbc.witness_floor_workflow { expected_witness_floor_yml, WitnessFloorGenerated, WitnessFloorGenerationRefused, build_lane_job_id, floor_lane_job_id, witness_floor_workflow_job_id, required_ci_measurement_publish_condition, required_ci_measurement_bound_steps, witness_floor_run_step, required_lanes_roster, witness_floor_lane_job_ids, build_lane_needs, witness_floor_lane_needs } +import gunbc.witness_floor_workflow { expected_witness_floor_yml, WitnessFloorGenerated, WitnessFloorGenerationRefused, build_lane_job_id, floor_lane_job_id, witness_floor_workflow_job_id, required_ci_measurement_publish_condition, required_ci_measurement_bound_steps, witness_floor_run_step, required_lanes_roster, witness_floor_lane_job_ids, build_lane_needs, witness_floor_lane_needs, v2_native_lane_needs } +import gunbc.witness_v2_native_route { required_v2_native_job_name } import gunbc.fabric_witness_run { required_ci_lane_build, required_ci_lane_witnesses } import gunbc.required_lanes_gate { required_lanes_gate_unrenderable_stmts } import gunbc.fleet_converge_workflow { fleet_converge_workflow } @@ -253,13 +254,20 @@ test fn w_RED_floor_measurement_publishes_before_separate_adjudication() -> Bool test fn w_RED_lane_contexts_collide_with_no_other_emitted_workflow() -> Bool { !any(fleet_converge_workflow.jobs, j => j.id == build_lane_job_id) && !any(fleet_converge_workflow.jobs, j => j.id == floor_lane_job_id) + && !any(fleet_converge_workflow.jobs, j => j.id == required_v2_native_job_name) && build_lane_job_id != floor_lane_job_id + && build_lane_job_id != required_v2_native_job_name && build_lane_job_id != witness_floor_workflow_job_id + && floor_lane_job_id != required_v2_native_job_name && floor_lane_job_id != witness_floor_workflow_job_id + && required_v2_native_job_name != witness_floor_workflow_job_id } -// NEITHER LANE WAITS ON THE OTHER, WHICH IS THE ENTIRE CONTENT OF "PARALLEL" -- and the -// aggregation job waits on both, which is what keeps the split fail-closed. +// NO LANE WAITS ON ANOTHER, WHICH IS THE ENTIRE CONTENT OF "PARALLEL" -- and the +// aggregation job waits on all of them, which is what keeps the split fail-closed. The +// roster was two lanes at the split (build, floor); the required v2-native lane's enrolment +// (gunbc.witness_v2_native_route) makes it three, and the witness name keeps the split's +// original identity rather than re-authoring the row. // // THE EARLIER FORM OF THIS ROW ASSERTED THAT THE FILE CONTAINED NO `needs:` AT ALL, and that // was the right claim for a workflow shape that was WRONG. Review of gunbc#9203 found it: the @@ -271,17 +279,18 @@ test fn w_RED_lane_contexts_collide_with_no_other_emitted_workflow() -> Bool { // // The claim is therefore stated at the grain that actually matters. The typed lane jobs must // carry no `needs`, while the one roster from which the aggregate derives its `needs` must carry -// exactly both lane identities. Reading those narrow producers avoids serializing the aggregate's +// exactly the three lane identities. Reading those narrow producers avoids serializing the aggregate's // large gate program merely to recover fields that existed before serialization. This remains a // strictly sharper discriminator than the old token count: it distinguishes the serialization // being forbidden from the aggregation being demanded, which "no needs anywhere" could not. // -// It is a RED that can be authored in both directions: giving either lane job a `needs` row +// It is a RED that can be authored in both directions: giving any lane job a `needs` row // fails the negatives, and dropping the aggregator's fails the positive. test fn w_RED_neither_lane_waits_on_the_other() -> Bool { length(xs: build_lane_needs()) == 0 && length(xs: witness_floor_lane_needs()) == 0 - && length(xs: non_empty_to_list(xs: required_lanes_roster())) == 2 + && length(xs: v2_native_lane_needs()) == 0 + && length(xs: non_empty_to_list(xs: required_lanes_roster())) == 3 && count_where( xs: non_empty_to_list(xs: required_lanes_roster()), predicate: fn(lane) { lane.job_id == build_lane_job_id }, @@ -290,6 +299,10 @@ test fn w_RED_neither_lane_waits_on_the_other() -> Bool { xs: non_empty_to_list(xs: required_lanes_roster()), predicate: fn(lane) { lane.job_id == floor_lane_job_id }, ) == 1 + && count_where( + xs: non_empty_to_list(xs: required_lanes_roster()), + predicate: fn(lane) { lane.job_id == required_v2_native_job_name }, + ) == 1 } // THE FABRIC LANE STILL RUNS AND NO LONGER GATES, AND THIS ROW ASSERTS BOTH HALVES.