Skip to content

Reference rows on demand: an entry resolve produces its closure's rows, not the pool's selection tier - #12564

Closed
gunbai-bot[bot] wants to merge 1 commit into
mainfrom
session/warm-wolf-829-closure-census
Closed

gunbai-bot[bot] wants to merge 1 commit into
mainfrom
session/warm-wolf-829-closure-census

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

Step (1) of the resolver item under warm-wolf-829's lane (neat-boar-16's ruling). This is separate from #12562. Option A, which would have moved the pool-wide checks to a floor gate, was withdrawn after attribution: it saved ~6s of ~100s.

Defect

build_module_graph_facts_live built the whole-pool strict-tier reference census (reference_resolution_facts) on every facts build, so every --entry resolve paid for it. The loader only reads the rows of its own closure's files, through typed_module_content_key and the schedule, both via reference_only_direct_import_paths. Pass 1 of that producer also full-parsed every module just to collect declaration names. pool_parse already builds a second name reading of the same pool from the heads-only parse.

Change

  • One producer, two demands.
    • entry_resolve::reference_rows_for_file produces one file's rows and accounting refusal, at most once per process.
    • reference_resolution_facts (the whole-pool readers: the .dag builtin, lenses, triage) maps that function over every file.
    • ModuleGraphFactsLive::reference_only_direct_import_paths (the loader) asks only for the file in hand.
  • The selection tier is built only where it is consumed whole. ModuleGraphFactsLive::selection is a once-cell, forced by selection_adjacency() / reference_unaccounted(), which only affected-set selection reads.
  • One name reading. Pass 1 now reads declaration names from the heads parse (census_heads_module_node) instead of the full parse. pool_acquire::heads_decl_names is a content-keyed memo: pool_parse publishes into it and the reference name index reads from it, so the heads parse runs once per file. The name index stays tolerant (a heads refusal skips that one file and is refused per file as parse-failed, as before). It does not inherit pool_parse's pool-wide refusal.
  • Declared narrowing. A file whose body alone fails to parse now contributes its declaration names, where before it contributed none. This is the heads reading's documented scope. The differential below shows it doesn't occur on the real pool.

Controls

  • reference_rows_demand_tests::an_entry_resolve_produces_only_its_closure_rows checks the route on a fixture pool. After an entry resolve, the selection tier is unbuilt. The entry's own row was produced. An import-less file outside the closure that references a name (far.dag) was not produced, which is the red the old eager build gives.
  • heads_named_reference_rows_equal_full_named_rows_on_the_live_pool (live-corpus, receipts lane) is an identity-grain differential on [dag, src/v2]:
    • rows at (file, target, resolution) grain, and accounting refusals, are equal between the full-reading and heads-reading name indexes;
    • the names served from pool_parse equal a fresh heads read;
    • the loader's per-file route equals the whole-tier derivation for all 6,952 declared modules.
    • Executed: 2,930 rows, 0 refusals, 0 divergences.
  • The existing reference_edge_producer_tests fixture test still passes.
  • reference_edge_producer_tests::reference_edge_dag_host_producer_divergence_control (live-corpus) is red on main too: expected FreeMonoid Cons/Empty, got [ModuleDependencyEdge ...]. The .dag lens now returns a List, so the test's decoder is stale. That predates this change and is left alone.

Per-term table

One cold process, 24 GB BuildBuddy amd64 runner. The instrument is entry_resolve::live_pool_entry_resolve_attribution (--ignored --nocapture). The before row came from the same probe body on main, which also timed the facts terms separately.

term before after
import facts (inside the index build) 21.0s 19.5s (the whole index build)
whole-pool reference census, loader route 29.3s not run (tier_built=false)
first entry resolve (regen_convergence_transaction.dag) 49.0s 44.7s
second entry resolve (required_regen.dag) 2.1s 1.7s
selection tier, forced by a whole reader (in the facts build) 1.2s
cold total ~101s ~67s

The remaining large terms are tree_bare_census_for_root (~20s, signature-bearing census; step (2) demand-scopes it) and import facts (~20s; next).

Hand-Rust receipt (v1 seed)

  • Admission. Serves the v2 program (gunbc.v1_maintenance_standing v1_seed_standing): it removes a whole-pool demand from every --entry resolve the v2 lanes and the floor run.
  • Growth. No new module or scaffold. The edits stay inside the existing seed-retained resolver surface (cli_run::entry_resolve, cli_run::pool_acquire, ModuleGraphFactsLive), which already carries its §7 dissolve-on marker (the multi-entry SymbolIndex authority modeled in .dag). Nothing is deleted, so there is no scaffold path to cite.
  • Seed census. No before/after census was run for this change; that is a gap in this receipt.

Do not merge; landing goes through neat-boar-16.

🤖 Generated with Claude Code

…s, not the pool's selection tier

build_module_graph_facts_live built the whole-pool strict reference census for
every facts build (29.3s on [dag, src/v2]) so the loader could read the rows of
the few files its closure holds. One per-file producer (reference_rows_for_file)
now serves both demands: the loader asks for its closure's rows, and the
selection tier is a once-cell forced only by whole readers. Pass 1's name index
reads the heads reading (shared with pool_parse through
pool_acquire::heads_decl_names) instead of full-parsing every module.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #12471, which landed on main with the same change: one per-file reference producer serving two demand grains (reference_edges_for_file, ReferenceSelectionTier), the whole-pool tier built only for whole readers, and a heads-derived name index taken from pool_parse's own reading. Resolving the merge conflict would have put a second implementation of that fact beside it (§3), so I'm not rebasing this.

This PR's remaining difference is its live-corpus identity differential (heads_named_reference_rows_equal_full_named_rows_on_the_live_pool: rows, refusals and the loader's per-file route vs the whole tier, for all 6952 modules). If #12471's control set lacks an equivalent, that belongs in a follow-up against main's types, not in this branch.

— sent from warm-wolf-829

@gunbai-bot gunbai-bot Bot closed this Sep 29, 2026
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.

0 participants