Skip to content

The rule that decides whether the loader's bare-reference channel pulls a module - #11943

Merged
briansrls merged 1 commit into
mainfrom
session/nimble-cat-13
Sep 21, 2026
Merged

briansrls merged 1 commit into
mainfrom
session/nimble-cat-13

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

The answer: two gates, in this order

Gate one — v1_compiler.cli_run build_both_closure_edge_index. The bare half of the closure runs only when source_declares_import_lines(&source.content) is false. A single import line anywhere in a file turns the entire bare channel off for that file, for every name in it.

Gate two — v1_compiler.cli_run visit_bare_reference_providers. Inside the channel, a name is pulled only when the census answers GlobalBareUniqueBinding (or closure_bare_disposition answers UniqueOnChain) and its local pullable predicate holds: the reference is in call position, or the declaration has params, or it carries a type_annotation, or its connective is not NoConnective. Names that are self-declared, explicitly imported, substrate vocabulary (std_types::kernel_type_set / container_type_arity) or a test fn/test data row are skipped earlier.

AmbiguousOnChain is the one arm that refuses loudly. Every other decline is a bare continue — no diagnostic, no count, nothing carried past the loader.

A nullary type alias (type ProcessNodeId = NonEmptyStr where brand(...)) has no params, no type annotation and NoConnective, so a reference to it in type position satisfies no arm of pullable and its module is not pulled. A coproduct or record type is pulled from the same position.

The discriminating set — four compiles, one hermetic source root

fixtures/bare_reference_channel/, four modules, no std. Same invocation each time:

gunbc compile --output-dir <tmp> --source-root fixtures/bare_reference_channel \
  --entry fixtures/bare_reference_channel/<name>.dag --target rust
entry condition result
bare_record_consumer no imports; bare ref to a RECORD type pulls — resolved 2 sources, 0 blocking, 1 unlisted import use advisory
bare_alias_consumer no imports; bare ref to type BrcAliasId = Int does not pull — resolved 1 sources, error: unresolved type 'BrcAliasId'
imported_record_consumer the same bare ref as row 1 plus one unrelated import line does not pull — error: unresolved type 'BrcRecordId'
transitive_alias_consumer no imports; bare call into a module that imports the alias home pulls the callee; the alias arrives as a passenger — resolved 3 sources, 0 blocking

Rows 1/2 differ in exactly pullable. Rows 1/3 differ in exactly source_declares_import_lines. Under GUNBC_BARE_PULL_TRACE=1 the pulled cases print a [bare-pull] line naming the census state; the declined cases print nothing.

The standing fact, corrected

A .dag import list does not bind at name resolution — an unlisted name still resolves through the corpus-wide bare census (v1_compiler.infer_env global_bare_fallback_invariant), which is why an unlisted use is an advisory. It does bind at the loader: gate one. So "it has an import header" is not a safety property — a partially-imported file is strictly more dangerous than a zero-import one, because its first import line silently switched the channel off for every other name.

The falsified hypothesis, restored and explained

#11940 proposed that alias-shaped references are not pullable, then falsified it on test.claim.extdeps_version_base, which reaches extdeps.version through the same alias shape and compiles clean. The alias half was right; the counterexample resolves for a different reason. That witness also bare-references min_coreutils_version, a data declaration with a type annotation, so pullable holds, extdeps.tools.gnu_coreutils is pulled, and extdeps.version arrives inside that module's import closure. Row 4 above reproduces exactly that shape hermetically: the same alias reference that errors in row 2 resolves in row 4 because of what else the file named. Whether a bare reference resolves is not a property of the reference.

Rung, ceiling, trigger (§4b) — gunbc.recurring_failure_mode.bare_reference_channel_declines_a_pull_in_silence

Found at rung 1, and only for the eventual symptom: the unresolved type is reported where it sits, so the file's own compile is loud. What is silent is the decline (no arm records it) and the demotion ([census] N indexed modules … enter the name census only is a count with no identities and no causes). Under a floor run that count is the whole record, and the located reading arrives 13 modules away wearing an effect-modelling error's name — §5's absorbing fallback.

Ceiling 3, structurally guaranteed — and not reachable in this change. The condition is decidable: at the decline the loader holds the file, the name, the declaring module and the reason. What is missing is a carrier, not a check: nothing conveys a declined name past the loader, so the resolver that later says unresolved type 'X' cannot name the module that was not pulled, and the callee-registry join cannot say callee module M is census-only. Authoring a refusal at the decline site today would either fire on names that legitimately need no pull, or restate a conclusion the loader cannot yet reach.

Next-rung trigger, at capability grain: a per-file record of every bare-channel decline (name, declining arm, declaring module where the census named one), produced by the loader and consumed by both downstream reporters — the unresolved-name diagnostic and the callee-registry join — sufficient for each to name the unpulled module at the site that failed. A trigger naming only the loader-side record would be satisfied while both distant symptoms stayed unlocated.

Separately flagged, as a proposal and not a measurement: pullable is a heuristic standing where a decidable fact was available — the kind of the declaration the census resolved to is known exactly, and the predicate instead asks four proxy questions about node shape and declines on all-no. §4 rules that in a closed system a heuristic is never necessary; §5 names the confidence threshold that selects such an arm as the tell that locates anemic modelling. The principled repair is to pull the declarer of any bare name the census resolved, whatever its shape, and to price the closure growth rather than guess at it. That is a measured corpus-wide change, not an edit that rides in on this row.

Evidence and its limits

All four fixture results and the row's own compile were produced by a gunbc built from this branch on the remote runner (cargo build --release -p v1-compiler --bin gunbc). The row module parses and typechecks with 0 diagnostics as its own entry against a faithful two-module stub of std.types / gunbc.recurring_failure_mode. A whole-corpus --entry compile of the row against dag + src/v2 was attempted and OOM-killed (EXIT=137) during reconcile on the remote runner — a standing property of whole-corpus compiles here, not a verdict on this file; it got through frontend and normalize.

No loader behaviour is changed by this PR. No module's imports are edited: the instance work was #11940's and is not widened here.

@gunbai-bot
gunbai-bot Bot force-pushed the session/nimble-cat-13 branch from a6dfbf6 to 51fee12 Compare September 21, 2026 07:59
@gunbai-bot gunbai-bot Bot changed the title Compiler: what decides whether the bare-reference channel pulls a module The rule that decides whether the loader's bare-reference channel pulls a module Sep 21, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 21, 2026 07:59
…ls a module

Two gates, both named at their owning symbol, with a hermetic four-entry
discriminating set and a recurring_failure_mode row for the silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot force-pushed the session/nimble-cat-13 branch from 729c27f to 729a1f5 Compare September 21, 2026 08:31
@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Both findings in review 69515 verified and fixed in 729a1f5.

1 — the README was a second copy, already drifted. Confirmed: it was the only README.md under fixtures/, it restated the row's own content, and the drift was real in both directions — the row said "four modules" where the directory holds seven .dag files (four entries plus three homes), and the README said "Four compiles" beside it. Deleted. The row is the single authority and now says "FOUR ENTRIES over seven modules in one source root", which is what the directory actually is.

2 — transcribed output, and the fixtures dangling. Also confirmed: grep -rln fixtures/bare_reference_channel --include=*.dag --include=*.rs outside the diff returns nothing.

  • The counts are gone. resolved 2 sources / resolved 1 sources / resolved 3 sources / 0 blocking are replaced by the outcome each compile establishes — PULLS versus DOES NOT PULL, and the refusal identity (unresolved type BrcAliasId, unresolved type BrcRecordId). Those are what the pair discriminates; the numbers were incidental and had no producer.
  • The set is now declared as a frontier with its consumer and trigger stated beside it, per §3c, rather than left to be noticed. The named consumer is an instrument target under gunbc test //gunbc/instruments:<label> — a gunbc.instrument_targets label with a gunbc.target_binding producer — that runs the four entries and asserts per entry PULLED versus DECLINED plus the refusal identity.
  • Its trigger is deliberately the same loader-side decline record the row's next-rung trigger already names, and that is a judgement worth stating rather than a deferral: today an instrument could only infer a decline from whether a downstream type happened to resolve, which walls the symptom and not the decision. Once a decline is a value rather than a continue, the instrument observes the decision directly. Building it first would enrol a control over the wrong subject.
  • Until it lands the row says, in those words, that the set is evidence a reader re-runs and that this is weaker than an enrolled control.

Not changed: no loader behaviour, and no module's imports — the instance work was #11940's.

Re-validated after the edit: the row parses and typechecks as its own entry with 0 diagnostics against a faithful two-module stub of std.types / gunbc.recurring_failure_mode, on a gunbc built from this branch. (A whole-corpus --entry compile of it OOM-kills in reconcile on the remote runner — a standing property here, not a verdict on this file.)

— sent from nimble-cat-13

@gunbai-bot

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

On the non-blocking push in review 69516 — it is right, and I want it on the record rather than quietly accepted.

My "it would wall the symptom and not the decision" argument is true about what such an instrument observes, and it does not settle what the row needs. The row's weakness today is that its recorded outcomes rot with nobody touching either end; an outcome-grained instrument (clean compile + unlisted import use advisory versus unresolved type BrcAliasId) fixes exactly that, is authorable against the compiler as it stands, and does not have to wait for the decline carrier. The two are different walls at different grains and the second does not block the first:

  • an outcome-grained instrument keeps the four recorded outcomes honest, and goes red if either gate moves;
  • a decision-grained instrument, once a decline is a value rather than a continue, observes the pull decision directly instead of inferring it from whether a downstream type resolved.

So the frontier's trigger as written is stronger than the first of those needs. I am not rewriting it under an approval — changing a stated §4b trigger deserves its own diff and its own reading, not a late edit to a row being landed. Carrying it as the named follow-up: an gunbc.instrument_targets label plus a gunbc.target_binding producer running the four entries at outcome grain, which is a change this row's frontier text should be amended by when it lands.

— sent from nimble-cat-13

@briansrls
briansrls added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit 619b138 Sep 21, 2026
4 checks passed
@briansrls
briansrls deleted the session/nimble-cat-13 branch September 21, 2026 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant