From cf0ea968234fb63752f42b1f3e6446d1bcabe2f5 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 03:51:09 +0000 Subject: [PATCH 01/69] v2: carry the authored test marker on a DESIGN 4c channel keyed by occurrence and member Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/body_lowering_fold.dag | 6 +- src/v2/compiler/test_marker_capture.dag | 150 ++++++++++++++++++ src/v2/std/declaration_marker.dag | 40 +++++ .../claim/parse/test_marker_channel_test.dag | 123 ++++++++++++++ 4 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 src/v2/compiler/test_marker_capture.dag create mode 100644 src/v2/std/declaration_marker.dag create mode 100644 src/v2/test/claim/parse/test_marker_channel_test.dag diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index 455e6cfabb8..67a9552c333 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -2064,9 +2064,9 @@ fn body_lower_fn_decl_named_member_wrap(arrow: Node, fn_name: Symbol) -> Node { // 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. +// member node: it is authored source data under DESIGN section 4c and rides its own channel, +// v2.compiler.test_marker_capture over the same parse tree, so the semantic member stays identical +// to a plain fn's. 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) diff --git a/src/v2/compiler/test_marker_capture.dag b/src/v2/compiler/test_marker_capture.dag new file mode 100644 index 00000000000..372d1ed84c6 --- /dev/null +++ b/src/v2/compiler/test_marker_capture.dag @@ -0,0 +1,150 @@ +module v2.compiler.test_marker_capture + +import std.occurrence_identity { OccurrenceMinted, OccurrenceProjected, OccurrenceSynthetic } +import v2.compiler.parse { ParseArtifact } +import v2.extdeps.languages.dag { + dag_binding_branded_atom_identity, + parse_production_captured_child_optional, + parse_production_emitted_identity_optional +} +import v2.std.collection { Absent, List, Present, Optional } +import v2.std.algebra { list_snoc_item } +import v2.std.declaration_marker { TestMarkedDeclaration, TestMarkerChannel } +import v2.std.diagnostic { + Accepted, + Diagnostic, + ExternalContractUnknown, + Outcome, + Rejected, + Unavailable, + node_locus, + outcome_accepted, + outcome_rejected +} +import v2.std.node { Edge, Node, Symbol } +import v2.std.node_query { NamedChildAmbiguous, NamedChildFound, NamedChildMissing, named_child_lookup } + +// The capture half of the DESIGN section 4c channel for the `test` marker: a pure reader over the +// parse tree, run beside lowering and never inside it. Lowering is unchanged -- a `test fn` still +// lowers to the same named member a plain `fn` does -- so every semantic consumer is byte-identical +// by construction, and the marker survives here instead of being dropped. +// +// Every shell the capture cannot read is a typed refusal rather than a skipped row: a skipped row +// would make a marked declaration read as unmarked, which is the absent-reads-as-false class the +// channel exists to close. +// +// CONSUMER, a declared frontier: the refusal of references to test code at name resolution +// (v2.compiler.name_resolve) lands in the next change and reads this channel. Until then the only +// executing reader is v2.test.claim.parse.test_marker_channel. +fn test_marker_capture(artifact: ParseArtifact) -> Outcome { + let no_rows: List = [] + match test_marker_capture_walk(n: artifact.tree, acc: outcome_accepted(value: no_rows)) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: rows, diagnostics: d } => + Accepted { value: TestMarkerChannel { marked: rows }, diagnostics: d } + } +} + +fn test_marker_capture_walk( + n: Node, + acc: Outcome> +) -> Outcome> { + match acc { + Rejected { diagnostics: _ } => acc + Accepted { value: rows, diagnostics: d } => + match parse_production_emitted_identity_optional(node: n) { + Present { value: emitted } => + if emitted == ^dag_surface_test_fn_decl { + match test_marked_declaration(shell: n) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: row, diagnostics: _ } => + Accepted { value: list_snoc_item(xs: rows, item: row), diagnostics: d } + } + } else { + test_marker_capture_children(n: n, acc: acc) + } + Absent => test_marker_capture_children(n: n, acc: acc) + } + } +} + +fn test_marker_capture_children( + n: Node, + acc: Outcome> +) -> Outcome> { + fold(n.children, init: acc, f: fn(a, e) { test_marker_capture_walk(n: e.target, acc: a) }) +} + +fn test_marker_diagnostic(reason: Symbol, n: Node) -> Diagnostic { + Diagnostic { + reason: reason, + at: node_locus(node: n), + correction: Unavailable { reason: ExternalContractUnknown } + } +} + +fn test_marker_named_child(n: Node, name: Symbol) -> Optional { + match named_child_lookup(root: n, name: name) { + NamedChildFound { target: t } => Present { value: t } + NamedChildMissing => Absent + NamedChildAmbiguous => Absent + } +} + +// The shell is `sequence(ident marker, fn_decl | data_decl)`. Both declaration forms put the +// declared ident immediately after their leading keyword, so one reading covers both. +fn test_marked_declaration(shell: Node) -> Outcome { + match shell.occurrence_id { + OccurrenceMinted { id: id } => test_marked_declaration_at(shell: shell, id: id) + OccurrenceProjected { id: _, caused_by: _ } => + outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_shell_not_authored, n: shell)) + OccurrenceSynthetic => + outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_shell_not_authored, n: shell)) + } +} + +fn test_marked_declaration_at(shell: Node, id: std.occurrence_identity.OccurrenceId) -> Outcome { + match parse_production_captured_child_optional(node: shell) { + Absent => outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_shape_unread, n: shell)) + Present { value: captured } => + match test_marker_named_child(n: captured, name: ^grammar_sequence_left_node_projection) { + Absent => outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_shape_unread, n: shell)) + Present { value: marker_node } => + if test_marker_lexeme_is_test(marker_node: marker_node) { + match test_marker_named_child(n: captured, name: ^grammar_sequence_right_node_projection) { + Absent => outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_shape_unread, n: shell)) + Present { value: decl } => + match test_marked_declared_name(decl: decl) { + Absent => outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_name_unread, n: decl)) + Present { value: name } => + outcome_accepted(value: TestMarkedDeclaration { occurrence: id, name: name }) + } + } + } else { + outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_marker_not_test, n: marker_node)) + } + } + } +} + +fn test_marker_lexeme_is_test(marker_node: Node) -> Bool { + match dag_binding_branded_atom_identity(node: marker_node) { + Present { value: marker } => marker == ^test + Absent => false + } +} + +fn test_marked_declared_name(decl: Node) -> Optional { + match parse_production_captured_child_optional(node: decl) { + Absent => Absent + Present { value: captured } => + match test_marker_named_child(n: captured, name: ^grammar_sequence_right_node_projection) { + Absent => Absent + Present { value: after_keyword } => + match test_marker_named_child(n: after_keyword, name: ^grammar_sequence_left_node_projection) { + Absent => Absent + Present { value: ident_node } => dag_binding_branded_atom_identity(node: ident_node) + } + } + } +} diff --git a/src/v2/std/declaration_marker.dag b/src/v2/std/declaration_marker.dag new file mode 100644 index 00000000000..0e1c408050d --- /dev/null +++ b/src/v2/std/declaration_marker.dag @@ -0,0 +1,40 @@ +module v2.std.declaration_marker + +import std.occurrence_identity { OccurrenceId } +import v2.std.collection { List } +import v2.std.node { Symbol } + +// The authored `test` marker is source data of the DESIGN section 4c kind: it segregates test code +// from serving code, and it may not alter any semantic occurrence identity, semantic graph, +// resolution result or hash. So it rides this channel, beside the semantic tree and disjoint from +// it, exactly as the annotation channel rides beside AuthoredParseArtifact.semantic. v1 carries the +// same fact as v1.std.core DeclarationMarker on the Node itself; v2's Node has no field for it, and +// a structural child edge would change the content hash of every test declaration. +// +// Only marked declarations are rows. Absence is the unmarked arm, and it is decidable because the +// capture that produces this channel reads every top-level item of the module it is handed. +// +// Two keys, because they answer two questions. `occurrence` is the authored occurrence of the +// marked item and locates it. `name` is the declared member symbol, and it is the key a reference +// binds on: lowering rebuilds each declaration as a synthetic named member, so the authored +// occurrence id does not survive to resolve and cannot be the join key there. +type TestMarkedDeclaration { + occurrence: OccurrenceId + name: Symbol +} + +type TestMarkerChannel { + marked: List +} + +fn test_marker_channel_empty() -> TestMarkerChannel { + TestMarkerChannel { marked: [] } +} + +fn test_marker_channel_marks(channel: TestMarkerChannel, name: Symbol) -> Bool { + fold(channel.marked, init: false, f: fn(acc, row) { acc || (row.name == name) }) +} + +fn test_marker_channel_count(channel: TestMarkerChannel) -> Int { + fold(channel.marked, init: 0, f: fn(acc, _row) { acc + 1 }) +} diff --git a/src/v2/test/claim/parse/test_marker_channel_test.dag b/src/v2/test/claim/parse/test_marker_channel_test.dag new file mode 100644 index 00000000000..2d59eaa167f --- /dev/null +++ b/src/v2/test/claim/parse/test_marker_channel_test.dag @@ -0,0 +1,123 @@ +module v2.test.claim.parse.test_marker_channel + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE DESIGN SECTION 4c OBLIGATION, READ AT ONE INTERFACE: the `test` marker is captured on a +// channel disjoint from the semantic tree. So a `test fn` and a plain `fn` of the same name must +// DIFFER in v2.compiler.test_marker_capture and be IDENTICAL in the semantic projection, which is +// v2.compiler.normalize's tree read through content_hash (occurrence ids are not hashed, so the +// comparison is over structure alone). Both halves run the real producers -- tokenize, parse_module, +// normalize -- because the claim is about what those producers emit, not about a designed tree. +// +// The equality half is vacuous if both sides refuse or both collapse to one constant, so the +// hash-differs control below must stay red-capable: renaming the declaration moves the hash. + +fn test_marker_parsed(src: String, file: Symbol) -> Outcome { + let lm = dag_language_model() + match tokenize(text: src, file: file, rules: lm.lex) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: ts, diagnostics: _ } => parse_module(tokens: ts, grammar: lm.grammar) + } +} + +fn test_marker_capture_of(src: String, file: Symbol) -> Outcome { + match test_marker_parsed(src: src, file: file) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: artifact, diagnostics: _ } => test_marker_capture(artifact: artifact) + } +} + +fn test_marker_marks_only(src: String, file: Symbol, name: Symbol) -> Bool { + match test_marker_capture_of(src: src, file: file) { + Rejected { diagnostics: _ } => false + Accepted { value: channel, diagnostics: _ } => + (test_marker_channel_count(channel: channel) == 1) + && test_marker_channel_marks(channel: channel, name: name) + } +} + +fn test_marker_refusal_reason(src: String, file: Symbol) -> String { + match test_marker_capture_of(src: src, file: file) { + Accepted { value: _, diagnostics: _ } => "ACCEPTED" + Rejected { diagnostics: d } => symbol_lexeme(sym: d.head.reason) + } +} + +fn test_marker_semantic_hash(src: String, file: Symbol) -> Optional { + match test_marker_parsed(src: src, file: file) { + Rejected { diagnostics: _ } => Absent + Accepted { value: artifact, diagnostics: _ } => + match normalize(parse_tree: artifact.tree) { + Rejected { diagnostics: _ } => Absent + Accepted { value: normalized, diagnostics: _ } => + Present { value: content_hash(n: normalized.root) } + } + } +} + +data marked_fn_source: String = "module m.t\n\ntest fn witness_holds() -> Bool {\n true\n}\n" + +data plain_fn_source: String = "module m.t\n\nfn witness_holds() -> Bool {\n true\n}\n" + +data renamed_plain_fn_source: String = "module m.t\n\nfn other_holds() -> Bool {\n true\n}\n" + +data marked_data_source: String = "module m.t\n\ntest data witness_value: Bool = true\n" + +data mixed_source: String = "module m.t\n\nfn serving() -> Bool {\n true\n}\n\ntest fn witness_holds() -> Bool {\n true\n}\n" + +data non_test_marker_source: String = "module m.t\n\nprobe fn witness_holds() -> Bool {\n true\n}\n" + +// TREATMENT: the marker survives lowering's erasure, keyed by the declared member. +test fn test_fn_marker_is_captured_holds() -> Bool { + test_marker_marks_only(src: marked_fn_source, file: ^test_marker_fn_subject, name: ^witness_holds) +} + +// The second authored form the marker admits. +test fn test_data_marker_is_captured_holds() -> Bool { + test_marker_marks_only(src: marked_data_source, file: ^test_marker_data_subject, name: ^witness_value) +} + +// CONTROL: a module mixing serving and test code marks exactly the test declaration. +test fn only_the_marked_declaration_is_captured_holds() -> Bool { + test_marker_marks_only(src: mixed_source, file: ^test_marker_mixed_subject, name: ^witness_holds) +} + +// CONTROL: an unmarked fn is absent from the channel. +test fn plain_fn_is_not_captured_holds() -> Bool { + match test_marker_capture_of(src: plain_fn_source, file: ^test_marker_plain_subject) { + Rejected { diagnostics: _ } => false + Accepted { value: channel, diagnostics: _ } => test_marker_channel_count(channel: channel) == 0 + } +} + +// STANDING RED: an identifier in marker position that is not `test` is refused, never recorded as a +// marker and never skipped. +test fn non_test_marker_is_refused_holds() -> Bool { + test_marker_refusal_reason(src: non_test_marker_source, file: ^test_marker_non_test_subject) + == "test_marker_reason_marker_not_test" +} + +// THE 4c EQUALITY: marking a declaration leaves the semantic projection unchanged. +test fn marked_and_plain_fn_share_one_semantic_projection_holds() -> Bool { + match test_marker_semantic_hash(src: marked_fn_source, file: ^test_marker_fn_subject) { + Absent => false + Present { value: marked } => + match test_marker_semantic_hash(src: plain_fn_source, file: ^test_marker_plain_subject) { + Absent => false + Present { value: plain } => marked == plain + } + } +} + +// CONTROL for the equality: the projection does discriminate declarations, so its agreement above +// is a reading and not a constant. +test fn renamed_fn_moves_the_semantic_projection_holds() -> Bool { + match test_marker_semantic_hash(src: plain_fn_source, file: ^test_marker_plain_subject) { + Absent => false + Present { value: plain } => + match test_marker_semantic_hash(src: renamed_plain_fn_source, file: ^test_marker_renamed_subject) { + Absent => false + Present { value: renamed } => !(plain == renamed) + } + } +} From ff3a17b4a48dc7a6cfe1b706ac7416958d8a3821 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 04:49:36 +0000 Subject: [PATCH 02/69] WIP v2: refuse references to test code at name resolution Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/03_name_resolve.dag | 45 +++-- src/v2/compiler/03_normalize.dag | 8 +- src/v2/compiler/03_resolve.dag | 155 +++++++++++++++--- src/v2/compiler/normalized_tree.dag | 23 ++- src/v2/compiler/test_marker_capture.dag | 10 +- src/v2/std/declaration_marker.dag | 82 ++++++++- .../arrow_body_form_witness_test.dag | 3 +- ...transform_binary_infix_witness_helpers.dag | 3 +- .../wave1_gate1_normalize_add_helpers.dag | 5 +- .../claim/claim_pipeline/resolve_test.dag | 3 +- .../long/door_real_module_probe_test.dag | 4 +- .../body_lowering_normalize_add_test.dag | 5 +- .../program_assembly_multi_file_test.dag | 6 +- .../manual/resolve_compile_anchor_test.dag | 11 +- .../admission_fail_closed_test.dag | 3 +- .../malformed_imported_root_test.dag | 3 +- .../test_code_reference_wall_test.dag | 137 ++++++++++++++++ ...ame_resolve_cross_tree_resolution_test.dag | 10 +- .../claim/normalized_tree_admission_test.dag | 14 +- .../parse/parse_binding_fidelity_test.dag | 9 +- .../claim/parse/test_marker_channel_test.dag | 2 +- .../resolve/namespace_only_policy_test.dag | 6 +- ...alified_module_projection_resolve_test.dag | 6 +- 23 files changed, 473 insertions(+), 80 deletions(-) create mode 100644 src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 456d04ea4ca..b79fdf69938 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -4,6 +4,7 @@ import v2.std.language_model { LanguageModel } import v2.compiler.symbol_index_fill { symbol_index_fill_module_roots } import v2.compiler.normalized_tree { NormalizedTree, normalized_tree_roots_to_nodes } +import v2.std.declaration_marker { TestCodeIndex, test_code_index_add_module, test_code_index_empty } import v2.std.symbol_index { SymbolIndex, empty_symbol_index } import v2.compiler.resolve { DeclaredBinding, @@ -102,9 +103,12 @@ type Admission { // Shared by every subject admitted against the same language and root collection. // The Outcome stays with the caller so validation diagnostics retain their original // per-subject refusal boundary when the context is constructed before admission. +// `test_code` is every root's test-marker channel keyed by the paths resolution binds to, built in +// the same pass that names the roots, so no root can be resolvable while its markers are unread. type ValidatedModuleRoots { roots: FreeMonoid by_name: Map + test_code: TestCodeIndex } type ResolutionContext { @@ -147,7 +151,7 @@ type AdmissionState | AdmissionRejected { diagnostics: NonEmptyDiagnostics } type ModuleRootValidateState - = ModuleRootsValid { seen: Map, diagnostics: Diagnostics } + = ModuleRootsValid { seen: Map, test_code: TestCodeIndex, diagnostics: Diagnostics } | ModuleRootsDuplicate { diagnostics: Diagnostics } | ModuleRootsProjectionRejected { diagnostics: NonEmptyDiagnostics } @@ -225,14 +229,14 @@ fn admit_export_symbol( fn validate_module_roots(roots: FreeMonoid) -> Outcome { match fold_list( xs: roots, - empty: ModuleRootsValid { seen: empty_map(), diagnostics: v2.std.diagnostic.None }, + empty: ModuleRootsValid { seen: empty_map(), test_code: test_code_index_empty(), diagnostics: v2.std.diagnostic.None }, cons: fn(acc, root) { match acc { ModuleRootsDuplicate { diagnostics: d } => ModuleRootsDuplicate { diagnostics: d } ModuleRootsProjectionRejected { diagnostics: d } => ModuleRootsProjectionRejected { diagnostics: d } - ModuleRootsValid { seen: prior, diagnostics: acc_diag } => + ModuleRootsValid { seen: prior, test_code: prior_test_code, diagnostics: acc_diag } => if well_formed(n: root.root) { match qualified_name_from_module_node(root: root.root) { Rejected { diagnostics: d } => @@ -245,6 +249,11 @@ fn validate_module_roots(roots: FreeMonoid) -> Outcome ModuleRootsValid { seen: map_insert(m: prior, key: name, value: root), + test_code: test_code_index_add_module( + index: prior_test_code, + module_qn: name, + channel: root.test_markers + ), diagnostics: merged_diag } Present { value: _ } => @@ -262,8 +271,8 @@ fn validate_module_roots(roots: FreeMonoid) -> Outcome - Accepted { value: ValidatedModuleRoots { roots: roots, by_name: by_name }, diagnostics: d } + ModuleRootsValid { seen: by_name, test_code: test_code, diagnostics: d } => + Accepted { value: ValidatedModuleRoots { roots: roots, by_name: by_name, test_code: test_code }, diagnostics: d } ModuleRootsDuplicate { diagnostics: d } => Rejected { diagnostics: rejected_with_pending( @@ -561,7 +570,10 @@ fn admission_namespace_base(lm: LanguageModel) -> Namespace { bindings: empty_map(), canonical_symbols: dag_language_model_canonical_symbols(lm: lm), symbol_index: empty_symbol_index(), - module_qn: Empty + module_qn: Empty, + test_code: test_code_index_empty(), + declared_in: Empty, + imported_origins: empty_map() } } @@ -601,7 +613,9 @@ fn admitted_subject_from_bindings( base: Namespace, subject_module_root: NormalizedTree, symbol_index: SymbolIndex, - module_qn: QualifiedName + module_qn: QualifiedName, + origins: Map, + test_code: TestCodeIndex ) -> AdmittedSubject { AdmittedSubject { namespace: Namespace { @@ -611,7 +625,10 @@ fn admitted_subject_from_bindings( ), canonical_symbols: base.canonical_symbols, symbol_index: symbol_index, - module_qn: module_qn + module_qn: module_qn, + test_code: test_code, + declared_in: module_qn, + imported_origins: origins }, subject_root: subject_module_root } @@ -677,14 +694,16 @@ fn namespace_for_subject_root( Rejected { diagnostics: rejected_with_pending(pending: validate_diag, rejected: d) } - AdmissionAccepted { bindings: b, origins: _ } => + AdmissionAccepted { bindings: b, origins: o } => Accepted { value: admitted_subject_from_bindings( bindings: b, base: base, subject_module_root: subject_module_root, symbol_index: shared.symbol_index, - module_qn: admission.subject.name + module_qn: admission.subject.name, + origins: o, + test_code: shared.roots.test_code ), diagnostics: validate_diag } @@ -698,14 +717,16 @@ fn namespace_for_subject_root( index: index, active_roots: active_roots ) { - AdmissionAccepted { bindings: b, origins: _ } => + AdmissionAccepted { bindings: b, origins: o } => Accepted { value: admitted_subject_from_bindings( bindings: b, base: base, subject_module_root: subject_module_root, symbol_index: shared.symbol_index, - module_qn: admission.subject.name + module_qn: admission.subject.name, + origins: o, + test_code: shared.roots.test_code ), diagnostics: validate_diag } diff --git a/src/v2/compiler/03_normalize.dag b/src/v2/compiler/03_normalize.dag index dec642c9a77..16e92d6be19 100644 --- a/src/v2/compiler/03_normalize.dag +++ b/src/v2/compiler/03_normalize.dag @@ -38,6 +38,7 @@ import v2.compiler.body_lowering_fold { body_lower_is_deferred_lower_at_normalize } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } +import v2.compiler.test_marker_capture { test_marker_capture } import v2.compiler.namespace_graft { module_header_containment_graft, namespace_graft_is_graft_candidate @@ -217,7 +218,12 @@ fn normalize(parse_tree: ParseTree) -> Outcome { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: grafted, diagnostics: d1 } => if well_formed(n: grafted) { - admit_normalized_tree(root: grafted, diagnostics: d1) + match test_marker_capture(tree: parse_tree) { + Rejected { diagnostics: r } => + Rejected { diagnostics: rejected_with_pending(pending: d1, rejected: r) } + Accepted { value: markers, diagnostics: _ } => + admit_normalized_tree(root: grafted, test_markers: markers, diagnostics: d1) + } } else { Rejected { diagnostics: rejected_with_pending( diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 09dcc66785b..bd49e696a3f 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -2,6 +2,15 @@ module v2.compiler.resolve import v2.std.language_model { LanguageModel } import v2.compiler.normalized_tree { NormalizedTree } +import v2.std.declaration_marker { + ReachesNoTestCode, + ReachesTestMarkedDeclaration, + ReachesTestModuleFromServingCode, + TestCodeIndex, + test_code_index_add_module, + test_code_index_empty, + test_code_reach +} import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { @@ -10,7 +19,8 @@ import v2.extdeps.languages.dag { dag_node_is_module_root_conj, dag_resolve_preserve_module_metadata_subtree, dag_surface_module_header_metadata_edge, - dag_surface_operator_canonicalization_lookup + dag_surface_operator_canonicalization_lookup, + qualified_name_from_module_node } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { length, list_snoc_item } @@ -19,7 +29,8 @@ import v2.std.collection { PointwisePower, empty_map, map_get, - map_insert + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -32,6 +43,7 @@ import v2.std.compilers.target_model { target_model_canonical_operation_wire_nod import v2.std.diagnostic { Correction, Diagnostic, + Diagnostics, node_locus, Outcome, UserInputBoundary, @@ -84,11 +96,19 @@ import std.occurrence_identity { NodeOccurrenceIdentity, OccurrenceId } type ResolvedTree = Node +// `test_code`, `declared_in` and `imported_origins` exist for one decision: whether a reference binds +// to test code (owner ruling 2026-09-16/17). Root-scope bindings come from exactly two sources: the +// subject's own declarations, which live under `declared_in`, and admitted imports, whose origin +// module `imported_origins` records. So a root hit is judged by the same qualified path a +// symbol-index hit answers with. type Namespace { bindings: Map canonical_symbols: PointwisePower symbol_index: SymbolIndex module_qn: QualifiedName + test_code: TestCodeIndex + declared_in: QualifiedName + imported_origins: Map } type Scope @@ -123,7 +143,10 @@ fn empty_namespace() -> Namespace { bindings: empty_map(), canonical_symbols: empty_canonical_symbol_set(), symbol_index: empty_symbol_index(), - module_qn: Empty + module_qn: Empty, + test_code: test_code_index_empty(), + declared_in: Empty, + imported_origins: empty_map() } } @@ -229,14 +252,33 @@ fn add_bind_atom_binder(n: Node, acc: Map) -> Map Namespace { + let bindings = namespace_bindings_augment_from_module_atoms( + root: tree.root, + bindings: empty_map() + ) + let declared_in = namespace_tree_module_qn(tree: tree) Namespace { - bindings: namespace_bindings_augment_from_module_atoms( - root: tree.root, - bindings: empty_map() - ), + bindings: bindings, canonical_symbols: dag_language_model_canonical_symbols(lm: lm), symbol_index: empty_symbol_index(), - module_qn: Empty + module_qn: Empty, + test_code: test_code_index_add_module( + index: test_code_index_empty(), + module_qn: declared_in, + channel: tree.test_markers + ), + declared_in: declared_in, + imported_origins: empty_map() + } +} + +// A single-tree namespace has no module path of its own (module_qn stays Empty, as before), but its +// test-code index still needs the path its declarations live under. A root that names no module +// keys them under Empty, which is the same key both sides of the lookup use. +fn namespace_tree_module_qn(tree: NormalizedTree) -> QualifiedName { + match qualified_name_from_module_node(root: tree.root) { + Accepted { value: qn, diagnostics: _ } => qn + Rejected { diagnostics: _ } => Empty } } @@ -383,7 +425,7 @@ fn symbol_index_node_identity(node: Node, fallback: Symbol) -> Symbol { } type SymbolIndexAtomLookup - = SymbolIndexAtomHit { canonical: Symbol } + = SymbolIndexAtomHit { canonical: Symbol, path: QualifiedName } | SymbolIndexAtomAmbiguous | SymbolIndexAtomUnbound @@ -396,9 +438,10 @@ fn lookup_symbol_index_atom_identity( position: symbol_index_lookup_position(ctx: ctx), name: name ) { - LexicalHit { node: node, path: _ } => + LexicalHit { node: node, path: path } => SymbolIndexAtomHit { - canonical: symbol_index_node_identity(node: node, fallback: name) + canonical: symbol_index_node_identity(node: node, fallback: name), + path: path } LexicalAmbiguous { candidates: _ } => SymbolIndexAtomAmbiguous LexicalUnbound => @@ -408,9 +451,10 @@ fn lookup_symbol_index_atom_identity( index: ctx.namespace.symbol_index, name: name ) { - GlobalBareHit { node: node, path: _ } => + GlobalBareHit { node: node, path: path } => SymbolIndexAtomHit { - canonical: symbol_index_node_identity(node: node, fallback: name) + canonical: symbol_index_node_identity(node: node, fallback: name), + path: path } GlobalBareLookupAmbiguous => SymbolIndexAtomAmbiguous GlobalBareLookupUnbound => SymbolIndexAtomUnbound @@ -456,6 +500,68 @@ fn try_resolve_qualified_name_node(ctx: ResolveContext, n: Node) -> Optional Bool { + match s { + ScopeFrame { locals: m, outer: o } => + match map_lookup(m: m, key: name) { + Present { value: _ } => true + Absent => scope_frames_bind(s: o, name: name) + } + ScopeRoot { module: _ } => false + } +} + +fn root_binding_origin(namespace: Namespace, name: Symbol) -> QualifiedName { + match map_lookup(m: namespace.imported_origins, key: name) { + Present { value: origin } => origin + Absent => namespace.declared_in + } +} + +fn test_code_referenced_diagnostic(reason: Symbol, n: Node) -> Diagnostic { + Diagnostic { + reason: reason, + at: node_locus(node: n), + correction: Unavailable { reason: UserInputBoundary } + } +} + +// THE WALL (owner ruling 2026-09-16/17): `test` segregates test code from serving code. No code may +// reference a test-marked declaration, a test fn in the same module included, and serving code may +// not depend on any declaration of a module that declares tests. Both are decided here, where the +// reference binds, from the path it bound to, and both refuse with the reference's own locus. +fn resolve_atom_bound( + ctx: ResolveContext, + n: Node, + canonical: Symbol, + path: QualifiedName, + pending: Diagnostics +) -> Outcome { + match test_code_reach(index: ctx.namespace.test_code, subject: ctx.namespace.declared_in, path: path) { + ReachesNoTestCode => + Accepted { + value: canonical_atom(identity: canonical, occurrence_id: n.occurrence_id), + diagnostics: pending + } + ReachesTestMarkedDeclaration => + Rejected { + diagnostics: rejected_with_pending( + pending: pending, + rejected: diagnostics_singleton(d: test_code_referenced_diagnostic(reason: ^resolve_reason_test_code_referenced, n: n)) + ) + } + ReachesTestModuleFromServingCode => + Rejected { + diagnostics: rejected_with_pending( + pending: pending, + rejected: diagnostics_singleton(d: test_code_referenced_diagnostic(reason: ^resolve_reason_serving_code_depends_on_test_code, n: n)) + ) + } + } +} + fn resolve_atom(ctx: ResolveContext, n: Node, identity: Symbol) -> Outcome { if dag_node_is_int_literal_atom(node: n) { Accepted { value: n, diagnostics: None } @@ -463,17 +569,24 @@ fn resolve_atom(ctx: ResolveContext, n: Node, identity: Symbol) -> Outcome match lookup_chain(s: ctx.scope, name: identity) { Accepted { value: found, diagnostics: pending } => match found { Present { value: canonical } => - Accepted { - value: canonical_atom(identity: canonical, occurrence_id: n.occurrence_id), - diagnostics: pending + if scope_frames_bind(s: ctx.scope, name: identity) { + Accepted { + value: canonical_atom(identity: canonical, occurrence_id: n.occurrence_id), + diagnostics: pending + } + } else { + resolve_atom_bound( + ctx: ctx, + n: n, + canonical: canonical, + path: qualified_name_snoc(qn: root_binding_origin(namespace: ctx.namespace, name: identity), segment: identity), + pending: pending + ) } Absent => match lookup_symbol_index_atom_identity(ctx: ctx, name: identity) { - SymbolIndexAtomHit { canonical: canonical } => - Accepted { - value: canonical_atom(identity: canonical, occurrence_id: n.occurrence_id), - diagnostics: pending - } + SymbolIndexAtomHit { canonical: canonical, path: path } => + resolve_atom_bound(ctx: ctx, n: n, canonical: canonical, path: path, pending: pending) SymbolIndexAtomAmbiguous => Rejected { diagnostics: rejected_with_pending( diff --git a/src/v2/compiler/normalized_tree.dag b/src/v2/compiler/normalized_tree.dag index 7ceb60c2183..1b8d9b436a8 100644 --- a/src/v2/compiler/normalized_tree.dag +++ b/src/v2/compiler/normalized_tree.dag @@ -17,12 +17,18 @@ import v2.std.diagnostic { import std.algebra { Empty, FreeMonoid } import v2.std.algebra { fold_list, list_snoc_item } import v2.std.node { Node } +import v2.std.declaration_marker { TestMarkerChannel, test_marker_channel_empty } // A NormalizedTree is the ADMITTED product of normalize: a root whose own lowering left no wrapper // behind. It was an alias for Node until BL-1, which is why retention evidence reached resolve as a // bare Node and was dropped there. Sealing the carrier makes the drop unwritable. +// +// `test_markers` is the DESIGN section 4c channel for the authored `test` marker, sealed beside the +// root rather than inside it: `root` is byte-identical for a `test fn` and a plain `fn`, and every +// consumer about Node structure reads `root` alone. Name resolution is the channel's consumer. type NormalizedTree sole_constructor { root: Node, + test_markers: TestMarkerChannel, } // Rung: ACCEPTED REFINEMENT WITH EXECUTING REFUSAL, not structural impossibility. The invalid state @@ -39,7 +45,11 @@ fn normalized_tree_retention_diagnostic(root: Node) -> Diagnostic { } } -fn admit_normalized_tree(root: Node, diagnostics: Diagnostics) -> Outcome { +fn admit_normalized_tree( + root: Node, + test_markers: TestMarkerChannel, + diagnostics: Diagnostics +) -> Outcome { if body_lowering_diagnostics_carry_wrapper_retention(d: diagnostics) { Rejected { diagnostics: rejected_with_pending( @@ -48,7 +58,7 @@ fn admit_normalized_tree(root: Node, diagnostics: Diagnostics) -> Outcome) -> FreeMono ) } +// THE UNMARKED DOOR, AND ITS NAME SAYS SO. Its roots arrive as already-lowered Nodes, from which the +// authored `test` marker cannot be recovered (lowering erases it by design), so every root is admitted +// with an empty marker channel. That is a statement about the caller's input, not a default: a caller +// holding source goes through v2.compiler.normalize, which captures the channel from the parse tree. +// // The plural door: the same decision per element, refusing as a whole so a partially admitted list // has no representation. // @@ -71,7 +86,7 @@ fn normalized_tree_roots_to_nodes(roots: FreeMonoid) -> FreeMono // would accumulate N+1 copies of one set. The decision is still per element (the same verdict each // time, reading only the shared diagnostics), the refusal carries its pending set exactly once via // admit_normalized_tree, and the accepted diagnostics attach once at the end. -fn admit_normalized_roots( +fn admit_unmarked_normalized_roots( roots: FreeMonoid, diagnostics: Diagnostics ) -> Outcome> { @@ -82,7 +97,7 @@ fn admit_normalized_roots( match acc { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: admitted, diagnostics: _ } => - match admit_normalized_tree(root: root, diagnostics: diagnostics) { + match admit_normalized_tree(root: root, test_markers: test_marker_channel_empty(), diagnostics: diagnostics) { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: one, diagnostics: _ } => Accepted { diff --git a/src/v2/compiler/test_marker_capture.dag b/src/v2/compiler/test_marker_capture.dag index 372d1ed84c6..fc9d6ff2bd3 100644 --- a/src/v2/compiler/test_marker_capture.dag +++ b/src/v2/compiler/test_marker_capture.dag @@ -1,7 +1,6 @@ module v2.compiler.test_marker_capture import std.occurrence_identity { OccurrenceMinted, OccurrenceProjected, OccurrenceSynthetic } -import v2.compiler.parse { ParseArtifact } import v2.extdeps.languages.dag { dag_binding_branded_atom_identity, parse_production_captured_child_optional, @@ -33,12 +32,11 @@ import v2.std.node_query { NamedChildAmbiguous, NamedChildFound, NamedChildMissi // would make a marked declaration read as unmarked, which is the absent-reads-as-false class the // channel exists to close. // -// CONSUMER, a declared frontier: the refusal of references to test code at name resolution -// (v2.compiler.name_resolve) lands in the next change and reads this channel. Until then the only -// executing reader is v2.test.claim.parse.test_marker_channel. -fn test_marker_capture(artifact: ParseArtifact) -> Outcome { +// CONSUMER: v2.compiler.normalize runs it over the same parse tree it lowers and seals the result +// beside the root in NormalizedTree, where name resolution reads it. +fn test_marker_capture(tree: Node) -> Outcome { let no_rows: List = [] - match test_marker_capture_walk(n: artifact.tree, acc: outcome_accepted(value: no_rows)) { + match test_marker_capture_walk(n: tree, acc: outcome_accepted(value: no_rows)) { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: rows, diagnostics: d } => Accepted { value: TestMarkerChannel { marked: rows }, diagnostics: d } diff --git a/src/v2/std/declaration_marker.dag b/src/v2/std/declaration_marker.dag index 0e1c408050d..5cc86a10429 100644 --- a/src/v2/std/declaration_marker.dag +++ b/src/v2/std/declaration_marker.dag @@ -1,8 +1,11 @@ module v2.std.declaration_marker import std.occurrence_identity { OccurrenceId } -import v2.std.collection { List } +import std.algebra { Empty } +import v2.std.algebra { fold_list, list_snoc_item } +import v2.std.collection { Absent, List, Map, Present, empty_map, map_insert, map_lookup } import v2.std.node { Symbol } +import v2.std.qualified_name { QualifiedName, qualified_name_snoc } // The authored `test` marker is source data of the DESIGN section 4c kind: it segregates test code // from serving code, and it may not alter any semantic occurrence identity, semantic graph, @@ -38,3 +41,80 @@ fn test_marker_channel_marks(channel: TestMarkerChannel, name: Symbol) -> Bool { fn test_marker_channel_count(channel: TestMarkerChannel) -> Int { fold(channel.marked, init: 0, f: fn(acc, _row) { acc + 1 }) } + +// THE RESOLUTION-SIDE READING OF THE CHANNEL: the compilation's marked declarations as the qualified +// paths a reference binds to, plus the modules that declare any test code. Both keys are the paths +// v2.compiler.symbol_index_fill indexes (module path, then one segment per named member), so a +// binding answered by the symbol index is judged by the path it answered with and nothing else. +type TestCodeIndex { + marked_paths: Map + test_modules: Map +} + +// Why a reference is refused. A marked declaration may be referenced by nothing, a test fn in the +// same module included. Any other member of a module that declares tests is test code too, and only +// another test module may depend on it. +type TestCodeReach + = ReachesNoTestCode + | ReachesTestMarkedDeclaration + | ReachesTestModuleFromServingCode + +fn test_code_index_empty() -> TestCodeIndex { + TestCodeIndex { marked_paths: empty_map(), test_modules: empty_map() } +} + +fn test_code_index_add_module( + index: TestCodeIndex, + module_qn: QualifiedName, + channel: TestMarkerChannel +) -> TestCodeIndex { + if test_marker_channel_count(channel: channel) == 0 { + index + } else { + TestCodeIndex { + marked_paths: fold(channel.marked, init: index.marked_paths, f: fn(acc, row) { + map_insert(m: acc, key: qualified_name_snoc(qn: module_qn, segment: row.name), value: true) + }), + test_modules: map_insert(m: index.test_modules, key: module_qn, value: true) + } + } +} + +fn test_code_index_declares_tests(index: TestCodeIndex, module_qn: QualifiedName) -> Bool { + match map_lookup(m: index.test_modules, key: module_qn) { + Present { value: _ } => true + Absent => false + } +} + +type TestModulePrefixWalk { + prefix: QualifiedName + found: Bool +} + +fn test_code_index_path_in_test_module(index: TestCodeIndex, path: QualifiedName) -> Bool { + let walk = fold_list(xs: path, empty: TestModulePrefixWalk { prefix: Empty, found: false }, cons: fn(acc, segment) { + if acc.found { + acc + } else { + let next = list_snoc_item(xs: acc.prefix, item: segment) + TestModulePrefixWalk { prefix: next, found: test_code_index_declares_tests(index: index, module_qn: next) } + } + }) + walk.found +} + +// `subject` is the module the reference is written in. +fn test_code_reach(index: TestCodeIndex, subject: QualifiedName, path: QualifiedName) -> TestCodeReach { + match map_lookup(m: index.marked_paths, key: path) { + Present { value: _ } => ReachesTestMarkedDeclaration + Absent => + if test_code_index_declares_tests(index: index, module_qn: subject) { + ReachesNoTestCode + } else if test_code_index_path_in_test_module(index: index, path: path) { + ReachesTestModuleFromServingCode + } else { + ReachesNoTestCode + } + } +} diff --git a/src/v2/test/claim/body_lowering/arrow_body_form_witness_test.dag b/src/v2/test/claim/body_lowering/arrow_body_form_witness_test.dag index e134244d021..4e1769e5bd8 100644 --- a/src/v2/test/claim/body_lowering/arrow_body_form_witness_test.dag +++ b/src/v2/test/claim/body_lowering/arrow_body_form_witness_test.dag @@ -1,6 +1,7 @@ module v2.test.claim.body_lowering.arrow_body_form_witness import std.occurrence_identity { OccurrenceSynthetic } +import v2.std.declaration_marker { test_marker_channel_empty } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } import v2.compiler.resolve { resolve } import v2.extdeps.languages.dag { @@ -123,7 +124,7 @@ fn arrow_body_form_zero_arrow_fixture() -> Optional { // arm FAILS the witness rather than skipping it — an unadmittable fixture is a defect in // the fixture, not a reason to report green. fn abf_admitted_add_arrow_tree() -> Outcome { - admit_normalized_tree(root: dag_add_arrow_with_body_node(), diagnostics: None) + admit_normalized_tree(test_markers: test_marker_channel_empty(), root: dag_add_arrow_with_body_node(), diagnostics: None) } test fn arrow_body_form_eval_value_vertical_holds() -> Bool { diff --git a/src/v2/test/claim/body_lowering/infer_transform_binary_infix_witness_helpers.dag b/src/v2/test/claim/body_lowering/infer_transform_binary_infix_witness_helpers.dag index aa9e655551b..e75deaa29f6 100644 --- a/src/v2/test/claim/body_lowering/infer_transform_binary_infix_witness_helpers.dag +++ b/src/v2/test/claim/body_lowering/infer_transform_binary_infix_witness_helpers.dag @@ -6,6 +6,7 @@ import v2.compiler.infer { inferred_facts_witness_for_node } import v2.compiler.resolve { resolve } +import v2.std.declaration_marker { test_marker_channel_empty } import v2.compiler.normalized_tree { admit_normalized_tree } import v2.extdeps.languages.dag { dag_int_literal_fixture_one, @@ -52,7 +53,7 @@ fn infer_transform_witness_resolved_add_arrow() -> Optional { match wave1_gate1_normalized_add_module() { Absent => Absent Present { value: tree } => - match admit_normalized_tree(root: tree, diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: tree, diagnostics: None) { Rejected { diagnostics: _ } => Absent Accepted { value: normalized, diagnostics: _ } => match resolve(tree: normalized, lm: dag_language_model()) { diff --git a/src/v2/test/claim/body_lowering/wave1_gate1_normalize_add_helpers.dag b/src/v2/test/claim/body_lowering/wave1_gate1_normalize_add_helpers.dag index 532f224790a..1cd49e41e97 100644 --- a/src/v2/test/claim/body_lowering/wave1_gate1_normalize_add_helpers.dag +++ b/src/v2/test/claim/body_lowering/wave1_gate1_normalize_add_helpers.dag @@ -1,6 +1,7 @@ module v2.test.claim.body_lowering.wave1_gate1_normalize_add_helpers import v2.compiler.normalize { normalize } +import v2.std.declaration_marker { test_marker_channel_empty } import v2.compiler.normalized_tree { admit_normalized_tree } import v2.compiler.parse { parse_module } import v2.compiler.resolve { resolve } @@ -65,7 +66,7 @@ fn wave1_gate1_normalized_add_resolves_holds() -> Bool { match wave1_gate1_normalized_add_module() { Absent => false Present { value: tree } => - match admit_normalized_tree(root: tree, diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: tree, diagnostics: None) { Rejected { diagnostics: _ } => false Accepted { value: normalized, diagnostics: _ } => match resolve(tree: normalized, lm: dag_language_model()) { @@ -100,7 +101,7 @@ fn wave1_gate1_resolved_add_arrow_transform_holds() -> Bool { match wave1_gate1_normalized_add_module() { Absent => false Present { value: tree } => - match admit_normalized_tree(root: tree, diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: tree, diagnostics: None) { Rejected { diagnostics: _ } => false Accepted { value: normalized, diagnostics: _ } => match resolve(tree: normalized, lm: dag_language_model()) { diff --git a/src/v2/test/claim/claim_pipeline/resolve_test.dag b/src/v2/test/claim/claim_pipeline/resolve_test.dag index b656571cde9..3d8a46b157b 100644 --- a/src/v2/test/claim/claim_pipeline/resolve_test.dag +++ b/src/v2/test/claim/claim_pipeline/resolve_test.dag @@ -2,6 +2,7 @@ module v2.test.claim_pipeline.resolve import std.occurrence_identity { OccurrenceSynthetic } import v2.compiler.normalize { normalize } +import v2.std.declaration_marker { test_marker_channel_empty } import v2.compiler.normalized_tree { admit_normalized_tree } import v2.compiler.resolve { resolve } import v2.extdeps.languages.dag { @@ -67,7 +68,7 @@ data spine_resolve_reject_module_subject: Node = Node { // self-bound every Atom node anywhere in the tree -- including this // reference -- so it resolved without ever consulting canonical_symbols. test fn spine_resolve_rejects_noncanonical_root_atom() -> Bool { - match admit_normalized_tree(root: spine_resolve_reject_module_subject, diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: spine_resolve_reject_module_subject, diagnostics: None) { Accepted { value: normalized, diagnostics: _ } => match resolve(tree: normalized, lm: dag_language_model_surface_empty_prelude()) { Accepted { value: _, diagnostics: _ } => false diff --git a/src/v2/test/claim/long/door_real_module_probe_test.dag b/src/v2/test/claim/long/door_real_module_probe_test.dag index 0f7d41fc9ee..348cafe9cbc 100644 --- a/src/v2/test/claim/long/door_real_module_probe_test.dag +++ b/src/v2/test/claim/long/door_real_module_probe_test.dag @@ -1,6 +1,6 @@ module v2.test.long.door_real_module_probe -import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_roots } +import v2.compiler.normalized_tree { NormalizedTree, admit_unmarked_normalized_roots } import v2.std.diagnostic { Accepted, None, Outcome, Rejected } import v2.compiler.name_resolve { resolve_with_admission_policy } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } @@ -83,7 +83,7 @@ fn quad_subject_with_import_use() -> Node { } fn quad_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: quad_subject_with_import_use(), tail: Cons { diff --git a/src/v2/test/claim/manual/body_lowering_normalize_add_test.dag b/src/v2/test/claim/manual/body_lowering_normalize_add_test.dag index 1e7b1574481..2329bbc9b3a 100644 --- a/src/v2/test/claim/manual/body_lowering_normalize_add_test.dag +++ b/src/v2/test/claim/manual/body_lowering_normalize_add_test.dag @@ -8,6 +8,7 @@ import v2.compiler.body_lowering_fold { body_lower_try_infix_transform, body_lower_unwrap_captured } +import v2.std.declaration_marker { test_marker_channel_empty } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } import v2.compiler.normalize { normalize } import v2.compiler.resolve { resolve } @@ -359,7 +360,7 @@ fn body_lowering_tree_contains_atom(root: Node, wanted: Symbol) -> Bool { test fn body_lowering_fixture_resolves() -> Bool { let lm = dag_language_model() - match admit_normalized_tree(root: dag_add_arrow_with_body_fixture, diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: dag_add_arrow_with_body_fixture, diagnostics: None) { Rejected { diagnostics: _ } => false Accepted { value: admitted, diagnostics: _ } => match resolve(tree: admitted, lm: lm) { @@ -729,7 +730,7 @@ test fn body_lowering_normalized_arrow_root_resolves() -> Bool { match body_lower_find_arrow_in_module(root: tree.root) { Absent => false Present { value: arrow } => - match admit_normalized_tree(root: arrow, diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: arrow, diagnostics: None) { Rejected { diagnostics: _ } => false Accepted { value: admitted_arrow, diagnostics: _ } => match resolve(tree: admitted_arrow, lm: lm) { diff --git a/src/v2/test/claim/manual/program_assembly_multi_file_test.dag b/src/v2/test/claim/manual/program_assembly_multi_file_test.dag index 92b7c052008..30f0829b0a3 100644 --- a/src/v2/test/claim/manual/program_assembly_multi_file_test.dag +++ b/src/v2/test/claim/manual/program_assembly_multi_file_test.dag @@ -1,7 +1,7 @@ module v2.test.manual.program_assembly_multi_file import std.occurrence_identity { OccurrenceSynthetic } -import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_roots } +import v2.compiler.normalized_tree { NormalizedTree, admit_unmarked_normalized_roots } import extdeps.communication.medium { Lossless, Medium } import v2.compiler.name_resolve { Admission, @@ -115,7 +115,7 @@ fn pa_subject_with_import_use() -> Node { } fn pa_cross_file_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: pa_graft_module_root(root: pa_subject_with_import_use()), tail: Cons { @@ -134,7 +134,7 @@ fn pa_cross_file_roots() -> Outcome> { } fn pa_duplicate_module_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: pa_graft_module_root( root: pa_module_root( diff --git a/src/v2/test/claim/manual/resolve_compile_anchor_test.dag b/src/v2/test/claim/manual/resolve_compile_anchor_test.dag index eb560955492..97181837627 100644 --- a/src/v2/test/claim/manual/resolve_compile_anchor_test.dag +++ b/src/v2/test/claim/manual/resolve_compile_anchor_test.dag @@ -1,7 +1,8 @@ module v2.test.manual.resolve_compile_anchor import std.occurrence_identity { OccurrenceSynthetic } -import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_roots, admit_normalized_tree } +import v2.std.declaration_marker { test_marker_channel_empty } +import v2.compiler.normalized_tree { NormalizedTree, admit_unmarked_normalized_roots, admit_normalized_tree } import v2.compiler.name_resolve { Admission, Import, @@ -60,7 +61,7 @@ fn service_atom_conj_tree() -> Node { } fn anchor_resolve_service_atom_via_canonical_symbols() -> Outcome { - match admit_normalized_tree(root: service_atom_conj_tree(), diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: service_atom_conj_tree(), diagnostics: None) { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: tree, diagnostics: _ } => resolve(tree: tree, lm: dag_language_model_void()) @@ -68,7 +69,7 @@ fn anchor_resolve_service_atom_via_canonical_symbols() -> Outcome } fn anchor_resolve_empty_prelude_service_atom() -> Outcome { - match admit_normalized_tree(root: service_atom_conj_tree(), diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: service_atom_conj_tree(), diagnostics: None) { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: tree, diagnostics: _ } => resolve(tree: tree, lm: dag_language_model_surface_empty_prelude()) @@ -77,6 +78,7 @@ fn anchor_resolve_empty_prelude_service_atom() -> Outcome { fn anchor_resolve_multi_edge_conj_via_canonical_symbols() -> Outcome { match admit_normalized_tree( + test_markers: test_marker_channel_empty(), root: Node { kind: TypeNode { connective: Conj }, children: [ @@ -112,6 +114,7 @@ fn anchor_resolve_multi_edge_conj_via_canonical_symbols() -> Outcome Outcome { match admit_normalized_tree( + test_markers: test_marker_channel_empty(), root: Node { kind: TypeNode { connective: Conj }, children: [ @@ -260,7 +263,7 @@ fn resolve_subject_name() -> QualifiedName { } fn resolve_roots_single_import() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: resolve_subject_module_root_with_import_use(), tail: Cons { diff --git a/src/v2/test/claim/name_resolve/admission_fail_closed_test.dag b/src/v2/test/claim/name_resolve/admission_fail_closed_test.dag index 96f9dc5c01e..7eaf3d8390a 100644 --- a/src/v2/test/claim/name_resolve/admission_fail_closed_test.dag +++ b/src/v2/test/claim/name_resolve/admission_fail_closed_test.dag @@ -18,6 +18,7 @@ import v2.compiler.name_resolve { module_root_find, validate_module_roots } +import v2.std.declaration_marker { test_marker_channel_empty } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } import v2.std.cross_tree.resolution { source_root_index_empty } import v2.extdeps.languages.dag { @@ -127,7 +128,7 @@ fn fc_with_roots( } fn fc_admitted(n: Node) -> Outcome { - admit_normalized_tree(root: n, diagnostics: None) + admit_normalized_tree(test_markers: test_marker_channel_empty(), root: n, diagnostics: None) } fn fc_duplicate_roots() -> Outcome> { diff --git a/src/v2/test/claim/name_resolve/malformed_imported_root_test.dag b/src/v2/test/claim/name_resolve/malformed_imported_root_test.dag index 983108aac81..81714c0706c 100644 --- a/src/v2/test/claim/name_resolve/malformed_imported_root_test.dag +++ b/src/v2/test/claim/name_resolve/malformed_imported_root_test.dag @@ -9,6 +9,7 @@ import v2.compiler.name_resolve { ImportVisible, admit_import_entry } +import v2.std.declaration_marker { test_marker_channel_empty } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } import v2.std.cross_tree.resolution { source_root_index_empty } import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } @@ -63,7 +64,7 @@ fn malformed_entry_name() -> QualifiedName { } fn roots_with_malformed_entry() -> Outcome> { - bind_outcome(o: admit_normalized_tree(root: malformed_root_node(), diagnostics: None), f: fn(a) { + bind_outcome(o: admit_normalized_tree(test_markers: test_marker_channel_empty(), root: malformed_root_node(), diagnostics: None), f: fn(a) { outcome_accepted(value: Cons { head: a, tail: Empty }) }) } diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag new file mode 100644 index 00000000000..5941ecb7a45 --- /dev/null +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -0,0 +1,137 @@ +module v2.test.name_resolve.test_code_reference_wall + +import std.algebra { Cons, Empty, FreeMonoid } +import v2.compiler.name_resolve { Admission, ResolutionSubject, resolve_with_admission } +import v2.compiler.normalized_tree { NormalizedTree } +import v2.extdeps.languages.dag { qualified_name_from_module_node } +import v2.std.compilers.lexing { symbol_lexeme } +import v2.std.diagnostic { Accepted, Outcome, Rejected } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE WALL AT ONE INTERFACE: name resolution, handed modules the real front end produced from +// source (tokenize, parse_module, normalize -- which is also where the `test` marker is captured +// onto NormalizedTree). Each claim reports the head refusal reason or ACCEPTED, so a red that +// refuses for some OTHER reason (an unbound name, a malformed root) does not pass for the wall. +// +// The owner ruling (2026-09-16/17) has two halves, and each has a red and a control here: +// a test-marked declaration may be referenced by NOTHING, a test fn in the same module included; +// and serving code may not depend on any declaration of a module that declares tests, while +// another test module may. + +fn wall_normalized(src: String, file: Symbol) -> Outcome { + let lm = dag_language_model() + match tokenize(text: src, file: file, rules: lm.lex) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: ts, diagnostics: _ } => + match parse_module(tokens: ts, grammar: lm.grammar) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: artifact, diagnostics: _ } => normalize(parse_tree: artifact.tree) + } + } +} + +fn wall_verdict(outcome: Outcome) -> String { + match outcome { + Accepted { value: _, diagnostics: _ } => "ACCEPTED" + Rejected { diagnostics: d } => symbol_lexeme(sym: d.head.reason) + } +} + +// Resolves `subject_src` with `peer_src` beside it as the only other root. +fn wall_resolve_with_peer(subject_src: String, peer_src: String) -> String { + match wall_normalized(src: subject_src, file: ^wall_subject_file) { + Rejected { diagnostics: _ } => "SUBJECT_REFUSED_BEFORE_RESOLVE" + Accepted { value: subject, diagnostics: _ } => + match wall_normalized(src: peer_src, file: ^wall_peer_file) { + Rejected { diagnostics: _ } => "PEER_REFUSED_BEFORE_RESOLVE" + Accepted { value: peer, diagnostics: _ } => + match qualified_name_from_module_node(root: subject.root) { + Rejected { diagnostics: _ } => "SUBJECT_UNNAMED" + Accepted { value: subject_qn, diagnostics: _ } => + wall_verdict(outcome: resolve_with_admission( + lm: dag_language_model(), + roots: Cons { head: subject, tail: Cons { head: peer, tail: Empty } }, + admission: Admission { + subject: ResolutionSubject { name: subject_qn }, + imports: Empty + } + )) + } + } + } +} + +// The single-tree door, v2.compiler.resolve resolve, which builds its namespace from the tree alone. +fn wall_resolve_alone(src: String) -> String { + match wall_normalized(src: src, file: ^wall_alone_file) { + Rejected { diagnostics: _ } => "SUBJECT_REFUSED_BEFORE_RESOLVE" + Accepted { value: tree, diagnostics: _ } => wall_verdict(outcome: resolve(tree: tree, lm: dag_language_model())) + } +} + +data test_calls_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool {\n true\n}\n\ntest fn rollup() -> Bool {\n leaf()\n}\n" + +data serving_calls_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool {\n true\n}\n\nfn helper() -> Bool {\n leaf()\n}\n" + +data test_calls_helper_source: String = "module wall.same\n\nfn helper() -> Bool {\n true\n}\n\ntest fn uses_helper() -> Bool {\n helper()\n}\n" + +data local_shadows_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool {\n true\n}\n\nfn helper(leaf: Bool) -> Bool {\n leaf\n}\n" + +data peer_test_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool {\n true\n}\n\ntest fn peer_leaf() -> Bool {\n shared_fixture()\n}\n" + +data peer_serving_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool {\n true\n}\n" + +data serving_reaches_peer_test_fn_source: String = "module wall.serving\n\nfn uses_peer() -> Bool {\n peer_leaf()\n}\n" + +data serving_reaches_peer_test_module_source: String = "module wall.serving\n\nfn uses_peer() -> Bool {\n shared_fixture()\n}\n" + +data test_module_reaches_peer_test_module_source: String = "module wall.consumer\n\nfn local() -> Bool {\n true\n}\n\ntest fn consumer_holds() -> Bool {\n shared_fixture() && local()\n}\n" + +// RED: a test fn calling another test fn in its own module. This is the case the ruling names +// explicitly, and it refuses on both resolution doors. +test fn test_fn_calling_a_test_fn_is_refused_holds() -> Bool { + (wall_resolve_alone(src: test_calls_test_source) == "resolve_reason_test_code_referenced") + && (wall_resolve_with_peer(subject_src: test_calls_test_source, peer_src: peer_serving_module_source) + == "resolve_reason_test_code_referenced") +} + +// RED: serving code calling a test fn in its own module. +test fn serving_fn_calling_a_test_fn_is_refused_holds() -> Bool { + wall_resolve_alone(src: serving_calls_test_source) == "resolve_reason_test_code_referenced" +} + +// RED: serving code calling a test fn another module declares. +test fn serving_code_calling_another_modules_test_fn_is_refused_holds() -> Bool { + wall_resolve_with_peer(subject_src: serving_reaches_peer_test_fn_source, peer_src: peer_test_module_source) + == "resolve_reason_test_code_referenced" +} + +// RED: serving code depending on an UNMARKED declaration of a module that declares tests. +test fn serving_code_depending_on_a_test_module_is_refused_holds() -> Bool { + wall_resolve_with_peer(subject_src: serving_reaches_peer_test_module_source, peer_src: peer_test_module_source) + == "resolve_reason_serving_code_depends_on_test_code" +} + +// CONTROL: the intended shape, a test fn calling an ordinary fn, resolves. +test fn test_fn_calling_an_ordinary_fn_resolves_holds() -> Bool { + wall_resolve_alone(src: test_calls_helper_source) == "ACCEPTED" +} + +// CONTROL: a local binder that shares a test fn's name binds to the local, not to the test fn. +test fn a_local_binder_shadowing_a_test_fn_resolves_holds() -> Bool { + wall_resolve_alone(src: local_shadows_test_source) == "ACCEPTED" +} + +// CONTROL for the dependency half: the same reference from the same serving module resolves once the +// peer declares no tests, so the red above is about the peer's test code and nothing else. +test fn serving_code_depending_on_a_serving_module_resolves_holds() -> Bool { + wall_resolve_with_peer(subject_src: serving_reaches_peer_test_module_source, peer_src: peer_serving_module_source) + == "ACCEPTED" +} + +// CONTROL: a test module may depend on another test module's ordinary declarations. +test fn test_module_depending_on_a_test_module_resolves_holds() -> Bool { + wall_resolve_with_peer(subject_src: test_module_reaches_peer_test_module_source, peer_src: peer_test_module_source) + == "ACCEPTED" +} diff --git a/src/v2/test/claim/name_resolve_cross_tree_resolution_test.dag b/src/v2/test/claim/name_resolve_cross_tree_resolution_test.dag index ee00f6d73f8..90a6b9b4b0e 100644 --- a/src/v2/test/claim/name_resolve_cross_tree_resolution_test.dag +++ b/src/v2/test/claim/name_resolve_cross_tree_resolution_test.dag @@ -1,7 +1,7 @@ module v2.test.claim.name_resolve_cross_tree_resolution import std.occurrence_identity { OccurrenceSynthetic } -import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_roots } +import v2.compiler.normalized_tree { NormalizedTree, admit_unmarked_normalized_roots } import v2.compiler.name_resolve { Admission, Import, @@ -157,7 +157,7 @@ fn ct_both_trees() -> SourceRootSet { } fn ct_dag_same_tree_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: ct_graft_module_root( root: ct_subject_with_import_use( @@ -195,7 +195,7 @@ fn ct_dag_same_tree_admission() -> Admission { } fn ct_v2_same_tree_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: ct_graft_module_root( root: ct_subject_with_import_use( @@ -237,7 +237,7 @@ fn ct_v2_same_tree_admission() -> Admission { } fn ct_cross_tree_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: ct_graft_module_root( root: ct_subject_with_import_use( @@ -291,7 +291,7 @@ fn ct_v2_to_dag_index() -> SourceRootIndex { } fn ct_dag_to_v2_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: ct_graft_module_root( root: ct_subject_with_import_use( diff --git a/src/v2/test/claim/normalized_tree_admission_test.dag b/src/v2/test/claim/normalized_tree_admission_test.dag index 29382c69bfa..dfeb62841f4 100644 --- a/src/v2/test/claim/normalized_tree_admission_test.dag +++ b/src/v2/test/claim/normalized_tree_admission_test.dag @@ -2,7 +2,8 @@ module v2.test.claim.normalized_tree_admission import v2.compiler.body_lowering_fold { body_lower_wrapper_retained_shell } import std.algebra { Cons, Empty, FreeMonoid } -import v2.compiler.normalized_tree { admit_normalized_roots, admit_normalized_tree } +import v2.std.declaration_marker { test_marker_channel_empty } +import v2.compiler.normalized_tree { admit_unmarked_normalized_roots, admit_normalized_tree } import v2.std.diagnostic { Accepted, Diagnostic, @@ -56,6 +57,7 @@ fn is_rejected(o: Outcome) -> Bool { test fn a_retained_root_is_refused_at_the_carrier_door() -> Bool { is_rejected(o: admit_normalized_tree( + test_markers: test_marker_channel_empty(), root: probe_shell(), diagnostics: Some { diagnostics: NonEmptyDiagnostics { head: retention_diagnostic(), tail: [] } @@ -65,6 +67,7 @@ test fn a_retained_root_is_refused_at_the_carrier_door() -> Bool { test fn retention_is_found_past_the_diagnostic_head_RED() -> Bool { is_rejected(o: admit_normalized_tree( + test_markers: test_marker_channel_empty(), root: probe_shell(), diagnostics: Some { diagnostics: NonEmptyDiagnostics { @@ -76,11 +79,12 @@ test fn retention_is_found_past_the_diagnostic_head_RED() -> Bool { } test fn a_clean_root_is_admitted_positive_control() -> Bool { - (is_rejected(o: admit_normalized_tree(root: probe_shell(), diagnostics: None)) == false) + (is_rejected(o: admit_normalized_tree(test_markers: test_marker_channel_empty(), root: probe_shell(), diagnostics: None)) == false) } test fn an_unrelated_diagnostic_does_not_refuse_RED() -> Bool { (is_rejected(o: admit_normalized_tree( + test_markers: test_marker_channel_empty(), root: probe_shell(), diagnostics: Some { diagnostics: NonEmptyDiagnostics { head: unrelated_diagnostic(), tail: [] } @@ -90,7 +94,7 @@ test fn an_unrelated_diagnostic_does_not_refuse_RED() -> Bool { test fn the_production_retention_producer_is_refused_by_the_door() -> Bool { match body_lower_wrapper_retained_shell(shell: probe_shell()) { - Accepted { value: v, diagnostics: d } => is_rejected(o: admit_normalized_tree(root: v, diagnostics: d)) + Accepted { value: v, diagnostics: d } => is_rejected(o: admit_normalized_tree(test_markers: test_marker_channel_empty(), root: v, diagnostics: d)) Rejected { diagnostics: _ } => false } } @@ -100,7 +104,7 @@ fn two_probe_roots() -> FreeMonoid { } test fn plural_admission_does_not_duplicate_shared_diagnostics_RED() -> Bool { - match admit_normalized_roots( + match admit_unmarked_normalized_roots( roots: two_probe_roots(), diagnostics: Some { diagnostics: NonEmptyDiagnostics { head: unrelated_diagnostic(), tail: [] } @@ -118,7 +122,7 @@ test fn plural_admission_does_not_duplicate_shared_diagnostics_RED() -> Bool { } test fn plural_admission_refuses_a_retained_batch() -> Bool { - match admit_normalized_roots( + match admit_unmarked_normalized_roots( roots: two_probe_roots(), diagnostics: Some { diagnostics: NonEmptyDiagnostics { head: retention_diagnostic(), tail: [] } diff --git a/src/v2/test/claim/parse/parse_binding_fidelity_test.dag b/src/v2/test/claim/parse/parse_binding_fidelity_test.dag index 80421802907..264f6f60924 100644 --- a/src/v2/test/claim/parse/parse_binding_fidelity_test.dag +++ b/src/v2/test/claim/parse/parse_binding_fidelity_test.dag @@ -1,7 +1,8 @@ module v2.test.parse.parse_binding_fidelity import std.occurrence_identity { OccurrenceSynthetic } -import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_roots, admit_normalized_tree } +import v2.std.declaration_marker { test_marker_channel_empty } +import v2.compiler.normalized_tree { NormalizedTree, admit_unmarked_normalized_roots, admit_normalized_tree } import v2.compiler.name_resolve { Admission, Import, @@ -135,7 +136,7 @@ fn pbf_subject_with_import_use() -> Node { } fn pbf_cross_file_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: pbf_graft_module_root(root: pbf_subject_with_import_use()), tail: Cons { @@ -200,7 +201,7 @@ fn pbf_unbound_atom_tree() -> Node { } fn pbf_resolve_unbound_symbol_rejects() -> Bool { - match admit_normalized_tree(root: pbf_unbound_atom_tree(), diagnostics: None) { + match admit_normalized_tree(test_markers: test_marker_channel_empty(), root: pbf_unbound_atom_tree(), diagnostics: None) { Rejected { diagnostics: _ } => false Accepted { value: admitted_tree, diagnostics: _ } => match resolve( @@ -239,7 +240,7 @@ fn pbf_subject_with_ambiguous_import_use() -> Node { } fn pbf_ambiguous_import_roots() -> Outcome> { - admit_normalized_roots( + admit_unmarked_normalized_roots( roots: Cons { head: pbf_graft_module_root(root: pbf_subject_with_ambiguous_import_use()), tail: Cons { diff --git a/src/v2/test/claim/parse/test_marker_channel_test.dag b/src/v2/test/claim/parse/test_marker_channel_test.dag index 2d59eaa167f..67999317a54 100644 --- a/src/v2/test/claim/parse/test_marker_channel_test.dag +++ b/src/v2/test/claim/parse/test_marker_channel_test.dag @@ -23,7 +23,7 @@ fn test_marker_parsed(src: String, file: Symbol) -> Outcome { fn test_marker_capture_of(src: String, file: Symbol) -> Outcome { match test_marker_parsed(src: src, file: file) { Rejected { diagnostics: d } => Rejected { diagnostics: d } - Accepted { value: artifact, diagnostics: _ } => test_marker_capture(artifact: artifact) + Accepted { value: artifact, diagnostics: _ } => test_marker_capture(tree: artifact.tree) } } diff --git a/src/v2/test/claim/resolve/namespace_only_policy_test.dag b/src/v2/test/claim/resolve/namespace_only_policy_test.dag index 6c1d2ba7d3d..c130cd1496f 100644 --- a/src/v2/test/claim/resolve/namespace_only_policy_test.dag +++ b/src/v2/test/claim/resolve/namespace_only_policy_test.dag @@ -1,6 +1,7 @@ module v2.test.resolve.namespace_only_policy import std.occurrence_identity { OccurrenceSynthetic } +import v2.std.declaration_marker { test_code_index_empty } import v2.compiler.resolve { ResolveContext, Namespace, @@ -44,7 +45,10 @@ fn policy_pilot_namespace_with_index( bindings: empty_map(), canonical_symbols: empty_canonical_symbol_set(), symbol_index: symbol_index, - module_qn: module_qn + module_qn: module_qn, + test_code: test_code_index_empty(), + declared_in: module_qn, + imported_origins: empty_map() } } diff --git a/src/v2/test/claim/resolve/qualified_module_projection_resolve_test.dag b/src/v2/test/claim/resolve/qualified_module_projection_resolve_test.dag index f6d454294a9..93eb3b5c7d3 100644 --- a/src/v2/test/claim/resolve/qualified_module_projection_resolve_test.dag +++ b/src/v2/test/claim/resolve/qualified_module_projection_resolve_test.dag @@ -1,6 +1,7 @@ module v2.test.resolve.qualified_module_projection_resolve import std.occurrence_identity { OccurrenceSynthetic } +import v2.std.declaration_marker { test_code_index_empty } import v2.compiler.resolve { Namespace, ResolveContext, @@ -96,7 +97,10 @@ fn qmpr_namespace(index: SymbolIndex) -> Namespace { bindings: empty_map(), canonical_symbols: empty_canonical_symbol_set(), symbol_index: index, - module_qn: qmpr_consumer_qn + module_qn: qmpr_consumer_qn, + test_code: test_code_index_empty(), + declared_in: qmpr_consumer_qn, + imported_origins: empty_map() } } From da171d29e244f49550f438d8f7341b53d9f6a8ec Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 06:41:13 +0000 Subject: [PATCH 03/69] Measure the 4c equality at the lowering interface with one parse, inside the new-witness eval-step budget Co-Authored-By: Claude Opus 5 (1M context) --- .../claim/parse/test_marker_channel_test.dag | 161 ++++++++++++++---- 1 file changed, 130 insertions(+), 31 deletions(-) diff --git a/src/v2/test/claim/parse/test_marker_channel_test.dag b/src/v2/test/claim/parse/test_marker_channel_test.dag index 2d59eaa167f..632737c0594 100644 --- a/src/v2/test/claim/parse/test_marker_channel_test.dag +++ b/src/v2/test/claim/parse/test_marker_channel_test.dag @@ -3,14 +3,19 @@ module v2.test.claim.parse.test_marker_channel data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // THE DESIGN SECTION 4c OBLIGATION, READ AT ONE INTERFACE: the `test` marker is captured on a -// channel disjoint from the semantic tree. So a `test fn` and a plain `fn` of the same name must -// DIFFER in v2.compiler.test_marker_capture and be IDENTICAL in the semantic projection, which is -// v2.compiler.normalize's tree read through content_hash (occurrence ids are not hashed, so the -// comparison is over structure alone). Both halves run the real producers -- tokenize, parse_module, -// normalize -- because the claim is about what those producers emit, not about a designed tree. +// channel disjoint from the semantic tree. So a marked declaration must DIFFER from an unmarked one in +// v2.compiler.test_marker_capture and be IDENTICAL to it in the semantic projection. The capture half +// runs tokenize, parse_module and the capture. The projection half runs v2.compiler.normalize +// normalize_node over the real parse of one marked declaration twice -- on the marked shell and on +// the declaration inside it -- and compares the two through content_hash (occurrence ids are not +// hashed). That is the marker's whole contribution to lowering, measured at the lowering interface +// with ONE parse, because a second parse and normalize put the claim over the floor's new-witness +// eval-step budget. It uses the `test data` form: `test fn` and `test data` share one production and +// one lowering arm (v2.compiler.body_lowering_fold body_lower_test_fn_decl_to_member), and the data +// form is the one that fits the budget. // // The equality half is vacuous if both sides refuse or both collapse to one constant, so the -// hash-differs control below must stay red-capable: renaming the declaration moves the hash. +// hash-differs control below must stay red-capable: a different declaration moves the hash. fn test_marker_parsed(src: String, file: Symbol) -> Outcome { let lm = dag_language_model() @@ -43,24 +48,125 @@ fn test_marker_refusal_reason(src: String, file: Symbol) -> String { } } -fn test_marker_semantic_hash(src: String, file: Symbol) -> Optional { - match test_marker_parsed(src: src, file: file) { - Rejected { diagnostics: _ } => Absent +fn test_marker_find_emitted(n: Node, emitted: Symbol) -> Optional { + match parse_production_emitted_identity_optional(node: n) { + Present { value: e } => + if e == emitted { optional_present(value: n) } else { test_marker_find_emitted_under(n: n, emitted: emitted) } + Absent => test_marker_find_emitted_under(n: n, emitted: emitted) + } +} + +fn test_marker_find_emitted_under(n: Node, emitted: Symbol) -> Optional { + fold(n.children, init: optional_absent(), f: fn(acc, e) { + match acc { + Present { value: _ } => acc + Absent => test_marker_find_emitted(n: e.target, emitted: emitted) + } + }) +} + +fn test_marker_lowered_hash(n: Node) -> Optional { + match normalize_node(n: n) { + Rejected { diagnostics: _ } => optional_absent() + Accepted { value: lowered, diagnostics: _ } => optional_present(value: content_hash(n: lowered)) + } +} + +// Both lowered hashes compared, or Absent when either side refused -- so a refusal can never read as +// a match. +fn test_marker_lowered_pair_agrees(left: Node, right: Node) -> Optional { + match test_marker_lowered_hash(n: left) { + Absent => optional_absent() + Present { value: l } => + match test_marker_lowered_hash(n: right) { + Absent => optional_absent() + Present { value: r } => optional_present(value: l == r) + } + } +} + +// The declaration inside the marked shell: the right projection of its `sequence(marker, decl)`. +fn test_marker_inner_declaration(shell: Node) -> Optional { + match parse_production_captured_child_optional(node: shell) { + Absent => optional_absent() + Present { value: captured } => + match named_child_lookup(root: captured, name: ^grammar_sequence_right_node_projection) { + NamedChildFound { target: t } => optional_present(value: t) + NamedChildMissing => optional_absent() + NamedChildAmbiguous => optional_absent() + } + } +} + +// The marked shell of `marked_projection_source` against the declaration inside it. +fn test_marker_shell_agrees_with_its_declaration() -> Optional { + match test_marker_parsed(src: marked_projection_source, file: ^test_marker_projection_subject) { + Rejected { diagnostics: _ } => optional_absent() Accepted { value: artifact, diagnostics: _ } => - match normalize(parse_tree: artifact.tree) { - Rejected { diagnostics: _ } => Absent - Accepted { value: normalized, diagnostics: _ } => - Present { value: content_hash(n: normalized.root) } + match test_marker_find_emitted(n: artifact.tree, emitted: ^dag_surface_test_fn_decl) { + Absent => optional_absent() + Present { value: shell } => + match test_marker_inner_declaration(shell: shell) { + Absent => optional_absent() + Present { value: inner } => test_marker_lowered_pair_agrees(left: shell, right: inner) + } } } } +// The first two data declarations in source order, gathered in one walk. +type TestMarkerDeclarationPair { + first: Optional + second: Optional +} + +fn test_marker_gather_data_decls(n: Node, acc: TestMarkerDeclarationPair) -> TestMarkerDeclarationPair { + match acc.second { + Present { value: _ } => acc + Absent => + match parse_production_emitted_identity_optional(node: n) { + Present { value: id } => + if id == ^dag_surface_data_decl { + match acc.first { + Absent => TestMarkerDeclarationPair { first: optional_present(value: n), second: optional_absent() } + Present { value: _ } => TestMarkerDeclarationPair { first: acc.first, second: optional_present(value: n) } + } + } else { + fold(n.children, init: acc, f: fn(a, e) { test_marker_gather_data_decls(n: e.target, acc: a) }) + } + Absent => fold(n.children, init: acc, f: fn(a, e) { test_marker_gather_data_decls(n: e.target, acc: a) }) + } + } +} + +// The two unmarked declarations of `distinct_projection_source` against each other. +fn test_marker_distinct_declarations_agree() -> Optional { + match test_marker_parsed(src: distinct_projection_source, file: ^test_marker_distinct_subject) { + Rejected { diagnostics: _ } => optional_absent() + Accepted { value: artifact, diagnostics: _ } => + let pair = test_marker_gather_data_decls( + n: artifact.tree, + acc: TestMarkerDeclarationPair { first: optional_absent(), second: optional_absent() } + ) + match pair.first { + Absent => optional_absent() + Present { value: first } => + match pair.second { + Absent => optional_absent() + Present { value: second } => test_marker_lowered_pair_agrees(left: first, right: second) + } + } + } +} + +data marked_projection_source: String = "module m\n\ntest data w: Bool = true\n" + +data distinct_projection_source: String = "module m\ndata v: Int = 0\ndata w: Int = 1\n" + data marked_fn_source: String = "module m.t\n\ntest fn witness_holds() -> Bool {\n true\n}\n" data plain_fn_source: String = "module m.t\n\nfn witness_holds() -> Bool {\n true\n}\n" -data renamed_plain_fn_source: String = "module m.t\n\nfn other_holds() -> Bool {\n true\n}\n" - data marked_data_source: String = "module m.t\n\ntest data witness_value: Bool = true\n" data mixed_source: String = "module m.t\n\nfn serving() -> Bool {\n true\n}\n\ntest fn witness_holds() -> Bool {\n true\n}\n" @@ -97,27 +203,20 @@ test fn non_test_marker_is_refused_holds() -> Bool { == "test_marker_reason_marker_not_test" } -// THE 4c EQUALITY: marking a declaration leaves the semantic projection unchanged. -test fn marked_and_plain_fn_share_one_semantic_projection_holds() -> Bool { - match test_marker_semantic_hash(src: marked_fn_source, file: ^test_marker_fn_subject) { +// THE 4c EQUALITY: the marker contributes nothing to lowering. The marked shell lowers to exactly +// what the declaration inside it lowers to. +test fn marked_and_plain_declaration_share_one_semantic_projection_holds() -> Bool { + match test_marker_shell_agrees_with_its_declaration() { + Present { value: agrees } => agrees Absent => false - Present { value: marked } => - match test_marker_semantic_hash(src: plain_fn_source, file: ^test_marker_plain_subject) { - Absent => false - Present { value: plain } => marked == plain - } } } // CONTROL for the equality: the projection does discriminate declarations, so its agreement above -// is a reading and not a constant. -test fn renamed_fn_moves_the_semantic_projection_holds() -> Bool { - match test_marker_semantic_hash(src: plain_fn_source, file: ^test_marker_plain_subject) { +// is a reading and not a constant -- two declarations with different names and values differ. +test fn a_different_declaration_moves_the_semantic_projection_holds() -> Bool { + match test_marker_distinct_declarations_agree() { + Present { value: agrees } => !agrees Absent => false - Present { value: plain } => - match test_marker_semantic_hash(src: renamed_plain_fn_source, file: ^test_marker_renamed_subject) { - Absent => false - Present { value: renamed } => !(plain == renamed) - } } } From 7fea66164b4f3314bb6df1a3f29a20704325a117 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 06:54:18 +0000 Subject: [PATCH 04/69] Drop the unconsumed occurrence key and empty-channel constructor; count via std.types list_length Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/test_marker_capture.dag | 13 +------------ src/v2/std/declaration_marker.dag | 15 ++++----------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/v2/compiler/test_marker_capture.dag b/src/v2/compiler/test_marker_capture.dag index 372d1ed84c6..e7866c99ba6 100644 --- a/src/v2/compiler/test_marker_capture.dag +++ b/src/v2/compiler/test_marker_capture.dag @@ -1,6 +1,5 @@ module v2.compiler.test_marker_capture -import std.occurrence_identity { OccurrenceMinted, OccurrenceProjected, OccurrenceSynthetic } import v2.compiler.parse { ParseArtifact } import v2.extdeps.languages.dag { dag_binding_branded_atom_identity, @@ -94,16 +93,6 @@ fn test_marker_named_child(n: Node, name: Symbol) -> Optional { // The shell is `sequence(ident marker, fn_decl | data_decl)`. Both declaration forms put the // declared ident immediately after their leading keyword, so one reading covers both. fn test_marked_declaration(shell: Node) -> Outcome { - match shell.occurrence_id { - OccurrenceMinted { id: id } => test_marked_declaration_at(shell: shell, id: id) - OccurrenceProjected { id: _, caused_by: _ } => - outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_shell_not_authored, n: shell)) - OccurrenceSynthetic => - outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_shell_not_authored, n: shell)) - } -} - -fn test_marked_declaration_at(shell: Node, id: std.occurrence_identity.OccurrenceId) -> Outcome { match parse_production_captured_child_optional(node: shell) { Absent => outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_shape_unread, n: shell)) Present { value: captured } => @@ -117,7 +106,7 @@ fn test_marked_declaration_at(shell: Node, id: std.occurrence_identity.Occurrenc match test_marked_declared_name(decl: decl) { Absent => outcome_rejected(d: test_marker_diagnostic(reason: ^test_marker_reason_name_unread, n: decl)) Present { value: name } => - outcome_accepted(value: TestMarkedDeclaration { occurrence: id, name: name }) + outcome_accepted(value: TestMarkedDeclaration { name: name }) } } } else { diff --git a/src/v2/std/declaration_marker.dag b/src/v2/std/declaration_marker.dag index 0e1c408050d..941ad55e0bb 100644 --- a/src/v2/std/declaration_marker.dag +++ b/src/v2/std/declaration_marker.dag @@ -1,6 +1,5 @@ module v2.std.declaration_marker -import std.occurrence_identity { OccurrenceId } import v2.std.collection { List } import v2.std.node { Symbol } @@ -14,12 +13,10 @@ import v2.std.node { Symbol } // Only marked declarations are rows. Absence is the unmarked arm, and it is decidable because the // capture that produces this channel reads every top-level item of the module it is handed. // -// Two keys, because they answer two questions. `occurrence` is the authored occurrence of the -// marked item and locates it. `name` is the declared member symbol, and it is the key a reference -// binds on: lowering rebuilds each declaration as a synthetic named member, so the authored -// occurrence id does not survive to resolve and cannot be the join key there. +// Keyed by the declared member symbol, which is the key a reference binds on. Not by the authored +// occurrence id: lowering rebuilds each declaration as a synthetic named member, so that id does not +// survive to resolution and nothing downstream could join on it. type TestMarkedDeclaration { - occurrence: OccurrenceId name: Symbol } @@ -27,14 +24,10 @@ type TestMarkerChannel { marked: List } -fn test_marker_channel_empty() -> TestMarkerChannel { - TestMarkerChannel { marked: [] } -} - fn test_marker_channel_marks(channel: TestMarkerChannel, name: Symbol) -> Bool { fold(channel.marked, init: false, f: fn(acc, row) { acc || (row.name == name) }) } fn test_marker_channel_count(channel: TestMarkerChannel) -> Int { - fold(channel.marked, init: 0, f: fn(acc, _row) { acc + 1 }) + std.types.list_length(items: channel.marked) } From 7e48502bbf286c2301d1476ce0014412b18d2585 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 07:10:00 +0000 Subject: [PATCH 05/69] WIP wall claims: expression-bodied fixtures, one language model per claim, multi-root door only Co-Authored-By: Claude Opus 5 (1M context) --- .../test_code_reference_wall_test.dag | 70 +++++++++---------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index 5941ecb7a45..f090f00a344 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -1,12 +1,5 @@ module v2.test.name_resolve.test_code_reference_wall -import std.algebra { Cons, Empty, FreeMonoid } -import v2.compiler.name_resolve { Admission, ResolutionSubject, resolve_with_admission } -import v2.compiler.normalized_tree { NormalizedTree } -import v2.extdeps.languages.dag { qualified_name_from_module_node } -import v2.std.compilers.lexing { symbol_lexeme } -import v2.std.diagnostic { Accepted, Outcome, Rejected } - data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // THE WALL AT ONE INTERFACE: name resolution, handed modules the real front end produced from @@ -14,13 +7,18 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // onto NormalizedTree). Each claim reports the head refusal reason or ACCEPTED, so a red that // refuses for some OTHER reason (an unbound name, a malformed root) does not pass for the wall. // +// Every fixture body is expression-bodied (`fn f() -> T = expr`). A block body holding a call, +// `{ leaf() }`, lowers today to a Transform over the brace token with the call gone, so a block-bodied +// fixture would hand resolution no reference at all and every red would green for the wrong reason. +// // The owner ruling (2026-09-16/17) has two halves, and each has a red and a control here: // a test-marked declaration may be referenced by NOTHING, a test fn in the same module included; // and serving code may not depend on any declaration of a module that declares tests, while // another test module may. -fn wall_normalized(src: String, file: Symbol) -> Outcome { - let lm = dag_language_model() +// The language model is built ONCE per claim and handed to every stage: building it is a large share +// of a claim's cost, and each stage needs the same one. +fn wall_normalized(lm: LanguageModel, src: String, file: Symbol) -> Outcome { match tokenize(text: src, file: file, rules: lm.lex) { Rejected { diagnostics: d } => Rejected { diagnostics: d } Accepted { value: ts, diagnostics: _ } => @@ -38,19 +36,24 @@ fn wall_verdict(outcome: Outcome) -> String { } } -// Resolves `subject_src` with `peer_src` beside it as the only other root. +// Resolves `subject_src` with `peer_src` beside it as the only other root, through the multi-root +// door (v2.compiler.name_resolve resolve_with_admission) -- the one the native compile uses. The +// single-tree door, v2.compiler.resolve resolve, builds its namespace from the root's own Conj and +// does not see the members of a grafted module root at all, so it cannot bind a module-level +// reference either way and is not a door this wall can be measured at. fn wall_resolve_with_peer(subject_src: String, peer_src: String) -> String { - match wall_normalized(src: subject_src, file: ^wall_subject_file) { + let lm = dag_language_model() + match wall_normalized(lm: lm, src: subject_src, file: ^wall_subject_file) { Rejected { diagnostics: _ } => "SUBJECT_REFUSED_BEFORE_RESOLVE" Accepted { value: subject, diagnostics: _ } => - match wall_normalized(src: peer_src, file: ^wall_peer_file) { + match wall_normalized(lm: lm, src: peer_src, file: ^wall_peer_file) { Rejected { diagnostics: _ } => "PEER_REFUSED_BEFORE_RESOLVE" Accepted { value: peer, diagnostics: _ } => match qualified_name_from_module_node(root: subject.root) { Rejected { diagnostics: _ } => "SUBJECT_UNNAMED" Accepted { value: subject_qn, diagnostics: _ } => wall_verdict(outcome: resolve_with_admission( - lm: dag_language_model(), + lm: lm, roots: Cons { head: subject, tail: Cons { head: peer, tail: Empty } }, admission: Admission { subject: ResolutionSubject { name: subject_qn }, @@ -62,43 +65,35 @@ fn wall_resolve_with_peer(subject_src: String, peer_src: String) -> String { } } -// The single-tree door, v2.compiler.resolve resolve, which builds its namespace from the tree alone. -fn wall_resolve_alone(src: String) -> String { - match wall_normalized(src: src, file: ^wall_alone_file) { - Rejected { diagnostics: _ } => "SUBJECT_REFUSED_BEFORE_RESOLVE" - Accepted { value: tree, diagnostics: _ } => wall_verdict(outcome: resolve(tree: tree, lm: dag_language_model())) - } -} - -data test_calls_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool {\n true\n}\n\ntest fn rollup() -> Bool {\n leaf()\n}\n" +data test_calls_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool = true\n\ntest fn rollup() -> Bool = leaf()\n" -data serving_calls_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool {\n true\n}\n\nfn helper() -> Bool {\n leaf()\n}\n" +data serving_calls_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool = true\n\nfn helper() -> Bool = leaf()\n" -data test_calls_helper_source: String = "module wall.same\n\nfn helper() -> Bool {\n true\n}\n\ntest fn uses_helper() -> Bool {\n helper()\n}\n" +data test_calls_helper_source: String = "module wall.same\n\nfn helper() -> Bool = true\n\ntest fn uses_helper() -> Bool = helper()\n" -data local_shadows_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool {\n true\n}\n\nfn helper(leaf: Bool) -> Bool {\n leaf\n}\n" +data local_shadows_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool = true\n\nfn helper(leaf: Bool) -> Bool = leaf\n" -data peer_test_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool {\n true\n}\n\ntest fn peer_leaf() -> Bool {\n shared_fixture()\n}\n" +data peer_test_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool = true\n\ntest fn peer_leaf() -> Bool = shared_fixture()\n" -data peer_serving_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool {\n true\n}\n" +data peer_serving_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool = true\n" -data serving_reaches_peer_test_fn_source: String = "module wall.serving\n\nfn uses_peer() -> Bool {\n peer_leaf()\n}\n" +data serving_reaches_peer_test_fn_source: String = "module wall.serving\n\nfn uses_peer() -> Bool = peer_leaf()\n" -data serving_reaches_peer_test_module_source: String = "module wall.serving\n\nfn uses_peer() -> Bool {\n shared_fixture()\n}\n" +data serving_reaches_peer_test_module_source: String = "module wall.serving\n\nfn uses_peer() -> Bool = shared_fixture()\n" -data test_module_reaches_peer_test_module_source: String = "module wall.consumer\n\nfn local() -> Bool {\n true\n}\n\ntest fn consumer_holds() -> Bool {\n shared_fixture() && local()\n}\n" +data test_module_reaches_peer_test_module_source: String = "module wall.consumer\n\nfn local() -> Bool = true\n\ntest fn consumer_holds() -> Bool = shared_fixture()\n" // RED: a test fn calling another test fn in its own module. This is the case the ruling names -// explicitly, and it refuses on both resolution doors. +// explicitly. test fn test_fn_calling_a_test_fn_is_refused_holds() -> Bool { - (wall_resolve_alone(src: test_calls_test_source) == "resolve_reason_test_code_referenced") - && (wall_resolve_with_peer(subject_src: test_calls_test_source, peer_src: peer_serving_module_source) - == "resolve_reason_test_code_referenced") + wall_resolve_with_peer(subject_src: test_calls_test_source, peer_src: peer_serving_module_source) + == "resolve_reason_test_code_referenced" } // RED: serving code calling a test fn in its own module. test fn serving_fn_calling_a_test_fn_is_refused_holds() -> Bool { - wall_resolve_alone(src: serving_calls_test_source) == "resolve_reason_test_code_referenced" + wall_resolve_with_peer(subject_src: serving_calls_test_source, peer_src: peer_serving_module_source) + == "resolve_reason_test_code_referenced" } // RED: serving code calling a test fn another module declares. @@ -115,12 +110,12 @@ test fn serving_code_depending_on_a_test_module_is_refused_holds() -> Bool { // CONTROL: the intended shape, a test fn calling an ordinary fn, resolves. test fn test_fn_calling_an_ordinary_fn_resolves_holds() -> Bool { - wall_resolve_alone(src: test_calls_helper_source) == "ACCEPTED" + wall_resolve_with_peer(subject_src: test_calls_helper_source, peer_src: peer_serving_module_source) == "ACCEPTED" } // CONTROL: a local binder that shares a test fn's name binds to the local, not to the test fn. test fn a_local_binder_shadowing_a_test_fn_resolves_holds() -> Bool { - wall_resolve_alone(src: local_shadows_test_source) == "ACCEPTED" + wall_resolve_with_peer(subject_src: local_shadows_test_source, peer_src: peer_serving_module_source) == "ACCEPTED" } // CONTROL for the dependency half: the same reference from the same serving module resolves once the @@ -135,3 +130,4 @@ test fn test_module_depending_on_a_test_module_resolves_holds() -> Bool { wall_resolve_with_peer(subject_src: test_module_reaches_peer_test_module_source, peer_src: peer_test_module_source) == "ACCEPTED" } + From e535599af22b1ef76afe3969d6a2d464b4a9275c Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 07:25:46 +0000 Subject: [PATCH 06/69] Wall claims over supplied normalized roots, with one inhabitance claim for the route Co-Authored-By: Claude Opus 5 (1M context) --- .../test_code_reference_wall_test.dag | 312 +++++++++++++----- 1 file changed, 236 insertions(+), 76 deletions(-) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index f090f00a344..f6610ed182e 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -2,33 +2,101 @@ module v2.test.name_resolve.test_code_reference_wall data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly -// THE WALL AT ONE INTERFACE: name resolution, handed modules the real front end produced from -// source (tokenize, parse_module, normalize -- which is also where the `test` marker is captured -// onto NormalizedTree). Each claim reports the head refusal reason or ACCEPTED, so a red that -// refuses for some OTHER reason (an unbound name, a malformed root) does not pass for the wall. +// THE WALL AT ONE INTERFACE: name resolution (v2.compiler.name_resolve resolve_with_admission, the +// multi-root door the native compile uses), handed normalized module roots as SUPPLIED VALUES. Each +// claim reports the head refusal reason or ACCEPTED, so a red that refuses for some OTHER reason (an +// unbound name, a malformed root) does not pass for the wall. // -// Every fixture body is expression-bodied (`fn f() -> T = expr`). A block body holding a call, -// `{ leaf() }`, lowers today to a Transform over the brace token with the call gone, so a block-bodied -// fixture would hand resolution no reference at all and every red would green for the wrong reason. +// The owner ruling (2026-09-16/17) has two halves, and each has a red and a control here: a +// test-marked declaration may be referenced by NOTHING, a test fn in the same module included; and +// serving code may not depend on any declaration of a module that declares tests, while another +// test module may. // -// The owner ruling (2026-09-16/17) has two halves, and each has a red and a control here: -// a test-marked declaration may be referenced by NOTHING, a test fn in the same module included; -// and serving code may not depend on any declaration of a module that declares tests, while -// another test module may. - -// The language model is built ONCE per claim and handed to every stage: building it is a large share -// of a claim's cost, and each stage needs the same one. -fn wall_normalized(lm: LanguageModel, src: String, file: Symbol) -> Outcome { - match tokenize(text: src, file: file, rules: lm.lex) { - Rejected { diagnostics: d } => Rejected { diagnostics: d } - Accepted { value: ts, diagnostics: _ } => - match parse_module(tokens: ts, grammar: lm.grammar) { - Rejected { diagnostics: d } => Rejected { diagnostics: d } - Accepted { value: artifact, diagnostics: _ } => normalize(parse_tree: artifact.tree) - } +// WHY THE ROOTS ARE SUPPLIED (DESIGN section 3). Producing two roots from source costs two tokenize, +// parse and normalize passes (about 120k eval steps for two one-line modules) before resolution +// starts, and resolution itself is about 67k, so a from-source claim exceeds the floor's 72,300-step +// new-witness budget by roughly three times while its assertions inspect only the resolution verdict. +// The pairing obligation is discharged by `supplied_root_is_what_normalize_emits_holds`: the real +// front end, run on the source text below, emits a root whose content hash equals the supplied one +// and a marker channel that marks exactly the supplied names -- so a fixture that drifted from the +// producer turns that claim red rather than leaving these green over a tree nothing produces. +// +// The fixtures are expression-bodied (`fn f() -> T = g()`), because the member shape they encode is +// what normalize emits for that form. A block body holding a call is not a usable fixture today: +// `{ g() }` lowers to a Transform over the brace token with the call gone (tracked as its own work +// item), so it hands resolution no reference at all. + +fn wall_atom(identity: Symbol) -> Node { + Node { kind: TypeNode { connective: Atom { identity: identity } }, children: [], occurrence_id: OccurrenceSynthetic } +} + +fn wall_conj(children: List) -> Node { + Node { kind: TypeNode { connective: Conj }, children: children, occurrence_id: OccurrenceSynthetic } +} + +// `name: () -> Bool = body`, in the shape normalize emits for an expression-bodied, zero-parameter fn. +fn wall_member(name: Symbol, body: Node) -> Edge { + Edge { + label: Named { name: name }, + target: Node { + kind: TypeNode { connective: Arrow }, + children: [ + Edge { label: Positional, target: wall_conj(children: []) }, + Edge { label: Positional, target: wall_atom(identity: ^bool_node_symbol) }, + Edge { label: Positional, target: wall_atom(identity: ^bool_node_symbol) }, + Edge { label: Named { name: ^arrow_body_edge }, target: body } + ], + occurrence_id: OccurrenceSynthetic + } } } +// `name: (param: Bool) -> Bool = param` -- a parameter that shares a module-level name. +fn wall_member_returning_its_param(name: Symbol, param: Symbol) -> Edge { + Edge { + label: Named { name: name }, + target: Node { + kind: TypeNode { connective: Arrow }, + children: [ + Edge { + label: Positional, + target: wall_conj(children: [Edge { label: Named { name: param }, target: wall_atom(identity: ^bool_node_symbol) }]) + }, + Edge { label: Positional, target: wall_atom(identity: ^bool_node_symbol) }, + Edge { label: Positional, target: wall_atom(identity: ^bool_node_symbol) }, + Edge { label: Named { name: ^arrow_body_edge }, target: wall_atom(identity: param) } + ], + occurrence_id: OccurrenceSynthetic + } + } +} + +// `module wall.` with `members`, grafted as normalize grafts a two-segment module. +fn wall_module_root(leaf_segment: Symbol, members: List) -> Node { + wall_conj(children: [ + Edge { label: Named { name: ^grammar_production_identity_node_projection }, target: wall_atom(identity: ^dag_surface_module) }, + Edge { + label: Named { name: ^grammar_production_captured_node_projection }, + target: wall_conj(children: [ + Edge { + label: Named { name: ^wall }, + target: wall_conj(children: [Edge { label: Named { name: leaf_segment }, target: wall_conj(children: members) }]) + } + ]) + } + ]) +} + +fn wall_channel(marked: List) -> TestMarkerChannel { + TestMarkerChannel { + marked: fold(marked, init: [], f: fn(acc, name) { list_snoc_item(xs: acc, item: TestMarkedDeclaration { name: name }) }) + } +} + +fn wall_tree(root: Node, marked: List) -> Outcome { + admit_normalized_tree(root: root, test_markers: wall_channel(marked: marked), diagnostics: None) +} + fn wall_verdict(outcome: Outcome) -> String { match outcome { Accepted { value: _, diagnostics: _ } => "ACCEPTED" @@ -36,98 +104,190 @@ fn wall_verdict(outcome: Outcome) -> String { } } -// Resolves `subject_src` with `peer_src` beside it as the only other root, through the multi-root -// door (v2.compiler.name_resolve resolve_with_admission) -- the one the native compile uses. The -// single-tree door, v2.compiler.resolve resolve, builds its namespace from the root's own Conj and -// does not see the members of a grafted module root at all, so it cannot bind a module-level -// reference either way and is not a door this wall can be measured at. -fn wall_resolve_with_peer(subject_src: String, peer_src: String) -> String { - let lm = dag_language_model() - match wall_normalized(lm: lm, src: subject_src, file: ^wall_subject_file) { - Rejected { diagnostics: _ } => "SUBJECT_REFUSED_BEFORE_RESOLVE" - Accepted { value: subject, diagnostics: _ } => - match wall_normalized(lm: lm, src: peer_src, file: ^wall_peer_file) { - Rejected { diagnostics: _ } => "PEER_REFUSED_BEFORE_RESOLVE" - Accepted { value: peer, diagnostics: _ } => - match qualified_name_from_module_node(root: subject.root) { - Rejected { diagnostics: _ } => "SUBJECT_UNNAMED" - Accepted { value: subject_qn, diagnostics: _ } => - wall_verdict(outcome: resolve_with_admission( - lm: lm, - roots: Cons { head: subject, tail: Cons { head: peer, tail: Empty } }, - admission: Admission { - subject: ResolutionSubject { name: subject_qn }, - imports: Empty - } - )) - } - } +// Resolves module wall. as the only root: the door for the same-module halves, +// which need no second module and pay for none. +fn wall_resolve_alone(subject_segment: Symbol, subject: Outcome) -> String { + match subject { + Rejected { diagnostics: _ } => "SUBJECT_NOT_ADMITTED" + Accepted { value: s, diagnostics: _ } => + wall_verdict(outcome: resolve_with_admission( + lm: dag_language_model(), + roots: Cons { head: s, tail: Empty }, + admission: Admission { + subject: ResolutionSubject { name: Cons { head: ^wall, tail: Cons { head: subject_segment, tail: Empty } } }, + imports: Empty + } + )) } } -data test_calls_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool = true\n\ntest fn rollup() -> Bool = leaf()\n" - -data serving_calls_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool = true\n\nfn helper() -> Bool = leaf()\n" - -data test_calls_helper_source: String = "module wall.same\n\nfn helper() -> Bool = true\n\ntest fn uses_helper() -> Bool = helper()\n" +// Resolves module wall. with one peer root beside it. +fn wall_resolve(subject_segment: Symbol, subject: Outcome, peer: Outcome) -> String { + match subject { + Rejected { diagnostics: _ } => "SUBJECT_NOT_ADMITTED" + Accepted { value: s, diagnostics: _ } => + match peer { + Rejected { diagnostics: _ } => "PEER_NOT_ADMITTED" + Accepted { value: p, diagnostics: _ } => + wall_verdict(outcome: resolve_with_admission( + lm: dag_language_model(), + roots: Cons { head: s, tail: Cons { head: p, tail: Empty } }, + admission: Admission { + subject: ResolutionSubject { name: Cons { head: ^wall, tail: Cons { head: subject_segment, tail: Empty } } }, + imports: Empty + } + )) + } + } +} -data local_shadows_test_source: String = "module wall.same\n\ntest fn leaf() -> Bool = true\n\nfn helper(leaf: Bool) -> Bool = leaf\n" +// module wall.peer -- `fn shared_fixture() -> Bool = true`, `test fn peer_leaf() -> Bool = shared_fixture()` +fn peer_test_module() -> Outcome { + wall_tree( + root: wall_module_root(leaf_segment: ^peer, members: [ + wall_member(name: ^shared_fixture, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member(name: ^peer_leaf, body: wall_atom(identity: ^shared_fixture)) + ]), + marked: [^peer_leaf] + ) +} data peer_test_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool = true\n\ntest fn peer_leaf() -> Bool = shared_fixture()\n" -data peer_serving_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool = true\n" - -data serving_reaches_peer_test_fn_source: String = "module wall.serving\n\nfn uses_peer() -> Bool = peer_leaf()\n" +// module wall.peer -- `fn shared_fixture() -> Bool = true`, no test code. +fn peer_serving_module() -> Outcome { + wall_tree( + root: wall_module_root(leaf_segment: ^peer, members: [wall_member(name: ^shared_fixture, body: wall_atom(identity: ^dag_token_kw_true))]), + marked: [] + ) +} -data serving_reaches_peer_test_module_source: String = "module wall.serving\n\nfn uses_peer() -> Bool = shared_fixture()\n" +// A one-member serving module wall.serving whose only fn's body is `body`. +fn serving_module_with_body(body: Node) -> Outcome { + wall_tree(root: wall_module_root(leaf_segment: ^serving, members: [wall_member(name: ^uses_peer, body: body)]), marked: []) +} -data test_module_reaches_peer_test_module_source: String = "module wall.consumer\n\nfn local() -> Bool = true\n\ntest fn consumer_holds() -> Bool = shared_fixture()\n" +// INHABITANCE, THE ROUTE THE SUPPLIED ROOTS STAND ON: the real front end, on the source text the peer +// fixture encodes, emits the same root (by content hash, which does not read occurrence ids) and a +// channel marking exactly `peer_leaf`. The two halves are the two things every claim below reads. +test fn supplied_root_is_what_normalize_emits_holds() -> Bool { + let lm = dag_language_model() + match tokenize(text: peer_test_module_source, file: ^wall_peer_file, rules: lm.lex) { + Rejected { diagnostics: _ } => false + Accepted { value: ts, diagnostics: _ } => + match parse_module(tokens: ts, grammar: lm.grammar) { + Rejected { diagnostics: _ } => false + Accepted { value: artifact, diagnostics: _ } => + match normalize(parse_tree: artifact.tree) { + Rejected { diagnostics: _ } => false + Accepted { value: emitted, diagnostics: _ } => + match peer_test_module() { + Rejected { diagnostics: _ } => false + Accepted { value: supplied, diagnostics: _ } => + (content_hash(n: emitted.root) == content_hash(n: supplied.root)) + && (test_marker_channel_count(channel: emitted.test_markers) == 1) + && test_marker_channel_marks(channel: emitted.test_markers, name: ^peer_leaf) + } + } + } + } +} -// RED: a test fn calling another test fn in its own module. This is the case the ruling names -// explicitly. +// RED: a test fn calling another test fn in its own module -- the case the ruling names explicitly. test fn test_fn_calling_a_test_fn_is_refused_holds() -> Bool { - wall_resolve_with_peer(subject_src: test_calls_test_source, peer_src: peer_serving_module_source) - == "resolve_reason_test_code_referenced" + wall_resolve_alone( + subject_segment: ^same, + subject: wall_tree( + root: wall_module_root(leaf_segment: ^same, members: [ + wall_member(name: ^leaf, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member(name: ^rollup, body: wall_atom(identity: ^leaf)) + ]), + marked: [^leaf, ^rollup] + ) + ) == "resolve_reason_test_code_referenced" } // RED: serving code calling a test fn in its own module. test fn serving_fn_calling_a_test_fn_is_refused_holds() -> Bool { - wall_resolve_with_peer(subject_src: serving_calls_test_source, peer_src: peer_serving_module_source) - == "resolve_reason_test_code_referenced" + wall_resolve_alone( + subject_segment: ^same, + subject: wall_tree( + root: wall_module_root(leaf_segment: ^same, members: [ + wall_member(name: ^leaf, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member(name: ^helper, body: wall_atom(identity: ^leaf)) + ]), + marked: [^leaf] + ) + ) == "resolve_reason_test_code_referenced" } // RED: serving code calling a test fn another module declares. test fn serving_code_calling_another_modules_test_fn_is_refused_holds() -> Bool { - wall_resolve_with_peer(subject_src: serving_reaches_peer_test_fn_source, peer_src: peer_test_module_source) - == "resolve_reason_test_code_referenced" + wall_resolve( + subject_segment: ^serving, + subject: serving_module_with_body(body: wall_atom(identity: ^peer_leaf)), + peer: peer_test_module() + ) == "resolve_reason_test_code_referenced" } // RED: serving code depending on an UNMARKED declaration of a module that declares tests. test fn serving_code_depending_on_a_test_module_is_refused_holds() -> Bool { - wall_resolve_with_peer(subject_src: serving_reaches_peer_test_module_source, peer_src: peer_test_module_source) - == "resolve_reason_serving_code_depends_on_test_code" + wall_resolve( + subject_segment: ^serving, + subject: serving_module_with_body(body: wall_atom(identity: ^shared_fixture)), + peer: peer_test_module() + ) == "resolve_reason_serving_code_depends_on_test_code" } // CONTROL: the intended shape, a test fn calling an ordinary fn, resolves. test fn test_fn_calling_an_ordinary_fn_resolves_holds() -> Bool { - wall_resolve_with_peer(subject_src: test_calls_helper_source, peer_src: peer_serving_module_source) == "ACCEPTED" + wall_resolve_alone( + subject_segment: ^same, + subject: wall_tree( + root: wall_module_root(leaf_segment: ^same, members: [ + wall_member(name: ^helper, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member(name: ^uses_helper, body: wall_atom(identity: ^helper)) + ]), + marked: [^uses_helper] + ) + ) == "ACCEPTED" } -// CONTROL: a local binder that shares a test fn's name binds to the local, not to the test fn. +// CONTROL: a parameter that shares a test fn's name binds to the parameter, not to the test fn. test fn a_local_binder_shadowing_a_test_fn_resolves_holds() -> Bool { - wall_resolve_with_peer(subject_src: local_shadows_test_source, peer_src: peer_serving_module_source) == "ACCEPTED" + wall_resolve_alone( + subject_segment: ^same, + subject: wall_tree( + root: wall_module_root(leaf_segment: ^same, members: [ + wall_member(name: ^leaf, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member_returning_its_param(name: ^helper, param: ^leaf) + ]), + marked: [^leaf] + ) + ) == "ACCEPTED" } // CONTROL for the dependency half: the same reference from the same serving module resolves once the // peer declares no tests, so the red above is about the peer's test code and nothing else. test fn serving_code_depending_on_a_serving_module_resolves_holds() -> Bool { - wall_resolve_with_peer(subject_src: serving_reaches_peer_test_module_source, peer_src: peer_serving_module_source) - == "ACCEPTED" + wall_resolve( + subject_segment: ^serving, + subject: serving_module_with_body(body: wall_atom(identity: ^shared_fixture)), + peer: peer_serving_module() + ) == "ACCEPTED" } // CONTROL: a test module may depend on another test module's ordinary declarations. test fn test_module_depending_on_a_test_module_resolves_holds() -> Bool { - wall_resolve_with_peer(subject_src: test_module_reaches_peer_test_module_source, peer_src: peer_test_module_source) - == "ACCEPTED" + wall_resolve( + subject_segment: ^consumer, + subject: wall_tree( + root: wall_module_root(leaf_segment: ^consumer, members: [ + wall_member(name: ^consumer_holds, body: wall_atom(identity: ^shared_fixture)) + ]), + marked: [^consumer_holds] + ), + peer: peer_test_module() + ) == "ACCEPTED" } From c8dc17d14cdfa7198c281c118ef6c3510ec63c4c Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 09:54:36 +0000 Subject: [PATCH 07/69] Establish the dag canonical-symbol set once instead of rewalking the grammar per atom. Name resolution's ~63k-step membership check was grammar_carries_symbol converting the whole grammar to a Node on every unbound atom. Carry a derived map on LanguageModel.canonical_symbols so a one-member resolve stays under the new-witness eval-step budget. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 132 ++++++++++++++---- src/v2/std/compilers/lexing.dag | 22 ++- src/v2/std/grammar.dag | 98 ++++++++++++- .../one_member_cost_probe_test.dag | 127 +++++++++++++++++ 4 files changed, 344 insertions(+), 35 deletions(-) create mode 100644 src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 77fa34acc96..497b56453cc 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -10,7 +10,8 @@ import v2.std.collection { Map, empty_map, list_at_optional, - map_insert + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -47,6 +48,7 @@ import v2.std.grammar { Terminal, VoidGrammar, grammar_carries_symbol, + grammar_carried_symbol_map, formal_production_to_node, formal_productions_catalog_to_node, grammar_production_to_node, @@ -83,6 +85,7 @@ import v2.std.compilers.lexing { TokenRule, TriviaRule, WhitespaceChar, + lex_rule_set_token_class_map, lex_rule_set_token_class_member } import v2.std.node { @@ -2209,18 +2212,60 @@ fn dag_grammar() -> ParseGrammar { ModeledGrammar { root: dag_grammar_root() } } -fn dag_language_model_surface_prelude_atoms() -> PointwisePower { +fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { + map_insert(m: m, key: sym, value: true) +} + +fn dag_symbol_map_has(m: Map, sym: Symbol) -> Bool { + match map_lookup(m: m, key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { - sym == ^dag_c3_surface_sugar_service || - sym == ^dag_c3_surface_sugar_fn || - sym == ^dag_c3_surface_sugar_type || - sym == ^dag_c3_surface_sugar_operation || - dag_lex_token_symbol(sym: sym) + dag_symbol_map_has(m: m, sym: sym) } } } +fn dag_surface_sugar_symbol_map(m: Map) -> Map { + dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put(m: m, sym: ^dag_c3_surface_sugar_service), + sym: ^dag_c3_surface_sugar_fn + ), + sym: ^dag_c3_surface_sugar_type + ), + sym: ^dag_c3_surface_sugar_operation + ) +} + +fn dag_lex_token_class_insert(m: Map) -> Map { + fold_list( + xs: dag_lex_rules().rules, + empty: m, + cons: fn(acc, rule) { + match rule { + TokenRule { token_class: c, pattern: _ } => dag_symbol_map_put(m: acc, sym: c) + TriviaRule { pattern: _ } => acc + AnnotationRule { annotation_class: _, pattern: _ } => acc + } + } + ) +} + +fn dag_language_model_surface_prelude_map() -> Map { + dag_surface_sugar_symbol_map(m: lex_rule_set_token_class_map(rules: dag_lex_rules())) +} + +fn dag_language_model_surface_prelude_atoms() -> PointwisePower { + dag_pointwise_from_symbol_map(m: dag_language_model_surface_prelude_map()) +} + fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { PointwisePower { member: fn(_) { @@ -2229,29 +2274,59 @@ fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { } } +fn dag_language_model_binding_canonical_map(m: Map) -> Map { + dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put(m: m, sym: ^dag_binding_fn_add), + sym: ^dag_binding_param_x + ), + sym: ^dag_binding_param_y + ), + sym: ^dag_binding_type_int + ), + sym: ^dag_binding_fn_one_or_two + ), + sym: ^dag_binding_fn_one + ), + sym: ^dag_binding_bind_x + ), + sym: ^dag_binding_pick_lit_one + ), + sym: ^dag_binding_pick_lit_two + ), + sym: ^dag_binding_type_bool + ), + sym: ^bool_node_symbol + ), + sym: ^dag_loop_measure_sym + ) +} + fn dag_language_model_binding_canonical(sym: Symbol) -> Bool { - sym == ^dag_binding_fn_add - || sym == ^dag_binding_param_x - || sym == ^dag_binding_param_y - || sym == ^dag_binding_type_int - || sym == ^dag_binding_fn_one_or_two - || sym == ^dag_binding_fn_one - || sym == ^dag_binding_bind_x - || sym == ^dag_binding_pick_lit_one - || sym == ^dag_binding_pick_lit_two - || sym == ^dag_binding_type_bool - || sym == ^bool_node_symbol - || sym == ^dag_loop_measure_sym + dag_symbol_map_has(m: dag_language_model_binding_canonical_map(m: empty_map()), sym: sym) +} + +fn dag_canonical_symbol_map() -> Map { + dag_language_model_binding_canonical_map( + m: dag_surface_sugar_symbol_map( + m: dag_lex_token_class_insert( + m: grammar_carried_symbol_map(grammar: dag_grammar()) + ) + ) + ) } fn dag_canonical_symbols() -> PointwisePower { - PointwisePower { - member: fn(sym) { - dag_language_model_surface_prelude_atoms().member(sym) || - grammar_carries_symbol(grammar: dag_grammar(), sym: sym) || - dag_language_model_binding_canonical(sym: sym) - } - } + dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } fn dag_language_model_void() -> LanguageModel { @@ -2289,10 +2364,7 @@ fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower Bool { - lex_rule_set_token_class_member( - rules: dag_lex_rules(), - token_class: sym - ) + dag_symbol_map_has(m: lex_rule_set_token_class_map(rules: dag_lex_rules()), sym: sym) } fn dag_sync_tokens() -> List { diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index c176116ef9b..ec6347ad240 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -8,7 +8,11 @@ import std.disposition { Disposition, Scaffold, RealizationDispatch } import v2.std.algebra { fold_list, length, list_snoc_item, skip } import v2.std.collection { List, - list_at_optional + Map, + empty_map, + list_at_optional, + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -231,11 +235,23 @@ fn lex_rule_token_class_member(rule: LexRule, token_class: Symbol) -> Bool { } fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { + Present { value: _ } => true + Absent => false + } +} + +fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { fold_list( xs: rules.rules, - empty: false, + empty: empty_map(), cons: fn(acc, rule) { - acc || lex_rule_token_class_member(rule: rule, token_class: token_class) + match rule { + TokenRule { token_class: c, pattern: _ } => + map_insert(m: acc, key: c, value: true) + TriviaRule { pattern: _ } => acc + AnnotationRule { annotation_class: _, pattern: _ } => acc + } } ) } diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index a636c5f975e..2b9f979c7f8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -5,8 +5,12 @@ import std.algebra { Cons, Empty } import v2.std.algebra { fold_list, fold_list_node, list_append, list_snoc_item, zip_eq } import v2.std.collection { List, + Map, PointwisePower, - list_nth + empty_map, + list_nth, + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -1730,7 +1734,97 @@ fn grammar_symbol_fold_state(n: Node, sym: Symbol) -> GrammarSymbolFoldState { } fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { - grammar_symbol_fold_state(n: grammar_to_node(grammar: grammar), sym: sym).found + match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { + map_insert(m: m, key: sym, value: true) +} + +fn grammar_symbol_list_put(m: Map, xs: List) -> Map { + fold_list( + xs: xs, + empty: m, + cons: fn(acc, sym) { grammar_symbol_map_put(m: acc, sym: sym) } + ) +} + +fn grammar_node_atom_identities(n: Node) -> List { + fold_node( + n: n, + algebra: NodeFold { + init: fn(n0) { + match n0.kind { + TypeNode { connective: Atom { identity: id } } => + Cons { head: id, tail: Empty } + _ => Empty + } + }, + step: fn(acc, _e, child) { list_append(left: acc, right: child) } + } + ) +} + +fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map { + grammar_symbol_list_put(m: m, xs: grammar_node_atom_identities(n: n)) +} + +fn grammar_expr_carried_symbols(expr: GrammarExpr) -> List { + fold_grammar_expr( + expr: expr, + algebra: GrammarExprFold { + terminal: fn(token_class, stamp) { + match stamp { + StampClass => Cons { head: token_class, tail: Empty } + StampBinding { binding: b } => + Cons { head: token_class, tail: Cons { head: b, tail: Empty } } + StampLexeme => + Cons { + head: token_class, + tail: Cons { head: ^grammar_terminal_stamp_lexeme, tail: Empty } + } + } + }, + nonterminal: fn(production) { Cons { head: production, tail: Empty } }, + sequence: fn(left, right) { list_append(left: left, right: right) }, + choice: fn(left, right) { list_append(left: left, right: right) }, + optional: fn(element) { element }, + repeat: fn(element) { element } + } + ) +} + +fn grammar_production_insert_carried_symbols( + m: Map, + production: GrammarProduction +) -> Map { + grammar_node_insert_atom_identities( + m: grammar_symbol_list_put( + m: grammar_symbol_map_put(m: m, sym: production.name), + xs: grammar_expr_carried_symbols(expr: production.expression) + ), + n: production.emitted + ) +} + +fn grammar_root_carried_symbol_map(root: GrammarRoot) -> Map { + fold_list( + xs: root.productions, + empty: grammar_symbol_map_put(m: empty_map(), sym: root.start), + cons: fn(acc, production) { + grammar_production_insert_carried_symbols(m: acc, production: production) + } + ) +} + +fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { + match grammar { + VoidGrammar => empty_map() + ModeledGrammar { root: root } => grammar_root_carried_symbol_map(root: root) + } } type GrammarInterpretationDirection diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag new file mode 100644 index 00000000000..a7ccbb5e020 --- /dev/null +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -0,0 +1,127 @@ +module v2.test.name_resolve.one_member_cost_probe + +import std.occurrence_identity { OccurrenceSynthetic } +import v2.compiler.name_resolve { + Admission, + ResolutionSubject, + resolve_with_admission +} +import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } +import v2.compiler.resolve { ResolvedTree } +import v2.extdeps.languages.dag { + dag_grammar_atom, + dag_language_model, + dag_named_edge +} +import std.algebra { Cons, Empty, FreeMonoid } +import v2.std.algebra { fold_list_node } +import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } +import v2.std.logic { Bool } +import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } +import v2.std.qualified_name { QualifiedName } + +fn probe_qn_node(seg: Symbol) -> Node { + fold_list_node( + xs: Cons { head: seg, tail: Empty }, + item_node: fn(sym) { + Node { + kind: TypeNode { connective: Atom { identity: sym } }, + children: [], + occurrence_id: OccurrenceSynthetic + } + } + ) +} + +fn probe_one_member_root() -> Node { + Node { + kind: TypeNode { connective: Conj }, + children: [ + dag_named_edge( + name: ^dag_surface_module_header, + target: dag_grammar_atom(id: ^dag_surface_module_header) + ), + dag_named_edge( + name: ^dag_surface_module_header_qualified_name, + target: probe_qn_node(seg: ^probe_one_member_seg) + ), + Edge { + label: Named { name: ^probe_export_sym }, + target: Node { + kind: TypeNode { + connective: Atom { identity: ^dag_c3_surface_sugar_service } + }, + children: [], + occurrence_id: OccurrenceSynthetic + } + } + ], + occurrence_id: OccurrenceSynthetic + } +} + +fn probe_one_member_qn() -> QualifiedName { + Cons { head: ^probe_one_member_seg, tail: Empty } +} + +fn probe_one_member_roots() -> Outcome> { + bind_outcome( + o: admit_normalized_tree(root: probe_one_member_root(), diagnostics: None), + f: fn(a) { + outcome_accepted(value: Cons { head: a, tail: Empty }) + } + ) +} + +fn probe_with_roots(f: fn(FreeMonoid) -> Bool) -> Bool { + match probe_one_member_roots() { + Rejected { diagnostics: _ } => false + Accepted { value: roots, diagnostics: _ } => f(roots) + } +} + +fn probe_resolved_keeps_service_atom(tree: ResolvedTree) -> Bool { + match tree.kind { + TypeNode { connective: Atom { identity: id } } => + id == ^dag_c3_surface_sugar_service + _ => + fold(tree.children, init: false, f: fn(acc, e) { + acc || probe_resolved_keeps_service_atom(tree: e.target) + }) + } +} + +test fn one_member_module_resolves_under_full_language_model() -> Bool { + probe_with_roots(f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) { + Accepted { value: resolved, diagnostics: _ } => + probe_resolved_keeps_service_atom(tree: resolved) + Rejected { diagnostics: _ } => false + } + }) +} + +test fn one_member_missing_subject_still_refuses() -> Bool { + probe_with_roots(f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { + name: Cons { head: ^probe_missing_subject_seg, tail: Empty } + }, + imports: Empty + } + ) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} From 150eb33400e92cb551f5fcf6b5a841da7b5fd433 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:00:01 +0000 Subject: [PATCH 08/69] Prove the carried-symbol map matches the node-fold oracle and that a too-wide map still refuses. The production path uses the derived map; grammar_carries_symbol remains the grammar-to-node fold so a complete tiny grammar can compare every membership both ways, and an uncanonical export still rejects under the full language model. Co-authored-by: Cursor --- src/v2/std/grammar.dag | 5 +- .../one_member_cost_probe_test.dag | 128 ++++++++++++++---- 2 files changed, 106 insertions(+), 27 deletions(-) diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index 2b9f979c7f8..fc86b6703b8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -1734,10 +1734,7 @@ fn grammar_symbol_fold_state(n: Node, sym: Symbol) -> GrammarSymbolFoldState { } fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { - match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { - Present { value: _ } => true - Absent => false - } + grammar_symbol_fold_state(n: grammar_to_node(grammar: grammar), sym: sym).found } fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index a7ccbb5e020..8fbf09fabb9 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -9,15 +9,30 @@ import v2.compiler.name_resolve { import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } import v2.compiler.resolve { ResolvedTree } import v2.extdeps.languages.dag { + dag_canonical_symbols, dag_grammar_atom, dag_language_model, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list_node } +import v2.std.collection { map_lookup } import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } +import v2.std.grammar { + GrammarProduction, + GrammarRoot, + ModeledGrammar, + ParseGrammar, + StampClass, + Terminal, + grammar_atom, + grammar_carried_symbol_map, + grammar_carries_symbol, + grammar_empty_sync_tokens +} import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } +import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } fn probe_qn_node(seg: Symbol) -> Node { @@ -33,7 +48,7 @@ fn probe_qn_node(seg: Symbol) -> Node { ) } -fn probe_one_member_root() -> Node { +fn probe_one_member_root_export(export_sym: Symbol) -> Node { Node { kind: TypeNode { connective: Conj }, children: [ @@ -48,9 +63,7 @@ fn probe_one_member_root() -> Node { Edge { label: Named { name: ^probe_export_sym }, target: Node { - kind: TypeNode { - connective: Atom { identity: ^dag_c3_surface_sugar_service } - }, + kind: TypeNode { connective: Atom { identity: export_sym } }, children: [], occurrence_id: OccurrenceSynthetic } @@ -64,52 +77,116 @@ fn probe_one_member_qn() -> QualifiedName { Cons { head: ^probe_one_member_seg, tail: Empty } } -fn probe_one_member_roots() -> Outcome> { +fn probe_roots_for_export(export_sym: Symbol) -> Outcome> { bind_outcome( - o: admit_normalized_tree(root: probe_one_member_root(), diagnostics: None), + o: admit_normalized_tree( + root: probe_one_member_root_export(export_sym: export_sym), + diagnostics: None + ), f: fn(a) { outcome_accepted(value: Cons { head: a, tail: Empty }) } ) } -fn probe_with_roots(f: fn(FreeMonoid) -> Bool) -> Bool { - match probe_one_member_roots() { +fn probe_with_export_roots( + export_sym: Symbol, + f: fn(FreeMonoid) -> Bool +) -> Bool { + match probe_roots_for_export(export_sym: export_sym) { Rejected { diagnostics: _ } => false Accepted { value: roots, diagnostics: _ } => f(roots) } } -fn probe_resolved_keeps_service_atom(tree: ResolvedTree) -> Bool { +fn probe_resolve(roots: FreeMonoid) -> Outcome { + resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) +} + +fn probe_resolved_export_identity(tree: ResolvedTree, wanted: Symbol) -> Bool { match tree.kind { - TypeNode { connective: Atom { identity: id } } => - id == ^dag_c3_surface_sugar_service + TypeNode { connective: Atom { identity: id } } => id == wanted _ => fold(tree.children, init: false, f: fn(acc, e) { - acc || probe_resolved_keeps_service_atom(tree: e.target) + acc || probe_resolved_export_identity(tree: e.target, wanted: wanted) }) } } +fn map_carries(grammar: ParseGrammar, sym: Symbol) -> Bool { + match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn eq_probe_grammar() -> ParseGrammar { + ModeledGrammar { + root: GrammarRoot { + start: ^eq_probe_start, + productions: Cons { + head: GrammarProduction { + name: ^eq_probe_start, + expression: Terminal { + token_class: ^eq_probe_tok, + stamp: StampClass + }, + emitted: grammar_atom(identity: ^eq_probe_emitted) + }, + tail: Empty + }, + sync_tokens: grammar_empty_sync_tokens() + } + } +} + +fn map_agrees_with_node_fold(sym: Symbol) -> Bool { + let g = eq_probe_grammar() + map_carries(grammar: g, sym: sym) == grammar_carries_symbol(grammar: g, sym: sym) +} + +test fn carried_symbol_map_agrees_with_node_fold_on_complete_probe_grammar() -> Bool { + map_agrees_with_node_fold(sym: ^eq_probe_start) + && map_agrees_with_node_fold(sym: ^eq_probe_tok) + && map_agrees_with_node_fold(sym: ^eq_probe_emitted) + && map_agrees_with_node_fold(sym: ^eq_probe_absent) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_start) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_tok) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_emitted) + && !map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_absent) +} + test fn one_member_module_resolves_under_full_language_model() -> Bool { - probe_with_roots(f: fn(roots) { - match resolve_with_admission( - lm: dag_language_model(), - roots: roots, - admission: Admission { - subject: ResolutionSubject { name: probe_one_member_qn() }, - imports: Empty - } - ) { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { + match probe_resolve(roots: roots) { Accepted { value: resolved, diagnostics: _ } => - probe_resolved_keeps_service_atom(tree: resolved) + probe_resolved_export_identity( + tree: resolved, + wanted: ^dag_c3_surface_sugar_service + ) Rejected { diagnostics: _ } => false } }) } +test fn one_member_uncanonical_export_still_refuses() -> Bool { + probe_with_export_roots(export_sym: ^probe_uncanonical_export, f: fn(roots) { + match probe_resolve(roots: roots) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} + test fn one_member_missing_subject_still_refuses() -> Bool { - probe_with_roots(f: fn(roots) { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { match resolve_with_admission( lm: dag_language_model(), roots: roots, @@ -125,3 +202,8 @@ test fn one_member_missing_subject_still_refuses() -> Bool { } }) } + +test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { + dag_canonical_symbols().member(^dag_c3_surface_sugar_service) + && !dag_canonical_symbols().member(^probe_uncanonical_export) +} From eb3c052ad293c13a3e2edae8a0fe7b457746e4a9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:04:15 +0000 Subject: [PATCH 09/69] Collapse the dag-local lex token-class fold into std and drop unused membership wrappers. lex_rule_set_insert_token_classes is the single TokenRule walk; dag_canonical_symbol_map seeds it. Dead lex_rule_token_class_member, dag_lex_token_class_insert, and dag_language_model_binding_canonical go with the cutover. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 27 +++++--------------------- src/v2/std/compilers/lexing.dag | 33 ++++++++++++++++---------------- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 497b56453cc..ed07182807d 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -85,8 +85,8 @@ import v2.std.compilers.lexing { TokenRule, TriviaRule, WhitespaceChar, - lex_rule_set_token_class_map, - lex_rule_set_token_class_member + lex_rule_set_insert_token_classes, + lex_rule_set_token_class_map } import v2.std.node { Arrow, @@ -2244,20 +2244,6 @@ fn dag_surface_sugar_symbol_map(m: Map) -> Map { ) } -fn dag_lex_token_class_insert(m: Map) -> Map { - fold_list( - xs: dag_lex_rules().rules, - empty: m, - cons: fn(acc, rule) { - match rule { - TokenRule { token_class: c, pattern: _ } => dag_symbol_map_put(m: acc, sym: c) - TriviaRule { pattern: _ } => acc - AnnotationRule { annotation_class: _, pattern: _ } => acc - } - } - ) -} - fn dag_language_model_surface_prelude_map() -> Map { dag_surface_sugar_symbol_map(m: lex_rule_set_token_class_map(rules: dag_lex_rules())) } @@ -2311,15 +2297,12 @@ fn dag_language_model_binding_canonical_map(m: Map) -> Map Bool { - dag_symbol_map_has(m: dag_language_model_binding_canonical_map(m: empty_map()), sym: sym) -} - fn dag_canonical_symbol_map() -> Map { dag_language_model_binding_canonical_map( m: dag_surface_sugar_symbol_map( - m: dag_lex_token_class_insert( - m: grammar_carried_symbol_map(grammar: dag_grammar()) + m: lex_rule_set_insert_token_classes( + m: grammar_carried_symbol_map(grammar: dag_grammar()), + rules: dag_lex_rules() ) ) ) diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index ec6347ad240..e50b3ae6a91 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -226,25 +226,13 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List Bool { - match rule { - TokenRule { token_class: c, pattern: _ } => c == token_class - TriviaRule { pattern: _ } => false - AnnotationRule { annotation_class: _, pattern: _ } => false - } -} - -fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { - match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { - Present { value: _ } => true - Absent => false - } -} - -fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { +fn lex_rule_set_insert_token_classes( + m: Map, + rules: LexRuleSet +) -> Map { fold_list( xs: rules.rules, - empty: empty_map(), + empty: m, cons: fn(acc, rule) { match rule { TokenRule { token_class: c, pattern: _ } => @@ -256,6 +244,17 @@ fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { ) } +fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { + lex_rule_set_insert_token_classes(m: empty_map(), rules: rules) +} + +fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { + Present { value: _ } => true + Absent => false + } +} + type LexPatternFold { empty: R literal: fn(v2.std.text.String) -> R From e5e9c5b8af6c080cc55cdcca574c807c6354f6d8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:19:19 +0000 Subject: [PATCH 10/69] Wall inhabitance over a one-member module Co-Authored-By: Claude Opus 5 (1M context) --- .../test_code_reference_wall_test.dag | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index f6610ed182e..4350bb44eb7 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -152,7 +152,17 @@ fn peer_test_module() -> Outcome { ) } -data peer_test_module_source: String = "module wall.peer\n\nfn shared_fixture() -> Bool = true\n\ntest fn peer_leaf() -> Bool = shared_fixture()\n" +// The smallest module that carries both facts the supplied roots rely on: a member of the +// expression-bodied shape, and a test marker on it. One member, because the front end's cost +// grows with the source and this claim pays for a real tokenize, parse and normalize. +fn inhabitance_module() -> Outcome { + wall_tree( + root: wall_module_root(leaf_segment: ^peer, members: [wall_member(name: ^peer_leaf, body: wall_atom(identity: ^shared_fixture))]), + marked: [^peer_leaf] + ) +} + +data inhabitance_module_source: String = "module wall.peer\ntest fn peer_leaf() -> Bool = shared_fixture()\n" // module wall.peer -- `fn shared_fixture() -> Bool = true`, no test code. fn peer_serving_module() -> Outcome { @@ -167,12 +177,13 @@ fn serving_module_with_body(body: Node) -> Outcome { wall_tree(root: wall_module_root(leaf_segment: ^serving, members: [wall_member(name: ^uses_peer, body: body)]), marked: []) } -// INHABITANCE, THE ROUTE THE SUPPLIED ROOTS STAND ON: the real front end, on the source text the peer -// fixture encodes, emits the same root (by content hash, which does not read occurrence ids) and a +// INHABITANCE, THE ROUTE THE SUPPLIED ROOTS STAND ON: the real front end, on the source text the +// inhabitance fixture encodes (built with the same wall_member and wall_module_root every claim below +// uses), emits the same root (by content hash, which does not read occurrence ids) and a // channel marking exactly `peer_leaf`. The two halves are the two things every claim below reads. test fn supplied_root_is_what_normalize_emits_holds() -> Bool { let lm = dag_language_model() - match tokenize(text: peer_test_module_source, file: ^wall_peer_file, rules: lm.lex) { + match tokenize(text: inhabitance_module_source, file: ^wall_peer_file, rules: lm.lex) { Rejected { diagnostics: _ } => false Accepted { value: ts, diagnostics: _ } => match parse_module(tokens: ts, grammar: lm.grammar) { @@ -181,7 +192,7 @@ test fn supplied_root_is_what_normalize_emits_holds() -> Bool { match normalize(parse_tree: artifact.tree) { Rejected { diagnostics: _ } => false Accepted { value: emitted, diagnostics: _ } => - match peer_test_module() { + match inhabitance_module() { Rejected { diagnostics: _ } => false Accepted { value: supplied, diagnostics: _ } => (content_hash(n: emitted.root) == content_hash(n: supplied.root)) @@ -291,3 +302,4 @@ test fn test_module_depending_on_a_test_module_resolves_holds() -> Bool { ) == "ACCEPTED" } + From b8f8cbe5743d66aeeab3ef15b75c764634b4c33d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:27:28 +0000 Subject: [PATCH 11/69] Build the canonical-symbol map in resolution context, not in dag_language_model. Eager map fill on LanguageModel charged every tokenize/parse claim ~33k steps. The set is established once on ResolutionContext (and in build_program_namespace) where resolve actually members it; LM construction is back to ~3.7k. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 21 ++++++++++--------- src/v2/compiler/03_resolve.dag | 4 ++-- src/v2/extdeps/languages/dag.dag | 2 +- .../one_member_cost_probe_test.dag | 4 ++++ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 456d04ea4ca..19f44e72134 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,7 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_language_model_canonical_symbols, + dag_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -31,7 +31,7 @@ import v2.compiler.namespace_graft { } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list } -import v2.std.collection { Absent, Map, Present, List, empty_map, map_get, map_lookup, map_insert, optional_absent, optional_present } +import v2.std.collection { Absent, Map, Present, List, PointwisePower, empty_map, map_get, map_lookup, map_insert, optional_absent, optional_present } import v2.std.diagnostic { Accepted, Diagnostic, @@ -111,6 +111,7 @@ type ResolutionContext { lm: LanguageModel roots: ValidatedModuleRoots symbol_index: SymbolIndex + canonical_symbols: PointwisePower } fn resolution_context( @@ -127,7 +128,8 @@ fn resolution_context( symbol_index: symbol_index_fill_module_roots( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) - ) + ), + canonical_symbols: dag_canonical_symbols() }, diagnostics: d } @@ -556,10 +558,10 @@ fn admit_imports( ) } -fn admission_namespace_base(lm: LanguageModel) -> Namespace { +fn admission_namespace_base(canonical_symbols: PointwisePower) -> Namespace { Namespace { bindings: empty_map(), - canonical_symbols: dag_language_model_canonical_symbols(lm: lm), + canonical_symbols: canonical_symbols, symbol_index: empty_symbol_index(), module_qn: Empty } @@ -579,11 +581,11 @@ fn subject_module_root_for_name( fn namespace_admission_initial_state( policy: NameResolutionPolicy, - lm: LanguageModel, + canonical_symbols: PointwisePower, subject_module_root: NormalizedTree, subject_qn: QualifiedName ) -> AdmissionState { - let base = admission_namespace_base(lm: lm) + let base = admission_namespace_base(canonical_symbols: canonical_symbols) match policy { NamespaceOnlyY => admission_initial(base: base) @@ -660,13 +662,12 @@ fn namespace_for_subject_root( active_roots: FreeMonoid, policy: NameResolutionPolicy ) -> Outcome { - let lm = shared.lm let validated_roots = shared.roots if well_formed(n: subject_module_root.root) { - let base = admission_namespace_base(lm: lm) + let base = admission_namespace_base(canonical_symbols: shared.canonical_symbols) let initial = namespace_admission_initial_state( policy: policy, - lm: lm, + canonical_symbols: shared.canonical_symbols, subject_module_root: subject_module_root, subject_qn: admission.subject.name ) diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 09dcc66785b..9a70513876e 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,7 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_language_model_canonical_symbols, + dag_canonical_symbols, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, dag_resolve_preserve_module_metadata_subtree, @@ -234,7 +234,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_language_model_canonical_symbols(lm: lm), + canonical_symbols: dag_canonical_symbols(), symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index ed07182807d..9d24b8af1c7 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2327,7 +2327,7 @@ fn dag_language_model() -> LanguageModel { lex: dag_lex(), grammar: dag_grammar(), language_identity: ^dag_language_model_surface_id, - canonical_symbols: dag_canonical_symbols(), + canonical_symbols: dag_language_model_empty_canonical_symbol_set(), core: bool_model_core() } } diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 8fbf09fabb9..8c234c747d3 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -203,6 +203,10 @@ test fn one_member_missing_subject_still_refuses() -> Bool { }) } +test fn language_model_construction_does_not_fill_canonical_map() -> Bool { + dag_language_model().language_identity == ^dag_language_model_surface_id +} + test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { dag_canonical_symbols().member(^dag_c3_surface_sugar_service) && !dag_canonical_symbols().member(^probe_uncanonical_export) From bf46be5364a40bf2dfded9c30bc5c315863c5860 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:31:56 +0000 Subject: [PATCH 12/69] Enroll a tokenize/parse probe that never enters resolution. Parent asked for a floor claim the front end does not pay the canonical-symbol map. This claim only constructs the language model, tokenizes, and parses a one-member module. Co-authored-by: Cursor --- .../one_member_cost_probe_test.dag | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 8c234c747d3..c951e2bc224 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -7,7 +7,9 @@ import v2.compiler.name_resolve { resolve_with_admission } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } +import v2.compiler.parse { parse_module } import v2.compiler.resolve { ResolvedTree } +import v2.compiler.tokenize { tokenize } import v2.extdeps.languages.dag { dag_canonical_symbols, dag_grammar_atom, @@ -34,6 +36,7 @@ import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } +import v2.std.text { String } fn probe_qn_node(seg: Symbol) -> Node { fold_list_node( @@ -207,6 +210,24 @@ test fn language_model_construction_does_not_fill_canonical_map() -> Bool { dag_language_model().language_identity == ^dag_language_model_surface_id } +data probe_front_end_source: String = "module probe.one_member\n" + +test fn tokenize_parse_one_member_does_not_run_resolve() -> Bool { + let lm = dag_language_model() + match tokenize( + text: probe_front_end_source, + file: ^probe_front_end_file, + rules: lm.lex + ) { + Rejected { diagnostics: _ } => false + Accepted { value: tokens, diagnostics: _ } => + match parse_module(tokens: tokens, grammar: lm.grammar) { + Accepted { value: _, diagnostics: _ } => true + Rejected { diagnostics: _ } => false + } + } +} + test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { dag_canonical_symbols().member(^dag_c3_surface_sugar_service) && !dag_canonical_symbols().member(^probe_uncanonical_export) From 6a938b37bf65010f6a82e0f0060940479aea1778 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:43:47 +0000 Subject: [PATCH 13/69] Share the wall's real front-end pass warm; split inhabitance into root and channel claims Co-Authored-By: Claude Opus 5 (1M context) --- .../test_code_reference_wall_test.dag | 46 +++++++++++++------ src/v2/workflow/floor_pure_producer_share.dag | 15 +++++- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index 4350bb44eb7..ba6bd3ae4e6 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -180,30 +180,46 @@ fn serving_module_with_body(body: Node) -> Outcome { // INHABITANCE, THE ROUTE THE SUPPLIED ROOTS STAND ON: the real front end, on the source text the // inhabitance fixture encodes (built with the same wall_member and wall_module_root every claim below // uses), emits the same root (by content hash, which does not read occurrence ids) and a -// channel marking exactly `peer_leaf`. The two halves are the two things every claim below reads. -test fn supplied_root_is_what_normalize_emits_holds() -> Bool { +// channel marking exactly `peer_leaf` -- one claim per half, both reading the one shared front-end +// value below. They are the two things every claim below reads. +// THE SHARE POINT: the real front end's output for `inhabitance_module_source`, enrolled WARM in +// v2.workflow.floor_pure_producer_share. The floor forces it once, during strict preparation and +// outside every per-claim budget, and both inhabitance claims read the same value. A real +// tokenize, parse and normalize of even a one-member module costs about 75k eval steps, over the +// 72,300 new-witness budget by itself, so paying it once and sharing it is the only way the real +// path gets onto the floor at all. +fn inhabitance_normalized() -> Outcome { let lm = dag_language_model() match tokenize(text: inhabitance_module_source, file: ^wall_peer_file, rules: lm.lex) { - Rejected { diagnostics: _ } => false + Rejected { diagnostics: d } => Rejected { diagnostics: d } Accepted { value: ts, diagnostics: _ } => match parse_module(tokens: ts, grammar: lm.grammar) { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: artifact, diagnostics: _ } => normalize(parse_tree: artifact.tree) + } + } +} + +test fn supplied_root_is_what_normalize_emits_holds() -> Bool { + match inhabitance_normalized() { + Rejected { diagnostics: _ } => false + Accepted { value: emitted, diagnostics: _ } => + match inhabitance_module() { Rejected { diagnostics: _ } => false - Accepted { value: artifact, diagnostics: _ } => - match normalize(parse_tree: artifact.tree) { - Rejected { diagnostics: _ } => false - Accepted { value: emitted, diagnostics: _ } => - match inhabitance_module() { - Rejected { diagnostics: _ } => false - Accepted { value: supplied, diagnostics: _ } => - (content_hash(n: emitted.root) == content_hash(n: supplied.root)) - && (test_marker_channel_count(channel: emitted.test_markers) == 1) - && test_marker_channel_marks(channel: emitted.test_markers, name: ^peer_leaf) - } - } + Accepted { value: supplied, diagnostics: _ } => content_hash(n: emitted.root) == content_hash(n: supplied.root) } } } +test fn supplied_channel_is_what_normalize_emits_holds() -> Bool { + match inhabitance_normalized() { + Rejected { diagnostics: _ } => false + Accepted { value: emitted, diagnostics: _ } => + (test_marker_channel_count(channel: emitted.test_markers) == 1) + && test_marker_channel_marks(channel: emitted.test_markers, name: ^peer_leaf) + } +} + // RED: a test fn calling another test fn in its own module -- the case the ruling names explicitly. test fn test_fn_calling_a_test_fn_is_refused_holds() -> Bool { wall_resolve_alone( diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index b9657a12944..7b318f858d5 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -467,6 +467,18 @@ import v2.std.collection { List } // reason the parse rows above keep normalize out: the claims in that file are ABOUT the collector, // so sharing the collection would hand each one a precomputed answer to the question it asks. The // walk stays per claim; only the stage no claim names is shared. +// v2.test.name_resolve.test_code_reference_wall inhabitance_normalized EARNS ITS ROW ON THE +// NEW-WITNESS STEP BUDGET, NOT THE CPU LINE. Recompute: each of its two consumers +// (supplied_root_is_what_normalize_emits_holds, supplied_channel_is_what_normalize_emits_holds) spends +// almost all of its eval steps on this one tokenize, parse and normalize of a one-member module, +// which by itself is over the floor's 72,300-step new-witness budget. Re-derive with claim_batch +// --entry on that file ([witness] eval_steps for either claim, against a claim reading only the +// supplied fixture). Sharing: two claims, one value. Serve below recompute: the value is +// Outcome -- Nodes, symbols and the TestMarkerChannel's symbol rows, portable to the +// bottom -- so a serve is an Rc hand-over. It is WARM: nullary, sources inline in the witness. The +// claims are about what normalize EMITS, and sharing does not hand them their answer: the shared +// value is the real producer's output, and each claim still computes its own verdict from it (a +// content-hash comparison against the supplied fixture, and a channel read). data floor_cross_claim_pure_producers_warm: List = [ "v2.test.native_decl_selection.collision_resolved", "v2.extdeps.languages.bash_command_fold.bash_fold_formal_productions", @@ -495,7 +507,8 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_literal_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.braced_fn_control_parsed", - "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized" + "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized", + "v2.test.name_resolve.test_code_reference_wall.inhabitance_normalized" ] // grammar_relation_row_for_emitted HAS NOW BEEN MEASURED ON THE THIRD CONJUNCT, AND IT PASSES. From 1c277dc172e1b9c8bfde3a3e644e48f689f95066 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:47:49 +0000 Subject: [PATCH 14/69] Serve body_lowering_normalized_module warm and release the arrow-root claim's cost-debt row Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/workflow/floor_cost_debt.dag | 2 +- src/v2/workflow/floor_pure_producer_share.dag | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/v2/workflow/floor_cost_debt.dag b/src/v2/workflow/floor_cost_debt.dag index dcc2333ad36..892747caa43 100644 --- a/src/v2/workflow/floor_cost_debt.dag +++ b/src/v2/workflow/floor_cost_debt.dag @@ -732,7 +732,7 @@ fn floor_cost_debt_proven_chunk_09() -> List { } fn floor_cost_debt_proven_chunk_10() -> List { - Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_fn_body_subtree_holds", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_fn_decl_found", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_has_plus_token", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_module_well_formed", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalize_accepts", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalize_yields_arrow", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_arrow_root_resolves", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_domain_has_two_params", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_left_operand_is_atom", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operand1_lexeme_is_x", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operand2_lexeme_is_y", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operator_is_plus", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_resolves", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_transform_has_three_children", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_return_holds", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_field_access_postfix_rejects", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_normalize_accepts_projection_module", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_projection_lowers_to_transform", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.dropping_the_transport_realization_roster_surfaces_more", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.withdrawing_the_admission_surfaces_the_edge_under_the_wide_root", tail: Empty {} } } } } } } } } } } } } } } } } } } } } + Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_fn_body_subtree_holds", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_fn_decl_found", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_has_plus_token", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_module_well_formed", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalize_accepts", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalize_yields_arrow", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_domain_has_two_params", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_left_operand_is_atom", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operand1_lexeme_is_x", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operand2_lexeme_is_y", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operator_is_plus", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_resolves", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_transform_has_three_children", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_return_holds", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_field_access_postfix_rejects", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_normalize_accepts_projection_module", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_projection_lowers_to_transform", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.dropping_the_transport_realization_roster_surfaces_more", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.withdrawing_the_admission_surfaces_the_edge_under_the_wide_root", tail: Empty {} } } } } } } } } } } } } } } } } } } } } fn floor_cost_debt_proven_chunk_11() -> List { diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index 7b318f858d5..e680c80eb65 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -479,6 +479,16 @@ import v2.std.collection { List } // claims are about what normalize EMITS, and sharing does not hand them their answer: the shared // value is the real producer's output, and each claim still computes its own verdict from it (a // content-hash comparison against the supplied fixture, and a channel read). +// +// v2.test.manual.body_lowering_normalize_add body_lowering_normalized_module IS THE SAME SHAPE, at +// scale: one tokenize, parse and normalize of the `add` module that thirteen claims in that file +// re-derive per claim. It was measured when the test-code wall (which adds a required marker channel +// to admit_normalized_tree) touched body_lowering_normalized_arrow_root_resolves, a cost-debt row. +// That claim is roughly three quarters this producer and one quarter its own resolve, so serving the +// producer brings it under the new-witness budget and its debt row is released in the same change. +// Re-derive with claim_batch --entry on that file: [witness] eval_steps for a claim that only forces +// the producer, against the arrow-root claim. The value is Optional, portable to the +// bottom. data floor_cross_claim_pure_producers_warm: List = [ "v2.test.native_decl_selection.collision_resolved", "v2.extdeps.languages.bash_command_fold.bash_fold_formal_productions", @@ -508,7 +518,8 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_literal_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.braced_fn_control_parsed", "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized", - "v2.test.name_resolve.test_code_reference_wall.inhabitance_normalized" + "v2.test.name_resolve.test_code_reference_wall.inhabitance_normalized", + "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_module" ] // grammar_relation_row_for_emitted HAS NOW BEEN MEASURED ON THE THIRD CONJUNCT, AND IT PASSES. From 739644cd83e493cec222740a4438fa0649bddf13 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 11:08:06 +0000 Subject: [PATCH 15/69] Derive resolve canonical symbols from the language model that was passed. Always installing dag_canonical_symbols() widened empty-prelude resolve: a service atom that used to refuse now accepted. Void grammars keep lm.canonical_symbols; modeled grammars build the map from that model's grammar and lex at resolution time. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 4 ++-- src/v2/compiler/03_resolve.dag | 4 ++-- src/v2/extdeps/languages/dag.dag | 20 +++++++++++++++++- src/v2/std/compilers/lexing.dag | 11 ++++++++++ .../one_member_cost_probe_test.dag | 21 ++++++++++++++++++- 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 19f44e72134..1dd52faa44b 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,7 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_canonical_symbols, + dag_resolution_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -129,7 +129,7 @@ fn resolution_context( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) ), - canonical_symbols: dag_canonical_symbols() + canonical_symbols: dag_resolution_canonical_symbols(lm: lm) }, diagnostics: d } diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 9a70513876e..545710e9690 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,7 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_canonical_symbols, + dag_resolution_canonical_symbols, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, dag_resolve_preserve_module_metadata_subtree, @@ -234,7 +234,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_canonical_symbols(), + canonical_symbols: dag_resolution_canonical_symbols(lm: lm), symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 9d24b8af1c7..7cc5dc202e1 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -86,7 +86,8 @@ import v2.std.compilers.lexing { TriviaRule, WhitespaceChar, lex_rule_set_insert_token_classes, - lex_rule_set_token_class_map + lex_rule_set_token_class_map, + lex_rules_insert_token_classes } import v2.std.node { Arrow, @@ -2312,6 +2313,23 @@ fn dag_canonical_symbols() -> PointwisePower { dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } +fn dag_resolution_canonical_symbols(lm: LanguageModel) -> PointwisePower { + match lm.grammar { + VoidGrammar => lm.canonical_symbols + ModeledGrammar { root: _ } => + dag_pointwise_from_symbol_map( + m: dag_language_model_binding_canonical_map( + m: dag_surface_sugar_symbol_map( + m: lex_rules_insert_token_classes( + m: grammar_carried_symbol_map(grammar: lm.grammar), + rules: lm.lex + ) + ) + ) + ) + } +} + fn dag_language_model_void() -> LanguageModel { LanguageModel { lex: dag_lex(), diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index e50b3ae6a91..8dbc6634af1 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -226,6 +226,17 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List, + rules: LexRules +) -> Map { + match rules { + VoidLexRules => m + ModeledLexRules { root: root } => + lex_rule_set_insert_token_classes(m: m, rules: root) + } +} + fn lex_rule_set_insert_token_classes( m: Map, rules: LexRuleSet diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index c951e2bc224..62afe57ebfb 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -14,6 +14,7 @@ import v2.extdeps.languages.dag { dag_canonical_symbols, dag_grammar_atom, dag_language_model, + dag_language_model_surface_empty_prelude, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } @@ -206,8 +207,26 @@ test fn one_member_missing_subject_still_refuses() -> Bool { }) } +test fn empty_prelude_service_atom_still_refuses() -> Bool { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model_surface_empty_prelude(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} + test fn language_model_construction_does_not_fill_canonical_map() -> Bool { - dag_language_model().language_identity == ^dag_language_model_surface_id + let lm = dag_language_model() + (lm.language_identity == ^dag_language_model_surface_id) + && !lm.canonical_symbols.member(^dag_surface_module_header) } data probe_front_end_source: String = "module probe.one_member\n" From a018772d9f9f8eaa760a034f5822f8a866126abf Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 11:40:33 +0000 Subject: [PATCH 16/69] Supply the empty marker channel in #11599's one-member probe fixture Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 8c234c747d3..72b62ac99e3 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -7,6 +7,7 @@ import v2.compiler.name_resolve { resolve_with_admission } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } +import v2.std.declaration_marker { test_marker_channel_empty } import v2.compiler.resolve { ResolvedTree } import v2.extdeps.languages.dag { dag_canonical_symbols, @@ -80,6 +81,7 @@ fn probe_one_member_qn() -> QualifiedName { fn probe_roots_for_export(export_sym: Symbol) -> Outcome> { bind_outcome( o: admit_normalized_tree( + test_markers: test_marker_channel_empty(), root: probe_one_member_root_export(export_sym: export_sym), diagnostics: None ), From 23daa3ea926481d5b21d350012027f646b6e79d9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 11:43:43 +0000 Subject: [PATCH 17/69] Derive the grammar symbol map from grammar_to_node and restore linear lex membership. The carried-symbol map was a parallel GrammarExpr walk; resolve still pays that set once, but the atoms now come from the same node projection as grammar_carries_symbol. Per-query token-class tests walk TokenRule again instead of rebuilding the map on body-lowering's hot path. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 6 +- src/v2/std/compilers/lexing.dag | 22 +++++-- src/v2/std/grammar.dag | 53 +---------------- .../one_member_cost_probe_test.dag | 57 ------------------- 4 files changed, 22 insertions(+), 116 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 7cc5dc202e1..44e24f753f0 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -87,6 +87,7 @@ import v2.std.compilers.lexing { WhitespaceChar, lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, + lex_rule_set_token_class_member, lex_rules_insert_token_classes } import v2.std.node { @@ -2365,7 +2366,10 @@ fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower Bool { - dag_symbol_map_has(m: lex_rule_set_token_class_map(rules: dag_lex_rules()), sym: sym) + lex_rule_set_token_class_member( + rules: dag_lex_rules(), + token_class: sym + ) } fn dag_sync_tokens() -> List { diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index 8dbc6634af1..89c6243da51 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -11,8 +11,7 @@ import v2.std.collection { Map, empty_map, list_at_optional, - map_insert, - map_lookup + map_insert } import v2.std.optional { Absent, @@ -259,13 +258,24 @@ fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { lex_rule_set_insert_token_classes(m: empty_map(), rules: rules) } -fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { - match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { - Present { value: _ } => true - Absent => false +fn lex_rule_token_class_member(rule: LexRule, token_class: Symbol) -> Bool { + match rule { + TokenRule { token_class: c, pattern: _ } => c == token_class + TriviaRule { pattern: _ } => false + AnnotationRule { annotation_class: _, pattern: _ } => false } } +fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + fold_list( + xs: rules.rules, + empty: false, + cons: fn(acc, rule) { + acc || lex_rule_token_class_member(rule: rule, token_class: token_class) + } + ) +} + type LexPatternFold { empty: R literal: fn(v2.std.text.String) -> R diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index fc86b6703b8..c94ec0925e8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -1769,59 +1769,8 @@ fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map List { - fold_grammar_expr( - expr: expr, - algebra: GrammarExprFold { - terminal: fn(token_class, stamp) { - match stamp { - StampClass => Cons { head: token_class, tail: Empty } - StampBinding { binding: b } => - Cons { head: token_class, tail: Cons { head: b, tail: Empty } } - StampLexeme => - Cons { - head: token_class, - tail: Cons { head: ^grammar_terminal_stamp_lexeme, tail: Empty } - } - } - }, - nonterminal: fn(production) { Cons { head: production, tail: Empty } }, - sequence: fn(left, right) { list_append(left: left, right: right) }, - choice: fn(left, right) { list_append(left: left, right: right) }, - optional: fn(element) { element }, - repeat: fn(element) { element } - } - ) -} - -fn grammar_production_insert_carried_symbols( - m: Map, - production: GrammarProduction -) -> Map { - grammar_node_insert_atom_identities( - m: grammar_symbol_list_put( - m: grammar_symbol_map_put(m: m, sym: production.name), - xs: grammar_expr_carried_symbols(expr: production.expression) - ), - n: production.emitted - ) -} - -fn grammar_root_carried_symbol_map(root: GrammarRoot) -> Map { - fold_list( - xs: root.productions, - empty: grammar_symbol_map_put(m: empty_map(), sym: root.start), - cons: fn(acc, production) { - grammar_production_insert_carried_symbols(m: acc, production: production) - } - ) -} - fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { - match grammar { - VoidGrammar => empty_map() - ModeledGrammar { root: root } => grammar_root_carried_symbol_map(root: root) - } + grammar_node_insert_atom_identities(m: empty_map(), n: grammar_to_node(grammar: grammar)) } type GrammarInterpretationDirection diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 62afe57ebfb..a53a67ad11f 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -19,23 +19,9 @@ import v2.extdeps.languages.dag { } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list_node } -import v2.std.collection { map_lookup } import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } -import v2.std.grammar { - GrammarProduction, - GrammarRoot, - ModeledGrammar, - ParseGrammar, - StampClass, - Terminal, - grammar_atom, - grammar_carried_symbol_map, - grammar_carries_symbol, - grammar_empty_sync_tokens -} import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } -import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } import v2.std.text { String } @@ -124,49 +110,6 @@ fn probe_resolved_export_identity(tree: ResolvedTree, wanted: Symbol) -> Bool { } } -fn map_carries(grammar: ParseGrammar, sym: Symbol) -> Bool { - match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { - Present { value: _ } => true - Absent => false - } -} - -fn eq_probe_grammar() -> ParseGrammar { - ModeledGrammar { - root: GrammarRoot { - start: ^eq_probe_start, - productions: Cons { - head: GrammarProduction { - name: ^eq_probe_start, - expression: Terminal { - token_class: ^eq_probe_tok, - stamp: StampClass - }, - emitted: grammar_atom(identity: ^eq_probe_emitted) - }, - tail: Empty - }, - sync_tokens: grammar_empty_sync_tokens() - } - } -} - -fn map_agrees_with_node_fold(sym: Symbol) -> Bool { - let g = eq_probe_grammar() - map_carries(grammar: g, sym: sym) == grammar_carries_symbol(grammar: g, sym: sym) -} - -test fn carried_symbol_map_agrees_with_node_fold_on_complete_probe_grammar() -> Bool { - map_agrees_with_node_fold(sym: ^eq_probe_start) - && map_agrees_with_node_fold(sym: ^eq_probe_tok) - && map_agrees_with_node_fold(sym: ^eq_probe_emitted) - && map_agrees_with_node_fold(sym: ^eq_probe_absent) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_start) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_tok) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_emitted) - && !map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_absent) -} - test fn one_member_module_resolves_under_full_language_model() -> Bool { probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { match probe_resolve(roots: roots) { From e96639cdd5bb6aa3ee4c4fca41e31ebbe3572aa6 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:17:57 +0000 Subject: [PATCH 18/69] Carry canonical symbols on the language model resolve actually uses. Resolution reads lm.canonical_symbols instead of a second dag-only derivation that discarded sibling models' declared sets. dag_language_model fills that field; tokenize/parse demand dag_lex and dag_grammar so they do not construct the map. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 3 +-- src/v2/compiler/03_resolve.dag | 3 +-- src/v2/extdeps/languages/dag.dag | 22 ++----------------- .../one_member_cost_probe_test.dag | 13 ++++++----- 4 files changed, 11 insertions(+), 30 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 1dd52faa44b..fdf045edf6f 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,6 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_resolution_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -129,7 +128,7 @@ fn resolution_context( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) ), - canonical_symbols: dag_resolution_canonical_symbols(lm: lm) + canonical_symbols: lm.canonical_symbols }, diagnostics: d } diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 545710e9690..97328d0981c 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,6 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_resolution_canonical_symbols, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, dag_resolve_preserve_module_metadata_subtree, @@ -234,7 +233,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_resolution_canonical_symbols(lm: lm), + canonical_symbols: lm.canonical_symbols, symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 44e24f753f0..af690d4d360 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -87,8 +87,7 @@ import v2.std.compilers.lexing { WhitespaceChar, lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, - lex_rule_set_token_class_member, - lex_rules_insert_token_classes + lex_rule_set_token_class_member } import v2.std.node { Arrow, @@ -2314,23 +2313,6 @@ fn dag_canonical_symbols() -> PointwisePower { dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } -fn dag_resolution_canonical_symbols(lm: LanguageModel) -> PointwisePower { - match lm.grammar { - VoidGrammar => lm.canonical_symbols - ModeledGrammar { root: _ } => - dag_pointwise_from_symbol_map( - m: dag_language_model_binding_canonical_map( - m: dag_surface_sugar_symbol_map( - m: lex_rules_insert_token_classes( - m: grammar_carried_symbol_map(grammar: lm.grammar), - rules: lm.lex - ) - ) - ) - ) - } -} - fn dag_language_model_void() -> LanguageModel { LanguageModel { lex: dag_lex(), @@ -2346,7 +2328,7 @@ fn dag_language_model() -> LanguageModel { lex: dag_lex(), grammar: dag_grammar(), language_identity: ^dag_language_model_surface_id, - canonical_symbols: dag_language_model_empty_canonical_symbol_set(), + canonical_symbols: dag_canonical_symbols(), core: bool_model_core() } } diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index a53a67ad11f..614d7eabb3a 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -12,9 +12,11 @@ import v2.compiler.resolve { ResolvedTree } import v2.compiler.tokenize { tokenize } import v2.extdeps.languages.dag { dag_canonical_symbols, + dag_grammar, dag_grammar_atom, dag_language_model, dag_language_model_surface_empty_prelude, + dag_lex, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } @@ -166,24 +168,23 @@ test fn empty_prelude_service_atom_still_refuses() -> Bool { }) } -test fn language_model_construction_does_not_fill_canonical_map() -> Bool { +test fn language_model_carries_its_canonical_symbols() -> Bool { let lm = dag_language_model() - (lm.language_identity == ^dag_language_model_surface_id) - && !lm.canonical_symbols.member(^dag_surface_module_header) + lm.canonical_symbols.member(^dag_c3_surface_sugar_service) + && !lm.canonical_symbols.member(^probe_uncanonical_export) } data probe_front_end_source: String = "module probe.one_member\n" test fn tokenize_parse_one_member_does_not_run_resolve() -> Bool { - let lm = dag_language_model() match tokenize( text: probe_front_end_source, file: ^probe_front_end_file, - rules: lm.lex + rules: dag_lex() ) { Rejected { diagnostics: _ } => false Accepted { value: tokens, diagnostics: _ } => - match parse_module(tokens: tokens, grammar: lm.grammar) { + match parse_module(tokens: tokens, grammar: dag_grammar()) { Accepted { value: _, diagnostics: _ } => true Rejected { diagnostics: _ } => false } From a81faad720fe8e77db8fa7e8df5bc06afb2d6f71 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:28:56 +0000 Subject: [PATCH 19/69] Delete unused lex/canonical wrappers left by the resolve cutover. lex_rules_insert_token_classes and dag_language_model_canonical_symbols had no callers. Map membership for the pointwise set is map_lookup at the closure, not a dag-local has_* wrapper. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 16 ++++------------ src/v2/std/compilers/lexing.dag | 11 ----------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index af690d4d360..bc645a6b096 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2217,17 +2217,13 @@ fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { map_insert(m: m, key: sym, value: true) } -fn dag_symbol_map_has(m: Map, sym: Symbol) -> Bool { - match map_lookup(m: m, key: sym) { - Present { value: _ } => true - Absent => false - } -} - fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { - dag_symbol_map_has(m: m, sym: sym) + match map_lookup(m: m, key: sym) { + Present { value: _ } => true + Absent => false + } } } } @@ -2343,10 +2339,6 @@ fn dag_language_model_surface_empty_prelude() -> LanguageModel { } } -fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower { - lm.canonical_symbols -} - fn dag_lex_token_symbol(sym: Symbol) -> Bool { lex_rule_set_token_class_member( rules: dag_lex_rules(), diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index 89c6243da51..c451c29ffcc 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -225,17 +225,6 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List, - rules: LexRules -) -> Map { - match rules { - VoidLexRules => m - ModeledLexRules { root: root } => - lex_rule_set_insert_token_classes(m: m, rules: root) - } -} - fn lex_rule_set_insert_token_classes( m: Map, rules: LexRuleSet From c1e81e65c90bbe7b69a46ca584547baea460357a Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:33:26 +0000 Subject: [PATCH 20/69] Warm-share the portable dag canonical-symbol map across floor claims. PointwisePower.member is a closure and cannot be stored. dag_canonical_symbol_map is the Map fill every resolving claim re-derived per frame; the floor serves that map once at preparation and dag_canonical_symbols wraps it at the consumer. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 3 +++ src/v2/workflow/floor_pure_producer_share.dag | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index bc645a6b096..30c7f36b409 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2294,6 +2294,9 @@ fn dag_language_model_binding_canonical_map(m: Map) -> Map Map { dag_language_model_binding_canonical_map( m: dag_surface_sugar_symbol_map( diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index b9657a12944..816a33f2165 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -467,6 +467,24 @@ import v2.std.collection { List } // reason the parse rows above keep normalize out: the claims in that file are ABOUT the collector, // so sharing the collection would hand each one a precomputed answer to the question it asks. The // walk stays per claim; only the stage no claim names is shared. +// DAG CANONICAL-SYMBOL MAP EARNS ITS ROW ON THE ENROLMENT-MARGIN CPU THE EVAL-STEP GATE +// CANNOT SEE. Sibling floor run 35340819603 (#11580 merging the resolve-cost head that +// establishes this map once per ResolutionContext) passed eval-steps and refused every +// resolving claim at the 302ms enrolment margin, including one-member resolve probes that +// sit near 40k steps. The same fill shows on grandfathered resolve claims. Re-derive with +// required_floor_claim_cost.tsv on that run plus claim_batch --entry +// src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag [witness] cpu vs eval_steps: +// the map fill is host work outside the step counter, paid once per claim frame because the +// eval-frame memo dies at the claim boundary. Sharing census is the call-site graph: every +// dag_language_model() resolve path reads dag_canonical_symbols(), which is a PointwisePower +// closure and therefore not portable (ServeCacheValueNotPortable, the prepare_grammar +// nullable_set shape). The share point is the Map it is built from -- +// dag_canonical_symbol_map -- symbols and bools only; dag_canonical_symbols wraps that map +// at the consumer. Serve-below-recompute is the header's zero-walk Rc hand-over of a small +// map against a fill that already exceeds the enrolment margin in-fold. WARM, NEVER +// CLAIM-FORCED, by the same ceiling arithmetic as dag_prepared_grammar: an in-fold first +// touch of this fill dies on the lane's margin and restarts per claim. The module is under +// src/v2/extdeps, so it resolves in every required-floor subject. data floor_cross_claim_pure_producers_warm: List = [ "v2.test.native_decl_selection.collision_resolved", "v2.extdeps.languages.bash_command_fold.bash_fold_formal_productions", @@ -495,7 +513,8 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_literal_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.braced_fn_control_parsed", - "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized" + "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized", + "v2.extdeps.languages.dag.dag_canonical_symbol_map" ] // grammar_relation_row_for_emitted HAS NOW BEEN MEASURED ON THE THIRD CONJUNCT, AND IT PASSES. From 5b771c1ab5b1c5cde4ff7c851914320bc927ea85 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:43:02 +0000 Subject: [PATCH 21/69] Fix the merged warm roster's separator Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/workflow/floor_pure_producer_share.dag | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index da4567449dd..a6bc76ab031 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -538,8 +538,7 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.parse.expression_bodied_fn_decl_parse.braced_fn_control_parsed", "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized", "v2.test.name_resolve.test_code_reference_wall.inhabitance_normalized", - "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_module" -// + "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_module", "v2.extdeps.languages.dag.dag_canonical_symbol_map" ] From 8024c04a5ef448e81fd9550a5a1ca1bd2901b39c Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 15:11:10 +0000 Subject: [PATCH 22/69] Wall: item-scoped rule, the qualified-path door checked, precedence and default shapes proven Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/03_resolve.dag | 37 ++++----- src/v2/std/declaration_marker.dag | 69 ++++------------- .../test_code_reference_wall_test.dag | 75 +++++++++++++------ 3 files changed, 82 insertions(+), 99 deletions(-) diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index d521d328cae..63179dde17d 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,6 @@ import v2.compiler.normalized_tree { NormalizedTree } import v2.std.declaration_marker { ReachesNoTestCode, ReachesTestMarkedDeclaration, - ReachesTestModuleFromServingCode, TestCodeIndex, test_code_index_add_module, test_code_index_empty, @@ -472,16 +471,16 @@ fn try_resolve_qualified_name_node(ctx: ResolveContext, n: Node) -> Optional optional_present( - value: Accepted { - value: canonical_atom( - identity: symbol_index_node_identity( - node: resolved_node, - fallback: ^resolve_reason_unbound_symbol - ), - occurrence_id: n.occurrence_id + value: resolve_atom_bound( + ctx: ctx, + n: n, + canonical: symbol_index_node_identity( + node: resolved_node, + fallback: ^resolve_reason_unbound_symbol ), - diagnostics: pending - } + path: path, + pending: pending + ) ) Absent => optional_present( @@ -527,10 +526,11 @@ fn test_code_referenced_diagnostic(reason: Symbol, n: Node) -> Diagnostic { } } -// THE WALL (owner ruling 2026-09-16/17): `test` segregates test code from serving code. No code may -// reference a test-marked declaration, a test fn in the same module included, and serving code may -// not depend on any declaration of a module that declares tests. Both are decided here, where the -// reference binds, from the path it bound to, and both refuse with the reference's own locus. +// THE WALL (owner ruling 2026-09-16/17): `test` segregates test code from serving code, and no code +// may reference a test-marked declaration, a test fn in the same module included. It is decided here, +// where the reference binds, from the path it bound to, and it refuses with the reference's own locus. +// Every door that binds a module-level name reaches this function: the root scope, the symbol index, +// and a qualified path (try_resolve_qualified_name_node). fn resolve_atom_bound( ctx: ResolveContext, n: Node, @@ -538,7 +538,7 @@ fn resolve_atom_bound( path: QualifiedName, pending: Diagnostics ) -> Outcome { - match test_code_reach(index: ctx.namespace.test_code, subject: ctx.namespace.declared_in, path: path) { + match test_code_reach(index: ctx.namespace.test_code, path: path) { ReachesNoTestCode => Accepted { value: canonical_atom(identity: canonical, occurrence_id: n.occurrence_id), @@ -551,13 +551,6 @@ fn resolve_atom_bound( rejected: diagnostics_singleton(d: test_code_referenced_diagnostic(reason: ^resolve_reason_test_code_referenced, n: n)) ) } - ReachesTestModuleFromServingCode => - Rejected { - diagnostics: rejected_with_pending( - pending: pending, - rejected: diagnostics_singleton(d: test_code_referenced_diagnostic(reason: ^resolve_reason_serving_code_depends_on_test_code, n: n)) - ) - } } } diff --git a/src/v2/std/declaration_marker.dag b/src/v2/std/declaration_marker.dag index ae813dd4765..b89942c9330 100644 --- a/src/v2/std/declaration_marker.dag +++ b/src/v2/std/declaration_marker.dag @@ -1,7 +1,5 @@ module v2.std.declaration_marker -import std.algebra { Empty } -import v2.std.algebra { fold_list, list_snoc_item } import v2.std.collection { Absent, List, Map, Present, empty_map, map_insert, map_lookup } import v2.std.node { Symbol } import v2.std.qualified_name { QualifiedName, qualified_name_snoc } @@ -43,24 +41,24 @@ fn test_marker_channel_count(channel: TestMarkerChannel) -> Int { } // THE RESOLUTION-SIDE READING OF THE CHANNEL: the compilation's marked declarations as the qualified -// paths a reference binds to, plus the modules that declare any test code. Both keys are the paths -// v2.compiler.symbol_index_fill indexes (module path, then one segment per named member), so a -// binding answered by the symbol index is judged by the path it answered with and nothing else. +// paths a reference binds to. The key is the path v2.compiler.symbol_index_fill indexes (module path, +// then one segment per named member), so a binding answered by the symbol index is judged by the path +// it answered with and nothing else. +// +// ITEM-SCOPED, NOT MODULE-SCOPED. Test code is the declarations the author marked, and nothing else: +// an unmarked helper that happens to live in a module with tests is ordinary code, and a module-level +// rule ("any test in the module makes all of it test code") is wrong in both directions -- it refuses +// references to ordinary helpers and says nothing finer about the marked ones. type TestCodeIndex { marked_paths: Map - test_modules: Map } -// Why a reference is refused. A marked declaration may be referenced by nothing, a test fn in the -// same module included. Any other member of a module that declares tests is test code too, and only -// another test module may depend on it. type TestCodeReach = ReachesNoTestCode | ReachesTestMarkedDeclaration - | ReachesTestModuleFromServingCode fn test_code_index_empty() -> TestCodeIndex { - TestCodeIndex { marked_paths: empty_map(), test_modules: empty_map() } + TestCodeIndex { marked_paths: empty_map() } } fn test_code_index_add_module( @@ -68,53 +66,16 @@ fn test_code_index_add_module( module_qn: QualifiedName, channel: TestMarkerChannel ) -> TestCodeIndex { - if test_marker_channel_count(channel: channel) == 0 { - index - } else { - TestCodeIndex { - marked_paths: fold(channel.marked, init: index.marked_paths, f: fn(acc, row) { - map_insert(m: acc, key: qualified_name_snoc(qn: module_qn, segment: row.name), value: true) - }), - test_modules: map_insert(m: index.test_modules, key: module_qn, value: true) - } - } -} - -fn test_code_index_declares_tests(index: TestCodeIndex, module_qn: QualifiedName) -> Bool { - match map_lookup(m: index.test_modules, key: module_qn) { - Present { value: _ } => true - Absent => false + TestCodeIndex { + marked_paths: fold(channel.marked, init: index.marked_paths, f: fn(acc, row) { + map_insert(m: acc, key: qualified_name_snoc(qn: module_qn, segment: row.name), value: true) + }) } } -type TestModulePrefixWalk { - prefix: QualifiedName - found: Bool -} - -fn test_code_index_path_in_test_module(index: TestCodeIndex, path: QualifiedName) -> Bool { - let walk = fold_list(xs: path, empty: TestModulePrefixWalk { prefix: Empty, found: false }, cons: fn(acc, segment) { - if acc.found { - acc - } else { - let next = list_snoc_item(xs: acc.prefix, item: segment) - TestModulePrefixWalk { prefix: next, found: test_code_index_declares_tests(index: index, module_qn: next) } - } - }) - walk.found -} - -// `subject` is the module the reference is written in. -fn test_code_reach(index: TestCodeIndex, subject: QualifiedName, path: QualifiedName) -> TestCodeReach { +fn test_code_reach(index: TestCodeIndex, path: QualifiedName) -> TestCodeReach { match map_lookup(m: index.marked_paths, key: path) { Present { value: _ } => ReachesTestMarkedDeclaration - Absent => - if test_code_index_declares_tests(index: index, module_qn: subject) { - ReachesNoTestCode - } else if test_code_index_path_in_test_module(index: index, path: path) { - ReachesTestModuleFromServingCode - } else { - ReachesNoTestCode - } + Absent => ReachesNoTestCode } } diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index ba6bd3ae4e6..93d0f14d8c9 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -7,10 +7,13 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // claim reports the head refusal reason or ACCEPTED, so a red that refuses for some OTHER reason (an // unbound name, a malformed root) does not pass for the wall. // -// The owner ruling (2026-09-16/17) has two halves, and each has a red and a control here: a -// test-marked declaration may be referenced by NOTHING, a test fn in the same module included; and -// serving code may not depend on any declaration of a module that declares tests, while another -// test module may. +// The owner ruling (2026-09-16/17): a test-marked declaration may be referenced by NOTHING, a test fn +// in the same module included. The wall is ITEM-scoped: an unmarked declaration is ordinary code even +// in a module that declares tests (a whole-module rule is wrong in both directions, the lesson of the +// v1 wall in gunbc#11505). The shapes below are the ones v1's reviewers found one per round, each +// with its red or its control: same-module and cross-module references, a QUALIFIED path to a test +// fn, precedence of a module's own declaration over an imported one of the same name, and a +// reference from a parameter default -- which v2 cannot express at all (it refuses at parse). // // WHY THE ROOTS ARE SUPPLIED (DESIGN section 3). Producing two roots from source costs two tokenize, // parse and normalize passes (about 120k eval steps for two one-line modules) before resolution @@ -257,15 +260,6 @@ test fn serving_code_calling_another_modules_test_fn_is_refused_holds() -> Bool ) == "resolve_reason_test_code_referenced" } -// RED: serving code depending on an UNMARKED declaration of a module that declares tests. -test fn serving_code_depending_on_a_test_module_is_refused_holds() -> Bool { - wall_resolve( - subject_segment: ^serving, - subject: serving_module_with_body(body: wall_atom(identity: ^shared_fixture)), - peer: peer_test_module() - ) == "resolve_reason_serving_code_depends_on_test_code" -} - // CONTROL: the intended shape, a test fn calling an ordinary fn, resolves. test fn test_fn_calling_an_ordinary_fn_resolves_holds() -> Bool { wall_resolve_alone( @@ -294,28 +288,63 @@ test fn a_local_binder_shadowing_a_test_fn_resolves_holds() -> Bool { ) == "ACCEPTED" } -// CONTROL for the dependency half: the same reference from the same serving module resolves once the -// peer declares no tests, so the red above is about the peer's test code and nothing else. -test fn serving_code_depending_on_a_serving_module_resolves_holds() -> Bool { +// CONTROL, ITEM SCOPE: an unmarked declaration of a module that declares tests is ordinary code, and +// serving code may reference it. +test fn an_ordinary_declaration_of_a_test_module_resolves_holds() -> Bool { wall_resolve( subject_segment: ^serving, subject: serving_module_with_body(body: wall_atom(identity: ^shared_fixture)), - peer: peer_serving_module() + peer: peer_test_module() ) == "ACCEPTED" } -// CONTROL: a test module may depend on another test module's ordinary declarations. -test fn test_module_depending_on_a_test_module_resolves_holds() -> Bool { +// `wall.peer.peer_leaf` as a qualified-path node, the spine qualified_name_from_node reads. +fn wall_qualified(segments: List) -> Node { + fold_list_node(xs: segments, item_node: fn(sym) { wall_atom(identity: sym) }) +} + +// RED: a QUALIFIED path to another module's test fn is refused at the qualified-path door +// (v2.compiler.resolve try_resolve_qualified_name_node), which binds by the whole path rather than a +// bare name. v2 lowering does not yet carry a qualified path in a fn BODY (`= wall.peer.peer_leaf` +// lowers to the atom `wall`; tracked as its own defect), so this supplies the node the door reads. +test fn a_qualified_path_to_a_test_fn_is_refused_holds() -> Bool { wall_resolve( - subject_segment: ^consumer, + subject_segment: ^serving, + subject: serving_module_with_body(body: wall_qualified(segments: [^wall, ^peer, ^peer_leaf])), + peer: peer_test_module() + ) == "resolve_reason_test_code_referenced" +} + +// CONTROL, PRECEDENCE: a module's own declaration beats a same-named declaration elsewhere. The +// subject declares an ordinary `peer_leaf`; the peer's `peer_leaf` is a test fn. The subject's +// reference binds to its own, so it resolves. +test fn a_modules_own_declaration_beats_a_same_named_test_fn_holds() -> Bool { + wall_resolve( + subject_segment: ^serving, subject: wall_tree( - root: wall_module_root(leaf_segment: ^consumer, members: [ - wall_member(name: ^consumer_holds, body: wall_atom(identity: ^shared_fixture)) + root: wall_module_root(leaf_segment: ^serving, members: [ + wall_member(name: ^peer_leaf, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member(name: ^uses_peer, body: wall_atom(identity: ^peer_leaf)) ]), - marked: [^consumer_holds] + marked: [] ), peer: peer_test_module() ) == "ACCEPTED" } +data default_reference_source: String = "module m\nfn f(x: T = g()) -> T = x\n" +// A reference from a parameter DEFAULT cannot be written in v2: the grammar has no default, and the +// source refuses at parse, before resolution could miss it. If this ever parses, the default has +// become a place a reference can hide, and it needs its own red. +test fn a_parameter_default_is_refused_at_parse_holds() -> Bool { + let lm = dag_language_model() + match tokenize(text: default_reference_source, file: ^wall_default_file, rules: lm.lex) { + Rejected { diagnostics: _ } => false + Accepted { value: ts, diagnostics: _ } => + match parse_module(tokens: ts, grammar: lm.grammar) { + Rejected { diagnostics: _ } => true + Accepted { value: _, diagnostics: _ } => false + } + } +} From 9ef6496910cf908c9c470b47a83ea4db8190ab91 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 15:22:01 +0000 Subject: [PATCH 23/69] lookup_chain reports which scope answered; drop the second walk Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/03_resolve.dag | 57 +++++++++++++++------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 63179dde17d..083a55ae307 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -339,13 +339,21 @@ fn resolve_ctx_with_under_module_root(ctx: ResolveContext, under_module_root: Bo } } -fn lookup_chain(s: Scope, name: Symbol) -> Outcome { +// Which scope answered a name, in the one walk that finds it. A local binder shadows the module +// scope, so only a ROOT hit can be a reference to a module-level declaration, and the test-code wall +// (resolve_atom_bound) reads that distinction from here rather than re-walking the chain. +type ScopeBinding + = BoundInFrame { canonical: Symbol } + | BoundAtRoot { canonical: Symbol } + | ScopeUnbound + +fn lookup_chain(s: Scope, name: Symbol) -> Outcome { match s { ScopeFrame { locals: m, outer: o } => match v2.std.collection.map_get(m, name) { Accepted { value: opt, diagnostics: d_here } => match opt { Present { value: v } => - Accepted { value: optional_present(value: v), diagnostics: d_here } + Accepted { value: BoundInFrame { canonical: v }, diagnostics: d_here } Absent => match lookup_chain(s: o, name: name) { Accepted { value: found, diagnostics: d_outer } => @@ -364,8 +372,8 @@ fn lookup_chain(s: Scope, name: Symbol) -> Outcome { ScopeRoot { module: mod } => match v2.std.collection.map_get(mod.bindings, name) { Accepted { value: opt, diagnostics: d } => match opt { - Present { value: v } => Accepted { value: optional_present(value: v), diagnostics: d } - Absent => Accepted { value: optional_absent(), diagnostics: d } + Present { value: v } => Accepted { value: BoundAtRoot { canonical: v }, diagnostics: d } + Absent => Accepted { value: ScopeUnbound, diagnostics: d } } Rejected { diagnostics: ds } => Rejected { diagnostics: ds } } @@ -498,19 +506,6 @@ fn try_resolve_qualified_name_node(ctx: ResolveContext, n: Node) -> Optional Bool { - match s { - ScopeFrame { locals: m, outer: o } => - match map_lookup(m: m, key: name) { - Present { value: _ } => true - Absent => scope_frames_bind(s: o, name: name) - } - ScopeRoot { module: _ } => false - } -} - fn root_binding_origin(namespace: Namespace, name: Symbol) -> QualifiedName { match map_lookup(m: namespace.imported_origins, key: name) { Present { value: origin } => origin @@ -560,22 +555,20 @@ fn resolve_atom(ctx: ResolveContext, n: Node, identity: Symbol) -> Outcome } else { match lookup_chain(s: ctx.scope, name: identity) { Accepted { value: found, diagnostics: pending } => match found { - Present { value: canonical } => - if scope_frames_bind(s: ctx.scope, name: identity) { - Accepted { - value: canonical_atom(identity: canonical, occurrence_id: n.occurrence_id), - diagnostics: pending - } - } else { - resolve_atom_bound( - ctx: ctx, - n: n, - canonical: canonical, - path: qualified_name_snoc(qn: root_binding_origin(namespace: ctx.namespace, name: identity), segment: identity), - pending: pending - ) + BoundInFrame { canonical: canonical } => + Accepted { + value: canonical_atom(identity: canonical, occurrence_id: n.occurrence_id), + diagnostics: pending } - Absent => + BoundAtRoot { canonical: canonical } => + resolve_atom_bound( + ctx: ctx, + n: n, + canonical: canonical, + path: qualified_name_snoc(qn: root_binding_origin(namespace: ctx.namespace, name: identity), segment: identity), + pending: pending + ) + ScopeUnbound => match lookup_symbol_index_atom_identity(ctx: ctx, name: identity) { SymbolIndexAtomHit { canonical: canonical, path: path } => resolve_atom_bound(ctx: ctx, n: n, canonical: canonical, path: path, pending: pending) From 5d2eebe89ccfdc0f617aaa220278d17ea05dd1bd Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 15:41:42 +0000 Subject: [PATCH 24/69] Use grammar_symbol_list_put for dag canonical sugar and binding symbols. dag_symbol_map_put was a second name for grammar_symbol_map_put. Nested puts become one fold over a symbol list. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 71 +++++++++++--------------------- 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 30c7f36b409..3881d8ed211 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -49,6 +49,7 @@ import v2.std.grammar { VoidGrammar, grammar_carries_symbol, grammar_carried_symbol_map, + grammar_symbol_list_put, formal_production_to_node, formal_productions_catalog_to_node, grammar_production_to_node, @@ -2213,10 +2214,6 @@ fn dag_grammar() -> ParseGrammar { ModeledGrammar { root: dag_grammar_root() } } -fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { - map_insert(m: m, key: sym, value: true) -} - fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { @@ -2229,15 +2226,14 @@ fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower } fn dag_surface_sugar_symbol_map(m: Map) -> Map { - dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put(m: m, sym: ^dag_c3_surface_sugar_service), - sym: ^dag_c3_surface_sugar_fn - ), - sym: ^dag_c3_surface_sugar_type - ), - sym: ^dag_c3_surface_sugar_operation + grammar_symbol_list_put( + m: m, + xs: [ + ^dag_c3_surface_sugar_service, + ^dag_c3_surface_sugar_fn, + ^dag_c3_surface_sugar_type, + ^dag_c3_surface_sugar_operation + ] ) } @@ -2258,39 +2254,22 @@ fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { } fn dag_language_model_binding_canonical_map(m: Map) -> Map { - dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put(m: m, sym: ^dag_binding_fn_add), - sym: ^dag_binding_param_x - ), - sym: ^dag_binding_param_y - ), - sym: ^dag_binding_type_int - ), - sym: ^dag_binding_fn_one_or_two - ), - sym: ^dag_binding_fn_one - ), - sym: ^dag_binding_bind_x - ), - sym: ^dag_binding_pick_lit_one - ), - sym: ^dag_binding_pick_lit_two - ), - sym: ^dag_binding_type_bool - ), - sym: ^bool_node_symbol - ), - sym: ^dag_loop_measure_sym + grammar_symbol_list_put( + m: m, + xs: [ + ^dag_binding_fn_add, + ^dag_binding_param_x, + ^dag_binding_param_y, + ^dag_binding_type_int, + ^dag_binding_fn_one_or_two, + ^dag_binding_fn_one, + ^dag_binding_bind_x, + ^dag_binding_pick_lit_one, + ^dag_binding_pick_lit_two, + ^dag_binding_type_bool, + ^bool_node_symbol, + ^dag_loop_measure_sym + ] ) } From 549e7a5f61b3630eba70aedc44a862eda0762d76 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 15:58:53 +0000 Subject: [PATCH 25/69] The marker borrows the 4c channel's shape, not its non-interference rule: resolution consumes it Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/body_lowering_fold.dag | 2 +- src/v2/compiler/test_marker_capture.dag | 3 ++- src/v2/std/declaration_marker.dag | 16 ++++++++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index 67a9552c333..da27542f8fe 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -2064,7 +2064,7 @@ fn body_lower_fn_decl_named_member_wrap(arrow: Node, fn_name: Symbol) -> Node { // 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: it is authored source data under DESIGN section 4c and rides its own channel, +// member node: it rides its own channel beside the semantic tree (the DESIGN section 4c shape), // v2.compiler.test_marker_capture over the same parse tree, so the semantic member stays identical // to a plain fn's. fn body_lower_test_fn_decl_to_member(shell: Node) -> Outcome { diff --git a/src/v2/compiler/test_marker_capture.dag b/src/v2/compiler/test_marker_capture.dag index e7866c99ba6..25c1589f41b 100644 --- a/src/v2/compiler/test_marker_capture.dag +++ b/src/v2/compiler/test_marker_capture.dag @@ -23,7 +23,8 @@ import v2.std.diagnostic { import v2.std.node { Edge, Node, Symbol } import v2.std.node_query { NamedChildAmbiguous, NamedChildFound, NamedChildMissing, named_child_lookup } -// The capture half of the DESIGN section 4c channel for the `test` marker: a pure reader over the +// The capture half of the `test` marker channel (v2.std.declaration_marker, shaped like the DESIGN +// section 4c annotation channel): a pure reader over the // parse tree, run beside lowering and never inside it. Lowering is unchanged -- a `test fn` still // lowers to the same named member a plain `fn` does -- so every semantic consumer is byte-identical // by construction, and the marker survives here instead of being dropped. diff --git a/src/v2/std/declaration_marker.dag b/src/v2/std/declaration_marker.dag index 941ad55e0bb..da9d028aa0a 100644 --- a/src/v2/std/declaration_marker.dag +++ b/src/v2/std/declaration_marker.dag @@ -3,12 +3,16 @@ module v2.std.declaration_marker import v2.std.collection { List } import v2.std.node { Symbol } -// The authored `test` marker is source data of the DESIGN section 4c kind: it segregates test code -// from serving code, and it may not alter any semantic occurrence identity, semantic graph, -// resolution result or hash. So it rides this channel, beside the semantic tree and disjoint from -// it, exactly as the annotation channel rides beside AuthoredParseArtifact.semantic. v1 carries the -// same fact as v1.std.core DeclarationMarker on the Node itself; v2's Node has no field for it, and -// a structural child edge would change the content hash of every test declaration. +// The authored `test` marker segregates test code from serving code (owner ruling 2026-09-16/17), and +// name resolution CONSUMES it: a reference to a marked declaration is refused there. What it must not +// touch is the semantic tree -- a marked declaration's semantic graph and content hash are identical +// to an unmarked one's -- so it rides this channel beside the tree rather than as a child edge. It +// borrows the SHAPE of the DESIGN section 4c annotation channel (captured beside the semantic result, +// as annotations ride beside AuthoredParseArtifact.semantic), not its non-interference rule: 4c +// governs `//` prose, which may alter nothing downstream, while `test` is a declaration marker whose +// whole purpose is a refusal. v1 carries the same fact as v1.std.core DeclarationMarker on the Node +// itself; v2's Node has no field for it, and a structural child edge would change the content hash of +// every test declaration. // // Only marked declarations are rows. Absence is the unmarked arm, and it is decidable because the // capture that produces this channel reads every top-level item of the module it is handed. From 1c53f1cbe1f14a028d5919d6f688400d6c423279 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 09:54:36 +0000 Subject: [PATCH 26/69] Establish the dag canonical-symbol set once instead of rewalking the grammar per atom. Name resolution's ~63k-step membership check was grammar_carries_symbol converting the whole grammar to a Node on every unbound atom. Carry a derived map on LanguageModel.canonical_symbols so a one-member resolve stays under the new-witness eval-step budget. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 132 ++++++++++++++---- src/v2/std/compilers/lexing.dag | 22 ++- src/v2/std/grammar.dag | 98 ++++++++++++- .../one_member_cost_probe_test.dag | 127 +++++++++++++++++ 4 files changed, 344 insertions(+), 35 deletions(-) create mode 100644 src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 3fe6b136275..bf2c57d2dbb 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -10,7 +10,8 @@ import v2.std.collection { Map, empty_map, list_at_optional, - map_insert + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -47,6 +48,7 @@ import v2.std.grammar { Terminal, VoidGrammar, grammar_carries_symbol, + grammar_carried_symbol_map, formal_production_to_node, formal_productions_catalog_to_node, grammar_production_to_node, @@ -83,6 +85,7 @@ import v2.std.compilers.lexing { TokenRule, TriviaRule, WhitespaceChar, + lex_rule_set_token_class_map, lex_rule_set_token_class_member, symbol_intern_lexeme } @@ -2240,18 +2243,60 @@ fn dag_grammar() -> ParseGrammar { ModeledGrammar { root: dag_grammar_root() } } -fn dag_language_model_surface_prelude_atoms() -> PointwisePower { +fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { + map_insert(m: m, key: sym, value: true) +} + +fn dag_symbol_map_has(m: Map, sym: Symbol) -> Bool { + match map_lookup(m: m, key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { - sym == ^dag_c3_surface_sugar_service || - sym == ^dag_c3_surface_sugar_fn || - sym == ^dag_c3_surface_sugar_type || - sym == ^dag_c3_surface_sugar_operation || - dag_lex_token_symbol(sym: sym) + dag_symbol_map_has(m: m, sym: sym) } } } +fn dag_surface_sugar_symbol_map(m: Map) -> Map { + dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put(m: m, sym: ^dag_c3_surface_sugar_service), + sym: ^dag_c3_surface_sugar_fn + ), + sym: ^dag_c3_surface_sugar_type + ), + sym: ^dag_c3_surface_sugar_operation + ) +} + +fn dag_lex_token_class_insert(m: Map) -> Map { + fold_list( + xs: dag_lex_rules().rules, + empty: m, + cons: fn(acc, rule) { + match rule { + TokenRule { token_class: c, pattern: _ } => dag_symbol_map_put(m: acc, sym: c) + TriviaRule { pattern: _ } => acc + AnnotationRule { annotation_class: _, pattern: _ } => acc + } + } + ) +} + +fn dag_language_model_surface_prelude_map() -> Map { + dag_surface_sugar_symbol_map(m: lex_rule_set_token_class_map(rules: dag_lex_rules())) +} + +fn dag_language_model_surface_prelude_atoms() -> PointwisePower { + dag_pointwise_from_symbol_map(m: dag_language_model_surface_prelude_map()) +} + fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { PointwisePower { member: fn(_) { @@ -2260,29 +2305,59 @@ fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { } } +fn dag_language_model_binding_canonical_map(m: Map) -> Map { + dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put(m: m, sym: ^dag_binding_fn_add), + sym: ^dag_binding_param_x + ), + sym: ^dag_binding_param_y + ), + sym: ^dag_binding_type_int + ), + sym: ^dag_binding_fn_one_or_two + ), + sym: ^dag_binding_fn_one + ), + sym: ^dag_binding_bind_x + ), + sym: ^dag_binding_pick_lit_one + ), + sym: ^dag_binding_pick_lit_two + ), + sym: ^dag_binding_type_bool + ), + sym: ^bool_node_symbol + ), + sym: ^dag_loop_measure_sym + ) +} + fn dag_language_model_binding_canonical(sym: Symbol) -> Bool { - sym == ^dag_binding_fn_add - || sym == ^dag_binding_param_x - || sym == ^dag_binding_param_y - || sym == ^dag_binding_type_int - || sym == ^dag_binding_fn_one_or_two - || sym == ^dag_binding_fn_one - || sym == ^dag_binding_bind_x - || sym == ^dag_binding_pick_lit_one - || sym == ^dag_binding_pick_lit_two - || sym == ^dag_binding_type_bool - || sym == ^bool_node_symbol - || sym == ^dag_loop_measure_sym + dag_symbol_map_has(m: dag_language_model_binding_canonical_map(m: empty_map()), sym: sym) +} + +fn dag_canonical_symbol_map() -> Map { + dag_language_model_binding_canonical_map( + m: dag_surface_sugar_symbol_map( + m: dag_lex_token_class_insert( + m: grammar_carried_symbol_map(grammar: dag_grammar()) + ) + ) + ) } fn dag_canonical_symbols() -> PointwisePower { - PointwisePower { - member: fn(sym) { - dag_language_model_surface_prelude_atoms().member(sym) || - grammar_carries_symbol(grammar: dag_grammar(), sym: sym) || - dag_language_model_binding_canonical(sym: sym) - } - } + dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } fn dag_language_model_void() -> LanguageModel { @@ -2320,10 +2395,7 @@ fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower Bool { - lex_rule_set_token_class_member( - rules: dag_lex_rules(), - token_class: sym - ) + dag_symbol_map_has(m: lex_rule_set_token_class_map(rules: dag_lex_rules()), sym: sym) } fn dag_sync_tokens() -> List { diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index c176116ef9b..ec6347ad240 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -8,7 +8,11 @@ import std.disposition { Disposition, Scaffold, RealizationDispatch } import v2.std.algebra { fold_list, length, list_snoc_item, skip } import v2.std.collection { List, - list_at_optional + Map, + empty_map, + list_at_optional, + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -231,11 +235,23 @@ fn lex_rule_token_class_member(rule: LexRule, token_class: Symbol) -> Bool { } fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { + Present { value: _ } => true + Absent => false + } +} + +fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { fold_list( xs: rules.rules, - empty: false, + empty: empty_map(), cons: fn(acc, rule) { - acc || lex_rule_token_class_member(rule: rule, token_class: token_class) + match rule { + TokenRule { token_class: c, pattern: _ } => + map_insert(m: acc, key: c, value: true) + TriviaRule { pattern: _ } => acc + AnnotationRule { annotation_class: _, pattern: _ } => acc + } } ) } diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index a636c5f975e..2b9f979c7f8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -5,8 +5,12 @@ import std.algebra { Cons, Empty } import v2.std.algebra { fold_list, fold_list_node, list_append, list_snoc_item, zip_eq } import v2.std.collection { List, + Map, PointwisePower, - list_nth + empty_map, + list_nth, + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -1730,7 +1734,97 @@ fn grammar_symbol_fold_state(n: Node, sym: Symbol) -> GrammarSymbolFoldState { } fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { - grammar_symbol_fold_state(n: grammar_to_node(grammar: grammar), sym: sym).found + match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { + map_insert(m: m, key: sym, value: true) +} + +fn grammar_symbol_list_put(m: Map, xs: List) -> Map { + fold_list( + xs: xs, + empty: m, + cons: fn(acc, sym) { grammar_symbol_map_put(m: acc, sym: sym) } + ) +} + +fn grammar_node_atom_identities(n: Node) -> List { + fold_node( + n: n, + algebra: NodeFold { + init: fn(n0) { + match n0.kind { + TypeNode { connective: Atom { identity: id } } => + Cons { head: id, tail: Empty } + _ => Empty + } + }, + step: fn(acc, _e, child) { list_append(left: acc, right: child) } + } + ) +} + +fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map { + grammar_symbol_list_put(m: m, xs: grammar_node_atom_identities(n: n)) +} + +fn grammar_expr_carried_symbols(expr: GrammarExpr) -> List { + fold_grammar_expr( + expr: expr, + algebra: GrammarExprFold { + terminal: fn(token_class, stamp) { + match stamp { + StampClass => Cons { head: token_class, tail: Empty } + StampBinding { binding: b } => + Cons { head: token_class, tail: Cons { head: b, tail: Empty } } + StampLexeme => + Cons { + head: token_class, + tail: Cons { head: ^grammar_terminal_stamp_lexeme, tail: Empty } + } + } + }, + nonterminal: fn(production) { Cons { head: production, tail: Empty } }, + sequence: fn(left, right) { list_append(left: left, right: right) }, + choice: fn(left, right) { list_append(left: left, right: right) }, + optional: fn(element) { element }, + repeat: fn(element) { element } + } + ) +} + +fn grammar_production_insert_carried_symbols( + m: Map, + production: GrammarProduction +) -> Map { + grammar_node_insert_atom_identities( + m: grammar_symbol_list_put( + m: grammar_symbol_map_put(m: m, sym: production.name), + xs: grammar_expr_carried_symbols(expr: production.expression) + ), + n: production.emitted + ) +} + +fn grammar_root_carried_symbol_map(root: GrammarRoot) -> Map { + fold_list( + xs: root.productions, + empty: grammar_symbol_map_put(m: empty_map(), sym: root.start), + cons: fn(acc, production) { + grammar_production_insert_carried_symbols(m: acc, production: production) + } + ) +} + +fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { + match grammar { + VoidGrammar => empty_map() + ModeledGrammar { root: root } => grammar_root_carried_symbol_map(root: root) + } } type GrammarInterpretationDirection diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag new file mode 100644 index 00000000000..a7ccbb5e020 --- /dev/null +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -0,0 +1,127 @@ +module v2.test.name_resolve.one_member_cost_probe + +import std.occurrence_identity { OccurrenceSynthetic } +import v2.compiler.name_resolve { + Admission, + ResolutionSubject, + resolve_with_admission +} +import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } +import v2.compiler.resolve { ResolvedTree } +import v2.extdeps.languages.dag { + dag_grammar_atom, + dag_language_model, + dag_named_edge +} +import std.algebra { Cons, Empty, FreeMonoid } +import v2.std.algebra { fold_list_node } +import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } +import v2.std.logic { Bool } +import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } +import v2.std.qualified_name { QualifiedName } + +fn probe_qn_node(seg: Symbol) -> Node { + fold_list_node( + xs: Cons { head: seg, tail: Empty }, + item_node: fn(sym) { + Node { + kind: TypeNode { connective: Atom { identity: sym } }, + children: [], + occurrence_id: OccurrenceSynthetic + } + } + ) +} + +fn probe_one_member_root() -> Node { + Node { + kind: TypeNode { connective: Conj }, + children: [ + dag_named_edge( + name: ^dag_surface_module_header, + target: dag_grammar_atom(id: ^dag_surface_module_header) + ), + dag_named_edge( + name: ^dag_surface_module_header_qualified_name, + target: probe_qn_node(seg: ^probe_one_member_seg) + ), + Edge { + label: Named { name: ^probe_export_sym }, + target: Node { + kind: TypeNode { + connective: Atom { identity: ^dag_c3_surface_sugar_service } + }, + children: [], + occurrence_id: OccurrenceSynthetic + } + } + ], + occurrence_id: OccurrenceSynthetic + } +} + +fn probe_one_member_qn() -> QualifiedName { + Cons { head: ^probe_one_member_seg, tail: Empty } +} + +fn probe_one_member_roots() -> Outcome> { + bind_outcome( + o: admit_normalized_tree(root: probe_one_member_root(), diagnostics: None), + f: fn(a) { + outcome_accepted(value: Cons { head: a, tail: Empty }) + } + ) +} + +fn probe_with_roots(f: fn(FreeMonoid) -> Bool) -> Bool { + match probe_one_member_roots() { + Rejected { diagnostics: _ } => false + Accepted { value: roots, diagnostics: _ } => f(roots) + } +} + +fn probe_resolved_keeps_service_atom(tree: ResolvedTree) -> Bool { + match tree.kind { + TypeNode { connective: Atom { identity: id } } => + id == ^dag_c3_surface_sugar_service + _ => + fold(tree.children, init: false, f: fn(acc, e) { + acc || probe_resolved_keeps_service_atom(tree: e.target) + }) + } +} + +test fn one_member_module_resolves_under_full_language_model() -> Bool { + probe_with_roots(f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) { + Accepted { value: resolved, diagnostics: _ } => + probe_resolved_keeps_service_atom(tree: resolved) + Rejected { diagnostics: _ } => false + } + }) +} + +test fn one_member_missing_subject_still_refuses() -> Bool { + probe_with_roots(f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { + name: Cons { head: ^probe_missing_subject_seg, tail: Empty } + }, + imports: Empty + } + ) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} From 7a9b55fb5596a048c618d903bb831b068ac96280 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:00:01 +0000 Subject: [PATCH 27/69] Prove the carried-symbol map matches the node-fold oracle and that a too-wide map still refuses. The production path uses the derived map; grammar_carries_symbol remains the grammar-to-node fold so a complete tiny grammar can compare every membership both ways, and an uncanonical export still rejects under the full language model. Co-authored-by: Cursor --- src/v2/std/grammar.dag | 5 +- .../one_member_cost_probe_test.dag | 128 ++++++++++++++---- 2 files changed, 106 insertions(+), 27 deletions(-) diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index 2b9f979c7f8..fc86b6703b8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -1734,10 +1734,7 @@ fn grammar_symbol_fold_state(n: Node, sym: Symbol) -> GrammarSymbolFoldState { } fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { - match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { - Present { value: _ } => true - Absent => false - } + grammar_symbol_fold_state(n: grammar_to_node(grammar: grammar), sym: sym).found } fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index a7ccbb5e020..8fbf09fabb9 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -9,15 +9,30 @@ import v2.compiler.name_resolve { import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } import v2.compiler.resolve { ResolvedTree } import v2.extdeps.languages.dag { + dag_canonical_symbols, dag_grammar_atom, dag_language_model, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list_node } +import v2.std.collection { map_lookup } import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } +import v2.std.grammar { + GrammarProduction, + GrammarRoot, + ModeledGrammar, + ParseGrammar, + StampClass, + Terminal, + grammar_atom, + grammar_carried_symbol_map, + grammar_carries_symbol, + grammar_empty_sync_tokens +} import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } +import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } fn probe_qn_node(seg: Symbol) -> Node { @@ -33,7 +48,7 @@ fn probe_qn_node(seg: Symbol) -> Node { ) } -fn probe_one_member_root() -> Node { +fn probe_one_member_root_export(export_sym: Symbol) -> Node { Node { kind: TypeNode { connective: Conj }, children: [ @@ -48,9 +63,7 @@ fn probe_one_member_root() -> Node { Edge { label: Named { name: ^probe_export_sym }, target: Node { - kind: TypeNode { - connective: Atom { identity: ^dag_c3_surface_sugar_service } - }, + kind: TypeNode { connective: Atom { identity: export_sym } }, children: [], occurrence_id: OccurrenceSynthetic } @@ -64,52 +77,116 @@ fn probe_one_member_qn() -> QualifiedName { Cons { head: ^probe_one_member_seg, tail: Empty } } -fn probe_one_member_roots() -> Outcome> { +fn probe_roots_for_export(export_sym: Symbol) -> Outcome> { bind_outcome( - o: admit_normalized_tree(root: probe_one_member_root(), diagnostics: None), + o: admit_normalized_tree( + root: probe_one_member_root_export(export_sym: export_sym), + diagnostics: None + ), f: fn(a) { outcome_accepted(value: Cons { head: a, tail: Empty }) } ) } -fn probe_with_roots(f: fn(FreeMonoid) -> Bool) -> Bool { - match probe_one_member_roots() { +fn probe_with_export_roots( + export_sym: Symbol, + f: fn(FreeMonoid) -> Bool +) -> Bool { + match probe_roots_for_export(export_sym: export_sym) { Rejected { diagnostics: _ } => false Accepted { value: roots, diagnostics: _ } => f(roots) } } -fn probe_resolved_keeps_service_atom(tree: ResolvedTree) -> Bool { +fn probe_resolve(roots: FreeMonoid) -> Outcome { + resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) +} + +fn probe_resolved_export_identity(tree: ResolvedTree, wanted: Symbol) -> Bool { match tree.kind { - TypeNode { connective: Atom { identity: id } } => - id == ^dag_c3_surface_sugar_service + TypeNode { connective: Atom { identity: id } } => id == wanted _ => fold(tree.children, init: false, f: fn(acc, e) { - acc || probe_resolved_keeps_service_atom(tree: e.target) + acc || probe_resolved_export_identity(tree: e.target, wanted: wanted) }) } } +fn map_carries(grammar: ParseGrammar, sym: Symbol) -> Bool { + match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn eq_probe_grammar() -> ParseGrammar { + ModeledGrammar { + root: GrammarRoot { + start: ^eq_probe_start, + productions: Cons { + head: GrammarProduction { + name: ^eq_probe_start, + expression: Terminal { + token_class: ^eq_probe_tok, + stamp: StampClass + }, + emitted: grammar_atom(identity: ^eq_probe_emitted) + }, + tail: Empty + }, + sync_tokens: grammar_empty_sync_tokens() + } + } +} + +fn map_agrees_with_node_fold(sym: Symbol) -> Bool { + let g = eq_probe_grammar() + map_carries(grammar: g, sym: sym) == grammar_carries_symbol(grammar: g, sym: sym) +} + +test fn carried_symbol_map_agrees_with_node_fold_on_complete_probe_grammar() -> Bool { + map_agrees_with_node_fold(sym: ^eq_probe_start) + && map_agrees_with_node_fold(sym: ^eq_probe_tok) + && map_agrees_with_node_fold(sym: ^eq_probe_emitted) + && map_agrees_with_node_fold(sym: ^eq_probe_absent) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_start) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_tok) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_emitted) + && !map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_absent) +} + test fn one_member_module_resolves_under_full_language_model() -> Bool { - probe_with_roots(f: fn(roots) { - match resolve_with_admission( - lm: dag_language_model(), - roots: roots, - admission: Admission { - subject: ResolutionSubject { name: probe_one_member_qn() }, - imports: Empty - } - ) { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { + match probe_resolve(roots: roots) { Accepted { value: resolved, diagnostics: _ } => - probe_resolved_keeps_service_atom(tree: resolved) + probe_resolved_export_identity( + tree: resolved, + wanted: ^dag_c3_surface_sugar_service + ) Rejected { diagnostics: _ } => false } }) } +test fn one_member_uncanonical_export_still_refuses() -> Bool { + probe_with_export_roots(export_sym: ^probe_uncanonical_export, f: fn(roots) { + match probe_resolve(roots: roots) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} + test fn one_member_missing_subject_still_refuses() -> Bool { - probe_with_roots(f: fn(roots) { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { match resolve_with_admission( lm: dag_language_model(), roots: roots, @@ -125,3 +202,8 @@ test fn one_member_missing_subject_still_refuses() -> Bool { } }) } + +test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { + dag_canonical_symbols().member(^dag_c3_surface_sugar_service) + && !dag_canonical_symbols().member(^probe_uncanonical_export) +} From fed71bacc4d68e7eb0d3972387ecc5de9c2cb49c Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:04:15 +0000 Subject: [PATCH 28/69] Collapse the dag-local lex token-class fold into std and drop unused membership wrappers. lex_rule_set_insert_token_classes is the single TokenRule walk; dag_canonical_symbol_map seeds it. Dead lex_rule_token_class_member, dag_lex_token_class_insert, and dag_language_model_binding_canonical go with the cutover. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 25 ++++-------------------- src/v2/std/compilers/lexing.dag | 33 ++++++++++++++++---------------- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index bf2c57d2dbb..a7f38030b33 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -85,8 +85,8 @@ import v2.std.compilers.lexing { TokenRule, TriviaRule, WhitespaceChar, + lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, - lex_rule_set_token_class_member, symbol_intern_lexeme } import v2.std.node { @@ -2275,20 +2275,6 @@ fn dag_surface_sugar_symbol_map(m: Map) -> Map { ) } -fn dag_lex_token_class_insert(m: Map) -> Map { - fold_list( - xs: dag_lex_rules().rules, - empty: m, - cons: fn(acc, rule) { - match rule { - TokenRule { token_class: c, pattern: _ } => dag_symbol_map_put(m: acc, sym: c) - TriviaRule { pattern: _ } => acc - AnnotationRule { annotation_class: _, pattern: _ } => acc - } - } - ) -} - fn dag_language_model_surface_prelude_map() -> Map { dag_surface_sugar_symbol_map(m: lex_rule_set_token_class_map(rules: dag_lex_rules())) } @@ -2342,15 +2328,12 @@ fn dag_language_model_binding_canonical_map(m: Map) -> Map Bool { - dag_symbol_map_has(m: dag_language_model_binding_canonical_map(m: empty_map()), sym: sym) -} - fn dag_canonical_symbol_map() -> Map { dag_language_model_binding_canonical_map( m: dag_surface_sugar_symbol_map( - m: dag_lex_token_class_insert( - m: grammar_carried_symbol_map(grammar: dag_grammar()) + m: lex_rule_set_insert_token_classes( + m: grammar_carried_symbol_map(grammar: dag_grammar()), + rules: dag_lex_rules() ) ) ) diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index ec6347ad240..e50b3ae6a91 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -226,25 +226,13 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List Bool { - match rule { - TokenRule { token_class: c, pattern: _ } => c == token_class - TriviaRule { pattern: _ } => false - AnnotationRule { annotation_class: _, pattern: _ } => false - } -} - -fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { - match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { - Present { value: _ } => true - Absent => false - } -} - -fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { +fn lex_rule_set_insert_token_classes( + m: Map, + rules: LexRuleSet +) -> Map { fold_list( xs: rules.rules, - empty: empty_map(), + empty: m, cons: fn(acc, rule) { match rule { TokenRule { token_class: c, pattern: _ } => @@ -256,6 +244,17 @@ fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { ) } +fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { + lex_rule_set_insert_token_classes(m: empty_map(), rules: rules) +} + +fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { + Present { value: _ } => true + Absent => false + } +} + type LexPatternFold { empty: R literal: fn(v2.std.text.String) -> R From d6a4d714ba915084be4d1f9c34609ab697567f58 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 16:05:32 +0000 Subject: [PATCH 29/69] normalized_tree: name the marker channel by its home, not as a 4c annotation Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/normalized_tree.dag | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/v2/compiler/normalized_tree.dag b/src/v2/compiler/normalized_tree.dag index 1b8d9b436a8..8e5731399dc 100644 --- a/src/v2/compiler/normalized_tree.dag +++ b/src/v2/compiler/normalized_tree.dag @@ -23,9 +23,10 @@ import v2.std.declaration_marker { TestMarkerChannel, test_marker_channel_empty // behind. It was an alias for Node until BL-1, which is why retention evidence reached resolve as a // bare Node and was dropped there. Sealing the carrier makes the drop unwritable. // -// `test_markers` is the DESIGN section 4c channel for the authored `test` marker, sealed beside the -// root rather than inside it: `root` is byte-identical for a `test fn` and a plain `fn`, and every -// consumer about Node structure reads `root` alone. Name resolution is the channel's consumer. +// `test_markers` is the authored `test` marker's channel (v2.std.declaration_marker), sealed +// beside the root rather than inside it: `root` is byte-identical for a `test fn` and a plain `fn`, +// and every consumer about Node structure reads `root` alone. Name resolution is the channel's +// consumer, and refuses references to what it marks. type NormalizedTree sole_constructor { root: Node, test_markers: TestMarkerChannel, From 99af043f3677d2661080f12dab14cdf60e2843da Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:27:28 +0000 Subject: [PATCH 30/69] Build the canonical-symbol map in resolution context, not in dag_language_model. Eager map fill on LanguageModel charged every tokenize/parse claim ~33k steps. The set is established once on ResolutionContext (and in build_program_namespace) where resolve actually members it; LM construction is back to ~3.7k. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 21 ++++++++++--------- src/v2/compiler/03_resolve.dag | 4 ++-- src/v2/extdeps/languages/dag.dag | 2 +- .../one_member_cost_probe_test.dag | 4 ++++ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 456d04ea4ca..19f44e72134 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,7 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_language_model_canonical_symbols, + dag_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -31,7 +31,7 @@ import v2.compiler.namespace_graft { } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list } -import v2.std.collection { Absent, Map, Present, List, empty_map, map_get, map_lookup, map_insert, optional_absent, optional_present } +import v2.std.collection { Absent, Map, Present, List, PointwisePower, empty_map, map_get, map_lookup, map_insert, optional_absent, optional_present } import v2.std.diagnostic { Accepted, Diagnostic, @@ -111,6 +111,7 @@ type ResolutionContext { lm: LanguageModel roots: ValidatedModuleRoots symbol_index: SymbolIndex + canonical_symbols: PointwisePower } fn resolution_context( @@ -127,7 +128,8 @@ fn resolution_context( symbol_index: symbol_index_fill_module_roots( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) - ) + ), + canonical_symbols: dag_canonical_symbols() }, diagnostics: d } @@ -556,10 +558,10 @@ fn admit_imports( ) } -fn admission_namespace_base(lm: LanguageModel) -> Namespace { +fn admission_namespace_base(canonical_symbols: PointwisePower) -> Namespace { Namespace { bindings: empty_map(), - canonical_symbols: dag_language_model_canonical_symbols(lm: lm), + canonical_symbols: canonical_symbols, symbol_index: empty_symbol_index(), module_qn: Empty } @@ -579,11 +581,11 @@ fn subject_module_root_for_name( fn namespace_admission_initial_state( policy: NameResolutionPolicy, - lm: LanguageModel, + canonical_symbols: PointwisePower, subject_module_root: NormalizedTree, subject_qn: QualifiedName ) -> AdmissionState { - let base = admission_namespace_base(lm: lm) + let base = admission_namespace_base(canonical_symbols: canonical_symbols) match policy { NamespaceOnlyY => admission_initial(base: base) @@ -660,13 +662,12 @@ fn namespace_for_subject_root( active_roots: FreeMonoid, policy: NameResolutionPolicy ) -> Outcome { - let lm = shared.lm let validated_roots = shared.roots if well_formed(n: subject_module_root.root) { - let base = admission_namespace_base(lm: lm) + let base = admission_namespace_base(canonical_symbols: shared.canonical_symbols) let initial = namespace_admission_initial_state( policy: policy, - lm: lm, + canonical_symbols: shared.canonical_symbols, subject_module_root: subject_module_root, subject_qn: admission.subject.name ) diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 0c9158067ec..fb00e447f0d 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,7 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_language_model_canonical_symbols, + dag_canonical_symbols, dag_node_is_float_literal_atom, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, @@ -237,7 +237,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_language_model_canonical_symbols(lm: lm), + canonical_symbols: dag_canonical_symbols(), symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index a7f38030b33..5f81f5aba71 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2358,7 +2358,7 @@ fn dag_language_model() -> LanguageModel { lex: dag_lex(), grammar: dag_grammar(), language_identity: ^dag_language_model_surface_id, - canonical_symbols: dag_canonical_symbols(), + canonical_symbols: dag_language_model_empty_canonical_symbol_set(), core: bool_model_core() } } diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 8fbf09fabb9..8c234c747d3 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -203,6 +203,10 @@ test fn one_member_missing_subject_still_refuses() -> Bool { }) } +test fn language_model_construction_does_not_fill_canonical_map() -> Bool { + dag_language_model().language_identity == ^dag_language_model_surface_id +} + test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { dag_canonical_symbols().member(^dag_c3_surface_sugar_service) && !dag_canonical_symbols().member(^probe_uncanonical_export) From 9949a1a47d99c64e3e18e1ca62fa744fa124492b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:31:56 +0000 Subject: [PATCH 31/69] Enroll a tokenize/parse probe that never enters resolution. Parent asked for a floor claim the front end does not pay the canonical-symbol map. This claim only constructs the language model, tokenizes, and parses a one-member module. Co-authored-by: Cursor --- .../one_member_cost_probe_test.dag | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 8c234c747d3..c951e2bc224 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -7,7 +7,9 @@ import v2.compiler.name_resolve { resolve_with_admission } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } +import v2.compiler.parse { parse_module } import v2.compiler.resolve { ResolvedTree } +import v2.compiler.tokenize { tokenize } import v2.extdeps.languages.dag { dag_canonical_symbols, dag_grammar_atom, @@ -34,6 +36,7 @@ import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } +import v2.std.text { String } fn probe_qn_node(seg: Symbol) -> Node { fold_list_node( @@ -207,6 +210,24 @@ test fn language_model_construction_does_not_fill_canonical_map() -> Bool { dag_language_model().language_identity == ^dag_language_model_surface_id } +data probe_front_end_source: String = "module probe.one_member\n" + +test fn tokenize_parse_one_member_does_not_run_resolve() -> Bool { + let lm = dag_language_model() + match tokenize( + text: probe_front_end_source, + file: ^probe_front_end_file, + rules: lm.lex + ) { + Rejected { diagnostics: _ } => false + Accepted { value: tokens, diagnostics: _ } => + match parse_module(tokens: tokens, grammar: lm.grammar) { + Accepted { value: _, diagnostics: _ } => true + Rejected { diagnostics: _ } => false + } + } +} + test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { dag_canonical_symbols().member(^dag_c3_surface_sugar_service) && !dag_canonical_symbols().member(^probe_uncanonical_export) From abc03c9c03e129b487c39c02b82e0e491c444d02 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 11:08:06 +0000 Subject: [PATCH 32/69] Derive resolve canonical symbols from the language model that was passed. Always installing dag_canonical_symbols() widened empty-prelude resolve: a service atom that used to refuse now accepted. Void grammars keep lm.canonical_symbols; modeled grammars build the map from that model's grammar and lex at resolution time. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 4 ++-- src/v2/compiler/03_resolve.dag | 4 ++-- src/v2/extdeps/languages/dag.dag | 18 ++++++++++++++++ src/v2/std/compilers/lexing.dag | 11 ++++++++++ .../one_member_cost_probe_test.dag | 21 ++++++++++++++++++- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 19f44e72134..1dd52faa44b 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,7 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_canonical_symbols, + dag_resolution_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -129,7 +129,7 @@ fn resolution_context( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) ), - canonical_symbols: dag_canonical_symbols() + canonical_symbols: dag_resolution_canonical_symbols(lm: lm) }, diagnostics: d } diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index fb00e447f0d..a418904b8ce 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,7 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_canonical_symbols, + dag_resolution_canonical_symbols, dag_node_is_float_literal_atom, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, @@ -237,7 +237,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_canonical_symbols(), + canonical_symbols: dag_resolution_canonical_symbols(lm: lm), symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 5f81f5aba71..f3982becad5 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -87,6 +87,7 @@ import v2.std.compilers.lexing { WhitespaceChar, lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, + lex_rules_insert_token_classes, symbol_intern_lexeme } import v2.std.node { @@ -2343,6 +2344,23 @@ fn dag_canonical_symbols() -> PointwisePower { dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } +fn dag_resolution_canonical_symbols(lm: LanguageModel) -> PointwisePower { + match lm.grammar { + VoidGrammar => lm.canonical_symbols + ModeledGrammar { root: _ } => + dag_pointwise_from_symbol_map( + m: dag_language_model_binding_canonical_map( + m: dag_surface_sugar_symbol_map( + m: lex_rules_insert_token_classes( + m: grammar_carried_symbol_map(grammar: lm.grammar), + rules: lm.lex + ) + ) + ) + ) + } +} + fn dag_language_model_void() -> LanguageModel { LanguageModel { lex: dag_lex(), diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index e50b3ae6a91..8dbc6634af1 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -226,6 +226,17 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List, + rules: LexRules +) -> Map { + match rules { + VoidLexRules => m + ModeledLexRules { root: root } => + lex_rule_set_insert_token_classes(m: m, rules: root) + } +} + fn lex_rule_set_insert_token_classes( m: Map, rules: LexRuleSet diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index c951e2bc224..62afe57ebfb 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -14,6 +14,7 @@ import v2.extdeps.languages.dag { dag_canonical_symbols, dag_grammar_atom, dag_language_model, + dag_language_model_surface_empty_prelude, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } @@ -206,8 +207,26 @@ test fn one_member_missing_subject_still_refuses() -> Bool { }) } +test fn empty_prelude_service_atom_still_refuses() -> Bool { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model_surface_empty_prelude(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} + test fn language_model_construction_does_not_fill_canonical_map() -> Bool { - dag_language_model().language_identity == ^dag_language_model_surface_id + let lm = dag_language_model() + (lm.language_identity == ^dag_language_model_surface_id) + && !lm.canonical_symbols.member(^dag_surface_module_header) } data probe_front_end_source: String = "module probe.one_member\n" From aae97d47a07877dae9055d2c12075009d36c4c71 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 11:43:43 +0000 Subject: [PATCH 33/69] Derive the grammar symbol map from grammar_to_node and restore linear lex membership. The carried-symbol map was a parallel GrammarExpr walk; resolve still pays that set once, but the atoms now come from the same node projection as grammar_carries_symbol. Per-query token-class tests walk TokenRule again instead of rebuilding the map on body-lowering's hot path. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 6 +- src/v2/std/compilers/lexing.dag | 22 +++++-- src/v2/std/grammar.dag | 53 +---------------- .../one_member_cost_probe_test.dag | 57 ------------------- 4 files changed, 22 insertions(+), 116 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index f3982becad5..452b935bc9e 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -87,6 +87,7 @@ import v2.std.compilers.lexing { WhitespaceChar, lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, + lex_rule_set_token_class_member, lex_rules_insert_token_classes, symbol_intern_lexeme } @@ -2396,7 +2397,10 @@ fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower Bool { - dag_symbol_map_has(m: lex_rule_set_token_class_map(rules: dag_lex_rules()), sym: sym) + lex_rule_set_token_class_member( + rules: dag_lex_rules(), + token_class: sym + ) } fn dag_sync_tokens() -> List { diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index 8dbc6634af1..89c6243da51 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -11,8 +11,7 @@ import v2.std.collection { Map, empty_map, list_at_optional, - map_insert, - map_lookup + map_insert } import v2.std.optional { Absent, @@ -259,13 +258,24 @@ fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { lex_rule_set_insert_token_classes(m: empty_map(), rules: rules) } -fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { - match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { - Present { value: _ } => true - Absent => false +fn lex_rule_token_class_member(rule: LexRule, token_class: Symbol) -> Bool { + match rule { + TokenRule { token_class: c, pattern: _ } => c == token_class + TriviaRule { pattern: _ } => false + AnnotationRule { annotation_class: _, pattern: _ } => false } } +fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + fold_list( + xs: rules.rules, + empty: false, + cons: fn(acc, rule) { + acc || lex_rule_token_class_member(rule: rule, token_class: token_class) + } + ) +} + type LexPatternFold { empty: R literal: fn(v2.std.text.String) -> R diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index fc86b6703b8..c94ec0925e8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -1769,59 +1769,8 @@ fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map List { - fold_grammar_expr( - expr: expr, - algebra: GrammarExprFold { - terminal: fn(token_class, stamp) { - match stamp { - StampClass => Cons { head: token_class, tail: Empty } - StampBinding { binding: b } => - Cons { head: token_class, tail: Cons { head: b, tail: Empty } } - StampLexeme => - Cons { - head: token_class, - tail: Cons { head: ^grammar_terminal_stamp_lexeme, tail: Empty } - } - } - }, - nonterminal: fn(production) { Cons { head: production, tail: Empty } }, - sequence: fn(left, right) { list_append(left: left, right: right) }, - choice: fn(left, right) { list_append(left: left, right: right) }, - optional: fn(element) { element }, - repeat: fn(element) { element } - } - ) -} - -fn grammar_production_insert_carried_symbols( - m: Map, - production: GrammarProduction -) -> Map { - grammar_node_insert_atom_identities( - m: grammar_symbol_list_put( - m: grammar_symbol_map_put(m: m, sym: production.name), - xs: grammar_expr_carried_symbols(expr: production.expression) - ), - n: production.emitted - ) -} - -fn grammar_root_carried_symbol_map(root: GrammarRoot) -> Map { - fold_list( - xs: root.productions, - empty: grammar_symbol_map_put(m: empty_map(), sym: root.start), - cons: fn(acc, production) { - grammar_production_insert_carried_symbols(m: acc, production: production) - } - ) -} - fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { - match grammar { - VoidGrammar => empty_map() - ModeledGrammar { root: root } => grammar_root_carried_symbol_map(root: root) - } + grammar_node_insert_atom_identities(m: empty_map(), n: grammar_to_node(grammar: grammar)) } type GrammarInterpretationDirection diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 62afe57ebfb..a53a67ad11f 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -19,23 +19,9 @@ import v2.extdeps.languages.dag { } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list_node } -import v2.std.collection { map_lookup } import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } -import v2.std.grammar { - GrammarProduction, - GrammarRoot, - ModeledGrammar, - ParseGrammar, - StampClass, - Terminal, - grammar_atom, - grammar_carried_symbol_map, - grammar_carries_symbol, - grammar_empty_sync_tokens -} import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } -import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } import v2.std.text { String } @@ -124,49 +110,6 @@ fn probe_resolved_export_identity(tree: ResolvedTree, wanted: Symbol) -> Bool { } } -fn map_carries(grammar: ParseGrammar, sym: Symbol) -> Bool { - match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { - Present { value: _ } => true - Absent => false - } -} - -fn eq_probe_grammar() -> ParseGrammar { - ModeledGrammar { - root: GrammarRoot { - start: ^eq_probe_start, - productions: Cons { - head: GrammarProduction { - name: ^eq_probe_start, - expression: Terminal { - token_class: ^eq_probe_tok, - stamp: StampClass - }, - emitted: grammar_atom(identity: ^eq_probe_emitted) - }, - tail: Empty - }, - sync_tokens: grammar_empty_sync_tokens() - } - } -} - -fn map_agrees_with_node_fold(sym: Symbol) -> Bool { - let g = eq_probe_grammar() - map_carries(grammar: g, sym: sym) == grammar_carries_symbol(grammar: g, sym: sym) -} - -test fn carried_symbol_map_agrees_with_node_fold_on_complete_probe_grammar() -> Bool { - map_agrees_with_node_fold(sym: ^eq_probe_start) - && map_agrees_with_node_fold(sym: ^eq_probe_tok) - && map_agrees_with_node_fold(sym: ^eq_probe_emitted) - && map_agrees_with_node_fold(sym: ^eq_probe_absent) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_start) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_tok) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_emitted) - && !map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_absent) -} - test fn one_member_module_resolves_under_full_language_model() -> Bool { probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { match probe_resolve(roots: roots) { From a5de5ce6b68ebeaad66947e70d48c157c30f8406 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:17:57 +0000 Subject: [PATCH 34/69] Carry canonical symbols on the language model resolve actually uses. Resolution reads lm.canonical_symbols instead of a second dag-only derivation that discarded sibling models' declared sets. dag_language_model fills that field; tokenize/parse demand dag_lex and dag_grammar so they do not construct the map. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 3 +-- src/v2/compiler/03_resolve.dag | 3 +-- src/v2/extdeps/languages/dag.dag | 20 +------------------ .../one_member_cost_probe_test.dag | 13 ++++++------ 4 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 1dd52faa44b..fdf045edf6f 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,6 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_resolution_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -129,7 +128,7 @@ fn resolution_context( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) ), - canonical_symbols: dag_resolution_canonical_symbols(lm: lm) + canonical_symbols: lm.canonical_symbols }, diagnostics: d } diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index a418904b8ce..6811b0356df 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,6 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_resolution_canonical_symbols, dag_node_is_float_literal_atom, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, @@ -237,7 +236,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_resolution_canonical_symbols(lm: lm), + canonical_symbols: lm.canonical_symbols, symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 452b935bc9e..615c6240810 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -88,7 +88,6 @@ import v2.std.compilers.lexing { lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, lex_rule_set_token_class_member, - lex_rules_insert_token_classes, symbol_intern_lexeme } import v2.std.node { @@ -2345,23 +2344,6 @@ fn dag_canonical_symbols() -> PointwisePower { dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } -fn dag_resolution_canonical_symbols(lm: LanguageModel) -> PointwisePower { - match lm.grammar { - VoidGrammar => lm.canonical_symbols - ModeledGrammar { root: _ } => - dag_pointwise_from_symbol_map( - m: dag_language_model_binding_canonical_map( - m: dag_surface_sugar_symbol_map( - m: lex_rules_insert_token_classes( - m: grammar_carried_symbol_map(grammar: lm.grammar), - rules: lm.lex - ) - ) - ) - ) - } -} - fn dag_language_model_void() -> LanguageModel { LanguageModel { lex: dag_lex(), @@ -2377,7 +2359,7 @@ fn dag_language_model() -> LanguageModel { lex: dag_lex(), grammar: dag_grammar(), language_identity: ^dag_language_model_surface_id, - canonical_symbols: dag_language_model_empty_canonical_symbol_set(), + canonical_symbols: dag_canonical_symbols(), core: bool_model_core() } } diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index a53a67ad11f..614d7eabb3a 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -12,9 +12,11 @@ import v2.compiler.resolve { ResolvedTree } import v2.compiler.tokenize { tokenize } import v2.extdeps.languages.dag { dag_canonical_symbols, + dag_grammar, dag_grammar_atom, dag_language_model, dag_language_model_surface_empty_prelude, + dag_lex, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } @@ -166,24 +168,23 @@ test fn empty_prelude_service_atom_still_refuses() -> Bool { }) } -test fn language_model_construction_does_not_fill_canonical_map() -> Bool { +test fn language_model_carries_its_canonical_symbols() -> Bool { let lm = dag_language_model() - (lm.language_identity == ^dag_language_model_surface_id) - && !lm.canonical_symbols.member(^dag_surface_module_header) + lm.canonical_symbols.member(^dag_c3_surface_sugar_service) + && !lm.canonical_symbols.member(^probe_uncanonical_export) } data probe_front_end_source: String = "module probe.one_member\n" test fn tokenize_parse_one_member_does_not_run_resolve() -> Bool { - let lm = dag_language_model() match tokenize( text: probe_front_end_source, file: ^probe_front_end_file, - rules: lm.lex + rules: dag_lex() ) { Rejected { diagnostics: _ } => false Accepted { value: tokens, diagnostics: _ } => - match parse_module(tokens: tokens, grammar: lm.grammar) { + match parse_module(tokens: tokens, grammar: dag_grammar()) { Accepted { value: _, diagnostics: _ } => true Rejected { diagnostics: _ } => false } From 4c834780ade79ac79a8db119dcfefed9056e1934 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:28:56 +0000 Subject: [PATCH 35/69] Delete unused lex/canonical wrappers left by the resolve cutover. lex_rules_insert_token_classes and dag_language_model_canonical_symbols had no callers. Map membership for the pointwise set is map_lookup at the closure, not a dag-local has_* wrapper. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 16 ++++------------ src/v2/std/compilers/lexing.dag | 11 ----------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 615c6240810..01105404e29 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2248,17 +2248,13 @@ fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { map_insert(m: m, key: sym, value: true) } -fn dag_symbol_map_has(m: Map, sym: Symbol) -> Bool { - match map_lookup(m: m, key: sym) { - Present { value: _ } => true - Absent => false - } -} - fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { - dag_symbol_map_has(m: m, sym: sym) + match map_lookup(m: m, key: sym) { + Present { value: _ } => true + Absent => false + } } } } @@ -2374,10 +2370,6 @@ fn dag_language_model_surface_empty_prelude() -> LanguageModel { } } -fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower { - lm.canonical_symbols -} - fn dag_lex_token_symbol(sym: Symbol) -> Bool { lex_rule_set_token_class_member( rules: dag_lex_rules(), diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index 89c6243da51..c451c29ffcc 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -225,17 +225,6 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List, - rules: LexRules -) -> Map { - match rules { - VoidLexRules => m - ModeledLexRules { root: root } => - lex_rule_set_insert_token_classes(m: m, rules: root) - } -} - fn lex_rule_set_insert_token_classes( m: Map, rules: LexRuleSet From 3e5da21411cd7b087ec7d3780612020af5cabd09 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:33:26 +0000 Subject: [PATCH 36/69] Warm-share the portable dag canonical-symbol map across floor claims. PointwisePower.member is a closure and cannot be stored. dag_canonical_symbol_map is the Map fill every resolving claim re-derived per frame; the floor serves that map once at preparation and dag_canonical_symbols wraps it at the consumer. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 3 +++ src/v2/workflow/floor_pure_producer_share.dag | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 01105404e29..3629844e18f 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2325,6 +2325,9 @@ fn dag_language_model_binding_canonical_map(m: Map) -> Map Map { dag_language_model_binding_canonical_map( m: dag_surface_sugar_symbol_map( diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index b9657a12944..816a33f2165 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -467,6 +467,24 @@ import v2.std.collection { List } // reason the parse rows above keep normalize out: the claims in that file are ABOUT the collector, // so sharing the collection would hand each one a precomputed answer to the question it asks. The // walk stays per claim; only the stage no claim names is shared. +// DAG CANONICAL-SYMBOL MAP EARNS ITS ROW ON THE ENROLMENT-MARGIN CPU THE EVAL-STEP GATE +// CANNOT SEE. Sibling floor run 35340819603 (#11580 merging the resolve-cost head that +// establishes this map once per ResolutionContext) passed eval-steps and refused every +// resolving claim at the 302ms enrolment margin, including one-member resolve probes that +// sit near 40k steps. The same fill shows on grandfathered resolve claims. Re-derive with +// required_floor_claim_cost.tsv on that run plus claim_batch --entry +// src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag [witness] cpu vs eval_steps: +// the map fill is host work outside the step counter, paid once per claim frame because the +// eval-frame memo dies at the claim boundary. Sharing census is the call-site graph: every +// dag_language_model() resolve path reads dag_canonical_symbols(), which is a PointwisePower +// closure and therefore not portable (ServeCacheValueNotPortable, the prepare_grammar +// nullable_set shape). The share point is the Map it is built from -- +// dag_canonical_symbol_map -- symbols and bools only; dag_canonical_symbols wraps that map +// at the consumer. Serve-below-recompute is the header's zero-walk Rc hand-over of a small +// map against a fill that already exceeds the enrolment margin in-fold. WARM, NEVER +// CLAIM-FORCED, by the same ceiling arithmetic as dag_prepared_grammar: an in-fold first +// touch of this fill dies on the lane's margin and restarts per claim. The module is under +// src/v2/extdeps, so it resolves in every required-floor subject. data floor_cross_claim_pure_producers_warm: List = [ "v2.test.native_decl_selection.collision_resolved", "v2.extdeps.languages.bash_command_fold.bash_fold_formal_productions", @@ -495,7 +513,8 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_literal_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.braced_fn_control_parsed", - "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized" + "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized", + "v2.extdeps.languages.dag.dag_canonical_symbol_map" ] // grammar_relation_row_for_emitted HAS NOW BEEN MEASURED ON THE THIRD CONJUNCT, AND IT PASSES. From 4bbf159d18bb069703661549f9f23dfbad6858c7 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 15:41:42 +0000 Subject: [PATCH 37/69] Use grammar_symbol_list_put for dag canonical sugar and binding symbols. dag_symbol_map_put was a second name for grammar_symbol_map_put. Nested puts become one fold over a symbol list. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 71 +++++++++++--------------------- 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 3629844e18f..e06eb9a5dac 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -49,6 +49,7 @@ import v2.std.grammar { VoidGrammar, grammar_carries_symbol, grammar_carried_symbol_map, + grammar_symbol_list_put, formal_production_to_node, formal_productions_catalog_to_node, grammar_production_to_node, @@ -2244,10 +2245,6 @@ fn dag_grammar() -> ParseGrammar { ModeledGrammar { root: dag_grammar_root() } } -fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { - map_insert(m: m, key: sym, value: true) -} - fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { @@ -2260,15 +2257,14 @@ fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower } fn dag_surface_sugar_symbol_map(m: Map) -> Map { - dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put(m: m, sym: ^dag_c3_surface_sugar_service), - sym: ^dag_c3_surface_sugar_fn - ), - sym: ^dag_c3_surface_sugar_type - ), - sym: ^dag_c3_surface_sugar_operation + grammar_symbol_list_put( + m: m, + xs: [ + ^dag_c3_surface_sugar_service, + ^dag_c3_surface_sugar_fn, + ^dag_c3_surface_sugar_type, + ^dag_c3_surface_sugar_operation + ] ) } @@ -2289,39 +2285,22 @@ fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { } fn dag_language_model_binding_canonical_map(m: Map) -> Map { - dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put(m: m, sym: ^dag_binding_fn_add), - sym: ^dag_binding_param_x - ), - sym: ^dag_binding_param_y - ), - sym: ^dag_binding_type_int - ), - sym: ^dag_binding_fn_one_or_two - ), - sym: ^dag_binding_fn_one - ), - sym: ^dag_binding_bind_x - ), - sym: ^dag_binding_pick_lit_one - ), - sym: ^dag_binding_pick_lit_two - ), - sym: ^dag_binding_type_bool - ), - sym: ^bool_node_symbol - ), - sym: ^dag_loop_measure_sym + grammar_symbol_list_put( + m: m, + xs: [ + ^dag_binding_fn_add, + ^dag_binding_param_x, + ^dag_binding_param_y, + ^dag_binding_type_int, + ^dag_binding_fn_one_or_two, + ^dag_binding_fn_one, + ^dag_binding_bind_x, + ^dag_binding_pick_lit_one, + ^dag_binding_pick_lit_two, + ^dag_binding_type_bool, + ^bool_node_symbol, + ^dag_loop_measure_sym + ] ) } From 38e5b5992b8624cdca542e07caf1a09b57d13ac8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 15:53:16 +0000 Subject: [PATCH 38/69] Stop copying canonical_symbols onto ResolutionContext. LanguageModel already owns the set. Admission reads shared.lm.canonical_symbols so the context cannot drift from the model it carries. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index fdf045edf6f..05e167e9db9 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -110,7 +110,6 @@ type ResolutionContext { lm: LanguageModel roots: ValidatedModuleRoots symbol_index: SymbolIndex - canonical_symbols: PointwisePower } fn resolution_context( @@ -127,8 +126,7 @@ fn resolution_context( symbol_index: symbol_index_fill_module_roots( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) - ), - canonical_symbols: lm.canonical_symbols + ) }, diagnostics: d } @@ -663,10 +661,10 @@ fn namespace_for_subject_root( ) -> Outcome { let validated_roots = shared.roots if well_formed(n: subject_module_root.root) { - let base = admission_namespace_base(canonical_symbols: shared.canonical_symbols) + let base = admission_namespace_base(canonical_symbols: shared.lm.canonical_symbols) let initial = namespace_admission_initial_state( policy: policy, - canonical_symbols: shared.canonical_symbols, + canonical_symbols: shared.lm.canonical_symbols, subject_module_root: subject_module_root, subject_qn: admission.subject.name ) From f14e9b66e5f67743e1328a72fe7cad6375c053f4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 16:17:03 +0000 Subject: [PATCH 39/69] Make grammar membership a lookup of the carried-symbol map. The atom walk inserts into the Map as it goes, so there is one authority and no quadratic list concat. grammar_carries_symbol no longer has a second fold. Co-authored-by: Cursor --- src/v2/std/grammar.dag | 78 +++++++++++++----------------------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index c94ec0925e8..817a0aeac23 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -42,11 +42,9 @@ import v2.std.node { Edge, Named, Node, - NodeFold, Positional, Symbol, TypeNode, - fold_node, } import v2.std.node_shape { stamp_node_shape_synthetic, type_shape } import v2.std.compilers.target_model { @@ -349,10 +347,6 @@ type GrammarRelationRow { type ParseTree = Node -type GrammarSymbolFoldState { - found: Bool -} - fn grammar_atom(identity: Symbol) -> Node { Node { kind: TypeNode { @@ -1703,40 +1697,6 @@ fn grammar_to_node(grammar: ParseGrammar) -> Node { } } -fn grammar_symbol_fold(sym: Symbol) -> NodeFold { - NodeFold { - init: fn(n0) { - grammar_symbol_fold_init(n: n0, sym: sym) - }, - step: grammar_symbol_fold_step - } -} - -fn grammar_symbol_fold_init(n: Node, sym: Symbol) -> GrammarSymbolFoldState { - match n.kind { - TypeNode { connective: Atom { identity: id } } => - GrammarSymbolFoldState { found: id == sym } - _ => - GrammarSymbolFoldState { found: false } - } -} - -fn grammar_symbol_fold_step( - acc: GrammarSymbolFoldState, - edge: Edge, - child_has_symbol: GrammarSymbolFoldState -) -> GrammarSymbolFoldState { - GrammarSymbolFoldState { found: acc.found || child_has_symbol.found } -} - -fn grammar_symbol_fold_state(n: Node, sym: Symbol) -> GrammarSymbolFoldState { - fold_node(n: n, algebra: grammar_symbol_fold(sym: sym)) -} - -fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { - grammar_symbol_fold_state(n: grammar_to_node(grammar: grammar), sym: sym).found -} - fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { map_insert(m: m, key: sym, value: true) } @@ -1749,30 +1709,38 @@ fn grammar_symbol_list_put(m: Map, xs: List) -> Map List { - fold_node( - n: n, - algebra: NodeFold { - init: fn(n0) { - match n0.kind { - TypeNode { connective: Atom { identity: id } } => - Cons { head: id, tail: Empty } - _ => Empty - } - }, - step: fn(acc, _e, child) { list_append(left: acc, right: child) } - } - ) +fn grammar_node_insert_atom_identities_child( + m: Map, + e: Edge +) -> Map { + grammar_node_insert_atom_identities(m: m, n: e.target) } fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map { - grammar_symbol_list_put(m: m, xs: grammar_node_atom_identities(n: n)) + fold_list( + xs: n.children, + empty: match n.kind { + TypeNode { connective: Atom { identity: id } } => + grammar_symbol_map_put(m: m, sym: id) + _ => m + }, + cons: fn(acc, e) { + grammar_node_insert_atom_identities_child(m: acc, e: e) + } + ) } fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { grammar_node_insert_atom_identities(m: empty_map(), n: grammar_to_node(grammar: grammar)) } +fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { + match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { + Present { value: _ } => true + Absent => false + } +} + type GrammarInterpretationDirection = GrammarInterpretBackward | GrammarInterpretForward From c0c73a1f0520198bbe95d1bb53031d9110c37479 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 16:19:47 +0000 Subject: [PATCH 40/69] Consume v2.std.algebra length and contains; name both producers of test-ness and declare the convergence frontier Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/body_lowering_fold.dag | 8 ++++++++ src/v2/std/declaration_marker.dag | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index da27542f8fe..cef0cc9d07b 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -2067,6 +2067,14 @@ fn body_lower_fn_decl_named_member_wrap(arrow: Node, fn_name: Symbol) -> Node { // member node: it rides its own channel beside the semantic tree (the DESIGN section 4c shape), // v2.compiler.test_marker_capture over the same parse tree, so the semantic member stays identical // to a plain fn's. +// +// TWO PRODUCERS OF TEST-NESS STAND TODAY, AND THIS SAYS SO RATHER THAN DROPPING ONE. The floor's +// witness universe is still derived by v2.workflow.floor_discovery_source_authority +// discover_floor_rows_for_source, a line scan for `test fn ` / `test data ` over source text that +// deliberately neither parses nor resolves (v2.workflow.floor_naming_hygiene names it a second +// parser). The capture above is the compiler's. Converging the floor's enumeration onto the +// captured channel is a declared frontier, not done here: it has to keep discovery from paying a +// parse of every file, which is a floor-cost question of its own. 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) diff --git a/src/v2/std/declaration_marker.dag b/src/v2/std/declaration_marker.dag index da9d028aa0a..99b8e6557cd 100644 --- a/src/v2/std/declaration_marker.dag +++ b/src/v2/std/declaration_marker.dag @@ -1,5 +1,6 @@ module v2.std.declaration_marker +import v2.std.algebra { contains, length } import v2.std.collection { List } import v2.std.node { Symbol } @@ -29,9 +30,9 @@ type TestMarkerChannel { } fn test_marker_channel_marks(channel: TestMarkerChannel, name: Symbol) -> Bool { - fold(channel.marked, init: false, f: fn(acc, row) { acc || (row.name == name) }) + contains(xs: channel.marked, item: TestMarkedDeclaration { name: name }, eq: fn(a, b) { a.name == b.name }) } fn test_marker_channel_count(channel: TestMarkerChannel) -> Int { - std.types.list_length(items: channel.marked) + length(xs: channel.marked) } From 8881a518df13334d2f15c78b9b53e4360eddf2d6 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 16:31:43 +0000 Subject: [PATCH 41/69] Take #11599's grammar.dag as-is: the rebased merge had doubled its additions Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/std/grammar.dag | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index fe6a49b2cda..817a0aeac23 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -1741,42 +1741,6 @@ fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { } } -fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { - map_insert(m: m, key: sym, value: true) -} - -fn grammar_symbol_list_put(m: Map, xs: List) -> Map { - fold_list( - xs: xs, - empty: m, - cons: fn(acc, sym) { grammar_symbol_map_put(m: acc, sym: sym) } - ) -} - -fn grammar_node_atom_identities(n: Node) -> List { - fold_node( - n: n, - algebra: NodeFold { - init: fn(n0) { - match n0.kind { - TypeNode { connective: Atom { identity: id } } => - Cons { head: id, tail: Empty } - _ => Empty - } - }, - step: fn(acc, _e, child) { list_append(left: acc, right: child) } - } - ) -} - -fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map { - grammar_symbol_list_put(m: m, xs: grammar_node_atom_identities(n: n)) -} - -fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { - grammar_node_insert_atom_identities(m: empty_map(), n: grammar_to_node(grammar: grammar)) -} - type GrammarInterpretationDirection = GrammarInterpretBackward | GrammarInterpretForward From 9530e809a8a82c620bf22fc41fec55d45da3698b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 16:52:23 +0000 Subject: [PATCH 42/69] Name all three producers of test-ness, give each frontier a trigger, retract realization_attempt's stale reason Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/body_lowering_fold.dag | 18 +++++++++++------- src/v2/compiler/test_marker_capture.dag | 7 ++++--- src/v2/workflow/realization_attempt.dag | 12 +++++++++--- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index cef0cc9d07b..e8b7bb94a59 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -2068,13 +2068,17 @@ fn body_lower_fn_decl_named_member_wrap(arrow: Node, fn_name: Symbol) -> Node { // v2.compiler.test_marker_capture over the same parse tree, so the semantic member stays identical // to a plain fn's. // -// TWO PRODUCERS OF TEST-NESS STAND TODAY, AND THIS SAYS SO RATHER THAN DROPPING ONE. The floor's -// witness universe is still derived by v2.workflow.floor_discovery_source_authority -// discover_floor_rows_for_source, a line scan for `test fn ` / `test data ` over source text that -// deliberately neither parses nor resolves (v2.workflow.floor_naming_hygiene names it a second -// parser). The capture above is the compiler's. Converging the floor's enumeration onto the -// captured channel is a declared frontier, not done here: it has to keep discovery from paying a -// parse of every file, which is a floor-cost question of its own. +// THREE PRODUCERS OF TEST-NESS STAND TODAY, AND THIS NAMES ALL OF THEM RATHER THAN DROPPING ONE. +// (1) The capture above, the compiler's. (2) The floor's witness universe, derived by +// v2.workflow.floor_discovery_source_authority discover_floor_rows_for_source, a line scan for +// `test fn ` / `test data ` over source text that deliberately neither parses nor resolves +// (v2.workflow.floor_naming_hygiene names it a second parser). TRIGGER for converging it onto this +// channel: the floor preparing a parse artifact for each discovered source, at which point discovery +// reads the marks from that parse instead of rescanning the bytes. (3) v2.workflow.realization_attempt +// capture_test_markers, which strips the marker from source text before ingest. Its stated reason, +// that the grammar could not parse the marker, no longer holds (dag_production_test_fn_decl parses +// it and this arm lowers it), and its count has no reader. TRIGGER for deleting it: gunbc#11573 +// merging, which makes this capture the one on main; the deletion is its own change after that. 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) diff --git a/src/v2/compiler/test_marker_capture.dag b/src/v2/compiler/test_marker_capture.dag index 25c1589f41b..1010ad558bc 100644 --- a/src/v2/compiler/test_marker_capture.dag +++ b/src/v2/compiler/test_marker_capture.dag @@ -33,9 +33,10 @@ import v2.std.node_query { NamedChildAmbiguous, NamedChildFound, NamedChildMissi // would make a marked declaration read as unmarked, which is the absent-reads-as-false class the // channel exists to close. // -// CONSUMER, a declared frontier: the refusal of references to test code at name resolution -// (v2.compiler.name_resolve) lands in the next change and reads this channel. Until then the only -// executing reader is v2.test.claim.parse.test_marker_channel. +// CONSUMER, a declared frontier. The consumer is the refusal of references to test code at name +// resolution (v2.compiler.resolve resolve_atom_bound), reached through v2.compiler.normalize, which +// seals this channel beside the normalized root. TRIGGER: gunbc#11580 merging, the change that wires +// both. Until then the only executing reader is v2.test.claim.parse.test_marker_channel. fn test_marker_capture(artifact: ParseArtifact) -> Outcome { let no_rows: List = [] match test_marker_capture_walk(n: artifact.tree, acc: outcome_accepted(value: no_rows)) { diff --git a/src/v2/workflow/realization_attempt.dag b/src/v2/workflow/realization_attempt.dag index 3f8b0e9a391..299b9e3b7ba 100644 --- a/src/v2/workflow/realization_attempt.dag +++ b/src/v2/workflow/realization_attempt.dag @@ -143,6 +143,11 @@ fn collect_arrows(root: Node, acc: List) -> List { }) } +// DISSOLVE-ON MET, DELETION PENDING: the contextual `test` production (dag_production_test_fn_decl) +// parses the marker, and v2.compiler.test_marker_capture carries it on its own channel, so the +// condition below holds once gunbc#11573 is on main. Deleting this capture is its own change after +// that. The paragraph below is the scaffold's original reasoning, kept as its record. +// // STAGED SCAFFOLD (escalation msg_628838a5; dissolve-on: GrammarExpr contextual-keyword terminal // lands and dag fn_decl/data_decl productions carry the test marker structurally): the v2 dag // grammar cannot yet parse the authored test marker ('test' is a module-path segment, so it cannot @@ -367,9 +372,10 @@ fn attempt_assembled( // Sweep seam (CI2-0 Commit B): ONE assembly per entry closure, then one attempt_joined per required // test-fn identity — the exactly-once accounting grain the mandate names. The ingest is -// marker-captured at this seam only (test_marker_capture_note: the grammar cannot yet parse the -// authored test marker, so the capture scaffold rewrites line-leading markers and the count stays -// observable); an assembly refusal is replicated to EVERY identity in the entry with the +// marker-captured at this seam only (a scaffold whose original reason -- the grammar could not parse +// the authored test marker -- NO LONGER HOLDS: dag_production_test_fn_decl parses it and +// v2.compiler.test_marker_capture captures it. Retirement trigger: gunbc#11573 merging; the deletion +// is its own change after that); an assembly refusal is replicated to EVERY identity in the entry with the // reason-classified phase, because each identity is accountable and none of them reached its join. fn capture_markers_ingest(ingest: SourceRootIngest) -> SourceRootIngest { From c7cd02b761c3f7c3aa9d54339f69a44e4cd2ccff Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 09:54:36 +0000 Subject: [PATCH 43/69] Establish the dag canonical-symbol set once instead of rewalking the grammar per atom. Name resolution's ~63k-step membership check was grammar_carries_symbol converting the whole grammar to a Node on every unbound atom. Carry a derived map on LanguageModel.canonical_symbols so a one-member resolve stays under the new-witness eval-step budget. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 132 ++++++++++++++---- src/v2/std/compilers/lexing.dag | 22 ++- src/v2/std/grammar.dag | 98 ++++++++++++- .../one_member_cost_probe_test.dag | 127 +++++++++++++++++ 4 files changed, 344 insertions(+), 35 deletions(-) create mode 100644 src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 3fe6b136275..bf2c57d2dbb 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -10,7 +10,8 @@ import v2.std.collection { Map, empty_map, list_at_optional, - map_insert + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -47,6 +48,7 @@ import v2.std.grammar { Terminal, VoidGrammar, grammar_carries_symbol, + grammar_carried_symbol_map, formal_production_to_node, formal_productions_catalog_to_node, grammar_production_to_node, @@ -83,6 +85,7 @@ import v2.std.compilers.lexing { TokenRule, TriviaRule, WhitespaceChar, + lex_rule_set_token_class_map, lex_rule_set_token_class_member, symbol_intern_lexeme } @@ -2240,18 +2243,60 @@ fn dag_grammar() -> ParseGrammar { ModeledGrammar { root: dag_grammar_root() } } -fn dag_language_model_surface_prelude_atoms() -> PointwisePower { +fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { + map_insert(m: m, key: sym, value: true) +} + +fn dag_symbol_map_has(m: Map, sym: Symbol) -> Bool { + match map_lookup(m: m, key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { - sym == ^dag_c3_surface_sugar_service || - sym == ^dag_c3_surface_sugar_fn || - sym == ^dag_c3_surface_sugar_type || - sym == ^dag_c3_surface_sugar_operation || - dag_lex_token_symbol(sym: sym) + dag_symbol_map_has(m: m, sym: sym) } } } +fn dag_surface_sugar_symbol_map(m: Map) -> Map { + dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put(m: m, sym: ^dag_c3_surface_sugar_service), + sym: ^dag_c3_surface_sugar_fn + ), + sym: ^dag_c3_surface_sugar_type + ), + sym: ^dag_c3_surface_sugar_operation + ) +} + +fn dag_lex_token_class_insert(m: Map) -> Map { + fold_list( + xs: dag_lex_rules().rules, + empty: m, + cons: fn(acc, rule) { + match rule { + TokenRule { token_class: c, pattern: _ } => dag_symbol_map_put(m: acc, sym: c) + TriviaRule { pattern: _ } => acc + AnnotationRule { annotation_class: _, pattern: _ } => acc + } + } + ) +} + +fn dag_language_model_surface_prelude_map() -> Map { + dag_surface_sugar_symbol_map(m: lex_rule_set_token_class_map(rules: dag_lex_rules())) +} + +fn dag_language_model_surface_prelude_atoms() -> PointwisePower { + dag_pointwise_from_symbol_map(m: dag_language_model_surface_prelude_map()) +} + fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { PointwisePower { member: fn(_) { @@ -2260,29 +2305,59 @@ fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { } } +fn dag_language_model_binding_canonical_map(m: Map) -> Map { + dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put( + m: dag_symbol_map_put(m: m, sym: ^dag_binding_fn_add), + sym: ^dag_binding_param_x + ), + sym: ^dag_binding_param_y + ), + sym: ^dag_binding_type_int + ), + sym: ^dag_binding_fn_one_or_two + ), + sym: ^dag_binding_fn_one + ), + sym: ^dag_binding_bind_x + ), + sym: ^dag_binding_pick_lit_one + ), + sym: ^dag_binding_pick_lit_two + ), + sym: ^dag_binding_type_bool + ), + sym: ^bool_node_symbol + ), + sym: ^dag_loop_measure_sym + ) +} + fn dag_language_model_binding_canonical(sym: Symbol) -> Bool { - sym == ^dag_binding_fn_add - || sym == ^dag_binding_param_x - || sym == ^dag_binding_param_y - || sym == ^dag_binding_type_int - || sym == ^dag_binding_fn_one_or_two - || sym == ^dag_binding_fn_one - || sym == ^dag_binding_bind_x - || sym == ^dag_binding_pick_lit_one - || sym == ^dag_binding_pick_lit_two - || sym == ^dag_binding_type_bool - || sym == ^bool_node_symbol - || sym == ^dag_loop_measure_sym + dag_symbol_map_has(m: dag_language_model_binding_canonical_map(m: empty_map()), sym: sym) +} + +fn dag_canonical_symbol_map() -> Map { + dag_language_model_binding_canonical_map( + m: dag_surface_sugar_symbol_map( + m: dag_lex_token_class_insert( + m: grammar_carried_symbol_map(grammar: dag_grammar()) + ) + ) + ) } fn dag_canonical_symbols() -> PointwisePower { - PointwisePower { - member: fn(sym) { - dag_language_model_surface_prelude_atoms().member(sym) || - grammar_carries_symbol(grammar: dag_grammar(), sym: sym) || - dag_language_model_binding_canonical(sym: sym) - } - } + dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } fn dag_language_model_void() -> LanguageModel { @@ -2320,10 +2395,7 @@ fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower Bool { - lex_rule_set_token_class_member( - rules: dag_lex_rules(), - token_class: sym - ) + dag_symbol_map_has(m: lex_rule_set_token_class_map(rules: dag_lex_rules()), sym: sym) } fn dag_sync_tokens() -> List { diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index c176116ef9b..ec6347ad240 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -8,7 +8,11 @@ import std.disposition { Disposition, Scaffold, RealizationDispatch } import v2.std.algebra { fold_list, length, list_snoc_item, skip } import v2.std.collection { List, - list_at_optional + Map, + empty_map, + list_at_optional, + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -231,11 +235,23 @@ fn lex_rule_token_class_member(rule: LexRule, token_class: Symbol) -> Bool { } fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { + Present { value: _ } => true + Absent => false + } +} + +fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { fold_list( xs: rules.rules, - empty: false, + empty: empty_map(), cons: fn(acc, rule) { - acc || lex_rule_token_class_member(rule: rule, token_class: token_class) + match rule { + TokenRule { token_class: c, pattern: _ } => + map_insert(m: acc, key: c, value: true) + TriviaRule { pattern: _ } => acc + AnnotationRule { annotation_class: _, pattern: _ } => acc + } } ) } diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index a636c5f975e..2b9f979c7f8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -5,8 +5,12 @@ import std.algebra { Cons, Empty } import v2.std.algebra { fold_list, fold_list_node, list_append, list_snoc_item, zip_eq } import v2.std.collection { List, + Map, PointwisePower, - list_nth + empty_map, + list_nth, + map_insert, + map_lookup } import v2.std.optional { Absent, @@ -1730,7 +1734,97 @@ fn grammar_symbol_fold_state(n: Node, sym: Symbol) -> GrammarSymbolFoldState { } fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { - grammar_symbol_fold_state(n: grammar_to_node(grammar: grammar), sym: sym).found + match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { + map_insert(m: m, key: sym, value: true) +} + +fn grammar_symbol_list_put(m: Map, xs: List) -> Map { + fold_list( + xs: xs, + empty: m, + cons: fn(acc, sym) { grammar_symbol_map_put(m: acc, sym: sym) } + ) +} + +fn grammar_node_atom_identities(n: Node) -> List { + fold_node( + n: n, + algebra: NodeFold { + init: fn(n0) { + match n0.kind { + TypeNode { connective: Atom { identity: id } } => + Cons { head: id, tail: Empty } + _ => Empty + } + }, + step: fn(acc, _e, child) { list_append(left: acc, right: child) } + } + ) +} + +fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map { + grammar_symbol_list_put(m: m, xs: grammar_node_atom_identities(n: n)) +} + +fn grammar_expr_carried_symbols(expr: GrammarExpr) -> List { + fold_grammar_expr( + expr: expr, + algebra: GrammarExprFold { + terminal: fn(token_class, stamp) { + match stamp { + StampClass => Cons { head: token_class, tail: Empty } + StampBinding { binding: b } => + Cons { head: token_class, tail: Cons { head: b, tail: Empty } } + StampLexeme => + Cons { + head: token_class, + tail: Cons { head: ^grammar_terminal_stamp_lexeme, tail: Empty } + } + } + }, + nonterminal: fn(production) { Cons { head: production, tail: Empty } }, + sequence: fn(left, right) { list_append(left: left, right: right) }, + choice: fn(left, right) { list_append(left: left, right: right) }, + optional: fn(element) { element }, + repeat: fn(element) { element } + } + ) +} + +fn grammar_production_insert_carried_symbols( + m: Map, + production: GrammarProduction +) -> Map { + grammar_node_insert_atom_identities( + m: grammar_symbol_list_put( + m: grammar_symbol_map_put(m: m, sym: production.name), + xs: grammar_expr_carried_symbols(expr: production.expression) + ), + n: production.emitted + ) +} + +fn grammar_root_carried_symbol_map(root: GrammarRoot) -> Map { + fold_list( + xs: root.productions, + empty: grammar_symbol_map_put(m: empty_map(), sym: root.start), + cons: fn(acc, production) { + grammar_production_insert_carried_symbols(m: acc, production: production) + } + ) +} + +fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { + match grammar { + VoidGrammar => empty_map() + ModeledGrammar { root: root } => grammar_root_carried_symbol_map(root: root) + } } type GrammarInterpretationDirection diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag new file mode 100644 index 00000000000..a7ccbb5e020 --- /dev/null +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -0,0 +1,127 @@ +module v2.test.name_resolve.one_member_cost_probe + +import std.occurrence_identity { OccurrenceSynthetic } +import v2.compiler.name_resolve { + Admission, + ResolutionSubject, + resolve_with_admission +} +import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } +import v2.compiler.resolve { ResolvedTree } +import v2.extdeps.languages.dag { + dag_grammar_atom, + dag_language_model, + dag_named_edge +} +import std.algebra { Cons, Empty, FreeMonoid } +import v2.std.algebra { fold_list_node } +import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } +import v2.std.logic { Bool } +import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } +import v2.std.qualified_name { QualifiedName } + +fn probe_qn_node(seg: Symbol) -> Node { + fold_list_node( + xs: Cons { head: seg, tail: Empty }, + item_node: fn(sym) { + Node { + kind: TypeNode { connective: Atom { identity: sym } }, + children: [], + occurrence_id: OccurrenceSynthetic + } + } + ) +} + +fn probe_one_member_root() -> Node { + Node { + kind: TypeNode { connective: Conj }, + children: [ + dag_named_edge( + name: ^dag_surface_module_header, + target: dag_grammar_atom(id: ^dag_surface_module_header) + ), + dag_named_edge( + name: ^dag_surface_module_header_qualified_name, + target: probe_qn_node(seg: ^probe_one_member_seg) + ), + Edge { + label: Named { name: ^probe_export_sym }, + target: Node { + kind: TypeNode { + connective: Atom { identity: ^dag_c3_surface_sugar_service } + }, + children: [], + occurrence_id: OccurrenceSynthetic + } + } + ], + occurrence_id: OccurrenceSynthetic + } +} + +fn probe_one_member_qn() -> QualifiedName { + Cons { head: ^probe_one_member_seg, tail: Empty } +} + +fn probe_one_member_roots() -> Outcome> { + bind_outcome( + o: admit_normalized_tree(root: probe_one_member_root(), diagnostics: None), + f: fn(a) { + outcome_accepted(value: Cons { head: a, tail: Empty }) + } + ) +} + +fn probe_with_roots(f: fn(FreeMonoid) -> Bool) -> Bool { + match probe_one_member_roots() { + Rejected { diagnostics: _ } => false + Accepted { value: roots, diagnostics: _ } => f(roots) + } +} + +fn probe_resolved_keeps_service_atom(tree: ResolvedTree) -> Bool { + match tree.kind { + TypeNode { connective: Atom { identity: id } } => + id == ^dag_c3_surface_sugar_service + _ => + fold(tree.children, init: false, f: fn(acc, e) { + acc || probe_resolved_keeps_service_atom(tree: e.target) + }) + } +} + +test fn one_member_module_resolves_under_full_language_model() -> Bool { + probe_with_roots(f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) { + Accepted { value: resolved, diagnostics: _ } => + probe_resolved_keeps_service_atom(tree: resolved) + Rejected { diagnostics: _ } => false + } + }) +} + +test fn one_member_missing_subject_still_refuses() -> Bool { + probe_with_roots(f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { + name: Cons { head: ^probe_missing_subject_seg, tail: Empty } + }, + imports: Empty + } + ) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} From d82a2ce5b9212bd27577af0691fc170bd2ac8b85 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:00:01 +0000 Subject: [PATCH 44/69] Prove the carried-symbol map matches the node-fold oracle and that a too-wide map still refuses. The production path uses the derived map; grammar_carries_symbol remains the grammar-to-node fold so a complete tiny grammar can compare every membership both ways, and an uncanonical export still rejects under the full language model. Co-authored-by: Cursor --- src/v2/std/grammar.dag | 5 +- .../one_member_cost_probe_test.dag | 128 ++++++++++++++---- 2 files changed, 106 insertions(+), 27 deletions(-) diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index 2b9f979c7f8..fc86b6703b8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -1734,10 +1734,7 @@ fn grammar_symbol_fold_state(n: Node, sym: Symbol) -> GrammarSymbolFoldState { } fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { - match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { - Present { value: _ } => true - Absent => false - } + grammar_symbol_fold_state(n: grammar_to_node(grammar: grammar), sym: sym).found } fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index a7ccbb5e020..8fbf09fabb9 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -9,15 +9,30 @@ import v2.compiler.name_resolve { import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } import v2.compiler.resolve { ResolvedTree } import v2.extdeps.languages.dag { + dag_canonical_symbols, dag_grammar_atom, dag_language_model, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list_node } +import v2.std.collection { map_lookup } import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } +import v2.std.grammar { + GrammarProduction, + GrammarRoot, + ModeledGrammar, + ParseGrammar, + StampClass, + Terminal, + grammar_atom, + grammar_carried_symbol_map, + grammar_carries_symbol, + grammar_empty_sync_tokens +} import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } +import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } fn probe_qn_node(seg: Symbol) -> Node { @@ -33,7 +48,7 @@ fn probe_qn_node(seg: Symbol) -> Node { ) } -fn probe_one_member_root() -> Node { +fn probe_one_member_root_export(export_sym: Symbol) -> Node { Node { kind: TypeNode { connective: Conj }, children: [ @@ -48,9 +63,7 @@ fn probe_one_member_root() -> Node { Edge { label: Named { name: ^probe_export_sym }, target: Node { - kind: TypeNode { - connective: Atom { identity: ^dag_c3_surface_sugar_service } - }, + kind: TypeNode { connective: Atom { identity: export_sym } }, children: [], occurrence_id: OccurrenceSynthetic } @@ -64,52 +77,116 @@ fn probe_one_member_qn() -> QualifiedName { Cons { head: ^probe_one_member_seg, tail: Empty } } -fn probe_one_member_roots() -> Outcome> { +fn probe_roots_for_export(export_sym: Symbol) -> Outcome> { bind_outcome( - o: admit_normalized_tree(root: probe_one_member_root(), diagnostics: None), + o: admit_normalized_tree( + root: probe_one_member_root_export(export_sym: export_sym), + diagnostics: None + ), f: fn(a) { outcome_accepted(value: Cons { head: a, tail: Empty }) } ) } -fn probe_with_roots(f: fn(FreeMonoid) -> Bool) -> Bool { - match probe_one_member_roots() { +fn probe_with_export_roots( + export_sym: Symbol, + f: fn(FreeMonoid) -> Bool +) -> Bool { + match probe_roots_for_export(export_sym: export_sym) { Rejected { diagnostics: _ } => false Accepted { value: roots, diagnostics: _ } => f(roots) } } -fn probe_resolved_keeps_service_atom(tree: ResolvedTree) -> Bool { +fn probe_resolve(roots: FreeMonoid) -> Outcome { + resolve_with_admission( + lm: dag_language_model(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) +} + +fn probe_resolved_export_identity(tree: ResolvedTree, wanted: Symbol) -> Bool { match tree.kind { - TypeNode { connective: Atom { identity: id } } => - id == ^dag_c3_surface_sugar_service + TypeNode { connective: Atom { identity: id } } => id == wanted _ => fold(tree.children, init: false, f: fn(acc, e) { - acc || probe_resolved_keeps_service_atom(tree: e.target) + acc || probe_resolved_export_identity(tree: e.target, wanted: wanted) }) } } +fn map_carries(grammar: ParseGrammar, sym: Symbol) -> Bool { + match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { + Present { value: _ } => true + Absent => false + } +} + +fn eq_probe_grammar() -> ParseGrammar { + ModeledGrammar { + root: GrammarRoot { + start: ^eq_probe_start, + productions: Cons { + head: GrammarProduction { + name: ^eq_probe_start, + expression: Terminal { + token_class: ^eq_probe_tok, + stamp: StampClass + }, + emitted: grammar_atom(identity: ^eq_probe_emitted) + }, + tail: Empty + }, + sync_tokens: grammar_empty_sync_tokens() + } + } +} + +fn map_agrees_with_node_fold(sym: Symbol) -> Bool { + let g = eq_probe_grammar() + map_carries(grammar: g, sym: sym) == grammar_carries_symbol(grammar: g, sym: sym) +} + +test fn carried_symbol_map_agrees_with_node_fold_on_complete_probe_grammar() -> Bool { + map_agrees_with_node_fold(sym: ^eq_probe_start) + && map_agrees_with_node_fold(sym: ^eq_probe_tok) + && map_agrees_with_node_fold(sym: ^eq_probe_emitted) + && map_agrees_with_node_fold(sym: ^eq_probe_absent) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_start) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_tok) + && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_emitted) + && !map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_absent) +} + test fn one_member_module_resolves_under_full_language_model() -> Bool { - probe_with_roots(f: fn(roots) { - match resolve_with_admission( - lm: dag_language_model(), - roots: roots, - admission: Admission { - subject: ResolutionSubject { name: probe_one_member_qn() }, - imports: Empty - } - ) { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { + match probe_resolve(roots: roots) { Accepted { value: resolved, diagnostics: _ } => - probe_resolved_keeps_service_atom(tree: resolved) + probe_resolved_export_identity( + tree: resolved, + wanted: ^dag_c3_surface_sugar_service + ) Rejected { diagnostics: _ } => false } }) } +test fn one_member_uncanonical_export_still_refuses() -> Bool { + probe_with_export_roots(export_sym: ^probe_uncanonical_export, f: fn(roots) { + match probe_resolve(roots: roots) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} + test fn one_member_missing_subject_still_refuses() -> Bool { - probe_with_roots(f: fn(roots) { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { match resolve_with_admission( lm: dag_language_model(), roots: roots, @@ -125,3 +202,8 @@ test fn one_member_missing_subject_still_refuses() -> Bool { } }) } + +test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { + dag_canonical_symbols().member(^dag_c3_surface_sugar_service) + && !dag_canonical_symbols().member(^probe_uncanonical_export) +} From 9b39e9979a090e1930cca18a4a60d42328439c32 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:04:15 +0000 Subject: [PATCH 45/69] Collapse the dag-local lex token-class fold into std and drop unused membership wrappers. lex_rule_set_insert_token_classes is the single TokenRule walk; dag_canonical_symbol_map seeds it. Dead lex_rule_token_class_member, dag_lex_token_class_insert, and dag_language_model_binding_canonical go with the cutover. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 25 ++++-------------------- src/v2/std/compilers/lexing.dag | 33 ++++++++++++++++---------------- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index bf2c57d2dbb..a7f38030b33 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -85,8 +85,8 @@ import v2.std.compilers.lexing { TokenRule, TriviaRule, WhitespaceChar, + lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, - lex_rule_set_token_class_member, symbol_intern_lexeme } import v2.std.node { @@ -2275,20 +2275,6 @@ fn dag_surface_sugar_symbol_map(m: Map) -> Map { ) } -fn dag_lex_token_class_insert(m: Map) -> Map { - fold_list( - xs: dag_lex_rules().rules, - empty: m, - cons: fn(acc, rule) { - match rule { - TokenRule { token_class: c, pattern: _ } => dag_symbol_map_put(m: acc, sym: c) - TriviaRule { pattern: _ } => acc - AnnotationRule { annotation_class: _, pattern: _ } => acc - } - } - ) -} - fn dag_language_model_surface_prelude_map() -> Map { dag_surface_sugar_symbol_map(m: lex_rule_set_token_class_map(rules: dag_lex_rules())) } @@ -2342,15 +2328,12 @@ fn dag_language_model_binding_canonical_map(m: Map) -> Map Bool { - dag_symbol_map_has(m: dag_language_model_binding_canonical_map(m: empty_map()), sym: sym) -} - fn dag_canonical_symbol_map() -> Map { dag_language_model_binding_canonical_map( m: dag_surface_sugar_symbol_map( - m: dag_lex_token_class_insert( - m: grammar_carried_symbol_map(grammar: dag_grammar()) + m: lex_rule_set_insert_token_classes( + m: grammar_carried_symbol_map(grammar: dag_grammar()), + rules: dag_lex_rules() ) ) ) diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index ec6347ad240..e50b3ae6a91 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -226,25 +226,13 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List Bool { - match rule { - TokenRule { token_class: c, pattern: _ } => c == token_class - TriviaRule { pattern: _ } => false - AnnotationRule { annotation_class: _, pattern: _ } => false - } -} - -fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { - match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { - Present { value: _ } => true - Absent => false - } -} - -fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { +fn lex_rule_set_insert_token_classes( + m: Map, + rules: LexRuleSet +) -> Map { fold_list( xs: rules.rules, - empty: empty_map(), + empty: m, cons: fn(acc, rule) { match rule { TokenRule { token_class: c, pattern: _ } => @@ -256,6 +244,17 @@ fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { ) } +fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { + lex_rule_set_insert_token_classes(m: empty_map(), rules: rules) +} + +fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { + Present { value: _ } => true + Absent => false + } +} + type LexPatternFold { empty: R literal: fn(v2.std.text.String) -> R From da428b5af94505e65317a8fad257f86f62d23cac Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:27:28 +0000 Subject: [PATCH 46/69] Build the canonical-symbol map in resolution context, not in dag_language_model. Eager map fill on LanguageModel charged every tokenize/parse claim ~33k steps. The set is established once on ResolutionContext (and in build_program_namespace) where resolve actually members it; LM construction is back to ~3.7k. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 21 ++++++++++--------- src/v2/compiler/03_resolve.dag | 4 ++-- src/v2/extdeps/languages/dag.dag | 2 +- .../one_member_cost_probe_test.dag | 4 ++++ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 456d04ea4ca..19f44e72134 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,7 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_language_model_canonical_symbols, + dag_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -31,7 +31,7 @@ import v2.compiler.namespace_graft { } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list } -import v2.std.collection { Absent, Map, Present, List, empty_map, map_get, map_lookup, map_insert, optional_absent, optional_present } +import v2.std.collection { Absent, Map, Present, List, PointwisePower, empty_map, map_get, map_lookup, map_insert, optional_absent, optional_present } import v2.std.diagnostic { Accepted, Diagnostic, @@ -111,6 +111,7 @@ type ResolutionContext { lm: LanguageModel roots: ValidatedModuleRoots symbol_index: SymbolIndex + canonical_symbols: PointwisePower } fn resolution_context( @@ -127,7 +128,8 @@ fn resolution_context( symbol_index: symbol_index_fill_module_roots( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) - ) + ), + canonical_symbols: dag_canonical_symbols() }, diagnostics: d } @@ -556,10 +558,10 @@ fn admit_imports( ) } -fn admission_namespace_base(lm: LanguageModel) -> Namespace { +fn admission_namespace_base(canonical_symbols: PointwisePower) -> Namespace { Namespace { bindings: empty_map(), - canonical_symbols: dag_language_model_canonical_symbols(lm: lm), + canonical_symbols: canonical_symbols, symbol_index: empty_symbol_index(), module_qn: Empty } @@ -579,11 +581,11 @@ fn subject_module_root_for_name( fn namespace_admission_initial_state( policy: NameResolutionPolicy, - lm: LanguageModel, + canonical_symbols: PointwisePower, subject_module_root: NormalizedTree, subject_qn: QualifiedName ) -> AdmissionState { - let base = admission_namespace_base(lm: lm) + let base = admission_namespace_base(canonical_symbols: canonical_symbols) match policy { NamespaceOnlyY => admission_initial(base: base) @@ -660,13 +662,12 @@ fn namespace_for_subject_root( active_roots: FreeMonoid, policy: NameResolutionPolicy ) -> Outcome { - let lm = shared.lm let validated_roots = shared.roots if well_formed(n: subject_module_root.root) { - let base = admission_namespace_base(lm: lm) + let base = admission_namespace_base(canonical_symbols: shared.canonical_symbols) let initial = namespace_admission_initial_state( policy: policy, - lm: lm, + canonical_symbols: shared.canonical_symbols, subject_module_root: subject_module_root, subject_qn: admission.subject.name ) diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 0c9158067ec..fb00e447f0d 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,7 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_language_model_canonical_symbols, + dag_canonical_symbols, dag_node_is_float_literal_atom, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, @@ -237,7 +237,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_language_model_canonical_symbols(lm: lm), + canonical_symbols: dag_canonical_symbols(), symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index a7f38030b33..5f81f5aba71 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2358,7 +2358,7 @@ fn dag_language_model() -> LanguageModel { lex: dag_lex(), grammar: dag_grammar(), language_identity: ^dag_language_model_surface_id, - canonical_symbols: dag_canonical_symbols(), + canonical_symbols: dag_language_model_empty_canonical_symbol_set(), core: bool_model_core() } } diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 8fbf09fabb9..8c234c747d3 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -203,6 +203,10 @@ test fn one_member_missing_subject_still_refuses() -> Bool { }) } +test fn language_model_construction_does_not_fill_canonical_map() -> Bool { + dag_language_model().language_identity == ^dag_language_model_surface_id +} + test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { dag_canonical_symbols().member(^dag_c3_surface_sugar_service) && !dag_canonical_symbols().member(^probe_uncanonical_export) From 9d920cf6caba56132d65a907935b9dcc4a2f999f Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 10:31:56 +0000 Subject: [PATCH 47/69] Enroll a tokenize/parse probe that never enters resolution. Parent asked for a floor claim the front end does not pay the canonical-symbol map. This claim only constructs the language model, tokenizes, and parses a one-member module. Co-authored-by: Cursor --- .../one_member_cost_probe_test.dag | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 8c234c747d3..c951e2bc224 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -7,7 +7,9 @@ import v2.compiler.name_resolve { resolve_with_admission } import v2.compiler.normalized_tree { NormalizedTree, admit_normalized_tree } +import v2.compiler.parse { parse_module } import v2.compiler.resolve { ResolvedTree } +import v2.compiler.tokenize { tokenize } import v2.extdeps.languages.dag { dag_canonical_symbols, dag_grammar_atom, @@ -34,6 +36,7 @@ import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } +import v2.std.text { String } fn probe_qn_node(seg: Symbol) -> Node { fold_list_node( @@ -207,6 +210,24 @@ test fn language_model_construction_does_not_fill_canonical_map() -> Bool { dag_language_model().language_identity == ^dag_language_model_surface_id } +data probe_front_end_source: String = "module probe.one_member\n" + +test fn tokenize_parse_one_member_does_not_run_resolve() -> Bool { + let lm = dag_language_model() + match tokenize( + text: probe_front_end_source, + file: ^probe_front_end_file, + rules: lm.lex + ) { + Rejected { diagnostics: _ } => false + Accepted { value: tokens, diagnostics: _ } => + match parse_module(tokens: tokens, grammar: lm.grammar) { + Accepted { value: _, diagnostics: _ } => true + Rejected { diagnostics: _ } => false + } + } +} + test fn dag_canonical_symbols_keep_prelude_and_reject_uncanonical() -> Bool { dag_canonical_symbols().member(^dag_c3_surface_sugar_service) && !dag_canonical_symbols().member(^probe_uncanonical_export) From d3bd26c06336cc805094c012ac2656dc67222fd0 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 11:08:06 +0000 Subject: [PATCH 48/69] Derive resolve canonical symbols from the language model that was passed. Always installing dag_canonical_symbols() widened empty-prelude resolve: a service atom that used to refuse now accepted. Void grammars keep lm.canonical_symbols; modeled grammars build the map from that model's grammar and lex at resolution time. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 4 ++-- src/v2/compiler/03_resolve.dag | 4 ++-- src/v2/extdeps/languages/dag.dag | 18 ++++++++++++++++ src/v2/std/compilers/lexing.dag | 11 ++++++++++ .../one_member_cost_probe_test.dag | 21 ++++++++++++++++++- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 19f44e72134..1dd52faa44b 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,7 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_canonical_symbols, + dag_resolution_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -129,7 +129,7 @@ fn resolution_context( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) ), - canonical_symbols: dag_canonical_symbols() + canonical_symbols: dag_resolution_canonical_symbols(lm: lm) }, diagnostics: d } diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index fb00e447f0d..a418904b8ce 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,7 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_canonical_symbols, + dag_resolution_canonical_symbols, dag_node_is_float_literal_atom, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, @@ -237,7 +237,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_canonical_symbols(), + canonical_symbols: dag_resolution_canonical_symbols(lm: lm), symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 5f81f5aba71..f3982becad5 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -87,6 +87,7 @@ import v2.std.compilers.lexing { WhitespaceChar, lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, + lex_rules_insert_token_classes, symbol_intern_lexeme } import v2.std.node { @@ -2343,6 +2344,23 @@ fn dag_canonical_symbols() -> PointwisePower { dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } +fn dag_resolution_canonical_symbols(lm: LanguageModel) -> PointwisePower { + match lm.grammar { + VoidGrammar => lm.canonical_symbols + ModeledGrammar { root: _ } => + dag_pointwise_from_symbol_map( + m: dag_language_model_binding_canonical_map( + m: dag_surface_sugar_symbol_map( + m: lex_rules_insert_token_classes( + m: grammar_carried_symbol_map(grammar: lm.grammar), + rules: lm.lex + ) + ) + ) + ) + } +} + fn dag_language_model_void() -> LanguageModel { LanguageModel { lex: dag_lex(), diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index e50b3ae6a91..8dbc6634af1 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -226,6 +226,17 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List, + rules: LexRules +) -> Map { + match rules { + VoidLexRules => m + ModeledLexRules { root: root } => + lex_rule_set_insert_token_classes(m: m, rules: root) + } +} + fn lex_rule_set_insert_token_classes( m: Map, rules: LexRuleSet diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index c951e2bc224..62afe57ebfb 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -14,6 +14,7 @@ import v2.extdeps.languages.dag { dag_canonical_symbols, dag_grammar_atom, dag_language_model, + dag_language_model_surface_empty_prelude, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } @@ -206,8 +207,26 @@ test fn one_member_missing_subject_still_refuses() -> Bool { }) } +test fn empty_prelude_service_atom_still_refuses() -> Bool { + probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { + match resolve_with_admission( + lm: dag_language_model_surface_empty_prelude(), + roots: roots, + admission: Admission { + subject: ResolutionSubject { name: probe_one_member_qn() }, + imports: Empty + } + ) { + Accepted { value: _, diagnostics: _ } => false + Rejected { diagnostics: _ } => true + } + }) +} + test fn language_model_construction_does_not_fill_canonical_map() -> Bool { - dag_language_model().language_identity == ^dag_language_model_surface_id + let lm = dag_language_model() + (lm.language_identity == ^dag_language_model_surface_id) + && !lm.canonical_symbols.member(^dag_surface_module_header) } data probe_front_end_source: String = "module probe.one_member\n" From e3facc8f0cd92799469e4ee5567652eae119b148 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 11:43:43 +0000 Subject: [PATCH 49/69] Derive the grammar symbol map from grammar_to_node and restore linear lex membership. The carried-symbol map was a parallel GrammarExpr walk; resolve still pays that set once, but the atoms now come from the same node projection as grammar_carries_symbol. Per-query token-class tests walk TokenRule again instead of rebuilding the map on body-lowering's hot path. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 6 +- src/v2/std/compilers/lexing.dag | 22 +++++-- src/v2/std/grammar.dag | 53 +---------------- .../one_member_cost_probe_test.dag | 57 ------------------- 4 files changed, 22 insertions(+), 116 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index f3982becad5..452b935bc9e 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -87,6 +87,7 @@ import v2.std.compilers.lexing { WhitespaceChar, lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, + lex_rule_set_token_class_member, lex_rules_insert_token_classes, symbol_intern_lexeme } @@ -2396,7 +2397,10 @@ fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower Bool { - dag_symbol_map_has(m: lex_rule_set_token_class_map(rules: dag_lex_rules()), sym: sym) + lex_rule_set_token_class_member( + rules: dag_lex_rules(), + token_class: sym + ) } fn dag_sync_tokens() -> List { diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index 8dbc6634af1..89c6243da51 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -11,8 +11,7 @@ import v2.std.collection { Map, empty_map, list_at_optional, - map_insert, - map_lookup + map_insert } import v2.std.optional { Absent, @@ -259,13 +258,24 @@ fn lex_rule_set_token_class_map(rules: LexRuleSet) -> Map { lex_rule_set_insert_token_classes(m: empty_map(), rules: rules) } -fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { - match map_lookup(m: lex_rule_set_token_class_map(rules: rules), key: token_class) { - Present { value: _ } => true - Absent => false +fn lex_rule_token_class_member(rule: LexRule, token_class: Symbol) -> Bool { + match rule { + TokenRule { token_class: c, pattern: _ } => c == token_class + TriviaRule { pattern: _ } => false + AnnotationRule { annotation_class: _, pattern: _ } => false } } +fn lex_rule_set_token_class_member(rules: LexRuleSet, token_class: Symbol) -> Bool { + fold_list( + xs: rules.rules, + empty: false, + cons: fn(acc, rule) { + acc || lex_rule_token_class_member(rule: rule, token_class: token_class) + } + ) +} + type LexPatternFold { empty: R literal: fn(v2.std.text.String) -> R diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index fc86b6703b8..c94ec0925e8 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -1769,59 +1769,8 @@ fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map List { - fold_grammar_expr( - expr: expr, - algebra: GrammarExprFold { - terminal: fn(token_class, stamp) { - match stamp { - StampClass => Cons { head: token_class, tail: Empty } - StampBinding { binding: b } => - Cons { head: token_class, tail: Cons { head: b, tail: Empty } } - StampLexeme => - Cons { - head: token_class, - tail: Cons { head: ^grammar_terminal_stamp_lexeme, tail: Empty } - } - } - }, - nonterminal: fn(production) { Cons { head: production, tail: Empty } }, - sequence: fn(left, right) { list_append(left: left, right: right) }, - choice: fn(left, right) { list_append(left: left, right: right) }, - optional: fn(element) { element }, - repeat: fn(element) { element } - } - ) -} - -fn grammar_production_insert_carried_symbols( - m: Map, - production: GrammarProduction -) -> Map { - grammar_node_insert_atom_identities( - m: grammar_symbol_list_put( - m: grammar_symbol_map_put(m: m, sym: production.name), - xs: grammar_expr_carried_symbols(expr: production.expression) - ), - n: production.emitted - ) -} - -fn grammar_root_carried_symbol_map(root: GrammarRoot) -> Map { - fold_list( - xs: root.productions, - empty: grammar_symbol_map_put(m: empty_map(), sym: root.start), - cons: fn(acc, production) { - grammar_production_insert_carried_symbols(m: acc, production: production) - } - ) -} - fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { - match grammar { - VoidGrammar => empty_map() - ModeledGrammar { root: root } => grammar_root_carried_symbol_map(root: root) - } + grammar_node_insert_atom_identities(m: empty_map(), n: grammar_to_node(grammar: grammar)) } type GrammarInterpretationDirection diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index 62afe57ebfb..a53a67ad11f 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -19,23 +19,9 @@ import v2.extdeps.languages.dag { } import std.algebra { Cons, Empty, FreeMonoid } import v2.std.algebra { fold_list_node } -import v2.std.collection { map_lookup } import v2.std.diagnostic { Accepted, None, Outcome, Rejected, bind_outcome, outcome_accepted } -import v2.std.grammar { - GrammarProduction, - GrammarRoot, - ModeledGrammar, - ParseGrammar, - StampClass, - Terminal, - grammar_atom, - grammar_carried_symbol_map, - grammar_carries_symbol, - grammar_empty_sync_tokens -} import v2.std.logic { Bool } import v2.std.node { Atom, Conj, Edge, Named, Node, Symbol, TypeNode } -import v2.std.optional { Absent, Present } import v2.std.qualified_name { QualifiedName } import v2.std.text { String } @@ -124,49 +110,6 @@ fn probe_resolved_export_identity(tree: ResolvedTree, wanted: Symbol) -> Bool { } } -fn map_carries(grammar: ParseGrammar, sym: Symbol) -> Bool { - match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { - Present { value: _ } => true - Absent => false - } -} - -fn eq_probe_grammar() -> ParseGrammar { - ModeledGrammar { - root: GrammarRoot { - start: ^eq_probe_start, - productions: Cons { - head: GrammarProduction { - name: ^eq_probe_start, - expression: Terminal { - token_class: ^eq_probe_tok, - stamp: StampClass - }, - emitted: grammar_atom(identity: ^eq_probe_emitted) - }, - tail: Empty - }, - sync_tokens: grammar_empty_sync_tokens() - } - } -} - -fn map_agrees_with_node_fold(sym: Symbol) -> Bool { - let g = eq_probe_grammar() - map_carries(grammar: g, sym: sym) == grammar_carries_symbol(grammar: g, sym: sym) -} - -test fn carried_symbol_map_agrees_with_node_fold_on_complete_probe_grammar() -> Bool { - map_agrees_with_node_fold(sym: ^eq_probe_start) - && map_agrees_with_node_fold(sym: ^eq_probe_tok) - && map_agrees_with_node_fold(sym: ^eq_probe_emitted) - && map_agrees_with_node_fold(sym: ^eq_probe_absent) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_start) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_tok) - && map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_emitted) - && !map_carries(grammar: eq_probe_grammar(), sym: ^eq_probe_absent) -} - test fn one_member_module_resolves_under_full_language_model() -> Bool { probe_with_export_roots(export_sym: ^dag_c3_surface_sugar_service, f: fn(roots) { match probe_resolve(roots: roots) { From 42892e52ad7c477dcbec408fb1590f6897c4ad6b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:17:57 +0000 Subject: [PATCH 50/69] Carry canonical symbols on the language model resolve actually uses. Resolution reads lm.canonical_symbols instead of a second dag-only derivation that discarded sibling models' declared sets. dag_language_model fills that field; tokenize/parse demand dag_lex and dag_grammar so they do not construct the map. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 3 +-- src/v2/compiler/03_resolve.dag | 3 +-- src/v2/extdeps/languages/dag.dag | 20 +------------------ .../one_member_cost_probe_test.dag | 13 ++++++------ 4 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 1dd52faa44b..fdf045edf6f 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -20,7 +20,6 @@ import v2.std.resolution_policy { default_name_resolution_policy } import v2.extdeps.languages.dag { - dag_resolution_canonical_symbols, dag_surface_module_header_metadata_edge, namespace_graft_already_grafted, namespace_graft_spine_segment_edge_optional, @@ -129,7 +128,7 @@ fn resolution_context( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) ), - canonical_symbols: dag_resolution_canonical_symbols(lm: lm) + canonical_symbols: lm.canonical_symbols }, diagnostics: d } diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index a418904b8ce..6811b0356df 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -5,7 +5,6 @@ import v2.compiler.normalized_tree { NormalizedTree } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import v2.extdeps.languages.dag { - dag_resolution_canonical_symbols, dag_node_is_float_literal_atom, dag_node_is_int_literal_atom, dag_node_is_module_root_conj, @@ -237,7 +236,7 @@ fn build_program_namespace(tree: NormalizedTree, lm: LanguageModel) -> Namespace root: tree.root, bindings: empty_map() ), - canonical_symbols: dag_resolution_canonical_symbols(lm: lm), + canonical_symbols: lm.canonical_symbols, symbol_index: empty_symbol_index(), module_qn: Empty } diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 452b935bc9e..615c6240810 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -88,7 +88,6 @@ import v2.std.compilers.lexing { lex_rule_set_insert_token_classes, lex_rule_set_token_class_map, lex_rule_set_token_class_member, - lex_rules_insert_token_classes, symbol_intern_lexeme } import v2.std.node { @@ -2345,23 +2344,6 @@ fn dag_canonical_symbols() -> PointwisePower { dag_pointwise_from_symbol_map(m: dag_canonical_symbol_map()) } -fn dag_resolution_canonical_symbols(lm: LanguageModel) -> PointwisePower { - match lm.grammar { - VoidGrammar => lm.canonical_symbols - ModeledGrammar { root: _ } => - dag_pointwise_from_symbol_map( - m: dag_language_model_binding_canonical_map( - m: dag_surface_sugar_symbol_map( - m: lex_rules_insert_token_classes( - m: grammar_carried_symbol_map(grammar: lm.grammar), - rules: lm.lex - ) - ) - ) - ) - } -} - fn dag_language_model_void() -> LanguageModel { LanguageModel { lex: dag_lex(), @@ -2377,7 +2359,7 @@ fn dag_language_model() -> LanguageModel { lex: dag_lex(), grammar: dag_grammar(), language_identity: ^dag_language_model_surface_id, - canonical_symbols: dag_language_model_empty_canonical_symbol_set(), + canonical_symbols: dag_canonical_symbols(), core: bool_model_core() } } diff --git a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag index a53a67ad11f..614d7eabb3a 100644 --- a/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag +++ b/src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag @@ -12,9 +12,11 @@ import v2.compiler.resolve { ResolvedTree } import v2.compiler.tokenize { tokenize } import v2.extdeps.languages.dag { dag_canonical_symbols, + dag_grammar, dag_grammar_atom, dag_language_model, dag_language_model_surface_empty_prelude, + dag_lex, dag_named_edge } import std.algebra { Cons, Empty, FreeMonoid } @@ -166,24 +168,23 @@ test fn empty_prelude_service_atom_still_refuses() -> Bool { }) } -test fn language_model_construction_does_not_fill_canonical_map() -> Bool { +test fn language_model_carries_its_canonical_symbols() -> Bool { let lm = dag_language_model() - (lm.language_identity == ^dag_language_model_surface_id) - && !lm.canonical_symbols.member(^dag_surface_module_header) + lm.canonical_symbols.member(^dag_c3_surface_sugar_service) + && !lm.canonical_symbols.member(^probe_uncanonical_export) } data probe_front_end_source: String = "module probe.one_member\n" test fn tokenize_parse_one_member_does_not_run_resolve() -> Bool { - let lm = dag_language_model() match tokenize( text: probe_front_end_source, file: ^probe_front_end_file, - rules: lm.lex + rules: dag_lex() ) { Rejected { diagnostics: _ } => false Accepted { value: tokens, diagnostics: _ } => - match parse_module(tokens: tokens, grammar: lm.grammar) { + match parse_module(tokens: tokens, grammar: dag_grammar()) { Accepted { value: _, diagnostics: _ } => true Rejected { diagnostics: _ } => false } From 166b55397b57b36f3cceada5cf1c69a5fd307a9b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:28:56 +0000 Subject: [PATCH 51/69] Delete unused lex/canonical wrappers left by the resolve cutover. lex_rules_insert_token_classes and dag_language_model_canonical_symbols had no callers. Map membership for the pointwise set is map_lookup at the closure, not a dag-local has_* wrapper. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 16 ++++------------ src/v2/std/compilers/lexing.dag | 11 ----------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 615c6240810..01105404e29 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2248,17 +2248,13 @@ fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { map_insert(m: m, key: sym, value: true) } -fn dag_symbol_map_has(m: Map, sym: Symbol) -> Bool { - match map_lookup(m: m, key: sym) { - Present { value: _ } => true - Absent => false - } -} - fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { - dag_symbol_map_has(m: m, sym: sym) + match map_lookup(m: m, key: sym) { + Present { value: _ } => true + Absent => false + } } } } @@ -2374,10 +2370,6 @@ fn dag_language_model_surface_empty_prelude() -> LanguageModel { } } -fn dag_language_model_canonical_symbols(lm: LanguageModel) -> PointwisePower { - lm.canonical_symbols -} - fn dag_lex_token_symbol(sym: Symbol) -> Bool { lex_rule_set_token_class_member( rules: dag_lex_rules(), diff --git a/src/v2/std/compilers/lexing.dag b/src/v2/std/compilers/lexing.dag index 89c6243da51..c451c29ffcc 100644 --- a/src/v2/std/compilers/lexing.dag +++ b/src/v2/std/compilers/lexing.dag @@ -225,17 +225,6 @@ fn lex_artifact_new(tokens: TokenStream, annotations: List, - rules: LexRules -) -> Map { - match rules { - VoidLexRules => m - ModeledLexRules { root: root } => - lex_rule_set_insert_token_classes(m: m, rules: root) - } -} - fn lex_rule_set_insert_token_classes( m: Map, rules: LexRuleSet From 0ddeb3bee72b00544e7487f8cc4855ff841f5ac5 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 12:33:26 +0000 Subject: [PATCH 52/69] Warm-share the portable dag canonical-symbol map across floor claims. PointwisePower.member is a closure and cannot be stored. dag_canonical_symbol_map is the Map fill every resolving claim re-derived per frame; the floor serves that map once at preparation and dag_canonical_symbols wraps it at the consumer. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 3 +++ src/v2/workflow/floor_pure_producer_share.dag | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 01105404e29..3629844e18f 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -2325,6 +2325,9 @@ fn dag_language_model_binding_canonical_map(m: Map) -> Map Map { dag_language_model_binding_canonical_map( m: dag_surface_sugar_symbol_map( diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index 886c205d29a..c36854b1982 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -467,6 +467,24 @@ import v2.std.collection { List } // reason the parse rows above keep normalize out: the claims in that file are ABOUT the collector, // so sharing the collection would hand each one a precomputed answer to the question it asks. The // walk stays per claim; only the stage no claim names is shared. +// DAG CANONICAL-SYMBOL MAP EARNS ITS ROW ON THE ENROLMENT-MARGIN CPU THE EVAL-STEP GATE +// CANNOT SEE. Sibling floor run 35340819603 (#11580 merging the resolve-cost head that +// establishes this map once per ResolutionContext) passed eval-steps and refused every +// resolving claim at the 302ms enrolment margin, including one-member resolve probes that +// sit near 40k steps. The same fill shows on grandfathered resolve claims. Re-derive with +// required_floor_claim_cost.tsv on that run plus claim_batch --entry +// src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag [witness] cpu vs eval_steps: +// the map fill is host work outside the step counter, paid once per claim frame because the +// eval-frame memo dies at the claim boundary. Sharing census is the call-site graph: every +// dag_language_model() resolve path reads dag_canonical_symbols(), which is a PointwisePower +// closure and therefore not portable (ServeCacheValueNotPortable, the prepare_grammar +// nullable_set shape). The share point is the Map it is built from -- +// dag_canonical_symbol_map -- symbols and bools only; dag_canonical_symbols wraps that map +// at the consumer. Serve-below-recompute is the header's zero-walk Rc hand-over of a small +// map against a fill that already exceeds the enrolment margin in-fold. WARM, NEVER +// CLAIM-FORCED, by the same ceiling arithmetic as dag_prepared_grammar: an in-fold first +// touch of this fill dies on the lane's margin and restarts per claim. The module is under +// src/v2/extdeps, so it resolves in every required-floor subject. data floor_cross_claim_pure_producers_warm: List = [ "v2.test.native_decl_selection.collision_resolved", "v2.extdeps.languages.bash_command_fold.bash_fold_formal_productions", @@ -496,7 +514,8 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_literal_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.braced_fn_control_parsed", "v2.test.claim.body_lowering_block_body_call.body_lowering_block_call_parsed", - "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized" + "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized", + "v2.extdeps.languages.dag.dag_canonical_symbol_map" ] // grammar_relation_row_for_emitted HAS NOW BEEN MEASURED ON THE THIRD CONJUNCT, AND IT PASSES. From 031983eb47a5f44f885edd2a61552b0991b222e9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 15:41:42 +0000 Subject: [PATCH 53/69] Use grammar_symbol_list_put for dag canonical sugar and binding symbols. dag_symbol_map_put was a second name for grammar_symbol_map_put. Nested puts become one fold over a symbol list. Co-authored-by: Cursor --- src/v2/extdeps/languages/dag.dag | 71 +++++++++++--------------------- 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/src/v2/extdeps/languages/dag.dag b/src/v2/extdeps/languages/dag.dag index 3629844e18f..e06eb9a5dac 100644 --- a/src/v2/extdeps/languages/dag.dag +++ b/src/v2/extdeps/languages/dag.dag @@ -49,6 +49,7 @@ import v2.std.grammar { VoidGrammar, grammar_carries_symbol, grammar_carried_symbol_map, + grammar_symbol_list_put, formal_production_to_node, formal_productions_catalog_to_node, grammar_production_to_node, @@ -2244,10 +2245,6 @@ fn dag_grammar() -> ParseGrammar { ModeledGrammar { root: dag_grammar_root() } } -fn dag_symbol_map_put(m: Map, sym: Symbol) -> Map { - map_insert(m: m, key: sym, value: true) -} - fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower { PointwisePower { member: fn(sym) { @@ -2260,15 +2257,14 @@ fn dag_pointwise_from_symbol_map(m: Map) -> PointwisePower } fn dag_surface_sugar_symbol_map(m: Map) -> Map { - dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put(m: m, sym: ^dag_c3_surface_sugar_service), - sym: ^dag_c3_surface_sugar_fn - ), - sym: ^dag_c3_surface_sugar_type - ), - sym: ^dag_c3_surface_sugar_operation + grammar_symbol_list_put( + m: m, + xs: [ + ^dag_c3_surface_sugar_service, + ^dag_c3_surface_sugar_fn, + ^dag_c3_surface_sugar_type, + ^dag_c3_surface_sugar_operation + ] ) } @@ -2289,39 +2285,22 @@ fn dag_language_model_empty_canonical_symbol_set() -> PointwisePower { } fn dag_language_model_binding_canonical_map(m: Map) -> Map { - dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put( - m: dag_symbol_map_put(m: m, sym: ^dag_binding_fn_add), - sym: ^dag_binding_param_x - ), - sym: ^dag_binding_param_y - ), - sym: ^dag_binding_type_int - ), - sym: ^dag_binding_fn_one_or_two - ), - sym: ^dag_binding_fn_one - ), - sym: ^dag_binding_bind_x - ), - sym: ^dag_binding_pick_lit_one - ), - sym: ^dag_binding_pick_lit_two - ), - sym: ^dag_binding_type_bool - ), - sym: ^bool_node_symbol - ), - sym: ^dag_loop_measure_sym + grammar_symbol_list_put( + m: m, + xs: [ + ^dag_binding_fn_add, + ^dag_binding_param_x, + ^dag_binding_param_y, + ^dag_binding_type_int, + ^dag_binding_fn_one_or_two, + ^dag_binding_fn_one, + ^dag_binding_bind_x, + ^dag_binding_pick_lit_one, + ^dag_binding_pick_lit_two, + ^dag_binding_type_bool, + ^bool_node_symbol, + ^dag_loop_measure_sym + ] ) } From 1a77ee3a56170507b44091d088a9aa181cc0f3e2 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 15:53:16 +0000 Subject: [PATCH 54/69] Stop copying canonical_symbols onto ResolutionContext. LanguageModel already owns the set. Admission reads shared.lm.canonical_symbols so the context cannot drift from the model it carries. Co-authored-by: Cursor --- src/v2/compiler/03_name_resolve.dag | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index fdf045edf6f..05e167e9db9 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -110,7 +110,6 @@ type ResolutionContext { lm: LanguageModel roots: ValidatedModuleRoots symbol_index: SymbolIndex - canonical_symbols: PointwisePower } fn resolution_context( @@ -127,8 +126,7 @@ fn resolution_context( symbol_index: symbol_index_fill_module_roots( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) - ), - canonical_symbols: lm.canonical_symbols + ) }, diagnostics: d } @@ -663,10 +661,10 @@ fn namespace_for_subject_root( ) -> Outcome { let validated_roots = shared.roots if well_formed(n: subject_module_root.root) { - let base = admission_namespace_base(canonical_symbols: shared.canonical_symbols) + let base = admission_namespace_base(canonical_symbols: shared.lm.canonical_symbols) let initial = namespace_admission_initial_state( policy: policy, - canonical_symbols: shared.canonical_symbols, + canonical_symbols: shared.lm.canonical_symbols, subject_module_root: subject_module_root, subject_qn: admission.subject.name ) From eaf88767a549ab246b3299baf15c0b2849c512b7 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 16:17:03 +0000 Subject: [PATCH 55/69] Make grammar membership a lookup of the carried-symbol map. The atom walk inserts into the Map as it goes, so there is one authority and no quadratic list concat. grammar_carries_symbol no longer has a second fold. Co-authored-by: Cursor --- src/v2/std/grammar.dag | 78 +++++++++++++----------------------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/src/v2/std/grammar.dag b/src/v2/std/grammar.dag index c94ec0925e8..817a0aeac23 100644 --- a/src/v2/std/grammar.dag +++ b/src/v2/std/grammar.dag @@ -42,11 +42,9 @@ import v2.std.node { Edge, Named, Node, - NodeFold, Positional, Symbol, TypeNode, - fold_node, } import v2.std.node_shape { stamp_node_shape_synthetic, type_shape } import v2.std.compilers.target_model { @@ -349,10 +347,6 @@ type GrammarRelationRow { type ParseTree = Node -type GrammarSymbolFoldState { - found: Bool -} - fn grammar_atom(identity: Symbol) -> Node { Node { kind: TypeNode { @@ -1703,40 +1697,6 @@ fn grammar_to_node(grammar: ParseGrammar) -> Node { } } -fn grammar_symbol_fold(sym: Symbol) -> NodeFold { - NodeFold { - init: fn(n0) { - grammar_symbol_fold_init(n: n0, sym: sym) - }, - step: grammar_symbol_fold_step - } -} - -fn grammar_symbol_fold_init(n: Node, sym: Symbol) -> GrammarSymbolFoldState { - match n.kind { - TypeNode { connective: Atom { identity: id } } => - GrammarSymbolFoldState { found: id == sym } - _ => - GrammarSymbolFoldState { found: false } - } -} - -fn grammar_symbol_fold_step( - acc: GrammarSymbolFoldState, - edge: Edge, - child_has_symbol: GrammarSymbolFoldState -) -> GrammarSymbolFoldState { - GrammarSymbolFoldState { found: acc.found || child_has_symbol.found } -} - -fn grammar_symbol_fold_state(n: Node, sym: Symbol) -> GrammarSymbolFoldState { - fold_node(n: n, algebra: grammar_symbol_fold(sym: sym)) -} - -fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { - grammar_symbol_fold_state(n: grammar_to_node(grammar: grammar), sym: sym).found -} - fn grammar_symbol_map_put(m: Map, sym: Symbol) -> Map { map_insert(m: m, key: sym, value: true) } @@ -1749,30 +1709,38 @@ fn grammar_symbol_list_put(m: Map, xs: List) -> Map List { - fold_node( - n: n, - algebra: NodeFold { - init: fn(n0) { - match n0.kind { - TypeNode { connective: Atom { identity: id } } => - Cons { head: id, tail: Empty } - _ => Empty - } - }, - step: fn(acc, _e, child) { list_append(left: acc, right: child) } - } - ) +fn grammar_node_insert_atom_identities_child( + m: Map, + e: Edge +) -> Map { + grammar_node_insert_atom_identities(m: m, n: e.target) } fn grammar_node_insert_atom_identities(m: Map, n: Node) -> Map { - grammar_symbol_list_put(m: m, xs: grammar_node_atom_identities(n: n)) + fold_list( + xs: n.children, + empty: match n.kind { + TypeNode { connective: Atom { identity: id } } => + grammar_symbol_map_put(m: m, sym: id) + _ => m + }, + cons: fn(acc, e) { + grammar_node_insert_atom_identities_child(m: acc, e: e) + } + ) } fn grammar_carried_symbol_map(grammar: ParseGrammar) -> Map { grammar_node_insert_atom_identities(m: empty_map(), n: grammar_to_node(grammar: grammar)) } +fn grammar_carries_symbol(grammar: ParseGrammar, sym: Symbol) -> Bool { + match map_lookup(m: grammar_carried_symbol_map(grammar: grammar), key: sym) { + Present { value: _ } => true + Absent => false + } +} + type GrammarInterpretationDirection = GrammarInterpretBackward | GrammarInterpretForward From 5584abc1a70ffc3cb8511a571ae37cc6e2a1de7b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 17:02:10 +0000 Subject: [PATCH 56/69] Supply the wall's Namespace fields in the weather float-literal probe fixture Co-Authored-By: Claude Opus 5 (1M context) --- .../test/claim/parse/weather_g0_prefix_parse_probe_test.dag | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/v2/test/claim/parse/weather_g0_prefix_parse_probe_test.dag b/src/v2/test/claim/parse/weather_g0_prefix_parse_probe_test.dag index 1c604d89417..00262838c81 100644 --- a/src/v2/test/claim/parse/weather_g0_prefix_parse_probe_test.dag +++ b/src/v2/test/claim/parse/weather_g0_prefix_parse_probe_test.dag @@ -3,6 +3,7 @@ module v2.test.parse.weather_g0_prefix_parse_probe import std.occurrence_identity { OccurrenceSynthetic } import std.algebra { Cons, Empty } import v2.compiler.parse { parse_module } +import v2.std.declaration_marker { test_code_index_empty } import v2.compiler.resolve { Namespace, ResolveContext, @@ -160,7 +161,10 @@ fn float_literal_resolve_ctx() -> ResolveContext { bindings: empty_map(), canonical_symbols: empty_canonical_symbol_set(), symbol_index: empty_symbol_index(), - module_qn: qualified_name_from_dotted_string(dotted: "m") + module_qn: qualified_name_from_dotted_string(dotted: "m"), + test_code: test_code_index_empty(), + declared_in: qualified_name_from_dotted_string(dotted: "m"), + imported_origins: empty_map() } ResolveContext { scope: scope_from_namespace(namespace: namespace), From afdcbb87e14e41768fa55e0af4a97b529c185b07 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 17:24:04 +0000 Subject: [PATCH 57/69] Delete realization_attempt's line-rewriting marker capture: its dissolve-on is met by this channel Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/body_lowering_fold.dag | 10 ++- src/v2/workflow/realization_attempt.dag | 82 +++---------------------- 2 files changed, 11 insertions(+), 81 deletions(-) diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index e8b7bb94a59..efa17734868 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -2068,17 +2068,15 @@ fn body_lower_fn_decl_named_member_wrap(arrow: Node, fn_name: Symbol) -> Node { // v2.compiler.test_marker_capture over the same parse tree, so the semantic member stays identical // to a plain fn's. // -// THREE PRODUCERS OF TEST-NESS STAND TODAY, AND THIS NAMES ALL OF THEM RATHER THAN DROPPING ONE. +// TWO PRODUCERS OF TEST-NESS STAND TODAY, AND THIS NAMES BOTH RATHER THAN DROPPING ONE. // (1) The capture above, the compiler's. (2) The floor's witness universe, derived by // v2.workflow.floor_discovery_source_authority discover_floor_rows_for_source, a line scan for // `test fn ` / `test data ` over source text that deliberately neither parses nor resolves // (v2.workflow.floor_naming_hygiene names it a second parser). TRIGGER for converging it onto this // channel: the floor preparing a parse artifact for each discovered source, at which point discovery -// reads the marks from that parse instead of rescanning the bytes. (3) v2.workflow.realization_attempt -// capture_test_markers, which strips the marker from source text before ingest. Its stated reason, -// that the grammar could not parse the marker, no longer holds (dag_production_test_fn_decl parses -// it and this arm lowers it), and its count has no reader. TRIGGER for deleting it: gunbc#11573 -// merging, which makes this capture the one on main; the deletion is its own change after that. +// reads the marks from that parse instead of rescanning the bytes. A third producer, +// v2.workflow.realization_attempt's line-rewriting capture, was deleted in the change that added +// this channel: its only reason was a grammar that could not parse the marker. 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) diff --git a/src/v2/workflow/realization_attempt.dag b/src/v2/workflow/realization_attempt.dag index 299b9e3b7ba..6f2b71f3d8d 100644 --- a/src/v2/workflow/realization_attempt.dag +++ b/src/v2/workflow/realization_attempt.dag @@ -143,56 +143,8 @@ fn collect_arrows(root: Node, acc: List) -> List { }) } -// DISSOLVE-ON MET, DELETION PENDING: the contextual `test` production (dag_production_test_fn_decl) -// parses the marker, and v2.compiler.test_marker_capture carries it on its own channel, so the -// condition below holds once gunbc#11573 is on main. Deleting this capture is its own change after -// that. The paragraph below is the scaffold's original reasoning, kept as its record. -// -// STAGED SCAFFOLD (escalation msg_628838a5; dissolve-on: GrammarExpr contextual-keyword terminal -// lands and dag fn_decl/data_decl productions carry the test marker structurally): the v2 dag -// grammar cannot yet parse the authored test marker ('test' is a module-path segment, so it cannot -// be a lex keyword; v1 once DISCARDED the marker pre-parse and re-derived test-ness by text scan, -// and now carries it as v1.std.core DeclarationMarker TestMarked — cited as the prior art this capture SUPERSEDES, -// not imitates: this seam captures and counts, that seam discarded, and the distinction is the -// point (ruling msg_64870b7f)). The realization ATTEMPT captures the marker at its own seam — -// line-leading 'test fn '/'test data ' rewritten to the unmarked form, capture COUNTED on the -// attempt row — so the census sweep can measure the rest of the pipeline while the grammar -// extension is priced as its own top-of-burn-down repair item (it gates 100 percent of test files). -// Test-ness itself is never derived from this capture: the discovery roster carries it. Scoped -// deliberately to attempt_entry_source; the canonical production pipeline does NOT route through -// this fn. - -type MarkedSource { - unmarked: String - captured_markers: Int -} - -fn capture_test_markers(source: String) -> MarkedSource { - let folded = fold( - split(s: source, delimiter: "\n"), - init: MarkedSource { unmarked: "", captured_markers: 0 }, - f: fn(acc, line) { - let stripped = - if starts_with(s: line, prefix: "test fn ") || starts_with(s: line, prefix: "test data ") { - MarkedSource { - unmarked: substring(s: line, start: 5, end: string_length(s: line)), - captured_markers: 1 - } - } else { - MarkedSource { unmarked: line, captured_markers: 0 } - } - MarkedSource { - unmarked: if acc.unmarked == "" { stripped.unmarked } else { concat(acc.unmarked, "\n", stripped.unmarked) }, - captured_markers: acc.captured_markers + stripped.captured_markers - } - } - ) - folded -} - fn attempt_entry_source(entry: String, source: String) -> EntryRealizationAttempt { - let marked = capture_test_markers(source: source) - match ingested_resolved_module_from_source(source: marked.unmarked, file: ^realization_attempt_entry_file) { + match ingested_resolved_module_from_source(source: source, file: ^realization_attempt_entry_file) { Rejected { diagnostics: ds } => attempt_refused_at( entry: entry, @@ -371,31 +323,11 @@ fn attempt_assembled( } // Sweep seam (CI2-0 Commit B): ONE assembly per entry closure, then one attempt_joined per required -// test-fn identity — the exactly-once accounting grain the mandate names. The ingest is -// marker-captured at this seam only (a scaffold whose original reason -- the grammar could not parse -// the authored test marker -- NO LONGER HOLDS: dag_production_test_fn_decl parses it and -// v2.compiler.test_marker_capture captures it. Retirement trigger: gunbc#11573 merging; the deletion -// is its own change after that); an assembly refusal is replicated to EVERY identity in the entry with the -// reason-classified phase, because each identity is accountable and none of them reached its join. - -fn capture_markers_ingest(ingest: SourceRootIngest) -> SourceRootIngest { - match ingest { - Empty => Empty - Cons { head: r, tail: t } => - Cons { - head: DagSourceReadWitness { - source: Medium { - carried: capture_test_markers(source: r.source.carried).unmarked, - fidelity: r.source.fidelity - }, - artifact: r.artifact, - compilation_unit: r.compilation_unit, - source_root: r.source_root - }, - tail: capture_markers_ingest(ingest: t) - } - } -} +// test-fn identity — the exactly-once accounting grain the mandate names. Sources are ingested as +// authored: the `test` marker parses (dag_production_test_fn_decl) and lowering reduces it to the +// plain member, so the line-rewriting capture scaffold that stood here is deleted. An assembly +// refusal is replicated to EVERY identity in the entry with the reason-classified phase, because +// each identity is accountable and none of them reached its join. fn entry_witness_source(ingest: SourceRootIngest, entry: String) -> Optional { fold_list(xs: ingest, empty: Absent, cons: fn(acc, r) { @@ -420,7 +352,7 @@ fn attempt_closure_rows( fn_names: List, entry: String ) -> List { - match assemble_program_from_ingest(ingest: capture_markers_ingest(ingest: ingest), admission: admission, lm: dag_language_model()) { + match assemble_program_from_ingest(ingest: ingest, admission: admission, lm: dag_language_model()) { Rejected { diagnostics: ds } => { let cause = first_located_cause(ds: ds) let phase = phase_for_reason(reason: cause, fallback: PhaseResolve) From 2e8369e184f0b6926dd545e8526a5148ed22d8a6 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 18 Sep 2026 19:26:46 +0000 Subject: [PATCH 58/69] Wall: a red and a control for the imported-binding door Co-Authored-By: Claude Opus 5 (1M context) --- .../test_code_reference_wall_test.dag | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index 93d0f14d8c9..8cc955ee360 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -348,3 +348,50 @@ test fn a_parameter_default_is_refused_at_parse_holds() -> Bool { } } } + +// Resolves module wall. under the IMPORT-SCOPED policy with `wall.peer` imported, so +// the peer's names bind at the subject's root scope as admitted imports and the wall reads their +// origin from Namespace.imported_origins (v2.compiler.resolve root_binding_origin). +fn wall_resolve_importing_peer(subject_segment: Symbol, subject: Outcome, peer: Outcome) -> String { + match subject { + Rejected { diagnostics: _ } => "SUBJECT_NOT_ADMITTED" + Accepted { value: s, diagnostics: _ } => + match peer { + Rejected { diagnostics: _ } => "PEER_NOT_ADMITTED" + Accepted { value: p, diagnostics: _ } => + wall_verdict(outcome: resolve_with_admission_policy( + lm: dag_language_model(), + roots: Cons { head: s, tail: Cons { head: p, tail: Empty } }, + admission: Admission { + subject: ResolutionSubject { name: Cons { head: ^wall, tail: Cons { head: subject_segment, tail: Empty } } }, + imports: Cons { + head: Import { target: Cons { head: ^wall, tail: Cons { head: ^peer, tail: Empty } }, visibility: ImportVisible }, + tail: Empty + } + }, + index: source_root_index_empty(), + active_roots: Empty, + policy: ImportScoped + )) + } + } +} + +// RED, THE IMPORT DOOR: a test fn reached through an admitted import is refused. Its origin comes from +// the admission's imported_origins, not the subject's own declarations. +test fn an_imported_test_fn_is_refused_holds() -> Bool { + wall_resolve_importing_peer( + subject_segment: ^serving, + subject: serving_module_with_body(body: wall_atom(identity: ^peer_leaf)), + peer: peer_test_module() + ) == "resolve_reason_test_code_referenced" +} + +// CONTROL for the import door: the same import, referencing the peer's ordinary declaration, resolves. +test fn an_imported_ordinary_declaration_resolves_holds() -> Bool { + wall_resolve_importing_peer( + subject_segment: ^serving, + subject: serving_module_with_body(body: wall_atom(identity: ^shared_fixture)), + peer: peer_test_module() + ) == "ACCEPTED" +} From cbc295b6a95ed1e8568c78a89031c14b7f88a800 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 19 Sep 2026 00:08:08 +0000 Subject: [PATCH 59/69] Adopt #11582's qualified_head_bound_on_chain onto lookup_chain's ScopeBinding; check test code before the qualified target's shape Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/compiler/03_resolve.dag | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 9c0ab2381f9..99f5df00ed0 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -530,7 +530,11 @@ fn qualified_head_bound_on_chain(ctx: ResolveContext, path: QualifiedName) -> Ou Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: found, diagnostics: d } => Accepted { - value: (match found { Present { value: _ } => true Absent => false }), + value: (match found { + BoundInFrame { canonical: _ } => true + BoundAtRoot { canonical: _ } => true + ScopeUnbound => false + }), diagnostics: d } } From 0bcb53e69e7efb6a7519efcb703882d1d148d82a Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 19 Sep 2026 01:07:54 +0000 Subject: [PATCH 60/69] Serve the projection control's parse warm: it measured just over the new-witness step budget after main moved Co-Authored-By: Claude Opus 5 (1M context) --- .../test/claim/parse/test_marker_channel_test.dag | 9 ++++++++- src/v2/workflow/floor_pure_producer_share.dag | 13 ++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/v2/test/claim/parse/test_marker_channel_test.dag b/src/v2/test/claim/parse/test_marker_channel_test.dag index 632737c0594..16fcb212e8d 100644 --- a/src/v2/test/claim/parse/test_marker_channel_test.dag +++ b/src/v2/test/claim/parse/test_marker_channel_test.dag @@ -139,9 +139,16 @@ fn test_marker_gather_data_decls(n: Node, acc: TestMarkerDeclarationPair) -> Tes } } +// THE SHARE POINT for the control below: the real parse of `distinct_projection_source`, enrolled +// WARM in v2.workflow.floor_pure_producer_share, so the floor pays the parse once outside the claim's +// budget and the claim pays only the two lowerings and hashes it is about. +fn test_marker_distinct_parsed() -> Outcome { + test_marker_parsed(src: distinct_projection_source, file: ^test_marker_distinct_subject) +} + // The two unmarked declarations of `distinct_projection_source` against each other. fn test_marker_distinct_declarations_agree() -> Optional { - match test_marker_parsed(src: distinct_projection_source, file: ^test_marker_distinct_subject) { + match test_marker_distinct_parsed() { Rejected { diagnostics: _ } => optional_absent() Accepted { value: artifact, diagnostics: _ } => let pair = test_marker_gather_data_decls( diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index 841eab7760f..88ab94269b1 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -496,6 +496,16 @@ import v2.std.collection { List } // v2.test.claim.namespace_xl0.qualified_site_enumerator_differential: the rows there name the two // site WALKS and share the one-module ingest the walks read, exactly as the sibling rows do. Both // values are Nodes; no closure. +// v2.test.claim.parse.test_marker_channel test_marker_distinct_parsed EARNS ITS ROW ON THE NEW-WITNESS +// STEP BUDGET. Its consumer, a_different_declaration_moves_the_semantic_projection_holds, is the +// red-capable control for the marker's projection equality: it parses a two-declaration module and +// compares the two declarations' lowered hashes. The parse is most of its cost, and on the floor it +// measured just over the 72,300-step new-witness budget. Re-derive with claim_batch --entry on that file +// ([witness] eval_steps for the claim) and the floor run's COMPLETED-OVER-COST line for it. Serving the +// parse leaves the claim paying only what it is about, the two normalize_node calls and their hashes. +// Serve below recompute: the value is Outcome, Nodes and the span index, portable to +// the bottom. WARM: nullary, source inline in the witness. The claim is about lowering, not parsing, +// so sharing the parse does not hand it its answer. data floor_cross_claim_pure_producers_warm: List = [ "v2.test.native_decl_selection.collision_resolved", "v2.extdeps.languages.bash_command_fold.bash_fold_formal_productions", @@ -536,7 +546,8 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.claim.namespace_xl0.cross_module_reference_resolution.xl0r_resolved_consumer_a", "v2.test.claim.namespace_xl0.cross_module_reference_resolution.xl0r_resolved_consumer_b", "v2.test.claim.namespace_xl0.qualified_site_enumerator_differential.qsd_normalized", - "v2.test.claim.namespace_xl0.qualified_site_enumerator_differential.qsd_parsed" + "v2.test.claim.namespace_xl0.qualified_site_enumerator_differential.qsd_parsed", + "v2.test.claim.parse.test_marker_channel.test_marker_distinct_parsed" ] // grammar_relation_row_for_emitted HAS NOW BEEN MEASURED ON THE THIRD CONJUNCT, AND IT PASSES. From 9f390cba68c9c99678235e5668bf5f52d44123c5 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 19 Sep 2026 01:38:58 +0000 Subject: [PATCH 61/69] floor_naming_hygiene: retract the stale grammar obstacle and point the line scan at its real trigger Co-Authored-By: Claude Opus 5 (1M context) --- src/v2/workflow/floor_naming_hygiene.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/workflow/floor_naming_hygiene.dag b/src/v2/workflow/floor_naming_hygiene.dag index 8b56ce8b461..d5566016aea 100644 --- a/src/v2/workflow/floor_naming_hygiene.dag +++ b/src/v2/workflow/floor_naming_hygiene.dag @@ -237,4 +237,4 @@ fn floor_entries_requiring_test_sidecar(entry_paths: List) -> List Date: Sat, 19 Sep 2026 15:53:25 +0000 Subject: [PATCH 62/69] Drop the unread ResolutionContext.canonical_symbols copy and repair the merge-mangled warm-share notes (review 68494) --- src/v2/compiler/03_name_resolve.dag | 4 +-- src/v2/workflow/floor_pure_producer_share.dag | 27 +++---------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/src/v2/compiler/03_name_resolve.dag b/src/v2/compiler/03_name_resolve.dag index 1e7ef072a0e..3cd7a1b5838 100644 --- a/src/v2/compiler/03_name_resolve.dag +++ b/src/v2/compiler/03_name_resolve.dag @@ -114,7 +114,6 @@ type ResolutionContext { lm: LanguageModel roots: ValidatedModuleRoots symbol_index: SymbolIndex - canonical_symbols: PointwisePower } fn resolution_context( @@ -131,8 +130,7 @@ fn resolution_context( symbol_index: symbol_index_fill_module_roots( index: empty_symbol_index(), roots: normalized_tree_roots_to_nodes(roots: validated_roots.roots) - ), - canonical_symbols: lm.canonical_symbols + ) }, diagnostics: d } diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index ef60d9dfd75..b1243fdd244 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -495,7 +495,8 @@ import v2.std.collection { List } // AND THE TWO ROWS AFTER THOSE ARE THE ENUMERATOR-DIFFERENTIAL FIXTURE, parsed and normalized, for // v2.test.claim.namespace_xl0.qualified_site_enumerator_differential: the rows there name the two // site WALKS and share the one-module ingest the walks read, exactly as the sibling rows do. Both -// values are Nodes; no closure., +// values are Nodes; no closure. +// // v2.test.name_resolve.test_code_reference_wall inhabitance_normalized EARNS ITS ROW ON THE // NEW-WITNESS STEP BUDGET, NOT THE CPU LINE. Recompute: each of its two consumers // (supplied_root_is_what_normalize_emits_holds, supplied_channel_is_what_normalize_emits_holds) spends @@ -519,26 +520,6 @@ import v2.std.collection { List } // the producer, against the arrow-root claim. The value is Optional, portable to the // bottom. // -// DAG CANONICAL-SYMBOL MAP EARNS ITS ROW ON THE ENROLMENT-MARGIN CPU THE EVAL-STEP GATE -// CANNOT SEE. Sibling floor run 35340819603 (#11580 merging the resolve-cost head that -// establishes this map once per ResolutionContext) passed eval-steps and refused every -// resolving claim at the 302ms enrolment margin, including one-member resolve probes that -// sit near 40k steps. The same fill shows on grandfathered resolve claims. Re-derive with -// required_floor_claim_cost.tsv on that run plus claim_batch --entry -// src/v2/test/claim/name_resolve/one_member_cost_probe_test.dag [witness] cpu vs eval_steps: -// the map fill is host work outside the step counter, paid once per claim frame because the -// eval-frame memo dies at the claim boundary. Sharing census is the call-site graph: every -// dag_language_model() resolve path reads dag_canonical_symbols(), which is a PointwisePower -// closure and therefore not portable (ServeCacheValueNotPortable, the prepare_grammar -// nullable_set shape). The share point is the Map it is built from -- -// dag_canonical_symbol_map -- symbols and bools only; dag_canonical_symbols wraps that map -// at the consumer. Serve-below-recompute is the header's zero-walk Rc hand-over of a small -// map against a fill that already exceeds the enrolment margin in-fold. WARM, NEVER -// CLAIM-FORCED, by the same ceiling arithmetic as dag_prepared_grammar: an in-fold first -// touch of this fill dies on the lane's margin and restarts per claim. The module is under -// src/v2/extdeps, so it resolves in every required-floor subject. -// -// values are Nodes; no closure. // v2.test.claim.parse.test_marker_channel test_marker_distinct_parsed EARNS ITS ROW ON THE NEW-WITNESS // STEP BUDGET. Its consumer, a_different_declaration_moves_the_semantic_projection_holds, is the // red-capable control for the marker's projection equality: it parses a two-declaration module and @@ -612,8 +593,8 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.claim.namespace_xl0.qualified_site_enumerator_differential.qsd_parsed", "v2.test.name_resolve.test_code_reference_wall.inhabitance_normalized", "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_module", - "v2.extdeps.languages.dag.dag_canonical_symbol_map", - "v2.test.claim.parse.test_marker_channel.test_marker_distinct_parsed" + "v2.test.claim.parse.test_marker_channel.test_marker_distinct_parsed", + "v2.extdeps.languages.dag.dag_canonical_symbol_map" ] // grammar_relation_row_for_emitted HAS NOW BEEN MEASURED ON THE THIRD CONJUNCT, AND IT PASSES. From 619977b1bbc9563b33adaec43a9d6763411e4e56 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 20 Sep 2026 03:48:29 +0000 Subject: [PATCH 63/69] Say which doors reach resolve_atom_bound and why the qualified door refuses inline (review 68833) --- src/v2/compiler/03_resolve.dag | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 99f5df00ed0..4e38692a218 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -629,8 +629,15 @@ fn test_code_referenced_diagnostic(reason: Symbol, n: Node) -> Diagnostic { // THE WALL (owner ruling 2026-09-16/17): `test` segregates test code from serving code, and no code // may reference a test-marked declaration, a test fn in the same module included. It is decided here, // where the reference binds, from the path it bound to, and it refuses with the reference's own locus. -// Every door that binds a module-level name reaches this function: the root scope, the symbol index, -// and a qualified path (try_resolve_qualified_name_node). +// TWO ENFORCEMENT SITES, ONE DECISION. This function is reached by the doors whose accepted result is +// a canonical atom -- the root scope and the symbol index. The qualified-path door +// (try_resolve_qualified_name_node) cannot route through it, because its accepted arm must first read +// the resolved node's kind and carry the chain's own pending diagnostics, so it refuses inline +// instead. Both sites decide from ONE authority, test_code_reach over ctx.namespace.test_code, and +// raise the same reason through the same test_code_referenced_diagnostic: a change to the RULE is a +// change to that fold, not to either site. What a later door must not do is bind a module-level name +// without asking test_code_reach at all -- the per-door reds in +// v2.test.claim.name_resolve.test_code_reference_wall are what hold that, one per door. fn resolve_atom_bound( ctx: ResolveContext, n: Node, From dfb9b9b253367b5378ea8f7a2fd9b3ff0119c678 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 20 Sep 2026 09:32:30 +0000 Subject: [PATCH 64/69] Wall: measure the import-shadow case review 69014 named, and record that admission closes it The review's reading of root_binding_origin is right: it consults imported_origins for a name the subject may also declare, and a shadowed name would be judged against the peer's path. The state is unreachable. A module that both imports a name and declares it is refused at ADMISSION as an ambiguous export, in BOTH directions, so the wall is never asked -- measured here, with a control showing the refusal is caused by the shadow and not by the fixture's shape. So this PR adds no shadow-aware origin lookup. One would guard a state admission has already made unwritable, which is validation restating a constraint the model carries (DESIGN section 5), and its red would not be authorable. --- .../test_code_reference_wall_test.dag | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index 8cc955ee360..894a6a25327 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -387,6 +387,68 @@ test fn an_imported_test_fn_is_refused_holds() -> Bool { ) == "resolve_reason_test_code_referenced" } +// THE SHADOW CASE THE WALL NEVER SEES (review 69014 on gunbc#11580). The review read a defect here: +// a module that BOTH imports a name and declares it would bind locally while Namespace.imported_origins +// still held the PEER's path, so root_binding_origin would hand the wall a path the reference never +// bound to -- refusing an ordinary local shadowed by an imported test fn, and, silently, ACCEPTING a +// reference to a local test fn shadowed by an ordinary import. The reading of root_binding_origin is +// right. THE STATE IS UNREACHABLE: admission refuses that module first, in BOTH directions, so the +// wall is never asked. These three rows are that measurement, and they are why this PR carries no +// shadow-aware origin lookup -- a lookup guarding a state admission has already made unwritable would +// be validation restating a constraint the model carries (DESIGN section 5, correctness by +// construction over validation), and its red would not be authorable. + +// RED: a subject that declares `peer_leaf` AND imports a peer exporting `peer_leaf` is refused at +// ADMISSION as an ambiguous export -- the direction where the local declaration is ordinary and the +// peer's is a test fn. +test fn a_local_declaration_shadowing_an_imported_test_fn_is_refused_at_admission_holds() -> Bool { + wall_resolve_importing_peer( + subject_segment: ^serving, + subject: wall_tree( + root: wall_module_root(leaf_segment: ^serving, members: [ + wall_member(name: ^peer_leaf, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member(name: ^uses_peer, body: wall_atom(identity: ^peer_leaf)) + ]), + marked: [] + ), + peer: peer_test_module() + ) == "resolve_ambiguous_export" +} + +// RED, THE OTHER DIRECTION, and the one the review called silent: the subject's own declaration is +// TEST-MARKED and the peer's is ordinary. Admission refuses this too, so no reference to test code is +// accepted without a diagnostic -- the arm DESIGN section 5 forbids is closed upstream of the wall. +test fn a_local_test_fn_shadowing_an_imported_ordinary_declaration_is_refused_at_admission_holds() -> Bool { + wall_resolve_importing_peer( + subject_segment: ^serving, + subject: wall_tree( + root: wall_module_root(leaf_segment: ^serving, members: [ + wall_member(name: ^shared_fixture, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member(name: ^uses_peer, body: wall_atom(identity: ^shared_fixture)) + ]), + marked: [^shared_fixture] + ), + peer: peer_test_module() + ) == "resolve_ambiguous_export" +} + +// CONTROL for the two above: the refusal is caused by the SHADOW and not by the shape of these +// fixtures. The same subject and the same import, with a locally declared name the peer does not +// export, resolves. Without this row the two reds could both be passing on a malformed module. +test fn a_local_declaration_the_peer_does_not_export_resolves_holds() -> Bool { + wall_resolve_importing_peer( + subject_segment: ^serving, + subject: wall_tree( + root: wall_module_root(leaf_segment: ^serving, members: [ + wall_member(name: ^only_local, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member(name: ^uses_peer, body: wall_atom(identity: ^only_local)) + ]), + marked: [] + ), + peer: peer_test_module() + ) == "ACCEPTED" +} + // CONTROL for the import door: the same import, referencing the peer's ordinary declaration, resolves. test fn an_imported_ordinary_declaration_resolves_holds() -> Bool { wall_resolve_importing_peer( @@ -395,3 +457,4 @@ test fn an_imported_ordinary_declaration_resolves_holds() -> Bool { peer: peer_test_module() ) == "ACCEPTED" } + From 973115b2136281a75e5d23d6d3fb354d93178b73 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 20 Sep 2026 09:57:31 +0000 Subject: [PATCH 65/69] Wall fixtures: the grafted module body carries its producer marker first supplied_root_is_what_normalize_emits_holds went red after #11694 landed on main: namespace_graft_build_body_conj now emits the leaf module body as a Conj whose first edge is grammar_production_identity_node_projection -> namespace_graft_module_body_marker_node(), ahead of the members. The member Arrow itself, its empty-Conj domain included, is unchanged; the earlier index-based comparison was shifted by that one leading edge. wall_module_root now builds the leaf body through wall_module_body, which calls the marker's one constructor rather than re-spelling the atom. All 16 claims in the file pass on a claim_batch built from this tree. Co-Authored-By: Claude Opus 5 (1M context) --- .../test_code_reference_wall_test.dag | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index 894a6a25327..3ef91627f63 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -74,7 +74,17 @@ fn wall_member_returning_its_param(name: Symbol, param: Symbol) -> Edge { } } -// `module wall.` with `members`, grafted as normalize grafts a two-segment module. +// `module wall.` with `members`, grafted as normalize grafts a two-segment module. The +// leaf body is the graft's body conj: its identity projection first, the producer marker +// namespace_graft_module_body_marker_node (v2.compiler.namespace_graft namespace_graft_build_body_conj, +// gunbc#11694), then the members. +fn wall_module_body(members: List) -> Node { + wall_conj(children: concat( + [Edge { label: Named { name: ^grammar_production_identity_node_projection }, target: namespace_graft_module_body_marker_node() }], + members + )) +} + fn wall_module_root(leaf_segment: Symbol, members: List) -> Node { wall_conj(children: [ Edge { label: Named { name: ^grammar_production_identity_node_projection }, target: wall_atom(identity: ^dag_surface_module) }, @@ -83,7 +93,7 @@ fn wall_module_root(leaf_segment: Symbol, members: List) -> Node { target: wall_conj(children: [ Edge { label: Named { name: ^wall }, - target: wall_conj(children: [Edge { label: Named { name: leaf_segment }, target: wall_conj(children: members) }]) + target: wall_conj(children: [Edge { label: Named { name: leaf_segment }, target: wall_module_body(members: members) }]) } ]) } @@ -457,4 +467,3 @@ test fn an_imported_ordinary_declaration_resolves_holds() -> Bool { peer: peer_test_module() ) == "ACCEPTED" } - From 2f91f3b8d08e247bec08009a09087d6cee5d10e4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 20 Sep 2026 10:15:30 +0000 Subject: [PATCH 66/69] Wall pairing claim: the inhabitance source covers every fixture builder inhabitance_module_source grows from one member to three, one per fixture builder: wall_member with a `true` body, wall_member_returning_its_param (the one-parameter domain), and wall_member with a name-reference body carrying the test marker. Before this the last two builders matched the producer only by measurement outside the claim, so a producer move there would have gone uncaught. The claim still asserts content_hash equality on the whole root. An annotation records that a red is read by rendering both trees with their edge labels, not by child index. All 16 claims pass on a claim_batch built from this tree. Co-Authored-By: Claude Opus 5 (1M context) --- .../test_code_reference_wall_test.dag | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index 3ef91627f63..12773b059c3 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -165,17 +165,24 @@ fn peer_test_module() -> Outcome { ) } -// The smallest module that carries both facts the supplied roots rely on: a member of the -// expression-bodied shape, and a test marker on it. One member, because the front end's cost -// grows with the source and this claim pays for a real tokenize, parse and normalize. +// The smallest module that carries every producer shape the supplied roots rely on, one member per +// fixture builder: wall_member with a `true` body (`shared_fixture`), wall_member_returning_its_param +// (`helper`, the one-parameter domain), and wall_member with a name-reference body carrying the test +// marker (`peer_leaf`). Three members because the builders have three distinct bodies; the front +// end's cost grows with the source and this claim pays for one real tokenize, parse and normalize. +// The only supplied node no source produces is wall_qualified, declared beside it. fn inhabitance_module() -> Outcome { wall_tree( - root: wall_module_root(leaf_segment: ^peer, members: [wall_member(name: ^peer_leaf, body: wall_atom(identity: ^shared_fixture))]), + root: wall_module_root(leaf_segment: ^peer, members: [ + wall_member(name: ^shared_fixture, body: wall_atom(identity: ^dag_token_kw_true)), + wall_member_returning_its_param(name: ^helper, param: ^leaf), + wall_member(name: ^peer_leaf, body: wall_atom(identity: ^shared_fixture)) + ]), marked: [^peer_leaf] ) } -data inhabitance_module_source: String = "module wall.peer\ntest fn peer_leaf() -> Bool = shared_fixture()\n" +data inhabitance_module_source: String = "module wall.peer\nfn shared_fixture() -> Bool = true\nfn helper(leaf: Bool) -> Bool = leaf\ntest fn peer_leaf() -> Bool = shared_fixture()\n" // module wall.peer -- `fn shared_fixture() -> Bool = true`, no test code. fn peer_serving_module() -> Outcome { @@ -201,6 +208,9 @@ fn serving_module_with_body(body: Node) -> Outcome { // tokenize, parse and normalize of even a one-member module costs about 75k eval steps, over the // 72,300 new-witness budget by itself, so paying it once and sharing it is the only way the real // path gets onto the floor at all. +// WHEN THE ROOT CLAIM REDS: render BOTH trees with their EDGE LABELS and compare those. A read by +// child index is what made the graft-marker drift (#11694, a leading edge on the module body) look +// like a change inside the member Arrow, and cost two lanes to find. fn inhabitance_normalized() -> Outcome { let lm = dag_language_model() match tokenize(text: inhabitance_module_source, file: ^wall_peer_file, rules: lm.lex) { From d3f4dad9625fca01c6b9ca34420496f0bd542e21 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 20 Sep 2026 11:23:22 +0000 Subject: [PATCH 67/69] Record the census that makes the shadow-lookup refusal sound: nothing can hand resolution an origin map admission never saw --- .../claim/name_resolve/test_code_reference_wall_test.dag | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index 12773b059c3..852b6eec148 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -413,7 +413,14 @@ test fn an_imported_test_fn_is_refused_holds() -> Bool { // bound to -- refusing an ordinary local shadowed by an imported test fn, and, silently, ACCEPTING a // reference to a local test fn shadowed by an ordinary import. The reading of root_binding_origin is // right. THE STATE IS UNREACHABLE: admission refuses that module first, in BOTH directions, so the -// wall is never asked. These three rows are that measurement, and they are why this PR carries no +// wall is never asked. AND NOTHING CAN MANUFACTURE THE COLLISION AROUND THAT ADMISSION: across the +// whole corpus there are eight constructions of Namespace.imported_origins, and seven build it EMPTY +// (v2.compiler.resolve empty_namespace and build_program_namespace, v2.compiler.name_resolve +// admission_namespace_base, and the four test fixtures that construct a Namespace directly). The one +// non-empty construction is admitted_subject_from_bindings, whose origins come only from an +// AdmissionAccepted, and the only writer into that map is admit_named_exports -- the same fold that +// refuses the collision. So a fixture cannot hand resolution an origin map that admission never saw. +// These three rows are that measurement, and they are why this PR carries no // shadow-aware origin lookup -- a lookup guarding a state admission has already made unwritable would // be validation restating a constraint the model carries (DESIGN section 5, correctness by // construction over validation), and its red would not be authorable. From 4f18f088c70168bb4e6af3cc26b3bcd4cf23d68e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 20 Sep 2026 12:38:23 +0000 Subject: [PATCH 68/69] Delete the last live test-fn reference the wall refuses, and its ledger rows review 69107 on gunbc#11580. cross_language_add_python_to_typescript_chain_status_holds called ts_effect_io_emit_holds(), a test fn in another module -- the same shape this PR's own import-door red proves refusable, and the class ~20 sibling call sites were deleted under in gunbc#11579. The fact stays asserted at its own declaration, which is enrolled in its own right; re-running it here asserted nothing new (DESIGN section 3, a witness discriminates at one interface). The two test_reference_debt rows it owed go with it, and the stage0 mirror is regenerated so the seed carries the same ledger as the authored .dag (first_generation_equal=true, main.rs the only declared divergence). --- src/v1/compile.dag | 4 +--- src/v1/stage0/src/v1_compiler_compile.rs | 2 +- .../manual/cross_language_add_python_to_typescript_test.dag | 4 ---- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/v1/compile.dag b/src/v1/compile.dag index 0fce979b1cc..5ad77f4807c 100644 --- a/src/v1/compile.dag +++ b/src/v1/compile.dag @@ -341,9 +341,7 @@ fn test_reference_debt() -> List { test_reference_row(module_name: "test.claim.spark.serving_arm_launch_witness_test", referrer: "w_all_serving_arm_launch_claims_hold", target: "test.claim.spark.serving_arm_launch_witness_test.w_every_rank_names_this_fleets_leg_and_hca", occurrences: 1), test_reference_row(module_name: "test.claim.spark.serving_arm_launch_witness_test", referrer: "w_all_serving_arm_launch_claims_hold", target: "test.claim.spark.serving_arm_launch_witness_test.w_the_engine_argv_states_the_profiles_batching_bounds", occurrences: 1), test_reference_row(module_name: "test.claim.spark.serving_arm_launch_witness_test", referrer: "w_all_serving_arm_launch_claims_hold", target: "test.claim.spark.serving_arm_launch_witness_test.w_the_group_b_arm_plans_exactly_four_ranks", occurrences: 1), - test_reference_row(module_name: "test.claim.spark.serving_arm_launch_witness_test", referrer: "w_all_serving_arm_launch_claims_hold", target: "test.claim.spark.serving_arm_launch_witness_test.w_the_utilisation_argv_is_the_profiles_value_at_full_precision", occurrences: 1), - test_reference_row(module_name: "v2.test.manual.cross_language_add_python_to_typescript", referrer: "", target: "v2.test.manual.typescript_effect_io_emit.ts_effect_io_emit_holds", occurrences: 1), - test_reference_row(module_name: "v2.test.manual.cross_language_add_python_to_typescript", referrer: "cross_language_add_python_to_typescript_chain_status_holds", target: "v2.test.manual.typescript_effect_io_emit.ts_effect_io_emit_holds", occurrences: 1) + test_reference_row(module_name: "test.claim.spark.serving_arm_launch_witness_test", referrer: "w_all_serving_arm_launch_claims_hold", target: "test.claim.spark.serving_arm_launch_witness_test.w_the_utilisation_argv_is_the_profiles_value_at_full_precision", occurrences: 1) ] } diff --git a/src/v1/stage0/src/v1_compiler_compile.rs b/src/v1/stage0/src/v1_compiler_compile.rs index 4ef039e1ab5..2c31d2f89ec 100644 --- a/src/v1/stage0/src/v1_compiler_compile.rs +++ b/src/v1/stage0/src/v1_compiler_compile.rs @@ -483,7 +483,7 @@ pub fn test_reference_debt() -> Rc>> { occurrences: 1, scope: TestReferenceRowScope::FixtureControlRow, dissolution: test_reference_debt_dissolution(), -}), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_absent_shape_refuses_silhouette".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_catalog_evidence_cannot_buy_placement_clearance".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_evidence_spendability_discriminates".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_mounting_axis_needs_no_shape_or_envelope".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_naming_is_alias_with_sku_unread".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_proof_load_receipt_is_load_bearing".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_real_row_axis_is_not_yet_locatable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_real_row_refuses_lift_role_on_axis".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_relayed_reading_makes_envelope_spendable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_search_summary_envelope_is_not_spendable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_source_reading_survives_conversion".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_absent_shape_refuses_silhouette".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_catalog_evidence_cannot_buy_placement_clearance".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_evidence_spendability_discriminates".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_mounting_axis_needs_no_shape_or_envelope".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_naming_is_alias_with_sku_unread".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_proof_load_receipt_is_load_bearing".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_real_row_axis_is_not_yet_locatable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_real_row_refuses_lift_role_on_axis".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_relayed_reading_makes_envelope_spendable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_search_summary_envelope_is_not_spendable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_source_reading_survives_conversion".to_string(), 1), test_reference_row("test.claim.materialization_provider_consumer_hand_rust_witness".to_string(), "".to_string(), "test.claim.materialization_provider_witness.incomplete_v1_shape_artifact_refuses_naming_the_diagnostic_union".to_string(), 1), test_reference_row("test.claim.materialization_provider_consumer_hand_rust_witness".to_string(), "".to_string(), "test.claim.materialization_provider_witness.red_declared_input_miss_stale_artifact_refuses_as_wrong_artifact".to_string(), 1), test_reference_row("test.claim.materialization_provider_consumer_hand_rust_witness".to_string(), "witness_model_refusal_controls_remain_distinct".to_string(), "test.claim.materialization_provider_witness.incomplete_v1_shape_artifact_refuses_naming_the_diagnostic_union".to_string(), 1), test_reference_row("test.claim.materialization_provider_consumer_hand_rust_witness".to_string(), "witness_model_refusal_controls_remain_distinct".to_string(), "test.claim.materialization_provider_witness.red_declared_input_miss_stale_artifact_refuses_as_wrong_artifact".to_string(), 1), test_reference_row("test.claim.native_witness_transition_receipt_witness".to_string(), "".to_string(), "v2.test.execution.native_selected_witness_bundle.native_selected_witness_bundle_cutover_evidence_holds".to_string(), 1), test_reference_row("test.claim.native_witness_transition_receipt_witness".to_string(), "cited_evidence_declaration_is_an_executing_witness".to_string(), "v2.test.execution.native_selected_witness_bundle.native_selected_witness_bundle_cutover_evidence_holds".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_a_socket_planned_rank_maps_no_device_and_cannot_lock_memory".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_an_unrun_subject_refuses_normal_serving_and_the_intent_changes_the_answer".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_every_planned_rank_maps_the_verbs_device_and_can_lock_memory".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_every_rank_keeps_the_transport_evidence_producer_on".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_every_rank_names_this_fleets_leg_and_hca".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_the_engine_argv_states_the_profiles_batching_bounds".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_the_group_b_arm_plans_exactly_four_ranks".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_the_utilisation_argv_is_the_profiles_value_at_full_precision".to_string(), 1), test_reference_row("v2.test.manual.cross_language_add_python_to_typescript".to_string(), "".to_string(), "v2.test.manual.typescript_effect_io_emit.ts_effect_io_emit_holds".to_string(), 1), test_reference_row("v2.test.manual.cross_language_add_python_to_typescript".to_string(), "cross_language_add_python_to_typescript_chain_status_holds".to_string(), "v2.test.manual.typescript_effect_io_emit.ts_effect_io_emit_holds".to_string(), 1)]) +}), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_absent_shape_refuses_silhouette".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_catalog_evidence_cannot_buy_placement_clearance".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_evidence_spendability_discriminates".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_mounting_axis_needs_no_shape_or_envelope".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_naming_is_alias_with_sku_unread".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_proof_load_receipt_is_load_bearing".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_real_row_axis_is_not_yet_locatable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_real_row_refuses_lift_role_on_axis".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_relayed_reading_makes_envelope_spendable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_search_summary_envelope_is_not_spendable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "".to_string(), "test.claim.climbing_hold_catalog_witness.witness_source_reading_survives_conversion".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_absent_shape_refuses_silhouette".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_catalog_evidence_cannot_buy_placement_clearance".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_evidence_spendability_discriminates".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_mounting_axis_needs_no_shape_or_envelope".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_naming_is_alias_with_sku_unread".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_proof_load_receipt_is_load_bearing".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_real_row_axis_is_not_yet_locatable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_real_row_refuses_lift_role_on_axis".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_relayed_reading_makes_envelope_spendable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_search_summary_envelope_is_not_spendable".to_string(), 1), test_reference_row("test.claim.climbing_hold_witness_roster".to_string(), "climbing_hold_witness_outcomes".to_string(), "test.claim.climbing_hold_catalog_witness.witness_source_reading_survives_conversion".to_string(), 1), test_reference_row("test.claim.materialization_provider_consumer_hand_rust_witness".to_string(), "".to_string(), "test.claim.materialization_provider_witness.incomplete_v1_shape_artifact_refuses_naming_the_diagnostic_union".to_string(), 1), test_reference_row("test.claim.materialization_provider_consumer_hand_rust_witness".to_string(), "".to_string(), "test.claim.materialization_provider_witness.red_declared_input_miss_stale_artifact_refuses_as_wrong_artifact".to_string(), 1), test_reference_row("test.claim.materialization_provider_consumer_hand_rust_witness".to_string(), "witness_model_refusal_controls_remain_distinct".to_string(), "test.claim.materialization_provider_witness.incomplete_v1_shape_artifact_refuses_naming_the_diagnostic_union".to_string(), 1), test_reference_row("test.claim.materialization_provider_consumer_hand_rust_witness".to_string(), "witness_model_refusal_controls_remain_distinct".to_string(), "test.claim.materialization_provider_witness.red_declared_input_miss_stale_artifact_refuses_as_wrong_artifact".to_string(), 1), test_reference_row("test.claim.native_witness_transition_receipt_witness".to_string(), "".to_string(), "v2.test.execution.native_selected_witness_bundle.native_selected_witness_bundle_cutover_evidence_holds".to_string(), 1), test_reference_row("test.claim.native_witness_transition_receipt_witness".to_string(), "cited_evidence_declaration_is_an_executing_witness".to_string(), "v2.test.execution.native_selected_witness_bundle.native_selected_witness_bundle_cutover_evidence_holds".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_a_socket_planned_rank_maps_no_device_and_cannot_lock_memory".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_an_unrun_subject_refuses_normal_serving_and_the_intent_changes_the_answer".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_every_planned_rank_maps_the_verbs_device_and_can_lock_memory".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_every_rank_keeps_the_transport_evidence_producer_on".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_every_rank_names_this_fleets_leg_and_hca".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_the_engine_argv_states_the_profiles_batching_bounds".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_the_group_b_arm_plans_exactly_four_ranks".to_string(), 1), test_reference_row("test.claim.spark.serving_arm_launch_witness_test".to_string(), "w_all_serving_arm_launch_claims_hold".to_string(), "test.claim.spark.serving_arm_launch_witness_test.w_the_utilisation_argv_is_the_profiles_value_at_full_precision".to_string(), 1)]) } pub fn test_reference_import_referrer() -> String { 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 c6d4cdbaa2e..af3c176e13b 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 @@ -3,9 +3,6 @@ module v2.test.manual.cross_language_add_python_to_typescript import v2.test.manual.python_grammar_claim { python_grammar_parse_fixture } -import v2.test.manual.typescript_effect_io_emit { - ts_effect_io_emit_holds -} import v2.test.manual.ingest_bridge { ingest_bridge_realized } @@ -144,7 +141,6 @@ test fn cross_language_add_python_to_typescript_chain_status_holds() -> Bool { python_core == python_emitted_add_fn_node() && cross_language_compile_on_real_tree_round_trip_holds(parse_tree: python_parse_tree) - && ts_effect_io_emit_holds() Rejected { diagnostics: _ } => false } Rejected { diagnostics: _ } => false From 9cc054bb476ba6cb64b3b04cef0f2f732b6b306b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 20 Sep 2026 14:45:33 +0000 Subject: [PATCH 69/69] Delete the three unmeasured warm rows, and join the qualified fixture to its producer Side-chat blockers on gunbc#11580. 1. The three floor_cross_claim_pure_producers_warm rows go, with their justification and the wall-test annotation claiming the producer is served WARM. One of them named test_marker_distinct_parsed, a declaration gunbc#11573 deleted -- and the nominal floor passed green over an unresolvable row, so that run never exercised roster admission and its green does not cover it. The other two derived serve-below-recompute from portability, which establishes only that a value CAN be served. body_lowering_normalized_arrow_root_resolves returns to floor_cost_debt, where it belongs without a share to justify its absence. 2. gunbc#11683 made a bare dotted body chain lower to the qualified-name spine, so wall_qualified stopped being unproducible the day it landed and the qualified-path red was supplying an input no longer joined to its producer -- the exact failure the pairing obligation exists to prevent. The paired source and fixture gain a fourth member (qualified_user = wall.peer.peer_leaf), so the whole-root content_hash comparison covers that builder and reds if the lowering moves again. --- .../test_code_reference_wall_test.dag | 24 +++++++----- src/v2/workflow/floor_cost_debt.dag | 2 +- src/v2/workflow/floor_pure_producer_share.dag | 37 ------------------- 3 files changed, 16 insertions(+), 47 deletions(-) diff --git a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag index 852b6eec148..173d11360b3 100644 --- a/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag +++ b/src/v2/test/claim/name_resolve/test_code_reference_wall_test.dag @@ -159,7 +159,8 @@ fn peer_test_module() -> Outcome { wall_tree( root: wall_module_root(leaf_segment: ^peer, members: [ wall_member(name: ^shared_fixture, body: wall_atom(identity: ^dag_token_kw_true)), - wall_member(name: ^peer_leaf, body: wall_atom(identity: ^shared_fixture)) + wall_member(name: ^peer_leaf, body: wall_atom(identity: ^shared_fixture)), + wall_member(name: ^qualified_user, body: wall_qualified(segments: [^wall, ^peer, ^peer_leaf])) ]), marked: [^peer_leaf] ) @@ -170,7 +171,11 @@ fn peer_test_module() -> Outcome { // (`helper`, the one-parameter domain), and wall_member with a name-reference body carrying the test // marker (`peer_leaf`). Three members because the builders have three distinct bodies; the front // end's cost grows with the source and this claim pays for one real tokenize, parse and normalize. -// The only supplied node no source produces is wall_qualified, declared beside it. +// SINCE gunbc#11683 THERE IS NO SUPPLIED NODE HERE THAT NO SOURCE PRODUCES. A bare dotted body chain +// now lowers to the qualified-name spine, so wall_qualified became producible the day that landed -- +// and a supplied builder whose producer has moved is exactly what the pairing obligation exists to +// catch. The fourth member of the paired source and fixture below (`qualified_user`) joins it to the +// real front end, so the whole-root content_hash comparison reds if that lowering moves again. fn inhabitance_module() -> Outcome { wall_tree( root: wall_module_root(leaf_segment: ^peer, members: [ @@ -182,7 +187,7 @@ fn inhabitance_module() -> Outcome { ) } -data inhabitance_module_source: String = "module wall.peer\nfn shared_fixture() -> Bool = true\nfn helper(leaf: Bool) -> Bool = leaf\ntest fn peer_leaf() -> Bool = shared_fixture()\n" +data inhabitance_module_source: String = "module wall.peer\nfn shared_fixture() -> Bool = true\nfn helper(leaf: Bool) -> Bool = leaf\ntest fn peer_leaf() -> Bool = shared_fixture()\nfn qualified_user() -> Bool = wall.peer.peer_leaf\n" // module wall.peer -- `fn shared_fixture() -> Bool = true`, no test code. fn peer_serving_module() -> Outcome { @@ -202,12 +207,13 @@ fn serving_module_with_body(body: Node) -> Outcome { // uses), emits the same root (by content hash, which does not read occurrence ids) and a // channel marking exactly `peer_leaf` -- one claim per half, both reading the one shared front-end // value below. They are the two things every claim below reads. -// THE SHARE POINT: the real front end's output for `inhabitance_module_source`, enrolled WARM in -// v2.workflow.floor_pure_producer_share. The floor forces it once, during strict preparation and -// outside every per-claim budget, and both inhabitance claims read the same value. A real -// tokenize, parse and normalize of even a one-member module costs about 75k eval steps, over the -// 72,300 new-witness budget by itself, so paying it once and sharing it is the only way the real -// path gets onto the floor at all. +// ONE PRODUCER, TWO READERS, AND NO WARM-SHARE ROW. Both inhabitance claims call this one function, +// so the source literal and the producer are shared; the VALUE is not. Claim frames are isolated, so +// each claim recomputes this front-end run. A row enrolling it would have to be admitted by a +// controlled row-present against row-absent measurement on the required floor, and the restored floor +// publishes no claim-cost table, no shared-fill ledger and no receipt to make one -- so the row that +// stood here is deleted rather than argued from the value being portable, which establishes only that +// it CAN be served, never that serving is cheaper. // WHEN THE ROOT CLAIM REDS: render BOTH trees with their EDGE LABELS and compare those. A read by // child index is what made the graft-marker drift (#11694, a leading edge on the module body) look // like a change inside the member Arrow, and cost two lanes to find. diff --git a/src/v2/workflow/floor_cost_debt.dag b/src/v2/workflow/floor_cost_debt.dag index 49fad1403f7..b4231f44153 100644 --- a/src/v2/workflow/floor_cost_debt.dag +++ b/src/v2/workflow/floor_cost_debt.dag @@ -733,7 +733,7 @@ fn floor_cost_debt_proven_chunk_09() -> List { } fn floor_cost_debt_proven_chunk_10() -> List { - Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_fn_body_subtree_holds", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_fn_decl_found", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_has_plus_token", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_module_well_formed", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalize_accepts", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalize_yields_arrow", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_domain_has_two_params", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_left_operand_is_atom", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operand1_lexeme_is_x", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operand2_lexeme_is_y", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operator_is_plus", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_resolves", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_transform_has_three_children", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_return_holds", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_field_access_postfix_rejects", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_normalize_accepts_projection_module", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_projection_lowers_to_transform", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.dropping_the_transport_realization_roster_surfaces_more", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.withdrawing_the_admission_surfaces_the_edge_under_the_wide_root", tail: Empty {} } } } } } } } } } } } } } } } } } } } + Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_fn_body_subtree_holds", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_fn_decl_found", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_has_plus_token", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_module_well_formed", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalize_accepts", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalize_yields_arrow", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_arrow_root_resolves", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_domain_has_two_params", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_left_operand_is_atom", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operand1_lexeme_is_x", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operand2_lexeme_is_y", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_operator_is_plus", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_resolves", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_transform_has_three_children", tail: Cons { head: "v2.test.manual.body_lowering_normalize_add.body_lowering_return_holds", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_field_access_postfix_rejects", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_normalize_accepts_projection_module", tail: Cons { head: "v2.test.manual.body_lowering_projection_call.body_lowering_projection_lowers_to_transform", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.dropping_the_transport_realization_roster_surfaces_more", tail: Cons { head: "v2.test.realization_vocabulary_containment.cli_vocabulary.rest_path_reaches_no_cli.withdrawing_the_admission_surfaces_the_edge_under_the_wide_root", tail: Empty {} } } } } } } } } } } } } } } } } } } } } } fn floor_cost_debt_proven_chunk_11() -> List { diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index ae330f29832..b8e63b08661 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -497,40 +497,6 @@ import v2.std.collection { List } // site WALKS and share the one-module ingest the walks read, exactly as the sibling rows do. Both // values are Nodes; no closure. // -// v2.test.name_resolve.test_code_reference_wall inhabitance_normalized EARNS ITS ROW ON THE -// NEW-WITNESS STEP BUDGET, NOT THE CPU LINE. Recompute: each of its two consumers -// (supplied_root_is_what_normalize_emits_holds, supplied_channel_is_what_normalize_emits_holds) spends -// almost all of its eval steps on this one tokenize, parse and normalize of a one-member module, -// which by itself is over the floor's 72,300-step new-witness budget. Re-derive with claim_batch -// --entry on that file ([witness] eval_steps for either claim, against a claim reading only the -// supplied fixture). Sharing: two claims, one value. Serve below recompute: the value is -// Outcome -- Nodes, symbols and the TestMarkerChannel's symbol rows, portable to the -// bottom -- so a serve is an Rc hand-over. It is WARM: nullary, sources inline in the witness. The -// claims are about what normalize EMITS, and sharing does not hand them their answer: the shared -// value is the real producer's output, and each claim still computes its own verdict from it (a -// content-hash comparison against the supplied fixture, and a channel read). -// -// v2.test.manual.body_lowering_normalize_add body_lowering_normalized_module IS THE SAME SHAPE, at -// scale: one tokenize, parse and normalize of the `add` module that thirteen claims in that file -// re-derive per claim. It was measured when the test-code wall (which adds a required marker channel -// to admit_normalized_tree) touched body_lowering_normalized_arrow_root_resolves, a cost-debt row. -// That claim is roughly three quarters this producer and one quarter its own resolve, so serving the -// producer brings it under the new-witness budget and its debt row is released in the same change. -// Re-derive with claim_batch --entry on that file: [witness] eval_steps for a claim that only forces -// the producer, against the arrow-root claim. The value is Optional, portable to the -// bottom. -// -// v2.test.claim.parse.test_marker_channel test_marker_distinct_parsed EARNS ITS ROW ON THE NEW-WITNESS -// STEP BUDGET. Its consumer, a_different_declaration_moves_the_semantic_projection_holds, is the -// red-capable control for the marker's projection equality: it parses a two-declaration module and -// compares the two declarations' lowered hashes. The parse is most of its cost, and on the floor it -// measured just over the 72,300-step new-witness budget. Re-derive with claim_batch --entry on that file -// ([witness] eval_steps for the claim) and the floor run's COMPLETED-OVER-COST line for it. Serving the -// parse leaves the claim paying only what it is about, the two normalize_node calls and their hashes. -// Serve below recompute: the value is Outcome, Nodes and the span index, portable to -// the bottom. WARM: nullary, source inline in the witness. The claim is about lowering, not parsing, -// so sharing the parse does not hand it its answer. -// // AND THE FINAL ROW: // DAG CANONICAL-SYMBOL MAP EARNS ITS ROW ON THE ENROLMENT-MARGIN CPU THE EVAL-STEP GATE // CANNOT SEE. Sibling floor run 35340819603 (#11580 merging the resolve-cost head that @@ -611,9 +577,6 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.claim.namespace_xl0.cross_module_reference_resolution.xl0r_resolved_consumer_b", "v2.test.claim.namespace_xl0.qualified_site_enumerator_differential.qsd_normalized", "v2.test.claim.namespace_xl0.qualified_site_enumerator_differential.qsd_parsed", - "v2.test.name_resolve.test_code_reference_wall.inhabitance_normalized", - "v2.test.manual.body_lowering_normalize_add.body_lowering_normalized_module", - "v2.test.claim.parse.test_marker_channel.test_marker_distinct_parsed", "v2.extdeps.languages.dag.dag_canonical_symbol_map" ]