Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions dag/gunbc/ci/ci_layer_roots.dag
Original file line number Diff line number Diff line change
Expand Up @@ -1720,27 +1720,27 @@ data falsifier_substrate_long_lane_rows: List<SubstrateLongLaneRow> = [
},
SubstrateLongLaneRow {
entry: "dag/test/claim/long/namespace_graft_body_dissolution_witness_test.dag",
function: "witness_type_fn_module_dissolution_refuses_RED",
reason: "~16s measured eval (valiant-boar-65, 2026-08-10): interpreted production lex+parse+normalize per control — the graft empty-observation-narrow guard matrix (operator ruling msg_d18a55de: the collector reporting a declaration-carrying module as declaration-free now REFUSES)",
function: "witness_type_fn_module_grafts",
reason: "~16s measured eval: interpreted tokenize/parse/normalize — type+fn source must graft (not namespace_graft_body_dissolved_refused)",
dissolution: unbound_dissolution(description: "witness realization lands the lex/parse natively or eval drops under gunbc_ci_fast_lane_witness_eval_budget on a measured run — then re-enroll per-PR discovery and delete this row")
},
SubstrateLongLaneRow {
entry: "dag/test/claim/long/namespace_graft_body_dissolution_witness_test.dag",
function: "witness_type_only_module_dissolution_refuses_RED",
reason: "same interpreted lex/parse/normalize wall as sibling witness_type_fn_module_dissolution_refuses_RED (~16s measured)",
dissolution: unbound_dissolution(description: "same as witness_type_fn_module_dissolution_refuses_RED row")
function: "witness_type_only_module_grafts",
reason: "same interpreted tokenize/parse/normalize wall as sibling witness_type_fn_module_grafts (~16s measured)",
dissolution: unbound_dissolution(description: "same as witness_type_fn_module_grafts row")
},
SubstrateLongLaneRow {
entry: "dag/test/claim/long/namespace_graft_body_dissolution_witness_test.dag",
function: "witness_lowered_body_module_still_grafts",
reason: "two full lex/parse/normalize passes over the lowered-body control (~35s measured; acceptance plus the non-vacuous declaration-presence read)",
dissolution: unbound_dissolution(description: "same as witness_type_fn_module_dissolution_refuses_RED row")
dissolution: unbound_dissolution(description: "same as witness_type_fn_module_grafts row")
},
SubstrateLongLaneRow {
entry: "dag/test/claim/long/namespace_graft_body_dissolution_witness_test.dag",
function: "witness_header_only_module_still_accepts",
reason: "same interpreted lex/parse/normalize wall as sibling witness_type_fn_module_dissolution_refuses_RED (~18s measured)",
dissolution: unbound_dissolution(description: "same as witness_type_fn_module_dissolution_refuses_RED row")
dissolution: unbound_dissolution(description: "same as witness_type_fn_module_grafts row")
},
SubstrateLongLaneRow {
entry: "dag/test/claim/witness_execution_class_live_census_test.dag",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,11 @@ import v2.compiler.parse { parse_module, ParseArtifact }
import v2.compiler.normalize { normalize }
import v2.extdeps.languages.dag { dag_lex_rules, dag_grammar }

// The empty-observation narrow refused at the graft (operator ruling msg_d18a55de, 2026-08-10):
// namespace_graft's body collector dissolved every declaration of a type-decl-carrying module into
// childless atoms and reported the module as declaration-free — normalize then ACCEPTED a hollow
// module shell, so a loud sugar refusal two commits earlier had become a silent drop (the §4b(3)
// rung demotion declared in gunbc#8080's body). module_header_containment_graft now REFUSES
// (namespace_graft_body_dissolved_refused) when the collected body is empty while the pre-graft
// tree carries top-level declarations. The discriminant is declaration PRESENCE, so a genuinely
// declaration-free module keeps its legitimate empty body. This guard covers total dissolution
// only; partial-drop fidelity belongs to the deeper graft repair. Controls: the formerly-hollow
// shapes now refuse WITH THE EXACT GUARD REASON asserted (discriminating them from the well_formed
// rejection class), a lowered-body module still grafts and its normalized tree still carries its
// declarations (asserted non-vacuously), and a header-only module still accepts.
// Empty flatten with surviving top-level-item alternatives reconstructs a declaration forest
// (operator ruling msg_d18a55de). Type-only and type+fn source must ACCEPT through tokenize/parse/
// normalize and must not carry namespace_graft_body_dissolved_refused. Header-only still accepts.
// Lost forest (top_level_item wrapper, no unit) still refuses — that RED is the synthetic in
// graft_shape_test, because source cannot author a unit-less wrapper.

fn artifact_tree(artifact: ParseArtifact) -> ParseTree {
artifact.tree
Expand All @@ -35,7 +28,7 @@ fn artifact_tree(artifact: ParseArtifact) -> ParseTree {
// the sweep's first_located_cause already skips. The assertion is therefore containment: the
// refusal CARRIES the guard's typed reason, wherever the pending carriers put it.
fn rejection_reasons(source: String) -> List<Symbol> {
match lex_walk_artifact(source: source, file: ^graft_guard_probe, rules: dag_lex_rules()) {
match lex_walk_artifact(source: chars(s: source), file: ^graft_guard_probe, rules: dag_lex_rules()) {
v2.std.diagnostic.Rejected { diagnostics: _ } => no_reasons()
v2.std.diagnostic.Accepted { value: a, diagnostics: _ } =>
match parse_module(tokens: lex_artifact_semantic_tokens(artifact: a), grammar: dag_grammar()) {
Expand Down Expand Up @@ -70,7 +63,7 @@ fn reasons_contain(reasons: List<Symbol>, sym: Symbol) -> Bool {
}

fn normalize_accepts(source: String) -> Bool {
match lex_walk_artifact(source: source, file: ^graft_guard_probe, rules: dag_lex_rules()) {
match lex_walk_artifact(source: chars(s: source), file: ^graft_guard_probe, rules: dag_lex_rules()) {
v2.std.diagnostic.Rejected { diagnostics: _ } => false
v2.std.diagnostic.Accepted { value: a, diagnostics: _ } =>
match parse_module(tokens: lex_artifact_semantic_tokens(artifact: a), grammar: dag_grammar()) {
Expand All @@ -85,7 +78,7 @@ fn normalize_accepts(source: String) -> Bool {
}

fn normalized_tree_optional(source: String) -> Node? {
match lex_walk_artifact(source: source, file: ^graft_guard_probe, rules: dag_lex_rules()) {
match lex_walk_artifact(source: chars(s: source), file: ^graft_guard_probe, rules: dag_lex_rules()) {
v2.std.diagnostic.Rejected { diagnostics: _ } => optional_absent()
v2.std.diagnostic.Accepted { value: a, diagnostics: _ } =>
match parse_module(tokens: lex_artifact_semantic_tokens(artifact: a), grammar: dag_grammar()) {
Expand Down Expand Up @@ -124,20 +117,28 @@ fn children_contain_atom(n: Node, sym: Symbol) -> Bool {
})
}

data mixed_type_fn_source: String = "module graft_guard_probe\n\ntype G1 = A1 | B1\n\nfn g1(x: Int) -> Int {\n let w = x\n w\n}\n"
data mixed_type_fn_source: String = "module graft_guard_probe\n\ntype G1 = A1 | B1\n\nfn g1(x: Int) -> Int { x }\n"

data type_only_source: String = "module graft_guard_probe\n\ntype G2 = A2 | B2\n"
data type_only_source: String = "module m\n\ntype A { x: Int }\n"

data lowered_body_source: String = "module graft_guard_probe\n\ndata d3: LiveTreeDisposition = SubstrateInputsOnly\n\nfn g3() -> List<Int> {\n fold_list(xs: [1, 2], empty: Empty, cons: fn(acc, item) {\n list_append(left: [item], right: acc)\n })\n}\n"

data header_only_source: String = "module graft_guard_probe\n"

test fn witness_type_fn_module_dissolution_refuses_RED() -> Bool {
reasons_contain(reasons: rejection_reasons(source: mixed_type_fn_source), sym: ^namespace_graft_body_dissolved_refused)
test fn witness_type_fn_module_grafts() -> Bool {
normalize_accepts(source: mixed_type_fn_source)
&& !reasons_contain(
reasons: rejection_reasons(source: mixed_type_fn_source),
sym: ^namespace_graft_body_dissolved_refused
)
}

test fn witness_type_only_module_dissolution_refuses_RED() -> Bool {
reasons_contain(reasons: rejection_reasons(source: type_only_source), sym: ^namespace_graft_body_dissolved_refused)
test fn witness_type_only_module_grafts() -> Bool {
normalize_accepts(source: type_only_source)
&& !reasons_contain(
reasons: rejection_reasons(source: type_only_source),
sym: ^namespace_graft_body_dissolved_refused
)
}

test fn witness_lowered_body_module_still_grafts() -> Bool {
Expand Down
151 changes: 141 additions & 10 deletions src/v2/compiler/namespace_graft.dag
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module v2.compiler.namespace_graft

import std.occurrence_identity { OccurrenceSynthetic }
import std.algebra { Cons, Empty, FreeMonoid }
import v2.std.algebra { fold_list_right, list_snoc_item }
import v2.std.collection { Absent, List, Present }
import v2.std.algebra { fold_list, fold_list_right, list_append, list_snoc_item }
import v2.std.collection { Absent, List, Present, optional_absent }
import v2.std.diagnostic {
Accepted,
Diagnostic,
Expand All @@ -18,6 +18,7 @@ import v2.std.diagnostic {
import v2.std.logic { Bool }
import v2.std.node {
Atom,
Cardinality,
Conj,
Edge,
Named,
Expand All @@ -29,9 +30,18 @@ import v2.std.node {
}
import v2.std.qualified_name { QualifiedName }
import v2.std.compilers.lexing { symbol_lexeme }
import v2.std.grammar {
GrammarExpr,
GrammarExprFold,
GrammarProduction,
fold_grammar_expr,
node_atom_identity_optional
}
import v2.extdeps.languages.dag {
dag_named_edge,
dag_surface_module_header_metadata_edge,
dag_grammar_root,
dag_grammar_top_level_item_expr,
namespace_graft_already_grafted,
namespace_graft_has_module_header,
namespace_graft_unwrap_module_working_root,
Expand Down Expand Up @@ -148,6 +158,126 @@ fn namespace_graft_collect_body_edges_from_node(node: Node) -> List<Edge> {
})
}

fn namespace_graft_grammar_expr_nonterminal_productions(expr: GrammarExpr) -> List<Symbol> {
fold_grammar_expr(
expr: expr,
algebra: GrammarExprFold {
terminal: fn(_, _) { [] },
nonterminal: fn(production) { [production] },
sequence: fn(left, right) { list_append(left: left, right: right) },
choice: fn(left, right) { list_append(left: left, right: right) },
optional: fn(xs) { xs },
repeat: fn(xs) { xs }
}
)
}

fn namespace_graft_production_emitted_identity_optional(production: GrammarProduction) -> Symbol? {
node_atom_identity_optional(node: production.emitted)
}

fn namespace_graft_emitted_identity_for_production_name(name: Symbol) -> Symbol? {
fold_list(
xs: dag_grammar_root().productions,
empty: optional_absent(),
cons: fn(acc, production) {
match acc {
Present { value: _ } => acc
Absent =>
match production.name == name {
true => namespace_graft_production_emitted_identity_optional(production: production)
false => optional_absent()
}
}
}
)
}

fn namespace_graft_top_level_item_alternative_emitted_ids() -> List<Symbol> {
fold_list(
xs: namespace_graft_grammar_expr_nonterminal_productions(expr: dag_grammar_top_level_item_expr()),
empty: [],
cons: fn(acc, production_name) {
match namespace_graft_emitted_identity_for_production_name(name: production_name) {
Present { value: emitted } => list_snoc_item(xs: acc, item: emitted)
Absent => acc
}
}
)
}

fn namespace_graft_id_is_top_level_item_unit(id: Symbol, unit_ids: List<Symbol>) -> Bool {
fold_list(
xs: unit_ids,
empty: false,
cons: fn(found, emitted) {
found || (emitted == id)
}
)
}

fn namespace_graft_collect_unit_nodes(node: Node) -> List<Node> {
namespace_graft_collect_unit_nodes_against(
node: node,
unit_ids: namespace_graft_top_level_item_alternative_emitted_ids()
)
}

fn namespace_graft_collect_unit_nodes_against(node: Node, unit_ids: List<Symbol>) -> List<Node> {
match parse_production_emitted_identity_optional(node: node) {
Present { value: id } =>
if namespace_graft_id_is_top_level_item_unit(id: id, unit_ids: unit_ids) {
[node]
} else {
fold(node.children, init: [], f: fn(acc, edge) {
concat(acc, namespace_graft_collect_unit_nodes_against(node: edge.target, unit_ids: unit_ids))
})
}
Absent =>
fold(node.children, init: [], f: fn(acc, edge) {
concat(acc, namespace_graft_collect_unit_nodes_against(node: edge.target, unit_ids: unit_ids))
})
}
}

fn namespace_graft_reconstruct_unit_edge(node: Node) -> Edge {
Edge {
label: Positional,
target: node
}
}

fn namespace_graft_reconstruct_declaration_forest(units: List<Node>) -> List<Edge> {
[
Edge {
label: Named { name: ^dag_surface_top_level_item },
target: Node {
kind: TypeNode { connective: Cardinality },
children: fold(units, init: [], f: fn(acc, unit) {
list_snoc_item(xs: acc, item: namespace_graft_reconstruct_unit_edge(node: unit))
}),
occurrence_id: OccurrenceSynthetic
}
}
]
}

fn namespace_graft_body_edges_for_graft(root: Node) -> List<Edge> {
let flattened = namespace_graft_collect_body_edges(root: root)
if count(flattened) == 0 {
let units = namespace_graft_collect_unit_nodes(
node: namespace_graft_unwrap_module_working_root(root: root)
)
if count(units) == 0 {
[]
} else {
namespace_graft_reconstruct_declaration_forest(units: units)
}
} else {
flattened
}
}

fn namespace_graft_is_graft_candidate(root: Node) -> Bool {
if namespace_graft_already_grafted(root: root) {
false
Expand All @@ -166,13 +296,14 @@ fn namespace_graft_collect_body_edges(root: Node) -> List<Edge> {

// The empty-observation narrow made unwritable (operator ruling msg_d18a55de): a
// collector that cannot express a module's declarations must not report "this module
// has none". A pre-graft tree that carries top-level declarations (the
// dag_surface_top_level_item identity atoms survive the normalize fold on every
// measured shape) while the collected body is EMPTY is the collector failing to
// carry the forest — a decidable comparison of two trees in hand, refused loudly.
// A module with no declarations at all (header only) keeps its legitimate empty
// body: the discriminant is declaration presence, not body emptiness alone. This
// guard covers total dissolution; partial-drop fidelity is the deeper graft repair.
// has none". Flattening parse-projection / transparent-sequence spines into a
// declaration's CHILDREN dropped type/data/import/fn shells (every child is itself
// a projection), so a module whose declarations contribute no function body looked
// identical to a LOST forest. Conj body edges must be uniquely Named (well_formed). Flattening
// already produces that shape for a nonempty forest, so a nonempty flatten is kept as-is.
// An empty flatten with surviving top-level-item alternatives reconstructs one Named forest
// (Cardinality of those units as Positional children, interiors kept). Empty flatten, no units,
// and a leftover dag_surface_top_level_item is a genuine loss and still refuses.
fn namespace_graft_subtree_contains_atom(n: Node, sym: Symbol) -> Bool {
match n.kind {
TypeNode { connective: Atom { identity: id } } =>
Expand Down Expand Up @@ -329,7 +460,7 @@ fn module_header_containment_graft(root: Node) -> Outcome<Node> {
correction: Unavailable { reason: ExternalContractUnknown }
})
Cons { head: _, tail: _ } => {
let body_edges = namespace_graft_collect_body_edges(root: root)
let body_edges = namespace_graft_body_edges_for_graft(root: root)
if namespace_graft_body_dissolved(root: root, body_edges: body_edges) {
outcome_rejected(Diagnostic {
reason: ^namespace_graft_body_dissolved_refused,
Expand Down
Loading
Loading