diff --git a/src/v1/00_core.dag b/src/v1/00_core.dag index 59d9d855e24..6f7749165d4 100644 --- a/src/v1/00_core.dag +++ b/src/v1/00_core.dag @@ -230,6 +230,8 @@ type CompilerDiagnostic | DeclaredTypeNotInhabited { position: String, expected: String, got: String, span: SourceSpan } | DeclaredTypeInhabitanceUndecided { position: String, reason: String, span: SourceSpan } | UnlistedImportUse { name: String, span: SourceSpan } + | ReferenceDerivedImportProviderUnknown { name: String, referencing_module: String, span: SourceSpan } + | ReferenceDerivedImportExportUnproven { name: String, referencing_module: String, provider_module: String, span: SourceSpan } | UnlistedVariantValueUse { name: String, span: SourceSpan } | AmbiguousReference { name: String, candidates: List, span: SourceSpan } | DataReferenceVisibilityBudgetExceeded { name: String, span: SourceSpan } @@ -384,6 +386,8 @@ fn diagnostic_to_span(d: CompilerDiagnostic) -> SourceSpan { DeclaredTypeNotInhabited { position: _, expected: _, got: _, span: s } => s DeclaredTypeInhabitanceUndecided { position: _, reason: _, span: s } => s UnlistedImportUse { name: _, span: s } => s + ReferenceDerivedImportProviderUnknown { name: _, referencing_module: _, span: s } => s + ReferenceDerivedImportExportUnproven { name: _, referencing_module: _, provider_module: _, span: s } => s UnlistedVariantValueUse { name: _, span: s } => s AmbiguousReference { name: _, candidates: _, span: s } => s DataReferenceVisibilityBudgetExceeded { name: _, span: s } => s @@ -469,6 +473,10 @@ fn diagnostic_to_message(d: CompilerDiagnostic) -> String { DeclaredTypeInhabitanceUndecided { position: pos, reason: r, span: _ } => concat("declared-type inhabitance is undecidable at the ", pos, " (", r, "): the modeled facts do not settle whether the produced value inhabits its declared type, so no verdict is asserted in either direction") UnlistedImportUse { name: n, span: _ } => concat("unlisted import use '", n, "' (referenced but not in any import's name list)") + ReferenceDerivedImportProviderUnknown { name: n, referencing_module: rm, span: _ } => + concat("no provider is present for '", n, "', referenced by module '", rm, "': the compile closure carries no module that declares the name, so no use-line can be synthesized and the emitted Rust references a name it never imported. The remedy is to AUTHOR AN IMPORT -- closure membership is driven by imports, so a name the module never imported is a name the closure was never asked to supply") + ReferenceDerivedImportExportUnproven { name: n, referencing_module: rm, provider_module: p, span: _ } => + concat("the provider of '", n, "' is present but its export could not be established: module '", rm, "' references the name, the registry maps it to '", p, "', and that module's transitive export surface does not establish it. The remedy is NOT an import -- the provider is already in the closure -- it is that the emitter cannot prove an export the provider may well hold") UnlistedVariantValueUse { name: n, span: _ } => concat("unlisted variant value use '", n, "' (a coproduct arm referenced but not in any import's name list)") AmbiguousReference { name: n, candidates: cs, span: _ } => concat("ambiguous reference '", n, "': ", to_string(value: cs |> count), " candidates: ", join(cs, separator: ", "), " — qualify by containment path, alias, or rename") DataReferenceVisibilityBudgetExceeded { name: n, span: _ } => @@ -529,6 +537,8 @@ data diagnostic_frontier_occurrence_key_note: String = "The canonical accessor f fn is_error_diagnostic(d: CompilerDiagnostic) -> Bool { match d { UnlistedImportUse { name: _, span: _ } => false + ReferenceDerivedImportProviderUnknown { name: _, referencing_module: _, span: _ } => false + ReferenceDerivedImportExportUnproven { name: _, referencing_module: _, provider_module: _, span: _ } => false UnlistedVariantValueUse { name: _, span: _ } => false MethodExistenceFrontierAdmitted { method: _, receiver_type: _, trigger: _, span: _ } => false ReceiverTypeUnestablished { method: _, span: _ } => false @@ -538,6 +548,8 @@ fn is_error_diagnostic(d: CompilerDiagnostic) -> Bool { } } +data reference_derived_import_refusal_severity_note: String = "owner: v1.compiler.emit_rust (reference_derived_use_line_plan's per-candidate disposition). TWO DIAGNOSTICS, ONE WIRED AND BLOCKING, ONE DECLARED AND PRODUCED BY NOTHING -- and which is which was decided by measurement, not by preference. ReferenceDerivedImportExportUnproven is emitted from CandidateExportProofFailed: the provider is present in the closure and its export could not be established, and the remedy is NOT an import -- the provider is already here -- but making the emitter able to prove an export it may already hold. IT IS ADVISORY, AND IT WAS BLOCKING FOR ONE CI RUN -- THE FLIP WAS APPROVED, EXECUTED, AND REFUTED BY ITS OWN FIRST EXECUTION OVER A SECOND CLOSURE, recorded here rather than repointed because a trigger that survives its own falsification inherits credibility a dead prediction no longer has. THE FLIP CONDITION WAS: an arm flips when its population over a NAMED CLOSURE is zero AND a discriminating RED is authorable. Both halves held and the conclusion was still wrong, which is the finding. The named closure was the regen seed closure (SeedV1 + DagCorpus), where the population is zero. The required build lane ALSO runs a v2-emission phase over a DIFFERENT closure -- entry:src/v2/compiler/00_compile.dag, 169 modules -- and there the population is EIGHTY-SEVEN, measured on run 33111325404 at b331e24cf7d, which refused the phase and reddened the required check. So the zero was a property of THE CLOSURE and not of the arm, and flipping globally on a single-closure measurement is the denominator error this very carrier already names one clause down for the sibling arm (registry-absent is ENTRY-RELATIVE rather than a fixed target). The same reasoning was available for this arm and was not applied to it. THE CORRECTED CONDITION, which is what the flip should have required: the population must be zero over EVERY closure a required phase compiles, not over one named closure -- a per-closure zero is a fact about that entry and licenses nothing about another. AND THE CLOSURES MUST BE NAMED HERE RATHER THAN DESCRIBED, because the phrase every-closure-a-required-phase-compiles names a set that MOVES: it grew when #9035 added the v2-emission phase and again when that phase's subject was widened from dag/std/abi.dag to the v2 pipeline root, and it grows whenever a phase is added or an entry roster widens. A described set leaves that to whoever remembers; a named one makes a phase addition visibly re-open this question. AS OF 2026-08-27 THE SET IS THREE, each with its own authority: the regen seed closure (v1.compiler.cli_run regen_source_roots, SeedV1 + DagCorpus), the v2-emission entries (gunbc.ci_layer_roots required_v2_emission_entries), and the emit-compile entries (gunbc.ci_layer_roots required_emit_compile_entries). Only the first two have been measured for this arm -- 0 and 87 -- and the third is UNMEASURED, so even the corrected condition is not currently evaluable and any future flip must measure it rather than assume it follows the seed. This warning was delivered before the flip rather than after it: deep-ant-102 stated that the zero was 0 over that closure and not over all closures, and that with every provider selected in the proof gate is the easy case; it was acknowledged in writing and not carried into the condition. It is recorded as a dropped warning rather than as a missing insight, because the two have different remedies. The bounded sequence ruled for this work said count == 0 flips and count > 0 IS THE BURNDOWN ROSTER AT IDENTITY GRAIN; the terminal event has now fired with the second answer, so the 87 is that roster and the arm stays advisory until it burns down. The population is NOT a stable number and must not be quoted as one: it is 0 over the seed closure and 87 over the v2 compiler entry, and no closure has been enumerated beyond those two. THE DIAGNOSTIC IS STILL TYPED, LOCATED AND COUNTED, which is the whole of what the brief asked -- the silent omission is closed either way; severity decides whether the line stops, not whether the omission is visible. A sample of what it reports, and the reason 87 is a real roster rather than noise: std.determinism references std.perturbation.PerturbationVerdict whose provider is in the closure with its export unestablished, and v2.lens.complexity_accumulator_copy.analyze accounts for a large share of the rest against providers that are likewise present. Under the previous condition: an arm flips when its population over a NAMED CLOSURE is zero AND A DISCRIMINATING RED IS AUTHORABLE. A zero alone is never sufficient, because an observation of zero on an arm that has never fired is indistinguishable in a count from an arm that CANNOT fire, and flipping the latter yields a permanently-green check cited as coverage, which 4b rates worse than absent. BOTH HALVES HELD AT THE TIME: the population is zero over the regen seed closure (claim_executor --required-regen --source-root dag --source-root src/v2, whose subject is regen_input_sources grown to a joint fixpoint over import, dotted-reference and bare-reference edges), and the RED is authorable and AUTHORED at the fixture boundary in v1.tests.claim.reference_derived_disposition_census_witness_test, which builds a CandidateExportProofFailed row and asserts the diagnostic. The emission still produces its files beside the diagnostic rather than returning none: production precedes adjudication, so the candidate tree survives the refusal and the refusal is what stops the line. ReferenceDerivedImportProviderUnknown is DECLARED HERE AND PRODUCED BY NOTHING. Its arm, CandidateRegistryAbsent, was instrumented and measured over the same closure and returned 473 rows over 63 names across 110 modules. THAT POPULATION IS MIXED, AND AN EARLIER REVISION OF THIS NOTE CALLED IT ALL TARGET VOCABULARY -- corrected 2026-08-27 after deep-ant-102 pushed back on the census SCOPE. Two classes are conflated inside it. (a) EXTINGUISHED -- Vec 88, bool 73, i64 48, BTreeSet: no .dag declaration exists anywhere and none can, because the PRODUCER is collect_item_realized_surface_names, which is rust_identifier_tokens over render_rust_type and so emits target-language SPELLINGS by construction (attribution corrected by clever-boar-140, who owns the coproduct: the candidate filter's emitted-source disjunct is an admission GATE over an already-proposed list and cannot itself propose anything, so narrowing it would delete genuine emitter-attested candidates and leave the real source untouched). reference_is_host_realized_builtin misses these because it is keyed on .dag vocabulary -- is_container_type reads std.types container_type_arity -- so Vec and BTreeSet, the RUST spellings of List and Set, pass a filter that exists precisely to remove host-realized names. (b) OUT-OF-CLOSURE -- empty_map 33 (v2.std.collection), Optional 25 (v2.std.optional), and its VARIANTS Present 47 and Absent 13: REAL .dag names whose providers exist and were not selected in. The variant half is not a detail: a consumer key that reads a declaration index's `declared` field alone reports no-provider-anywhere for every variant name in the corpus, because a variant is not a top-level declaration and such an index holds variants in a separate field -- so the key must read declared UNION variants, and two of these four names are the instance that proves it (clever-boar-140, who then settled it STRUCTURALLY rather than from this sample: build_item_info in v1.compiler.infer produces one ItemInfo per TOP-LEVEL ITEM and no arm descends into a coproduct's children, and the single item_registry insert is keyed on that name, so a variant name is absent from the registry under EVERY closure -- verified here against both sites). THAT FACT AND THIS ARM'S ORDERING COMPOSE, and the composition is why these two names appear under (b) rather than under (a): CandidateVariantDelegatedToParent is tested BEFORE the registry lookup, so a variant whose parent is IN closure is delegated and never reaches the registry at all, and the registry's structural inability to hold variants surfaces only when the parent is OUT of closure and type_summaries therefore cannot recognise the name as a variant. So Present and Absent are registry-absent HERE because Optional was out of closure, and would be delegated with it in -- which is exactly why this sample cannot discriminate the two readings, and why the structural argument above was needed instead. THE PROPORTION IS UNMEASURED IN BOTH DIRECTIONS, and the two ways of counting point OPPOSITE ways, which is the whole reason this population was misread twice. BY ROW the immovable names dominate: Vec 88 + bool 73 + Option 50 + i64 48 out of 473, each verified to have no .dag declaration anywhere. BY DISTINCT NAME the examined sample is 4 of 63 and ALL FOUR ARE MOVABLE. High-count immovable names at the head, movable names in the tail with small counts -- which is exactly the structure that hid the signal, and exactly why reading a sorted head answered the wrong question: empty_map's 33 rows were the third-largest count and were still classified as target vocabulary on the first pass. FIFTY-NINE NAMES ARE UNEXAMINED BY ANYONE. Neither the row-dominance nor the 4-of-4 may be quoted as a proportion; both are directional facts about different denominators. WHY (b) IS THERE AT ALL is a trap worth carrying: the census invocation passes --source-root dag --source-root src/v2, but the SUBJECT is regen_input_sources, whose roots are SeedV1 and DagCorpus and exclude src/v2 entirely -- stage0 IS the v1 seed, and a seed reaching into src/v2 would depend on the successor it bootstraps toward. The source-root FLAGS and the regen SUBJECT are not the same thing. So class (b) is precisely the closure-conditioned population this change exists to make visible, sitting inside the rows that had been written off, and registry-absent is ENTRY-RELATIVE rather than a fixed target: a reader who takes the number as stable has made the denominator error. THE 473 IS NOT A ROSTER OF UNFIXABLE ROWS. Wiring a diagnostic to it would still print a permanently-false report on every class-(a) row -- which is why the arm stays unwired even though part of the population is movable -- and the reason that is worse than shipping nothing is that IT TRAINS READERS TO IGNORE THE CHANNEL: a diagnostic nobody reads is worth less than an absent one, because the absent one is honest about its coverage. The variant stays declared because the class is real and its shape is settled; the trigger for wiring it is that class (a) leave the arm -- the candidate walk ceasing to propose target-language vocabulary -- after which the residue is class (b) and is exactly the closure-conditioned population worth reporting. A witness asserts zero diagnostics from that arm and fails the moment anyone wires it. THE TWO ARE SEPARATE DIAGNOSTICS RATHER THAN ONE WITH A CAUSE FIELD, because the question is upstream of severity: registry-absent is fixed by authoring an import, export-proof-failed by proving an export that already exists -- opposite remedies, different owners, different populations, different reachability -- and 4b files one row per class. That the two turned out to need different TREATMENT rather than merely different severity is the strongest evidence the split was right. A REFUTED PREDICTION, recorded rather than quietly repointed. Registry-absent was predicted to overlap heavily with UnlistedImportUse, both being described as 'referenced but never imported', and the prediction was made because closure membership is driven by imports. Measured on one run: 63 registry-absent names, 36 UnlistedImportUse names, INTERSECTION ZERO. UnlistedImportUse names .dag types masked at RESOLVE time; registry-absent is dominated by Rust tokens that never reached the resolver. The reasoning was wrong at the MECHANISM, not merely the magnitude. ProviderUnknown's trigger therefore does NOT point at the family-closure-SVN burndown as this carrier previously stated, and the falsification is recorded here rather than the trigger being silently repointed -- a repointed trigger would inherit the credibility of a prediction that just died, so whatever that trigger eventually becomes is argued fresh." + data where_refinement_deferral_reason_scaffold_note: String = "owner: v1.compiler.infer (00_core diagnostic partition). lane: where-refinement literal-wall enforcement. interim: WhereRefinementUnenforced.reason is a closed-string sum enrolled in is_where_refinement_unenforced_advisory_reason; any unlisted reason fails closed blocking. bound: only the five deferral strings emitted by 04_infer today; classifier/eval/equivalence arms must change together until coproduct lands. dissolve-on: feature:where-refinement-predicate-coproduct (WhereRefinementDeferralReason coproduct on the diagnostic carrier)." fn is_interpreter_blocking_diagnostic(d: CompilerDiagnostic) -> Bool { @@ -551,6 +563,8 @@ fn is_interpreter_blocking_diagnostic(d: CompilerDiagnostic) -> Bool { ReceiverTypeUnestablished { method: _, span: _ } => false ServiceConfigReferenceJudgmentDeferred { field: _, referenced_name: _, trigger: _, span: _ } => false DeclaredTypeInhabitanceUndecided { position: _, reason: _, span: _ } => false + ReferenceDerivedImportProviderUnknown { name: _, referencing_module: _, span: _ } => false + ReferenceDerivedImportExportUnproven { name: _, referencing_module: _, provider_module: _, span: _ } => false _ => true } } @@ -563,6 +577,8 @@ fn is_discovery_corpus_advisory_typecheck_diagnostic(d: CompilerDiagnostic) -> B ReceiverTypeUnestablished { method: _, span: _ } => true ServiceConfigReferenceJudgmentDeferred { field: _, referenced_name: _, trigger: _, span: _ } => true DeclaredTypeInhabitanceUndecided { position: _, reason: _, span: _ } => true + ReferenceDerivedImportProviderUnknown { name: _, referencing_module: _, span: _ } => true + ReferenceDerivedImportExportUnproven { name: _, referencing_module: _, provider_module: _, span: _ } => true WhereRefinementUnenforced { predicate: _, formal_type: _, reason: r, span: _ } => is_where_refinement_unenforced_advisory_reason(reason: r) _ => false diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index e3ecb737615..b9fc9698a44 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -20,7 +20,7 @@ import v1.std.core { CallSemantics, PlainCallSemantics, LookupCallSemantics, FunctionValueCallSemantics, MethodSemantics, PlainMethodSemantics, AlgebraMethodSemantics, ServiceMethodSemantics, MatchPattern, field_binding_pattern, TextFile, ErrorNode, make_error_node, InternalError, - UnlistedImportUse, AmbiguousAnonymousRecordLiteral, AmbiguousReference, DataReferenceVisibilityBudgetExceeded, ParameterDefaultFormNotAdmitted, SourceSpan, resource_use_resource, UnaryOpKind, StringPart, Text, + UnlistedImportUse, ReferenceDerivedImportProviderUnknown, ReferenceDerivedImportExportUnproven, AmbiguousAnonymousRecordLiteral, AmbiguousReference, DataReferenceVisibilityBudgetExceeded, ParameterDefaultFormNotAdmitted, SourceSpan, resource_use_resource, UnaryOpKind, StringPart, Text, Interpolation, transport_base_url, transport_has_auth, transport_auth_token, transport_auth_header_name, transport_headers, transport_env, transport_method, transport_path_template, transport_query, transport_request_body, transport_stdin, @@ -2718,7 +2718,9 @@ fn emit_rust(typed: ResolvedGraph) -> EmitResult { return EmitResult { files: [], diagnostics: filename_collisions } } let test_projections = ctx.test_projections - let module_files = typed.modules |> map(tm => emit_module_full_with_dispositions(typed_module: tm, ctx: ctx, typed_modules: typed.modules).file) + let module_emissions = typed.modules |> map(tm => emit_module_full_with_dispositions(typed_module: tm, ctx: ctx, typed_modules: typed.modules)) + let module_files = module_emissions |> map(e => e.file) + let import_refusals = module_emissions |> flat_map(e => e.import_refusals) let test_files = typed.modules |> map(tm => emit_test_file( module_name: authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module), @@ -2754,7 +2756,7 @@ fn emit_rust(typed: ResolvedGraph) -> EmitResult { let emitted_files = concat([cargo, lib_file, main_file], all_mod_files, compiler_tests_file, test_files) let population_manifest = emit_emitted_population_manifest(files: emitted_files) let files = concat(emitted_files, [population_manifest]) - EmitResult { files: files, diagnostics: [] } + EmitResult { files: files, diagnostics: import_refusals } } // THE EMITTER DECLARES THE POPULATION IT PRODUCED. This is the one place in the repository @@ -3444,7 +3446,7 @@ fn imported_names_in_use_line(line: String) -> List { } } -data reference_derived_use_lines_note: String = "emit_import_closure_root (§5). emit_imports wires a per-module use-line only for names in an authored import list. Namespace-only resolution (post-PR 6848) references cross-module names WITHOUT importing them, so the ref is KNOWN but the use-line is declined (advisory UnlistedImportUse, is_error_diagnostic=false) — a §5 fail-open (⊤-as-ignorance) that emits invalid Rust (E0422/E0433/E0425 downstream). This pass derives the missing use-lines from the SAME resolver signal, split by reference kind onto its precise authority (§2 Realization: one closure, two consumers): (1) TYPE refs come from the resolver's UnlistedImportUse diagnostics (04_resolve.dag resolve_node, masked && not-in-SVN at type positions) threaded through ResolvedGraph.diagnostics — zero-drift by construction, the resolver already applied its SVN mask AT RESOLVE TIME; (2) VALUE-position refs come from collect_value_ref_names, a NARROW walk that structurally excludes the type over-collection classes (container heads, field labels, deep-inferred type names): fn/data refs (FunctionValueBinding ExprVar + ExprCall callee names) AND record-literal type constructions (ExprRecordLit type name + its parent_enum) — the latter matter because a GENERIC user type constructed as `T{..}` (e.g. RealizedStep) is grounded by resolve_node (masked flips false into the defining-module descent) so it NEVER fires UnlistedImportUse, yet its bare `T` still needs a use-line; (3) TYPE-surface refs on item signatures come from collect_item_emit_surface_names (collect_item_type_surface_names + collect_value_emit_type_surface_names over data bodies) over each item's type_annotation, param types, inferred Resolved return/signature, AND value-position record-literal / variant-parent-enum surfaces the emitter will qualify (e.g. UriScheme::Https in a data anchor) — covering masked-at-resolve TYPE positions (e.g. partial-import return type PilotWidget) that never enter UnlistedImportUse and are not ExprVar/ExprCall harvests. Registry cross-module resolve + is_known_variant fallback keep variant constructors routed through their parent's import. CONSTRUCTION WALL (ROOT 3, §5): a candidate that registry-resolves to a provider module MUST also pass provider_proven_exports_symbol (name_in_transitive_export_surface) before any use-line is synthesized — never a plausible guess from the bare-name registry alone (the v1_rt::member fabrication class: registry homonym without export proof). NOTE the SVN authority is resolve-time-only: env.source_visible_names is built in 04_infer's unresolved_env and consumed by resolve_node, but is NOT persisted onto TypedModule.type_env (emit reads empty_map), so emit MUST NOT re-apply an SVN filter — it would be a no-op that (worse, when non-empty) diverges from the resolve-time mask. The union is instead already-imported filtered (a name already carried by an authored import / prelude / carrier use-line is skipped — this is what keeps a fully-imported SEED module zero-drift: its refs are all in an import line) and kernel filtered (no E0252 against the runtime prelude), then LOCAL-DECL filtered (any name the module itself declares — local_decl_names from authored_name_at over items, plus local_type_names aliases/phantoms — is never an import candidate: the containment tree binds a bare ref to the module's own declaration before any cross-module lookup, and the bare-name registry is last-write-wins, so a dual-tree homonym — dag/std List/Map/GroupCompletion vs their src/v2/std twins — steals the registry row and would otherwise synthesize a SELF-COLLIDING pub-use, the E0255 std_dup class measured on the 2026-07-22 curated 4-module baseline), then cross-module registry-resolved (a ref the registry maps to this same module is skipped), then reuses emit_specific_import_block for variant/reexport correctness with a §5 direct-emit fallback (arm (c): the name resolved via registry to provider AND export-proven). A candidate that registry-resolves to nothing, or resolves but fails export proof, is left unsynthesized (typed refusal at step-2 is future work); it never fabricates a use-line. SCOPE (emit_module_full): import-free modules run the full union (TYPE unlisted + VALUE refs) — the namespace-resolution case the post-PR-6848 regression is about. SUPERSEDED-MECHANISM, THIS CUT (eager-deer-389): the two sentences that follow describe the corpus_repr_is_faithful / HostNative gate, which NO LONGER EXISTS -- RustCorpusRepr and both its variants are deleted and the gate is gone, so import-bearing modules are no longer split by corpus mode. They are retained as the history of WHY the walk was gated at all (the spurious-use-line and seed zero-drift hazards below are real and still constrain any future gating), not as a live description; reference_use_lines_representation_invariant_note in this module carries the post-cut statement. Read them as quarry. AS WRITTEN BEFORE THE CUT: Import-bearing modules run reference_derived_use_lines ONLY when corpus_repr_is_faithful (FaithfulFreeMonoid / v2 namespace corpus): a partial-import namespace module (e.g. v2.std.node_query importing Outcome but calling outcome_with_diagnostics, or importing Outcome but annotating NamedEdgeTargetLookup) must synthesize BOTH the missing fn-value use-line AND the missing type use-line without duplicating names emit_imports already owns. HostNative import-bearing modules (v1 seed) get [] — running the walk there adds spurious/wrong use-lines (registry homonyms like kernel_span/is_type_variable) and breaks zero-drift seed regen." +data reference_derived_use_lines_note: String = "emit_import_closure_root (§5). emit_imports wires a per-module use-line only for names in an authored import list. Namespace-only resolution (post-PR 6848) references cross-module names WITHOUT importing them, so the ref is KNOWN but the use-line is declined (advisory UnlistedImportUse, is_error_diagnostic=false) — a §5 fail-open (⊤-as-ignorance) that emits invalid Rust (E0422/E0433/E0425 downstream). This pass derives the missing use-lines from the SAME resolver signal, split by reference kind onto its precise authority (§2 Realization: one closure, two consumers): (1) TYPE refs come from the resolver's UnlistedImportUse diagnostics (04_resolve.dag resolve_node, masked && not-in-SVN at type positions) threaded through ResolvedGraph.diagnostics — zero-drift by construction, the resolver already applied its SVN mask AT RESOLVE TIME; (2) VALUE-position refs come from collect_value_ref_names, a NARROW walk that structurally excludes the type over-collection classes (container heads, field labels, deep-inferred type names): fn/data refs (FunctionValueBinding ExprVar + ExprCall callee names) AND record-literal type constructions (ExprRecordLit type name + its parent_enum) — the latter matter because a GENERIC user type constructed as `T{..}` (e.g. RealizedStep) is grounded by resolve_node (masked flips false into the defining-module descent) so it NEVER fires UnlistedImportUse, yet its bare `T` still needs a use-line; (3) TYPE-surface refs on item signatures come from collect_item_emit_surface_names (collect_item_type_surface_names + collect_value_emit_type_surface_names over data bodies) over each item's type_annotation, param types, inferred Resolved return/signature, AND value-position record-literal / variant-parent-enum surfaces the emitter will qualify (e.g. UriScheme::Https in a data anchor) — covering masked-at-resolve TYPE positions (e.g. partial-import return type PilotWidget) that never enter UnlistedImportUse and are not ExprVar/ExprCall harvests. Registry cross-module resolve + is_known_variant fallback keep variant constructors routed through their parent's import. CONSTRUCTION WALL (ROOT 3, §5): a candidate that registry-resolves to a provider module MUST also pass provider_proven_exports_symbol (name_in_transitive_export_surface) before any use-line is synthesized — never a plausible guess from the bare-name registry alone (the v1_rt::member fabrication class: registry homonym without export proof). NOTE the SVN authority is resolve-time-only: env.source_visible_names is built in 04_infer's unresolved_env and consumed by resolve_node, but is NOT persisted onto TypedModule.type_env (emit reads empty_map), so emit MUST NOT re-apply an SVN filter — it would be a no-op that (worse, when non-empty) diverges from the resolve-time mask. The union is instead already-imported filtered (a name already carried by an authored import / prelude / carrier use-line is skipped — this is what keeps a fully-imported SEED module zero-drift: its refs are all in an import line) and kernel filtered (no E0252 against the runtime prelude), then LOCAL-DECL filtered (any name the module itself declares — local_decl_names from authored_name_at over items, plus local_type_names aliases/phantoms — is never an import candidate: the containment tree binds a bare ref to the module's own declaration before any cross-module lookup, and the bare-name registry is last-write-wins, so a dual-tree homonym — dag/std List/Map/GroupCompletion vs their src/v2/std twins — steals the registry row and would otherwise synthesize a SELF-COLLIDING pub-use, the E0255 std_dup class measured on the 2026-07-22 curated 4-module baseline), then cross-module registry-resolved (a ref the registry maps to this same module is skipped), then reuses emit_specific_import_block for variant/reexport correctness with a §5 direct-emit fallback (arm (c): the name resolved via registry to provider AND export-proven). A candidate that registry-resolves to nothing, or resolves but fails export proof, is left unsynthesized, and it never fabricates a use-line. STEP 2 HAS LANDED: the reason is no longer silent. Each such candidate carries a ReferenceDerivedCandidateDisposition -- CandidateRegistryAbsent or CandidateExportProofFailed, distinct because the remedies are opposite -- and CandidateExportProofFailed emits the typed, located, counted ReferenceDerivedImportExportUnproven. THE EMISSION IS NOT REFUSED, and the distinction matters because a reader of the phrase typed refusal will otherwise expect one: the diagnostic is ADVISORY, so emission completes and produces its files beside it. Measured on the required build lane at this branch head, entry:src/v2/compiler/00_compile.dag: emitted=175 blocking=0 -- so what step 2 closed is the SILENCE, not the emission. Whether the line stops is the severity question, and v1.compiler.core reference_derived_import_refusal_severity_note owns it, including why the blocking flip was executed and then falsified by its own first run over a second closure. SCOPE (emit_module_full): import-free modules run the full union (TYPE unlisted + VALUE refs) — the namespace-resolution case the post-PR-6848 regression is about. SUPERSEDED-MECHANISM, THIS CUT (eager-deer-389): the two sentences that follow describe the corpus_repr_is_faithful / HostNative gate, which NO LONGER EXISTS -- RustCorpusRepr and both its variants are deleted and the gate is gone, so import-bearing modules are no longer split by corpus mode. They are retained as the history of WHY the walk was gated at all (the spurious-use-line and seed zero-drift hazards below are real and still constrain any future gating), not as a live description; reference_use_lines_representation_invariant_note in this module carries the post-cut statement. Read them as quarry. AS WRITTEN BEFORE THE CUT: Import-bearing modules run reference_derived_use_lines ONLY when corpus_repr_is_faithful (FaithfulFreeMonoid / v2 namespace corpus): a partial-import namespace module (e.g. v2.std.node_query importing Outcome but calling outcome_with_diagnostics, or importing Outcome but annotating NamedEdgeTargetLookup) must synthesize BOTH the missing fn-value use-line AND the missing type use-line without duplicating names emit_imports already owns. HostNative import-bearing modules (v1 seed) get [] — running the walk there adds spurious/wrong use-lines (registry homonyms like kernel_span/is_type_variable) and breaks zero-drift seed regen." fn expand_variant_payload_struct_imports(names: List, type_summaries: Map, variant_to_enum: Map) -> List { names |> flat_map(n => @@ -3694,11 +3696,16 @@ fn local_coproduct_variant_names(items: List, source_indices: Map, source_indices: Map, export_sets: Map>, typed_modules: List, source_indices: Map, module_index: ModuleIndex) -> ReferenceDerivedCandidateDisposition { +fn reference_derived_candidate_disposition(name: String, this_module_name: String, registry: Map, export_sets: Map>, typed_modules: List, source_indices: Map, module_index: ModuleIndex, type_summaries: Map, variant_to_enum: Map) -> ReferenceDerivedCandidateDisposition { + if is_known_variant(type_summaries: type_summaries, name: name) { + match map_get(variant_to_enum, name) { + Present { value: parent } => + if parent == "" { CandidateVariantParentUnresolved } + else { CandidateVariantDelegatedToParent { parent_enum: parent } } + Absent => CandidateVariantParentUnresolved + } + } else { match map_get(registry, name) { Present { value: info } => if info.module_name == this_module_name { @@ -3744,6 +3803,91 @@ fn reference_derived_candidate_disposition(name: String, this_module_name: Strin } Absent => CandidateRegistryAbsent } + } +} + +// THE SURFACING HALF, AND IT SURFACES EXACTLY ONE OF THE TWO FAILING ARMS. #9439 factored this +// decision into a typed disposition and COUNTED it; its own note records that the census "is NOT +// SURFACED during an ordinary build". So both failing arms still vanished from the emission -- no +// diagnostic, no location, nothing a build could report -- which is the empty-observation narrow +// DESIGN names: the emitter answers "this name is not part of the interface" where the truth is "I +// could not prove that it was". Downstream the emitted crate references a name it never imported and +// rustc says E0422/E0425/E0433 far from the cause; upstream the deficit's frequency is zero by +// construction, so the wall's own gaps never rank for repair. +// +// This is a THIRD projection of rows that already exist -- beside the use-lines and the census -- and +// not a second decision. Nothing here re-derives the disposition, so the emitted crate cannot move. +// +// ONLY CandidateExportProofFailed BECOMES A DIAGNOSTIC, AND THAT IS A MEASUREMENT RESULT RATHER THAN +// A DESIGN PREFERENCE. Both arms were instrumented and run over the regen seed closure (the census +// invocation is recorded on the carrier note). Export-proof-failed returned ZERO. Registry-absent +// returned 473 rows over 63 distinct names across 110 modules -- and that population is MIXED, which +// an earlier revision of this comment got wrong by generalising from the head of a sorted list: +// +// (a) EXTINGUISHED Vec 88, bool 73, i64 48, BTreeSet -- no .dag declaration exists or can. The +// producer is collect_item_realized_surface_names, rust_identifier_tokens over +// render_rust_type, which emits target-language SPELLINGS by construction; +// reference_is_host_realized_builtin misses them because it is keyed on .dag +// vocabulary, so Vec and BTreeSet -- the Rust spellings of List and Set -- +// slip a filter built to remove exactly this. +// (b) OUT-OF-CLOSURE empty_map 33, Optional 25, and Optional's VARIANTS Present 47 and Absent 13 +// -- REAL .dag declarations in v2.std.collection and v2.std.optional whose +// providers were not selected into the measured closure. Movable, and +// precisely the closure-conditioned class this change exists to surface. +// +// The proportion is UNMEASURED IN BOTH DIRECTIONS, and the two ways of counting point OPPOSITE ways. +// BY ROW the immovable names dominate (88 + 73 + 50 + 48 of 473). BY DISTINCT NAME the examined +// sample is 4 of 63 and all four are movable. High counts at the immovable head, movable names in the +// tail with small counts -- the structure that hid the signal, and the reason a sorted head answered +// the wrong question here twice. Fifty-nine names are unexamined. Neither figure is a proportion. Class (b) is present because the census invocation's +// --source-root flags are NOT the regen SUBJECT: regen_input_sources is rooted at SeedV1 and +// DagCorpus and excludes src/v2 entirely, because stage0 IS the v1 seed and a seed reaching into +// src/v2 would depend on the successor it bootstraps toward. So registry-absent is ENTRY-RELATIVE, +// not a fixed target, and a reader treating the count as stable has made the denominator error. +// +// The arm stays unwired anyway, and the reason is (a) rather than the whole population: a diagnostic +// there would be permanently false on every class-(a) row, printed on every build in every module, +// and that is worse than shipping nothing because IT TRAINS READERS TO IGNORE THE CHANNEL -- an +// absent diagnostic is at least honest about its coverage. The trigger for wiring it is that class +// (a) LEAVE the arm; the residue is then class (b), which is worth reporting. +// +// A PREDICTION WAS REFUTED HERE AND IT IS RECORDED BECAUSE IT WAS DECISION-RELEVANT. Registry-absent +// was expected to overlap heavily with UnlistedImportUse -- both said to mean "referenced but never +// imported" -- and if it did not, one of the two mechanisms was not what anyone thought. Measured on +// the same run: 63 registry-absent names, 36 UnlistedImportUse names, INTERSECTION ZERO. The +// mechanisms have near-disjoint vocabularies, because UnlistedImportUse names `.dag` types masked at +// resolve time while registry-absent is dominated by Rust tokens that never passed through the +// resolver at all. They are not two views of one population. +// +// NO WILDCARD ARM, DELIBERATELY. Every non-reporting disposition is enumerated, so a SIXTH arm added +// to the coproduct fails to compile here rather than silently inheriting "produces no diagnostic". +// That matters because the coproduct now holds three not-applicable arms against two genuine drops, +// and a `_` would make the next drop arm silent by default -- total at the level examined, blind one +// level down, which is the shape this whole change exists to repair. +// +// LOCATION GRAIN, stated rather than implied: the referencing MODULE's declaration span, because a +// row carries (module, name, disposition) and the reference SITES are reduced to bare names by the +// candidate walk before any disposition is assigned. Carrying the originating span on the row is the +// named next rung. +fn reference_derived_row_diagnostics(rows: List, module_span: SourceSpan) -> List { + rows |> flat_map(r => + match r.disposition { + CandidateExportProofFailed { provider_module: p } => [make_error_node( + diagnostic: ReferenceDerivedImportExportUnproven { + name: r.name, + referencing_module: r.module_name, + provider_module: p, + span: module_span + }, + module_name: r.module_name + )] + CandidateSurvived { provider_module: _ } => [] + CandidateOwnModule => [] + CandidateVariantDelegatedToParent { parent_enum: _ } => [] + CandidateVariantParentUnresolved => [] + CandidateRegistryAbsent => [] + } + ) } fn reference_derived_candidate_survived(disposition: ReferenceDerivedCandidateDisposition) -> Bool { @@ -3760,6 +3904,8 @@ fn reference_derived_disposition_name(disposition: ReferenceDerivedCandidateDisp match disposition { CandidateSurvived { provider_module: _ } => "survived" CandidateOwnModule => "own-module" + CandidateVariantDelegatedToParent { parent_enum: _ } => "variant-delegated-to-parent" + CandidateVariantParentUnresolved => "variant-parent-unresolved" CandidateRegistryAbsent => "registry-absent" CandidateExportProofFailed { provider_module: _ } => "export-proof-failed" } @@ -3799,6 +3945,8 @@ type ReferenceDerivedCensus { candidates: Int survived: Int own_module: Int + variant_delegated_to_parent: Int + variant_parent_unresolved: Int registry_absent: Int export_proof_failed: Int } @@ -3808,6 +3956,8 @@ fn reference_derived_census(rows: List) -> Referen candidates: rows |> count, survived: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "survived") |> count, own_module: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "own-module") |> count, + variant_delegated_to_parent: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "variant-delegated-to-parent") |> count, + variant_parent_unresolved: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "variant-parent-unresolved") |> count, registry_absent: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "registry-absent") |> count, export_proof_failed: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "export-proof-failed") |> count } @@ -3853,7 +4003,7 @@ fn reference_derived_use_line_plan(items: List, unlisted_type_names: List< [ReferenceDerivedCandidateRow { module_name: this_module_name, name: name, - disposition: reference_derived_candidate_disposition(name: name, this_module_name: this_module_name, registry: registry, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) + disposition: reference_derived_candidate_disposition(name: name, this_module_name: this_module_name, registry: registry, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index, type_summaries: emit_info.type_summaries, variant_to_enum: emit_info.variant_to_enum) }] } ) @@ -3903,6 +4053,7 @@ fn reference_derived_use_lines(items: List, unlisted_type_names: List + import_refusals: List } fn emit_module_full_with_dispositions(typed_module: TypedModule, ctx: EmitRustContext, typed_modules: List) -> ModuleEmission { @@ -4034,7 +4185,8 @@ fn emit_module_full(typed_module: TypedModule, registry: Map, coproduct_wire_contract_validation_section, items_str, phantom_section, "\n") ModuleEmission { file: TextFile { path: concat(rust_source_root(), filename, rust_source_ext()), content: content }, - reference_rows: reference_plan.rows + reference_rows: reference_plan.rows, + import_refusals: reference_derived_row_diagnostics(rows: reference_plan.rows, module_span: m.span) } } diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 00ee29e135b..1ce72108082 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -5537,6 +5537,12 @@ pub fn compile_clean_diagnostic_histogram_key(d: &Rc) -> (String, Str "ServiceConfigReferenceJudgmentDeferred" } CompilerDiagnostic::UnlistedVariantValueUse { .. } => "UnlistedVariantValueUse", + CompilerDiagnostic::ReferenceDerivedImportProviderUnknown { .. } => { + "ReferenceDerivedImportProviderUnknown" + } + CompilerDiagnostic::ReferenceDerivedImportExportUnproven { .. } => { + "ReferenceDerivedImportExportUnproven" + } }; let name = match d.diagnostic.as_ref() { CompilerDiagnostic::UnresolvedImport { module_path, .. } => module_path.clone(), @@ -5581,6 +5587,8 @@ pub fn compile_clean_diagnostic_histogram_key(d: &Rc) -> (String, Str CompilerDiagnostic::DeclaredTypeInhabitanceUndecided { position, .. } => position.clone(), CompilerDiagnostic::UnlistedImportUse { name, .. } => name.clone(), CompilerDiagnostic::UnlistedVariantValueUse { name, .. } => name.clone(), + CompilerDiagnostic::ReferenceDerivedImportProviderUnknown { name, .. } => name.clone(), + CompilerDiagnostic::ReferenceDerivedImportExportUnproven { name, .. } => name.clone(), CompilerDiagnostic::AmbiguousReference { name, .. } => name.clone(), CompilerDiagnostic::DataReferenceVisibilityBudgetExceeded { name, .. } => name.clone(), CompilerDiagnostic::ParameterDefaultFormNotAdmitted { parameter, .. } => parameter.clone(), diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index c742cc39066..1978c7f1d7b 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -181,7 +181,8 @@ use crate::v1_std_core::CallTargetIdentity::{ use crate::v1_std_core::Cardinality::{CardOptional, Required}; use crate::v1_std_core::CompilerDiagnostic::{ AmbiguousAnonymousRecordLiteral, AmbiguousReference, DataReferenceVisibilityBudgetExceeded, - InternalError, ParameterDefaultFormNotAdmitted, UnlistedImportUse, + InternalError, ParameterDefaultFormNotAdmitted, ReferenceDerivedImportExportUnproven, + ReferenceDerivedImportProviderUnknown, UnlistedImportUse, }; use crate::v1_std_core::Connective::{Conj, Disj, NoConnective}; use crate::v1_std_core::ExprData::{ @@ -5553,18 +5554,28 @@ pub fn emit_rust(typed: Rc) -> Rc { }); } let test_projections = ctx.test_projections.clone(); - let module_files = Rc::new({ + let module_emissions = Rc::new({ let mut __result = Vec::new(); for tm in typed.modules.clone().iter().cloned() { - __result.push( - emit_module_full_with_dispositions( - tm.clone(), - ctx.clone(), - typed.modules.clone(), - ) - .file - .clone(), - ); + __result.push(emit_module_full_with_dispositions( + tm.clone(), + ctx.clone(), + typed.modules.clone(), + )); + } + __result + }); + let module_files = Rc::new({ + let mut __result = Vec::new(); + for e in module_emissions.iter().cloned() { + __result.push(e.file.clone()); + } + __result + }); + let import_refusals = Rc::new({ + let mut __result = Vec::new(); + for e in module_emissions.iter().cloned() { + __result.extend((*e.import_refusals.clone()).iter().cloned()); } __result }); @@ -5686,7 +5697,7 @@ pub fn emit_rust(typed: Rc) -> Rc { ); Rc::new(EmitResult { files: files.clone(), - diagnostics: Rc::new(vec![]), + diagnostics: import_refusals.clone(), }) } } @@ -7471,7 +7482,7 @@ pub fn imported_names_in_use_line(line: String) -> Rc> { pub fn reference_derived_use_lines_note() -> String { thread_local! { static CACHED: String = { - "emit_import_closure_root (§5). emit_imports wires a per-module use-line only for names in an authored import list. Namespace-only resolution (post-PR 6848) references cross-module names WITHOUT importing them, so the ref is KNOWN but the use-line is declined (advisory UnlistedImportUse, is_error_diagnostic=false) — a §5 fail-open (⊤-as-ignorance) that emits invalid Rust (E0422/E0433/E0425 downstream). This pass derives the missing use-lines from the SAME resolver signal, split by reference kind onto its precise authority (§2 Realization: one closure, two consumers): (1) TYPE refs come from the resolver's UnlistedImportUse diagnostics (04_resolve.dag resolve_node, masked && not-in-SVN at type positions) threaded through ResolvedGraph.diagnostics — zero-drift by construction, the resolver already applied its SVN mask AT RESOLVE TIME; (2) VALUE-position refs come from collect_value_ref_names, a NARROW walk that structurally excludes the type over-collection classes (container heads, field labels, deep-inferred type names): fn/data refs (FunctionValueBinding ExprVar + ExprCall callee names) AND record-literal type constructions (ExprRecordLit type name + its parent_enum) — the latter matter because a GENERIC user type constructed as `T{..}` (e.g. RealizedStep) is grounded by resolve_node (masked flips false into the defining-module descent) so it NEVER fires UnlistedImportUse, yet its bare `T` still needs a use-line; (3) TYPE-surface refs on item signatures come from collect_item_emit_surface_names (collect_item_type_surface_names + collect_value_emit_type_surface_names over data bodies) over each item's type_annotation, param types, inferred Resolved return/signature, AND value-position record-literal / variant-parent-enum surfaces the emitter will qualify (e.g. UriScheme::Https in a data anchor) — covering masked-at-resolve TYPE positions (e.g. partial-import return type PilotWidget) that never enter UnlistedImportUse and are not ExprVar/ExprCall harvests. Registry cross-module resolve + is_known_variant fallback keep variant constructors routed through their parent's import. CONSTRUCTION WALL (ROOT 3, §5): a candidate that registry-resolves to a provider module MUST also pass provider_proven_exports_symbol (name_in_transitive_export_surface) before any use-line is synthesized — never a plausible guess from the bare-name registry alone (the v1_rt::member fabrication class: registry homonym without export proof). NOTE the SVN authority is resolve-time-only: env.source_visible_names is built in 04_infer's unresolved_env and consumed by resolve_node, but is NOT persisted onto TypedModule.type_env (emit reads empty_map), so emit MUST NOT re-apply an SVN filter — it would be a no-op that (worse, when non-empty) diverges from the resolve-time mask. The union is instead already-imported filtered (a name already carried by an authored import / prelude / carrier use-line is skipped — this is what keeps a fully-imported SEED module zero-drift: its refs are all in an import line) and kernel filtered (no E0252 against the runtime prelude), then LOCAL-DECL filtered (any name the module itself declares — local_decl_names from authored_name_at over items, plus local_type_names aliases/phantoms — is never an import candidate: the containment tree binds a bare ref to the module's own declaration before any cross-module lookup, and the bare-name registry is last-write-wins, so a dual-tree homonym — dag/std List/Map/GroupCompletion vs their src/v2/std twins — steals the registry row and would otherwise synthesize a SELF-COLLIDING pub-use, the E0255 std_dup class measured on the 2026-07-22 curated 4-module baseline), then cross-module registry-resolved (a ref the registry maps to this same module is skipped), then reuses emit_specific_import_block for variant/reexport correctness with a §5 direct-emit fallback (arm (c): the name resolved via registry to provider AND export-proven). A candidate that registry-resolves to nothing, or resolves but fails export proof, is left unsynthesized (typed refusal at step-2 is future work); it never fabricates a use-line. SCOPE (emit_module_full): import-free modules run the full union (TYPE unlisted + VALUE refs) — the namespace-resolution case the post-PR-6848 regression is about. SUPERSEDED-MECHANISM, THIS CUT (eager-deer-389): the two sentences that follow describe the corpus_repr_is_faithful / HostNative gate, which NO LONGER EXISTS -- RustCorpusRepr and both its variants are deleted and the gate is gone, so import-bearing modules are no longer split by corpus mode. They are retained as the history of WHY the walk was gated at all (the spurious-use-line and seed zero-drift hazards below are real and still constrain any future gating), not as a live description; reference_use_lines_representation_invariant_note in this module carries the post-cut statement. Read them as quarry. AS WRITTEN BEFORE THE CUT: Import-bearing modules run reference_derived_use_lines ONLY when corpus_repr_is_faithful (FaithfulFreeMonoid / v2 namespace corpus): a partial-import namespace module (e.g. v2.std.node_query importing Outcome but calling outcome_with_diagnostics, or importing Outcome but annotating NamedEdgeTargetLookup) must synthesize BOTH the missing fn-value use-line AND the missing type use-line without duplicating names emit_imports already owns. HostNative import-bearing modules (v1 seed) get [] — running the walk there adds spurious/wrong use-lines (registry homonyms like kernel_span/is_type_variable) and breaks zero-drift seed regen.".to_string() + "emit_import_closure_root (§5). emit_imports wires a per-module use-line only for names in an authored import list. Namespace-only resolution (post-PR 6848) references cross-module names WITHOUT importing them, so the ref is KNOWN but the use-line is declined (advisory UnlistedImportUse, is_error_diagnostic=false) — a §5 fail-open (⊤-as-ignorance) that emits invalid Rust (E0422/E0433/E0425 downstream). This pass derives the missing use-lines from the SAME resolver signal, split by reference kind onto its precise authority (§2 Realization: one closure, two consumers): (1) TYPE refs come from the resolver's UnlistedImportUse diagnostics (04_resolve.dag resolve_node, masked && not-in-SVN at type positions) threaded through ResolvedGraph.diagnostics — zero-drift by construction, the resolver already applied its SVN mask AT RESOLVE TIME; (2) VALUE-position refs come from collect_value_ref_names, a NARROW walk that structurally excludes the type over-collection classes (container heads, field labels, deep-inferred type names): fn/data refs (FunctionValueBinding ExprVar + ExprCall callee names) AND record-literal type constructions (ExprRecordLit type name + its parent_enum) — the latter matter because a GENERIC user type constructed as `T{..}` (e.g. RealizedStep) is grounded by resolve_node (masked flips false into the defining-module descent) so it NEVER fires UnlistedImportUse, yet its bare `T` still needs a use-line; (3) TYPE-surface refs on item signatures come from collect_item_emit_surface_names (collect_item_type_surface_names + collect_value_emit_type_surface_names over data bodies) over each item's type_annotation, param types, inferred Resolved return/signature, AND value-position record-literal / variant-parent-enum surfaces the emitter will qualify (e.g. UriScheme::Https in a data anchor) — covering masked-at-resolve TYPE positions (e.g. partial-import return type PilotWidget) that never enter UnlistedImportUse and are not ExprVar/ExprCall harvests. Registry cross-module resolve + is_known_variant fallback keep variant constructors routed through their parent's import. CONSTRUCTION WALL (ROOT 3, §5): a candidate that registry-resolves to a provider module MUST also pass provider_proven_exports_symbol (name_in_transitive_export_surface) before any use-line is synthesized — never a plausible guess from the bare-name registry alone (the v1_rt::member fabrication class: registry homonym without export proof). NOTE the SVN authority is resolve-time-only: env.source_visible_names is built in 04_infer's unresolved_env and consumed by resolve_node, but is NOT persisted onto TypedModule.type_env (emit reads empty_map), so emit MUST NOT re-apply an SVN filter — it would be a no-op that (worse, when non-empty) diverges from the resolve-time mask. The union is instead already-imported filtered (a name already carried by an authored import / prelude / carrier use-line is skipped — this is what keeps a fully-imported SEED module zero-drift: its refs are all in an import line) and kernel filtered (no E0252 against the runtime prelude), then LOCAL-DECL filtered (any name the module itself declares — local_decl_names from authored_name_at over items, plus local_type_names aliases/phantoms — is never an import candidate: the containment tree binds a bare ref to the module's own declaration before any cross-module lookup, and the bare-name registry is last-write-wins, so a dual-tree homonym — dag/std List/Map/GroupCompletion vs their src/v2/std twins — steals the registry row and would otherwise synthesize a SELF-COLLIDING pub-use, the E0255 std_dup class measured on the 2026-07-22 curated 4-module baseline), then cross-module registry-resolved (a ref the registry maps to this same module is skipped), then reuses emit_specific_import_block for variant/reexport correctness with a §5 direct-emit fallback (arm (c): the name resolved via registry to provider AND export-proven). A candidate that registry-resolves to nothing, or resolves but fails export proof, is left unsynthesized, and it never fabricates a use-line. STEP 2 HAS LANDED: the reason is no longer silent. Each such candidate carries a ReferenceDerivedCandidateDisposition -- CandidateRegistryAbsent or CandidateExportProofFailed, distinct because the remedies are opposite -- and CandidateExportProofFailed emits the typed, located, counted ReferenceDerivedImportExportUnproven. THE EMISSION IS NOT REFUSED, and the distinction matters because a reader of the phrase typed refusal will otherwise expect one: the diagnostic is ADVISORY, so emission completes and produces its files beside it. Measured on the required build lane at this branch head, entry:src/v2/compiler/00_compile.dag: emitted=175 blocking=0 -- so what step 2 closed is the SILENCE, not the emission. Whether the line stops is the severity question, and v1.compiler.core reference_derived_import_refusal_severity_note owns it, including why the blocking flip was executed and then falsified by its own first run over a second closure. SCOPE (emit_module_full): import-free modules run the full union (TYPE unlisted + VALUE refs) — the namespace-resolution case the post-PR-6848 regression is about. SUPERSEDED-MECHANISM, THIS CUT (eager-deer-389): the two sentences that follow describe the corpus_repr_is_faithful / HostNative gate, which NO LONGER EXISTS -- RustCorpusRepr and both its variants are deleted and the gate is gone, so import-bearing modules are no longer split by corpus mode. They are retained as the history of WHY the walk was gated at all (the spurious-use-line and seed zero-drift hazards below are real and still constrain any future gating), not as a live description; reference_use_lines_representation_invariant_note in this module carries the post-cut statement. Read them as quarry. AS WRITTEN BEFORE THE CUT: Import-bearing modules run reference_derived_use_lines ONLY when corpus_repr_is_faithful (FaithfulFreeMonoid / v2 namespace corpus): a partial-import namespace module (e.g. v2.std.node_query importing Outcome but calling outcome_with_diagnostics, or importing Outcome but annotating NamedEdgeTargetLookup) must synthesize BOTH the missing fn-value use-line AND the missing type use-line without duplicating names emit_imports already owns. HostNative import-bearing modules (v1 seed) get [] — running the walk there adds spurious/wrong use-lines (registry homonyms like kernel_span/is_type_variable) and breaks zero-drift seed regen.".to_string() }; } CACHED.with(|c: &String| c.clone()) @@ -7864,29 +7875,11 @@ pub fn local_coproduct_variant_names( pub enum ReferenceDerivedCandidateDisposition { CandidateSurvived { provider_module: String }, CandidateOwnModule, + CandidateVariantDelegatedToParent { parent_enum: String }, + CandidateVariantParentUnresolved, CandidateRegistryAbsent, CandidateExportProofFailed { provider_module: String }, } -impl ReferenceDerivedCandidateDisposition { - pub fn provider_module(&self) -> String { - match self { - ReferenceDerivedCandidateDisposition::CandidateSurvived { - provider_module: __val, - .. - } => __val.clone(), - ReferenceDerivedCandidateDisposition::CandidateOwnModule => { - panic!("no provider_module on unit variant") - } - ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent => { - panic!("no provider_module on unit variant") - } - ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { - provider_module: __val, - .. - } => __val.clone(), - } - } -} #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct ReferenceDerivedCandidateRow { @@ -7903,36 +7896,100 @@ pub fn reference_derived_candidate_disposition( typed_modules: Rc>>, source_indices: Rc>>, module_index: Rc, + type_summaries: Rc>>, + variant_to_enum: Rc>, ) -> Rc { - match v1_rt::map_get(®istry, name.clone()) { - Some(info) => { - if (info.module_name.clone() == this_module_name.clone()) { - Rc::new(ReferenceDerivedCandidateDisposition::CandidateOwnModule) - } else { - if provider_proven_exports_symbol( - name.clone(), - info.module_name.clone(), - export_sets.clone(), - typed_modules.clone(), - source_indices.clone(), - module_index.clone(), - ) { - Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { - provider_module: info.module_name.clone(), - }) + if crate::v1_compiler_infer_emit_info::is_known_variant(type_summaries.clone(), name.clone()) { + match v1_rt::map_get(&variant_to_enum, name.clone()) { + Some(parent) => { + if (parent.clone() == "".to_string()) { + Rc::new(ReferenceDerivedCandidateDisposition::CandidateVariantParentUnresolved) } else { Rc::new( - ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { - provider_module: info.module_name.clone(), + ReferenceDerivedCandidateDisposition::CandidateVariantDelegatedToParent { + parent_enum: parent.clone(), }, ) } } + None => Rc::new(ReferenceDerivedCandidateDisposition::CandidateVariantParentUnresolved), + } + } else { + match v1_rt::map_get(®istry, name.clone()) { + Some(info) => { + if (info.module_name.clone() == this_module_name.clone()) { + Rc::new(ReferenceDerivedCandidateDisposition::CandidateOwnModule) + } else { + if provider_proven_exports_symbol( + name.clone(), + info.module_name.clone(), + export_sets.clone(), + typed_modules.clone(), + source_indices.clone(), + module_index.clone(), + ) { + Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: info.module_name.clone(), + }) + } else { + Rc::new( + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: info.module_name.clone(), + }, + ) + } + } + } + None => Rc::new(ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent), } - None => Rc::new(ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent), } } +pub fn reference_derived_row_diagnostics( + rows: Rc>>, + module_span: Rc, +) -> Rc>> { + Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + __result.extend( + (*match (*r.disposition.clone()).clone() { + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: p, + .. + } => Rc::new(vec![crate::v1_std_core::make_error_node( + Rc::new(CompilerDiagnostic::ReferenceDerivedImportExportUnproven { + name: r.name.clone(), + referencing_module: r.module_name.clone(), + provider_module: p.clone(), + span: module_span.clone(), + }), + r.module_name.clone(), + )]), + ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: _, + .. + } => Rc::new(vec![]), + ReferenceDerivedCandidateDisposition::CandidateOwnModule => Rc::new(vec![]), + ReferenceDerivedCandidateDisposition::CandidateVariantDelegatedToParent { + parent_enum: _, + .. + } => Rc::new(vec![]), + ReferenceDerivedCandidateDisposition::CandidateVariantParentUnresolved => { + Rc::new(vec![]) + } + ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent => { + Rc::new(vec![]) + } + }) + .iter() + .cloned(), + ); + } + __result + }) +} + pub fn reference_derived_candidate_survived( disposition: Rc, ) -> bool { @@ -7952,6 +8009,13 @@ pub fn reference_derived_disposition_name( provider_module: _, .. } => "survived".to_string(), ReferenceDerivedCandidateDisposition::CandidateOwnModule => "own-module".to_string(), + ReferenceDerivedCandidateDisposition::CandidateVariantDelegatedToParent { + parent_enum: _, + .. + } => "variant-delegated-to-parent".to_string(), + ReferenceDerivedCandidateDisposition::CandidateVariantParentUnresolved => { + "variant-parent-unresolved".to_string() + } ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent => { "registry-absent".to_string() } @@ -7967,6 +8031,8 @@ pub struct ReferenceDerivedCensus { pub candidates: i64, pub survived: i64, pub own_module: i64, + pub variant_delegated_to_parent: i64, + pub variant_parent_unresolved: i64, pub registry_absent: i64, pub export_proof_failed: i64, } @@ -8000,6 +8066,30 @@ pub fn reference_derived_census( __result }) .len() as i64), + variant_delegated_to_parent: (Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + if (reference_derived_disposition_name(r.disposition.clone()) + == "variant-delegated-to-parent".to_string()) + { + __result.push(r); + } + } + __result + }) + .len() as i64), + variant_parent_unresolved: (Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + if (reference_derived_disposition_name(r.disposition.clone()) + == "variant-parent-unresolved".to_string()) + { + __result.push(r); + } + } + __result + }) + .len() as i64), registry_absent: (Rc::new({ let mut __result = Vec::new(); for r in rows.iter().cloned() { @@ -8259,6 +8349,8 @@ pub fn reference_derived_use_line_plan( typed_modules.clone(), source_indices.clone(), module_index.clone(), + emit_info.type_summaries.clone(), + emit_info.variant_to_enum.clone(), ), })]) }) @@ -8375,6 +8467,7 @@ pub fn reference_derived_use_lines( pub struct ModuleEmission { pub file: Rc, pub reference_rows: Rc>>, + pub import_refusals: Rc>>, } pub fn emit_module_full_with_dispositions( @@ -8814,6 +8907,10 @@ pub fn emit_module_full( content: content.clone(), }), reference_rows: reference_plan.rows.clone(), + import_refusals: reference_derived_row_diagnostics( + reference_plan.rows.clone(), + m.span.clone(), + ), }) } } diff --git a/src/v1/stage0/src/v1_std_core.rs b/src/v1/stage0/src/v1_std_core.rs index 164c9a09a8f..b50f84e7746 100644 --- a/src/v1/stage0/src/v1_std_core.rs +++ b/src/v1/stage0/src/v1_std_core.rs @@ -575,6 +575,17 @@ pub enum CompilerDiagnostic { name: String, span: Rc, }, + ReferenceDerivedImportProviderUnknown { + name: String, + referencing_module: String, + span: Rc, + }, + ReferenceDerivedImportExportUnproven { + name: String, + referencing_module: String, + provider_module: String, + span: Rc, + }, UnlistedVariantValueUse { name: String, span: Rc, @@ -755,6 +766,8 @@ pub fn diagnostic_to_span(d: Rc) -> Rc { CompilerDiagnostic::DeclaredTypeNotInhabited { span: s, .. } => s.clone(), CompilerDiagnostic::DeclaredTypeInhabitanceUndecided { span: s, .. } => s.clone(), CompilerDiagnostic::UnlistedImportUse { span: s, .. } => s.clone(), + CompilerDiagnostic::ReferenceDerivedImportProviderUnknown { span: s, .. } => s.clone(), + CompilerDiagnostic::ReferenceDerivedImportExportUnproven { span: s, .. } => s.clone(), CompilerDiagnostic::UnlistedVariantValueUse { span: s, .. } => s.clone(), CompilerDiagnostic::AmbiguousReference { span: s, .. } => s.clone(), CompilerDiagnostic::DataReferenceVisibilityBudgetExceeded { span: s, .. } => s.clone(), @@ -814,6 +827,8 @@ pub fn diagnostic_to_message(d: Rc) -> String { CompilerDiagnostic::DeclaredTypeNotInhabited { position: pos, expected: e, got: g, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("value does not inhabit its declared type at the ".to_string(), pos.clone()), ": declared '".to_string()), e.clone()), "', produced '".to_string()), g.clone()), "'".to_string()), CompilerDiagnostic::DeclaredTypeInhabitanceUndecided { position: pos, reason: r, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("declared-type inhabitance is undecidable at the ".to_string(), pos.clone()), " (".to_string()), r.clone()), "): the modeled facts do not settle whether the produced value inhabits its declared type, so no verdict is asserted in either direction".to_string()), CompilerDiagnostic::UnlistedImportUse { name: n, .. } => v1_rt::concat(v1_rt::concat("unlisted import use '".to_string(), n.clone()), "' (referenced but not in any import's name list)".to_string()), + CompilerDiagnostic::ReferenceDerivedImportProviderUnknown { name: n, referencing_module: rm, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("no provider is present for '".to_string(), n.clone()), "', referenced by module '".to_string()), rm.clone()), "': the compile closure carries no module that declares the name, so no use-line can be synthesized and the emitted Rust references a name it never imported. The remedy is to AUTHOR AN IMPORT -- closure membership is driven by imports, so a name the module never imported is a name the closure was never asked to supply".to_string()), + CompilerDiagnostic::ReferenceDerivedImportExportUnproven { name: n, referencing_module: rm, provider_module: p, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("the provider of '".to_string(), n.clone()), "' is present but its export could not be established: module '".to_string()), rm.clone()), "' references the name, the registry maps it to '".to_string()), p.clone()), "', and that module's transitive export surface does not establish it. The remedy is NOT an import -- the provider is already in the closure -- it is that the emitter cannot prove an export the provider may well hold".to_string()), CompilerDiagnostic::UnlistedVariantValueUse { name: n, .. } => v1_rt::concat(v1_rt::concat("unlisted variant value use '".to_string(), n.clone()), "' (a coproduct arm referenced but not in any import's name list)".to_string()), CompilerDiagnostic::AmbiguousReference { name: n, candidates: cs, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("ambiguous reference '".to_string(), n.clone()), "': ".to_string()), ((cs.clone().len() as i64)).to_string()), " candidates: ".to_string()), cs.clone().join(&", ".to_string())), " — qualify by containment path, alias, or rename".to_string()), CompilerDiagnostic::DataReferenceVisibilityBudgetExceeded { name: n, .. } => v1_rt::concat(v1_rt::concat("visible declarations of '".to_string(), n.clone()), "' could not be enumerated: the import re-export walk exceeded its depth bound, so no verdict is asserted about how many declarations answer to the name".to_string()), @@ -890,6 +905,8 @@ pub fn diagnostic_frontier_occurrence_key_note() -> String { pub fn is_error_diagnostic(d: Rc) -> bool { match (*d.clone()).clone() { CompilerDiagnostic::UnlistedImportUse { .. } => false, + CompilerDiagnostic::ReferenceDerivedImportProviderUnknown { .. } => false, + CompilerDiagnostic::ReferenceDerivedImportExportUnproven { .. } => false, CompilerDiagnostic::UnlistedVariantValueUse { .. } => false, CompilerDiagnostic::MethodExistenceFrontierAdmitted { .. } => false, CompilerDiagnostic::ReceiverTypeUnestablished { .. } => false, @@ -900,6 +917,15 @@ pub fn is_error_diagnostic(d: Rc) -> bool { } } +pub fn reference_derived_import_refusal_severity_note() -> String { + thread_local! { + static CACHED: String = { + "owner: v1.compiler.emit_rust (reference_derived_use_line_plan's per-candidate disposition). TWO DIAGNOSTICS, ONE WIRED AND BLOCKING, ONE DECLARED AND PRODUCED BY NOTHING -- and which is which was decided by measurement, not by preference. ReferenceDerivedImportExportUnproven is emitted from CandidateExportProofFailed: the provider is present in the closure and its export could not be established, and the remedy is NOT an import -- the provider is already here -- but making the emitter able to prove an export it may already hold. IT IS ADVISORY, AND IT WAS BLOCKING FOR ONE CI RUN -- THE FLIP WAS APPROVED, EXECUTED, AND REFUTED BY ITS OWN FIRST EXECUTION OVER A SECOND CLOSURE, recorded here rather than repointed because a trigger that survives its own falsification inherits credibility a dead prediction no longer has. THE FLIP CONDITION WAS: an arm flips when its population over a NAMED CLOSURE is zero AND a discriminating RED is authorable. Both halves held and the conclusion was still wrong, which is the finding. The named closure was the regen seed closure (SeedV1 + DagCorpus), where the population is zero. The required build lane ALSO runs a v2-emission phase over a DIFFERENT closure -- entry:src/v2/compiler/00_compile.dag, 169 modules -- and there the population is EIGHTY-SEVEN, measured on run 33111325404 at b331e24cf7d, which refused the phase and reddened the required check. So the zero was a property of THE CLOSURE and not of the arm, and flipping globally on a single-closure measurement is the denominator error this very carrier already names one clause down for the sibling arm (registry-absent is ENTRY-RELATIVE rather than a fixed target). The same reasoning was available for this arm and was not applied to it. THE CORRECTED CONDITION, which is what the flip should have required: the population must be zero over EVERY closure a required phase compiles, not over one named closure -- a per-closure zero is a fact about that entry and licenses nothing about another. AND THE CLOSURES MUST BE NAMED HERE RATHER THAN DESCRIBED, because the phrase every-closure-a-required-phase-compiles names a set that MOVES: it grew when #9035 added the v2-emission phase and again when that phase's subject was widened from dag/std/abi.dag to the v2 pipeline root, and it grows whenever a phase is added or an entry roster widens. A described set leaves that to whoever remembers; a named one makes a phase addition visibly re-open this question. AS OF 2026-08-27 THE SET IS THREE, each with its own authority: the regen seed closure (v1.compiler.cli_run regen_source_roots, SeedV1 + DagCorpus), the v2-emission entries (gunbc.ci_layer_roots required_v2_emission_entries), and the emit-compile entries (gunbc.ci_layer_roots required_emit_compile_entries). Only the first two have been measured for this arm -- 0 and 87 -- and the third is UNMEASURED, so even the corrected condition is not currently evaluable and any future flip must measure it rather than assume it follows the seed. This warning was delivered before the flip rather than after it: deep-ant-102 stated that the zero was 0 over that closure and not over all closures, and that with every provider selected in the proof gate is the easy case; it was acknowledged in writing and not carried into the condition. It is recorded as a dropped warning rather than as a missing insight, because the two have different remedies. The bounded sequence ruled for this work said count == 0 flips and count > 0 IS THE BURNDOWN ROSTER AT IDENTITY GRAIN; the terminal event has now fired with the second answer, so the 87 is that roster and the arm stays advisory until it burns down. The population is NOT a stable number and must not be quoted as one: it is 0 over the seed closure and 87 over the v2 compiler entry, and no closure has been enumerated beyond those two. THE DIAGNOSTIC IS STILL TYPED, LOCATED AND COUNTED, which is the whole of what the brief asked -- the silent omission is closed either way; severity decides whether the line stops, not whether the omission is visible. A sample of what it reports, and the reason 87 is a real roster rather than noise: std.determinism references std.perturbation.PerturbationVerdict whose provider is in the closure with its export unestablished, and v2.lens.complexity_accumulator_copy.analyze accounts for a large share of the rest against providers that are likewise present. Under the previous condition: an arm flips when its population over a NAMED CLOSURE is zero AND A DISCRIMINATING RED IS AUTHORABLE. A zero alone is never sufficient, because an observation of zero on an arm that has never fired is indistinguishable in a count from an arm that CANNOT fire, and flipping the latter yields a permanently-green check cited as coverage, which 4b rates worse than absent. BOTH HALVES HELD AT THE TIME: the population is zero over the regen seed closure (claim_executor --required-regen --source-root dag --source-root src/v2, whose subject is regen_input_sources grown to a joint fixpoint over import, dotted-reference and bare-reference edges), and the RED is authorable and AUTHORED at the fixture boundary in v1.tests.claim.reference_derived_disposition_census_witness_test, which builds a CandidateExportProofFailed row and asserts the diagnostic. The emission still produces its files beside the diagnostic rather than returning none: production precedes adjudication, so the candidate tree survives the refusal and the refusal is what stops the line. ReferenceDerivedImportProviderUnknown is DECLARED HERE AND PRODUCED BY NOTHING. Its arm, CandidateRegistryAbsent, was instrumented and measured over the same closure and returned 473 rows over 63 names across 110 modules. THAT POPULATION IS MIXED, AND AN EARLIER REVISION OF THIS NOTE CALLED IT ALL TARGET VOCABULARY -- corrected 2026-08-27 after deep-ant-102 pushed back on the census SCOPE. Two classes are conflated inside it. (a) EXTINGUISHED -- Vec 88, bool 73, i64 48, BTreeSet: no .dag declaration exists anywhere and none can, because the PRODUCER is collect_item_realized_surface_names, which is rust_identifier_tokens over render_rust_type and so emits target-language SPELLINGS by construction (attribution corrected by clever-boar-140, who owns the coproduct: the candidate filter's emitted-source disjunct is an admission GATE over an already-proposed list and cannot itself propose anything, so narrowing it would delete genuine emitter-attested candidates and leave the real source untouched). reference_is_host_realized_builtin misses these because it is keyed on .dag vocabulary -- is_container_type reads std.types container_type_arity -- so Vec and BTreeSet, the RUST spellings of List and Set, pass a filter that exists precisely to remove host-realized names. (b) OUT-OF-CLOSURE -- empty_map 33 (v2.std.collection), Optional 25 (v2.std.optional), and its VARIANTS Present 47 and Absent 13: REAL .dag names whose providers exist and were not selected in. The variant half is not a detail: a consumer key that reads a declaration index's `declared` field alone reports no-provider-anywhere for every variant name in the corpus, because a variant is not a top-level declaration and such an index holds variants in a separate field -- so the key must read declared UNION variants, and two of these four names are the instance that proves it (clever-boar-140, who then settled it STRUCTURALLY rather than from this sample: build_item_info in v1.compiler.infer produces one ItemInfo per TOP-LEVEL ITEM and no arm descends into a coproduct's children, and the single item_registry insert is keyed on that name, so a variant name is absent from the registry under EVERY closure -- verified here against both sites). THAT FACT AND THIS ARM'S ORDERING COMPOSE, and the composition is why these two names appear under (b) rather than under (a): CandidateVariantDelegatedToParent is tested BEFORE the registry lookup, so a variant whose parent is IN closure is delegated and never reaches the registry at all, and the registry's structural inability to hold variants surfaces only when the parent is OUT of closure and type_summaries therefore cannot recognise the name as a variant. So Present and Absent are registry-absent HERE because Optional was out of closure, and would be delegated with it in -- which is exactly why this sample cannot discriminate the two readings, and why the structural argument above was needed instead. THE PROPORTION IS UNMEASURED IN BOTH DIRECTIONS, and the two ways of counting point OPPOSITE ways, which is the whole reason this population was misread twice. BY ROW the immovable names dominate: Vec 88 + bool 73 + Option 50 + i64 48 out of 473, each verified to have no .dag declaration anywhere. BY DISTINCT NAME the examined sample is 4 of 63 and ALL FOUR ARE MOVABLE. High-count immovable names at the head, movable names in the tail with small counts -- which is exactly the structure that hid the signal, and exactly why reading a sorted head answered the wrong question: empty_map's 33 rows were the third-largest count and were still classified as target vocabulary on the first pass. FIFTY-NINE NAMES ARE UNEXAMINED BY ANYONE. Neither the row-dominance nor the 4-of-4 may be quoted as a proportion; both are directional facts about different denominators. WHY (b) IS THERE AT ALL is a trap worth carrying: the census invocation passes --source-root dag --source-root src/v2, but the SUBJECT is regen_input_sources, whose roots are SeedV1 and DagCorpus and exclude src/v2 entirely -- stage0 IS the v1 seed, and a seed reaching into src/v2 would depend on the successor it bootstraps toward. The source-root FLAGS and the regen SUBJECT are not the same thing. So class (b) is precisely the closure-conditioned population this change exists to make visible, sitting inside the rows that had been written off, and registry-absent is ENTRY-RELATIVE rather than a fixed target: a reader who takes the number as stable has made the denominator error. THE 473 IS NOT A ROSTER OF UNFIXABLE ROWS. Wiring a diagnostic to it would still print a permanently-false report on every class-(a) row -- which is why the arm stays unwired even though part of the population is movable -- and the reason that is worse than shipping nothing is that IT TRAINS READERS TO IGNORE THE CHANNEL: a diagnostic nobody reads is worth less than an absent one, because the absent one is honest about its coverage. The variant stays declared because the class is real and its shape is settled; the trigger for wiring it is that class (a) leave the arm -- the candidate walk ceasing to propose target-language vocabulary -- after which the residue is class (b) and is exactly the closure-conditioned population worth reporting. A witness asserts zero diagnostics from that arm and fails the moment anyone wires it. THE TWO ARE SEPARATE DIAGNOSTICS RATHER THAN ONE WITH A CAUSE FIELD, because the question is upstream of severity: registry-absent is fixed by authoring an import, export-proof-failed by proving an export that already exists -- opposite remedies, different owners, different populations, different reachability -- and 4b files one row per class. That the two turned out to need different TREATMENT rather than merely different severity is the strongest evidence the split was right. A REFUTED PREDICTION, recorded rather than quietly repointed. Registry-absent was predicted to overlap heavily with UnlistedImportUse, both being described as 'referenced but never imported', and the prediction was made because closure membership is driven by imports. Measured on one run: 63 registry-absent names, 36 UnlistedImportUse names, INTERSECTION ZERO. UnlistedImportUse names .dag types masked at RESOLVE time; registry-absent is dominated by Rust tokens that never reached the resolver. The reasoning was wrong at the MECHANISM, not merely the magnitude. ProviderUnknown's trigger therefore does NOT point at the family-closure-SVN burndown as this carrier previously stated, and the falsification is recorded here rather than the trigger being silently repointed -- a repointed trigger would inherit the credibility of a prediction that just died, so whatever that trigger eventually becomes is argued fresh.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + pub fn where_refinement_deferral_reason_scaffold_note() -> String { thread_local! { static CACHED: String = { @@ -921,6 +947,8 @@ pub fn is_interpreter_blocking_diagnostic(d: Rc) -> bool { CompilerDiagnostic::ReceiverTypeUnestablished { .. } => false, CompilerDiagnostic::ServiceConfigReferenceJudgmentDeferred { .. } => false, CompilerDiagnostic::DeclaredTypeInhabitanceUndecided { .. } => false, + CompilerDiagnostic::ReferenceDerivedImportProviderUnknown { .. } => false, + CompilerDiagnostic::ReferenceDerivedImportExportUnproven { .. } => false, _ => true, } } @@ -933,6 +961,8 @@ pub fn is_discovery_corpus_advisory_typecheck_diagnostic(d: Rc true, CompilerDiagnostic::ServiceConfigReferenceJudgmentDeferred { .. } => true, CompilerDiagnostic::DeclaredTypeInhabitanceUndecided { .. } => true, + CompilerDiagnostic::ReferenceDerivedImportProviderUnknown { .. } => true, + CompilerDiagnostic::ReferenceDerivedImportExportUnproven { .. } => true, CompilerDiagnostic::WhereRefinementUnenforced { reason: r, .. } => { is_where_refinement_unenforced_advisory_reason(r.clone()) } diff --git a/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs b/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs index da3cfa17cdb..8a6fd3bad0d 100644 --- a/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs +++ b/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs @@ -1,24 +1,31 @@ // Generated by v1 compiler -- do not edit. // Source module: v1.tests.claim.reference_derived_disposition_census_witness_test +pub use crate::std_types::SourceSpan; pub use crate::v1_compiler_compile::compile_to_resolved; pub use crate::v1_compiler_compile::SourceFile; use crate::v1_compiler_emit_rust::ReferenceDerivedCandidateDisposition::{ CandidateExportProofFailed, CandidateOwnModule, CandidateRegistryAbsent, CandidateSurvived, + CandidateVariantDelegatedToParent, CandidateVariantParentUnresolved, }; pub use crate::v1_compiler_emit_rust::{ build_module_index, emit_rust_reference_derived_census, emit_rust_reference_derived_rows, reference_derived_candidate_disposition, reference_derived_census, - reference_derived_disposition_name, + reference_derived_disposition_name, reference_derived_row_diagnostics, }; pub use crate::v1_compiler_emit_rust::{ ReferenceDerivedCandidateDisposition, ReferenceDerivedCandidateRow, ReferenceDerivedCensus, }; +use crate::v1_compiler_infer_emit_info::TypeRepr::EnumRepr; +pub use crate::v1_compiler_infer_emit_info::{TypeRepr, TypeSummary}; use crate::v1_compiler_infer_items::ItemKind::FnItem; pub use crate::v1_compiler_infer_items::{ItemInfo, ItemKind}; use crate::v1_rt; use crate::v1_rt::{VecCompat, VecJoin}; -pub use crate::v1_std_core::{NewlineIndex, Node}; +use crate::v1_std_core::CompilerDiagnostic::*; +pub use crate::v1_std_core::ErrorNode; +pub use crate::v1_std_core::{diagnostic_to_message, is_error_diagnostic, no_span}; +pub use crate::v1_std_core::{CompilerDiagnostic, FieldSummary, NewlineIndex, Node}; use crate::NonEmptyBTreeSet; use crate::NonEmptyVec; use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; @@ -78,6 +85,8 @@ pub fn fixture_disposition( Rc::new(vec![]), v1_rt::rc_empty_map::>(), crate::v1_compiler_emit_rust::build_module_index(Rc::new(vec![])), + v1_rt::rc_empty_map::>(), + v1_rt::rc_empty_map::(), ), ) } @@ -112,6 +121,8 @@ pub fn cross_module_candidate_with_export_proof_survives() -> bool { Rc::new(vec![]), v1_rt::rc_empty_map::>(), crate::v1_compiler_emit_rust::build_module_index(Rc::new(vec![])), + v1_rt::rc_empty_map::>(), + v1_rt::rc_empty_map::(), ) == Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { provider_module: "fixture.provider".to_string(), })) @@ -146,11 +157,291 @@ pub fn census_counts_each_arm_separately() -> bool { }, ), }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "e".to_string(), + disposition: Rc::new( + ReferenceDerivedCandidateDisposition::CandidateVariantDelegatedToParent { + parent_enum: "E".to_string(), + }, + ), + }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "f".to_string(), + disposition: Rc::new( + ReferenceDerivedCandidateDisposition::CandidateVariantParentUnresolved, + ), + }), ]); let census = crate::v1_compiler_emit_rust::reference_derived_census(rows.clone()); - (((((census.candidates.clone() == 4) && (census.survived.clone() == 1)) + (((((((census.candidates.clone() == 6) && (census.survived.clone() == 1)) && (census.own_module.clone() == 1)) + && (census.variant_delegated_to_parent.clone() == 1)) + && (census.variant_parent_unresolved.clone() == 1)) && (census.registry_absent.clone() == 1)) && (census.export_proof_failed.clone() == 1)) } } + +pub fn variant_arm_red_note() -> String { + thread_local! { + static CACHED: String = { + "THE DISCRIMINATING RED FOR THE FIFTH ARM, and it is the one this file most needs. Before the arm existed a bare variant name reached the registry lookup, missed, and answered registry-absent -- so the fixture below is RED against the four-arm classifier and green against the five-arm one, which is what makes it evidence rather than decoration. The positive control beside it is the census row above: a name that is NOT a known variant still answers registry-absent, so the arm narrows the class rather than emptying it.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn fixture_variant_type_summaries() -> Rc>> { + v1_rt::rc_map_insert( + v1_rt::rc_empty_map::>(), + "E".to_string(), + Rc::new(TypeSummary { + name: "E".to_string(), + repr: Rc::new(TypeRepr::EnumRepr { unit_only: true }), + field_summaries: v1_rt::rc_empty_map::>(), + field_type_map: v1_rt::rc_empty_map::(), + field_import_surface_names: Rc::new(vec![]), + variant_name_set: v1_rt::rc_map_insert( + v1_rt::rc_empty_map::(), + "V".to_string(), + true, + ), + generic_param_names: Rc::new(vec![]), + has_fn_fields: false, + }), + ) +} + +pub fn known_variant_is_delegated_to_its_parent_not_registry_absent() -> bool { + (crate::v1_compiler_emit_rust::reference_derived_candidate_disposition( + "V".to_string(), + "fixture.consumer".to_string(), + v1_rt::rc_empty_map::>(), + v1_rt::rc_empty_map::>>(), + Rc::new(vec![]), + v1_rt::rc_empty_map::>(), + crate::v1_compiler_emit_rust::build_module_index(Rc::new(vec![])), + fixture_variant_type_summaries(), + v1_rt::rc_map_insert( + v1_rt::rc_empty_map::(), + "V".to_string(), + "E".to_string(), + ), + ) == Rc::new( + ReferenceDerivedCandidateDisposition::CandidateVariantDelegatedToParent { + parent_enum: "E".to_string(), + }, + )) +} + +pub fn ambiguous_parent_red_note() -> String { + thread_local! { + static CACHED: String = { + "THE REACHABLE HALF of the unresolved-parent arm, and the reason it is not merely a tidier name. derive_variant_to_enum inserts the EMPTY STRING as the parent when one variant name appears in two enums, so the lookup answers Present with a parent naming nothing. Without this arm that produced CandidateVariantDelegatedToParent { parent_enum: \"\" } -- a delegation to no one, wearing the payload that was supposed to make delegation honest. This fixture authors the collision directly and is RED against the arm-less form.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn fixture_colliding_variant_type_summaries() -> Rc>> { + v1_rt::rc_map_insert( + fixture_variant_type_summaries(), + "F".to_string(), + Rc::new(TypeSummary { + name: "F".to_string(), + repr: Rc::new(TypeRepr::EnumRepr { unit_only: true }), + field_summaries: v1_rt::rc_empty_map::>(), + field_type_map: v1_rt::rc_empty_map::(), + field_import_surface_names: Rc::new(vec![]), + variant_name_set: v1_rt::rc_map_insert( + v1_rt::rc_empty_map::(), + "V".to_string(), + true, + ), + generic_param_names: Rc::new(vec![]), + has_fn_fields: false, + }), + ) +} + +pub fn a_variant_whose_parent_is_ambiguous_is_not_delegated_to_nothing() -> bool { + (crate::v1_compiler_emit_rust::reference_derived_candidate_disposition( + "V".to_string(), + "fixture.consumer".to_string(), + v1_rt::rc_empty_map::>(), + v1_rt::rc_empty_map::>>(), + Rc::new(vec![]), + v1_rt::rc_empty_map::>(), + crate::v1_compiler_emit_rust::build_module_index(Rc::new(vec![])), + fixture_colliding_variant_type_summaries(), + v1_rt::rc_map_insert( + v1_rt::rc_empty_map::(), + "V".to_string(), + "".to_string(), + ), + ) == Rc::new(ReferenceDerivedCandidateDisposition::CandidateVariantParentUnresolved)) +} + +pub fn an_unresolved_parent_is_not_reported_as_registry_absent() -> bool { + (crate::v1_compiler_emit_rust::reference_derived_disposition_name(Rc::new( + ReferenceDerivedCandidateDisposition::CandidateVariantParentUnresolved, + )) != "registry-absent".to_string()) +} + +pub fn non_variant_name_still_answers_registry_absent() -> bool { + (crate::v1_compiler_emit_rust::reference_derived_disposition_name( + crate::v1_compiler_emit_rust::reference_derived_candidate_disposition( + "NotAVariant".to_string(), + "fixture.consumer".to_string(), + v1_rt::rc_empty_map::>(), + v1_rt::rc_empty_map::>>(), + Rc::new(vec![]), + v1_rt::rc_empty_map::>(), + crate::v1_compiler_emit_rust::build_module_index(Rc::new(vec![])), + fixture_variant_type_summaries(), + v1_rt::rc_map_insert( + v1_rt::rc_empty_map::(), + "V".to_string(), + "E".to_string(), + ), + ), + ) == "registry-absent".to_string()) +} + +pub fn only_export_proof_failed_produces_a_diagnostic() -> bool { + { + let rows = Rc::new(vec![ + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "a".to_string(), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: "p".to_string(), + }), + }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "b".to_string(), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateOwnModule), + }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "e".to_string(), + disposition: Rc::new( + ReferenceDerivedCandidateDisposition::CandidateVariantDelegatedToParent { + parent_enum: "E".to_string(), + }, + ), + }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "c".to_string(), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent), + }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "d".to_string(), + disposition: Rc::new( + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: "p".to_string(), + }, + ), + }), + ]); + ((crate::v1_compiler_emit_rust::reference_derived_row_diagnostics( + rows.clone(), + crate::v1_std_core::no_span(), + ) + .len() as i64) + == 1) + } +} + +pub fn the_export_unproven_diagnostic_is_advisory() -> bool { + { + let rows = Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "d".to_string(), + disposition: Rc::new( + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: "p".to_string(), + }, + ), + })]); + { + let mut __all = true; + for e in crate::v1_compiler_emit_rust::reference_derived_row_diagnostics( + rows.clone(), + crate::v1_std_core::no_span(), + ) + .iter() + .cloned() + { + if !(!crate::v1_std_core::is_error_diagnostic(e.diagnostic.clone())) { + __all = false; + break; + } + } + __all + } + } +} + +pub fn registry_absent_not_surfaced_note() -> String { + thread_local! { + static CACHED: String = { + "The RED that keeps registry-absent OUT of the diagnostic stream. Measured over the regen seed closure, that arm returns 473 rows over 63 names dominated by Rust target-language tokens -- Vec, bool, Option, i64, empty_map -- which the candidate walk proposes by tokenizing the module's own emitted Rust and for which no .dag provider can ever exist. Wiring it would print a false report on every build. This test fails the moment someone adds that arm to reference_derived_row_diagnostics, which is the intended wall until the candidate walk stops proposing target-language vocabulary.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn registry_absent_produces_no_diagnostic() -> bool { + { + let rows = Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "bool".to_string(), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent), + })]); + ((crate::v1_compiler_emit_rust::reference_derived_row_diagnostics( + rows.clone(), + crate::v1_std_core::no_span(), + ) + .len() as i64) + == 0) + } +} + +pub fn the_export_unproven_message_names_its_own_remedy() -> bool { + { + let rows = Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "d".to_string(), + disposition: Rc::new( + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: "p".to_string(), + }, + ), + })]); + { + let mut __all = true; + for e in crate::v1_compiler_emit_rust::reference_derived_row_diagnostics( + rows.clone(), + crate::v1_std_core::no_span(), + ) + .iter() + .cloned() + { + if !(v1_rt::string_contains( + &crate::v1_std_core::diagnostic_to_message(e.diagnostic.clone()), + "NOT an import".to_string(), + )) { + __all = false; + break; + } + } + __all + } + } +} diff --git a/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag b/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag index 9e8d1b76e1a..517c8b7d3e7 100644 --- a/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag +++ b/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag @@ -6,7 +6,10 @@ import v1.compiler.emit_rust { CandidateOwnModule, CandidateRegistryAbsent, CandidateExportProofFailed, + CandidateVariantDelegatedToParent, + CandidateVariantParentUnresolved, ReferenceDerivedCandidateRow, + reference_derived_row_diagnostics, ReferenceDerivedCensus, reference_derived_candidate_disposition, reference_derived_disposition_name, @@ -16,6 +19,10 @@ import v1.compiler.emit_rust { build_module_index } import v1.compiler.infer_items { ItemInfo, FnItem } +import v1.std.core { + ErrorNode, SourceSpan, no_span, diagnostic_to_message, is_error_diagnostic +} +import v1.compiler.infer_emit_info { TypeSummary, EnumRepr } import v1.compiler.compile { SourceFile, compile_to_resolved } data reference_derived_disposition_census_witness_note: String = "THE ROUTE HALF OF THIS FILE IS ABSENT AND THE REASON IS A WALL RATHER THAN A CHOICE, stated here because it is the first thing a reader of a five-row file should know: the end-to-end pair this file was designed around -- a two-module manifest whose consumer calls a peer fn in VALUE position with no import, against a control differing by one authored import line -- was authored and DOES NOT RUN. A nested compile is not evaluable in the interpreter: compile_to_resolved over an authored source vector refuses with NoSuchField Node.ident during the nested pipeline, measured 2026-08-27 on three separate subjects. That is not a property of this change; it is why the two nested-compile instruments the corpus already has (gunbc.compile_diagnostic_census and tools.multi_module_compile_fixture) both route through a HOST BUILTIN rather than calling the pipeline from .dag. The rows are not retained in a form that cannot execute, because a probe that always errors is not a red -- it is an inert artifact that would be counted as coverage. NEXT-RUNG TRIGGER FOR THE ROUTE: a nested-compile builtin returning the resolved graph, the shape tools.multi_module_compile_fixture already has, widened to carry the emitter reference-derived rows.\n\nWHERE THIS RUNS, STATED FIRST AND WITHOUT FLATTERY. THIS WITNESS IS NOT RUN BY CI. gunbc.ci_layer_roots witness_discovery_scan_dirs is [dag/test/claim, src/v2/test/claim/manual, src/v2/test/claim/emit] -- src/v1/tests/claim is not among them -- and the required run invokes claim_executor with --source-root dag --source-root src/v2, which could not resolve this file's import of v1.compiler.emit_rust even if discovery reached it. The scoped batch that once ran this directory died in the 2026-08-15 floor cut; gunbc.ci_layer_roots v1_claim_scoped_witness_batch_deleted_note records that and states the entries here have no executing consumer. The roster its sibling checkpoint_identity_keying_witness_test claims to be enrolled in, v1_claim_scoped_witness_entries, is not declared anywhere in dag/gunbc/ci_layer_roots.dag -- verified by grep in both directions rather than taken from the note. THE FIVE ROWS BELOW WERE RUN DIRECTLY ON 2026-08-27 AND ALL FIVE PASS. Direct execution is evidence the assertions HOLD, never evidence that CI runs them, and conflating those two is the rung inflation this directory has now recorded twice.\n\nWHAT THE SUBJECT IS. Since PR 6848 a cross-module name resolves whether or not it is imported. At TYPE positions the resolver says so advisorily (UnlistedImportUse, is_error_diagnostic false); at VALUE positions it says nothing at all, and the emitter silently synthesizes the use-line the author did not write. That synthesis is unchanged here and deliberately so. What changed is that the per-candidate decision behind it stopped being an inline flat_map whose three non-surviving arms all returned the empty list, and became a four-armed coproduct one census can count.\n\nWHY BOTH HALVES ARE HERE. The four unit rows discriminate the DECISION: each names a distinct arm, and two of them differ ONLY in whether the provider's export set proves the symbol, so no constant, disabled mechanism or name-keyed rule satisfies them together. The end-to-end rows discriminate the CENSUS ROUTE: a two-module manifest compiled through compile_to_resolved, whose consumer references a peer's fn in VALUE position with no import, against a control differing by exactly one authored import line. The unlisted arm must produce a survived row naming peer_answer; the control must produce no row for that name at all, because an already-provided name never reaches the decision. Neither can pass vacuously -- a census that always reported zero fails the first, and one that reported every reference fails the second.\n\nTHE RED IS AUTHORABLE AT THE FIXTURE BOUNDARY, which is what makes this a probe rather than a decoration. No Accepted corpus module can be relied on to hold a value-position unlisted use on demand, but a fixture manifest can author one directly, and this file does. DESIGN 4b: a check whose RED cannot be produced anywhere it runs is permanently green by construction and worse than absent, because it gets cited as coverage. What this file lacks is a RUNNER, not a reachable red.\n\nWHAT THIS DOES NOT ESTABLISH. Nothing about the SIZE of the live population -- these are fixtures; NO PRODUCER FOR A REAL FIGURE EXISTS YET, and this file does not name one: the only route from .dag to a corpus census is a nested compile, which the interpreter refuses, so any figure quoted today names nothing that can produce it. And nothing about the names the census never sees: a candidate already provided by an authored import, the prelude, a carrier use-line or the module's own declarations is filtered before the decision, so every count is a LOWER BOUND and the gap is unmeasured. Quote it as at least N.\n\nNEXT-RUNG TRIGGER FOR THIS FILE'S EVIDENCE, which is NOT the same trigger as the class's and must not be folded into it: one prepared subject spanning dag, src/v1 and src/v2 -- the restoration gunbc.ci_layer_roots already names for the deleted batch -- at which point these rows execute and this first paragraph deletes. The CLASS's own trigger (the value-position case joining the fail-closed wall, retiring the census and the synthesis together) lives on v1.compiler.emit_rust reference_derived_census_rung_note. Either can land first; neither closes the other." @@ -49,7 +56,9 @@ fn fixture_disposition(registry: Map, export_sets: Map Bool { export_sets: fixture_export_sets(module_name: "fixture.provider", name: "PeerName"), typed_modules: [], source_indices: empty_map(), - module_index: build_module_index(modules: []) + module_index: build_module_index(modules: []), + type_summaries: empty_map(), + variant_to_enum: empty_map() ) == CandidateSurvived { provider_module: "fixture.provider" } } @@ -84,12 +95,128 @@ test fn census_counts_each_arm_separately() -> Bool { ReferenceDerivedCandidateRow { module_name: "m", name: "a", disposition: CandidateSurvived { provider_module: "p" } }, ReferenceDerivedCandidateRow { module_name: "m", name: "b", disposition: CandidateOwnModule }, ReferenceDerivedCandidateRow { module_name: "m", name: "c", disposition: CandidateRegistryAbsent }, - ReferenceDerivedCandidateRow { module_name: "m", name: "d", disposition: CandidateExportProofFailed { provider_module: "p" } } + ReferenceDerivedCandidateRow { module_name: "m", name: "d", disposition: CandidateExportProofFailed { provider_module: "p" } }, + ReferenceDerivedCandidateRow { module_name: "m", name: "e", disposition: CandidateVariantDelegatedToParent { parent_enum: "E" } }, + ReferenceDerivedCandidateRow { module_name: "m", name: "f", disposition: CandidateVariantParentUnresolved } ] let census = reference_derived_census(rows: rows) - census.candidates == 4 + census.candidates == 6 && census.survived == 1 && census.own_module == 1 + && census.variant_delegated_to_parent == 1 + && census.variant_parent_unresolved == 1 && census.registry_absent == 1 && census.export_proof_failed == 1 } + +data variant_arm_red_note: String = "THE DISCRIMINATING RED FOR THE FIFTH ARM, and it is the one this file most needs. Before the arm existed a bare variant name reached the registry lookup, missed, and answered registry-absent -- so the fixture below is RED against the four-arm classifier and green against the five-arm one, which is what makes it evidence rather than decoration. The positive control beside it is the census row above: a name that is NOT a known variant still answers registry-absent, so the arm narrows the class rather than emptying it." + +fn fixture_variant_type_summaries() -> Map { + map_insert(empty_map(), "E", TypeSummary { + name: "E", + repr: EnumRepr { unit_only: true }, + field_summaries: empty_map(), + field_type_map: empty_map(), + field_import_surface_names: [], + variant_name_set: map_insert(empty_map(), "V", true), + generic_param_names: [], + has_fn_fields: false + }) +} + +test fn known_variant_is_delegated_to_its_parent_not_registry_absent() -> Bool { + reference_derived_candidate_disposition( + name: "V", + this_module_name: "fixture.consumer", + registry: empty_map(), + export_sets: empty_map(), + typed_modules: [], + source_indices: empty_map(), + module_index: build_module_index(modules: []), + type_summaries: fixture_variant_type_summaries(), + variant_to_enum: map_insert(empty_map(), "V", "E") + ) == CandidateVariantDelegatedToParent { parent_enum: "E" } +} + +data ambiguous_parent_red_note: String = "THE REACHABLE HALF of the unresolved-parent arm, and the reason it is not merely a tidier name. derive_variant_to_enum inserts the EMPTY STRING as the parent when one variant name appears in two enums, so the lookup answers Present with a parent naming nothing. Without this arm that produced CandidateVariantDelegatedToParent { parent_enum: \"\" } -- a delegation to no one, wearing the payload that was supposed to make delegation honest. This fixture authors the collision directly and is RED against the arm-less form." + +fn fixture_colliding_variant_type_summaries() -> Map { + map_insert(fixture_variant_type_summaries(), "F", TypeSummary { + name: "F", + repr: EnumRepr { unit_only: true }, + field_summaries: empty_map(), + field_type_map: empty_map(), + field_import_surface_names: [], + variant_name_set: map_insert(empty_map(), "V", true), + generic_param_names: [], + has_fn_fields: false + }) +} + +test fn a_variant_whose_parent_is_ambiguous_is_not_delegated_to_nothing() -> Bool { + reference_derived_candidate_disposition( + name: "V", + this_module_name: "fixture.consumer", + registry: empty_map(), + export_sets: empty_map(), + typed_modules: [], + source_indices: empty_map(), + module_index: build_module_index(modules: []), + type_summaries: fixture_colliding_variant_type_summaries(), + variant_to_enum: map_insert(empty_map(), "V", "") + ) == CandidateVariantParentUnresolved +} + +test fn an_unresolved_parent_is_not_reported_as_registry_absent() -> Bool { + reference_derived_disposition_name(disposition: CandidateVariantParentUnresolved) != "registry-absent" +} + +test fn non_variant_name_still_answers_registry_absent() -> Bool { + reference_derived_disposition_name(disposition: reference_derived_candidate_disposition( + name: "NotAVariant", + this_module_name: "fixture.consumer", + registry: empty_map(), + export_sets: empty_map(), + typed_modules: [], + source_indices: empty_map(), + module_index: build_module_index(modules: []), + type_summaries: fixture_variant_type_summaries(), + variant_to_enum: map_insert(empty_map(), "V", "E") + )) == "registry-absent" +} + +test fn only_export_proof_failed_produces_a_diagnostic() -> Bool { + let rows = [ + ReferenceDerivedCandidateRow { module_name: "m", name: "a", disposition: CandidateSurvived { provider_module: "p" } }, + ReferenceDerivedCandidateRow { module_name: "m", name: "b", disposition: CandidateOwnModule }, + ReferenceDerivedCandidateRow { module_name: "m", name: "e", disposition: CandidateVariantDelegatedToParent { parent_enum: "E" } }, + ReferenceDerivedCandidateRow { module_name: "m", name: "c", disposition: CandidateRegistryAbsent }, + ReferenceDerivedCandidateRow { module_name: "m", name: "d", disposition: CandidateExportProofFailed { provider_module: "p" } } + ] + reference_derived_row_diagnostics(rows: rows, module_span: no_span()) |> count == 1 +} + +test fn the_export_unproven_diagnostic_is_advisory() -> Bool { + let rows = [ + ReferenceDerivedCandidateRow { module_name: "m", name: "d", disposition: CandidateExportProofFailed { provider_module: "p" } } + ] + reference_derived_row_diagnostics(rows: rows, module_span: no_span()) + |> all(e => !is_error_diagnostic(d: e.diagnostic)) +} + +data registry_absent_not_surfaced_note: String = "The RED that keeps registry-absent OUT of the diagnostic stream. Measured over the regen seed closure, that arm returns 473 rows over 63 names dominated by Rust target-language tokens -- Vec, bool, Option, i64, empty_map -- which the candidate walk proposes by tokenizing the module's own emitted Rust and for which no .dag provider can ever exist. Wiring it would print a false report on every build. This test fails the moment someone adds that arm to reference_derived_row_diagnostics, which is the intended wall until the candidate walk stops proposing target-language vocabulary." + +test fn registry_absent_produces_no_diagnostic() -> Bool { + let rows = [ + ReferenceDerivedCandidateRow { module_name: "m", name: "bool", disposition: CandidateRegistryAbsent } + ] + reference_derived_row_diagnostics(rows: rows, module_span: no_span()) |> count == 0 +} + +test fn the_export_unproven_message_names_its_own_remedy() -> Bool { + let rows = [ + ReferenceDerivedCandidateRow { module_name: "m", name: "d", disposition: CandidateExportProofFailed { provider_module: "p" } } + ] + reference_derived_row_diagnostics(rows: rows, module_span: no_span()) + |> all(e => string_contains(s: diagnostic_to_message(d: e.diagnostic), pattern: "NOT an import")) +}