diff --git a/dag/gunbc/recurring_failure_mode/call_expression_erased_at_v2_body_lowering.dag b/dag/gunbc/recurring_failure_mode/call_expression_erased_at_v2_body_lowering.dag new file mode 100644 index 00000000000..9452197e847 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/call_expression_erased_at_v2_body_lowering.dag @@ -0,0 +1,30 @@ +module gunbc.recurring_failure_mode.call_expression_erased_at_v2_body_lowering + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data call_expression_erased_at_v2_body_lowering: RecurringFailureMode = RecurringFailureMode { + identity: "call_expression_erased_at_v2_body_lowering" as NonEmptyStr, + + receipts: [ + "**a call expression is erased by v2 normalize before resolve runs, so every reference it carries -- callee and arguments -- is invisible to name resolution and the refusal a missing declaration should raise has nothing to fire on** (INVALID STATE: v2.compiler.normalize, via v2.compiler.body_lowering_fold, lowers a function body that is a call into a Transform node whose only child is the `dag_token_lbrace` atom. The callee identity and every argument subtree are dropped. The module then passes normalize and resolve Accepted, with no diagnostic of any severity).", + + "HARM. v2 name resolution fails OPEN for the whole call-expression population, declared and undeclared alike. A call to a name no declaration provides is Accepted. The namespace cut's census rests on a delete-first premise, that removing an authority makes every real dependent REFUSE LOUDLY, and that premise is void for every dependent reached through a call. For the observed call shapes, erasure prevents the missing-callee resolution refusal from firing. Consequently a refusal roster cannot establish completeness of affected call dependents from absence of refusals. The affected population has not been established by these receipts. Native census mode separately performs no resolution and therefore cannot establish binding-refusal completeness. A native run with no passing call-bearing identities does not by itself identify lowering as the cause of every refused identity.", + + "DISTINGUISHING FACTS, because two neighbouring rows look like this one and are not. `unresolved_callee_conflated_with_dispatched_elsewhere` is the v1 ExprCall-tail class: there the callee reaches the classifier and the miss is absorbed later. Here the callee never reaches resolve at all. And the earlier dated measurements (v2.test.claim.staged_front_end unbound_symbol_reason_note, v2.test.claim.dag_acceptance frontier_arm_note) attributed the silence to resolve. That was the wrong stage: resolve is correct over the tree it receives, and the tree has already lost the call. A wall authored in resolve would therefore be a decoration (DESIGN section 4b), because the undeclared-callee RED and the declared-callee positive control arrive as identical trees.", + + "RECEIPT (2026-09-12, still-eagle-447, NAMESPACE XL-2 scoping). The normalized tree was dumped for five bodies via prepare_grammar, parse_module_prepared and normalize -- the calls v2.compiler.program_assembly program_assembly_read_to_normalized_root_prepared makes, and so the census route's own hand-off (v2.compiler.compile native_test_context_from_ingest). `{ k }` keeps `Atom:k`. `{ x }` over a parameter keeps `Atom:x`. `{ g(x: 1) }` with g declared, `{ zzz(x: 1) }` and `{ zzz(1) }` with zzz undeclared all become `Transform [ Atom:dag_token_lbrace ]`. The unprepared parse_module route (v2.compiler.staged_front_end run_front_end) dumps identically, so this is not a second lossy route beside a faithful one.", + + "EVIDENCE LEVEL, stated so the receipt is not read one rung high. The dumps ran the v2 .dag through the gunbc interpreter at main f9d37e43a1, and a structural node-count fold (not a printer) agrees: the parse tree grows with a call's arguments and nesting, while the normalized body of { g(x: 1) } is exactly one node larger than { k } -- the Transform and its lbrace atom -- and { g(x: h(y: 5)) } is no larger than that. A second named argument adds one node, so the lowering keeps something per extra argument, but none of the callee or argument content. That the natively emitted normalize behaves identically is not executed. The native route gives no contrary evidence: its receipt on the #10940 head (eager-raven-113, 2026-09-12) holds exactly one Passed identity, a literal-bodied smoke test, and every richer body refuses before or at eval (v2.compiler.infer node_grounding_frontier_note). So no route has been observed to lower a call faithfully.", + + "RUNG FOUND AT: outside the ladder -- silent wrongness. The compiler Accepts a program with an undeclared callee and reports nothing.", + + "CEILING: structurally guaranteed. Whether a name is bound in the containment index is decidable, and v2 resolve already refuses an unbound VALUE name with `resolve_reason_unbound_symbol`. The missing piece is not an authority but the lowering preserving what resolve needs.", + + "NEXT-RUNG TRIGGER, naming the capability: v2 body lowering PRESERVES every call expression's callee and argument subtrees into the normalized tree on every v2 route that feeds resolve, so that resolve receives the callee as a reference. The climb's evidence is executed, and resolution alone does not discharge it. The repair that fires this trigger must show, by execution: (i) a call with a DECLARED callee executes through the native route and returns the expected ARGUMENT-DEPENDENT result, so that the argument subtree is demonstrably carried and consumed rather than merely present; (ii) a call with an UNDECLARED callee refuses at RESOLVE with a typed, located missing-binding cause -- at resolve, not later at grounding, emission or eval, since a refusal arriving downstream is consistent with the callee never having reached resolve at all; (iii) controls that discriminate ARGUMENT-subtree loss specifically, over the additional-argument and nested-call shapes named above, such that restoring the erasure makes those regression assertions FAIL rather than leaving them green. An invalid specimen that is Accepted is a FAILING must-refuse assertion, not evidence of anything. The prepared and unprepared routes and the native realization are distinct evidence paths and one passing route does not stand in for the others. A resolve-side check, a census count, or a migration of known call sites does not fire this trigger, because none of them makes the call reach resolve. These are requirements on the repair's evidence, not executions owed before this row lands.", + + "CENSUS CONSEQUENCE, recorded because the cut consumes it: the whole-tree native census (the emitted closure binary's census mode) runs only tokenize, parse and normalize per file and never calls resolve, so its roster carries front-end refusals only. Restoring call lowering does not by itself add resolve refusals to that census. That needs the census mode to resolve per module as well.", + ], + + evidence: [], +} diff --git a/src/v2/test/claim/dag_acceptance_test.dag b/src/v2/test/claim/dag_acceptance_test.dag index 30b383b84da..ef739512a30 100644 --- a/src/v2/test/claim/dag_acceptance_test.dag +++ b/src/v2/test/claim/dag_acceptance_test.dag @@ -381,7 +381,7 @@ test fn acceptance_translate_row_is_determinate() -> Bool { data measured_chain_depth_note: String = "MEASURED 2026-08-21 ON THIS TREE, and recorded here rather than asserted as a test because it is a fact about the COMPILER, not about the harness: for `fn add(x: Int, y: Int) -> Int { x + y }` ingested from source, tokenize, parse, normalize, resolve and infer all pass, and TranslateTo(Rust) REJECTS. So the determinate-row assertions above currently observe a passed infer row and a rejected translate row. They are written as determinacy assertions on purpose — pinning the current side would make this file a defect pin that reds the moment v2 gains the derivation it is missing, and the thing worth protecting is that the harness always LOCATES the answer, not that the answer stays the same." -data frontier_arm_note: String = "MEASURED PER PATH, 2026-08-21, ON THE HARNESS'S FIRST REAL SUBJECT. Subject: a call to a name no declaration provides — `fn caller() -> Int { undefined_callee_zzz(a: 1) }` and its .dag-source twin inside this file. Reported per path because DESIGN section 4b requires it: source-to-v1-acceptance and source-to-v2-acceptance are different paths with independently different rungs, and a class's rung is the MINIMUM across them. Citing the strong path while the other stays quiet is the inflation that section names.\n\nV1 SEED PATH — REFUSES, typed and located: `resolve failed ... error: function 'undefined_callee_zzz' not found in scope`. The floor property holds there. CONTROL, because a refusal alone proves nothing: the same file shape with the callee DEFINED resolves, infers and EVALUATES to 1 through the same runner in the same session, refused only at the host return-type boundary. So the refusal is the treatment, not the setup.\n\nV2 PATH — the same property is answered two different ways depending on POSITION, and that asymmetry is inside v2's resolve. An undeclared BARE VALUE name refuses at resolve carrying `resolve_reason_unbound_symbol`, so the vocabulary exists and fires. An undeclared CALLEE passes resolve with no diagnostic of any severity, and then passes infer carrying `infer_grounding_not_derived` — a true statement about GROUNDING, which is a different property than the one that failed. Nothing on the v2 path reports the missing declaration itself.\n\nSO THE CLASS RUNG IS THE V2 PATH'S, and the grounding half is the declared FrontierAccepted state — a typed, located, counted diagnostic on an Accepted path, whose own carrier says so. The same reason symbol also drives outright REJECTIONS elsewhere, which is by design rather than a contradiction: infer emits it as a counted frontier row while every consumer demanding the derived fact turns it into a Violates witness. Severity is the consumer's, not the symbol's.\n\nWHAT IS NOT CONCLUDED HERE, deliberately: whether v2's resolve SHOULD refuse an undeclared callee, or whether deferring callee resolution past resolve is a considered design — method existence can be genuinely undecidable before types are known. This file files the MEASUREMENT, not a defect. What it establishes is that the question is now COUNTABLE per candidate, which is what a frontier row being its own constructor buys: a receipt that folded it into StagePassed would have reported this candidate as a clean pass through the entire chain." +data frontier_arm_note: String = "MEASURED PER PATH, 2026-08-21, ON THE HARNESS'S FIRST REAL SUBJECT. Subject: a call to a name no declaration provides — `fn caller() -> Int { undefined_callee_zzz(a: 1) }` and its .dag-source twin inside this file. Reported per path because DESIGN section 4b requires it: source-to-v1-acceptance and source-to-v2-acceptance are different paths with independently different rungs, and a class's rung is the MINIMUM across them. Citing the strong path while the other stays quiet is the inflation that section names.\n\nV1 SEED PATH — REFUSES, typed and located: `resolve failed ... error: function 'undefined_callee_zzz' not found in scope`. The floor property holds there. CONTROL, because a refusal alone proves nothing: the same file shape with the callee DEFINED resolves, infers and EVALUATES to 1 through the same runner in the same session, refused only at the host return-type boundary. So the refusal is the treatment, not the setup.\n\nV2 PATH — the same property is answered two different ways depending on POSITION. An undeclared BARE VALUE name refuses at resolve carrying `resolve_reason_unbound_symbol`, so the vocabulary exists and fires. An undeclared CALLEE passes resolve with no diagnostic of any severity. STAGE CORRECTION (2026-09-12): the asymmetry is NOT inside resolve -- the callee is erased before resolve runs (gunbc.recurring_failure_mode.call_expression_erased_at_v2_body_lowering). The module then passes infer carrying `infer_grounding_not_derived` — a true statement about GROUNDING, which is a different property than the one that failed. Nothing on the v2 path reports the missing declaration itself.\n\nSO THE CLASS RUNG IS THE V2 PATH'S, and the grounding half is the declared FrontierAccepted state — a typed, located, counted diagnostic on an Accepted path, whose own carrier says so. The same reason symbol also drives outright REJECTIONS elsewhere, which is by design rather than a contradiction: infer emits it as a counted frontier row while every consumer demanding the derived fact turns it into a Violates witness. Severity is the consumer's, not the symbol's.\n\nWHAT IS NOT CONCLUDED HERE, deliberately: whether v2's resolve SHOULD refuse an undeclared callee, or whether deferring callee resolution past resolve is a considered design — method existence can be genuinely undecidable before types are known. This file files the MEASUREMENT, not a defect. What it establishes is that the question is now COUNTABLE per candidate, which is what a frontier row being its own constructor buys: a receipt that folded it into StagePassed would have reported this candidate as a clean pass through the entire chain." fn frontier_diagnostics() -> Diagnostics { Some { diff --git a/src/v2/test/claim/staged_front_end_test.dag b/src/v2/test/claim/staged_front_end_test.dag index e14b1b5f3cf..5c5f2ae90b0 100644 --- a/src/v2/test/claim/staged_front_end_test.dag +++ b/src/v2/test/claim/staged_front_end_test.dag @@ -110,7 +110,7 @@ test fn staged_front_end_bounded_run_refuses_the_whole_front_end_question() -> B } } -data unbound_symbol_reason_note: String = "The floor property, pinned in the direction it should hold. An undeclared name in VALUE position must refuse at resolve, and it must refuse with the reason that names what went wrong rather than with any refusal at all — a stage that refuses for an unrelated cause would satisfy a looser assertion while the diagnostic vocabulary rotted. Only this direction is pinned: the sibling measurement, that an undeclared CALLEE passes resolve silently on the v2 path, is recorded in v2.test.claim.dag_acceptance frontier_arm_note as a dated measurement and is deliberately NOT asserted here, because a test that goes red when the compiler starts refusing the callee would be a defender for the gap rather than a check on it." +data unbound_symbol_reason_note: String = "The floor property, pinned in the direction it should hold. An undeclared name in VALUE position must refuse at resolve, and it must refuse with the reason that names what went wrong rather than with any refusal at all — a stage that refuses for an unrelated cause would satisfy a looser assertion while the diagnostic vocabulary rotted. Only this direction is pinned: the sibling measurement, that an undeclared CALLEE passes resolve silently on the v2 path (because v2 normalize erases the call before resolve runs, not because resolve admits it: gunbc.recurring_failure_mode.call_expression_erased_at_v2_body_lowering), is recorded in v2.test.claim.dag_acceptance frontier_arm_note as a dated measurement and is deliberately NOT asserted here, because a test that goes red when the compiler starts refusing the callee would be a defender for the gap rather than a check on it." fn first_refusal_reason(source: String) -> Symbol { fold(run_front_end(source: source, file: ^candidate_module).steps, init: ^no_refusal, f: fn(acc, step) {