diff --git a/dag/gunbc/floor/floor_demand.dag b/dag/gunbc/floor/floor_demand.dag index 32798945c3c..0a526b64ee5 100644 --- a/dag/gunbc/floor/floor_demand.dag +++ b/dag/gunbc/floor/floor_demand.dag @@ -909,14 +909,14 @@ type PeakLowerBoundCause | SwapPolicyIsRead { swap_max: ByteSize } // THE SUCCESSFUL ARM IS NAMED FOR WHAT IT ESTABLISHES, WHICH IS NOT EXACT DEMAND. It used to be -// called PeakIsUncensoredSampledMaximum, and that name asserted past its evidence in the way DESIGN 4d forbids: the +// called PeakIsDemand, and that name asserted past its evidence in the way DESIGN 4d forbids: the // arm is reached when the workload completed, the SAMPLED peak showed no stall, and swap could // not spill. Those remove two CENSORING mechanisms -- they do not make a sampled maximum the true // maximum. memory.stat carries no high-water counter, so this receipt's peak is the largest value // a PERIODIC sample caught, and a spike that rose and fell between two beats is invisible to it. // This module's own beat-cadence note has said exactly that from the start; the fold promoted // past it anyway, and the ARM NAME was the assertion, because a later reader consumes an arm -// called PeakIsUncensoredSampledMaximum as a fact rather than as the bet it is. +// called PeakIsDemand as a fact rather than as the bet it is. // // SO EVERY ARM OF THIS TYPE IS A LOWER BOUND, and they differ only in WHY. The uncensored arm is // the strongest reading available from this instrument and it is still a floor on demand. An diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 3201f32fa05..0f400424254 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -40800,11 +40800,50 @@ pub fn assemble_prepared_subject_closure( exclude_substrings: &[String], closure: Option<(&MultiEntryIndex, &[String], &[String])>, ) -> Result { - let full_index = build_module_index(source_roots); - let full_inventory = floor_source_inventory(&full_index); + let corpus = read_source_corpus_once(source_roots); + assemble_prepared_subject_from_corpus(&corpus, exclude_substrings, closure) +} + +/// THE CORPUS READ, AS A VALUE A CALLER CAN OWN AND LEND TO MORE THAN ONE DEMAND. +/// +/// `build_module_index` walks every source root and `read_to_string`s every `.dag` file; it is +/// not memoised and nothing above it carried the result, so a caller that prepared two subjects +/// read and indexed the WHOLE CORPUS TWICE. The floor is exactly that caller -- `run_required_floor` +/// prepares the policy closure and then the gate closure, and both call sites pass IDENTICAL source +/// roots, identical exclusions and the same entry index, differing only in their closure seeds. +/// +/// So this is DESIGN §2's authored duplication, and the repair is the one §2 names: several demands +/// with a shared-state least common ancestor CARRY the first value rather than caching the second. +/// The ancestor reads once and lends; there is no key, no invalidation rule and no provider, because +/// none of those is what was missing -- the value simply was not carried. +pub struct SourceCorpusRead { + index: ModuleSourceIndex, + inventory: Vec, +} + +pub fn read_source_corpus_once(source_roots: &[String]) -> SourceCorpusRead { + let index = build_module_index(source_roots); + let inventory = floor_source_inventory(&index); + SourceCorpusRead { index, inventory } +} + +/// The subject fold over a corpus the CALLER read. Identical to the wrapper above in every respect +/// except that it does not do the reading, so two subjects prepared from one read see one corpus. +pub fn assemble_prepared_subject_from_corpus( + corpus: &SourceCorpusRead, + exclude_substrings: &[String], + closure: Option<(&MultiEntryIndex, &[String], &[String])>, +) -> Result { + let full_index = &corpus.index; + let full_inventory = corpus.inventory.clone(); let mut discovery_exclusions: HashMap = HashMap::new(); let index: ModuleSourceIndex = match closure { - None => full_index, + // THE ONLY TWO PLACES THE INDEX WAS MOVED rather than read. Both now clone, and what they + // clone is `Rc` pointers plus their keys -- not file contents, and for the + // closure arm only the KEPT subset, which on the floor is a small fraction of the corpus. + // That is the cost of carrying one read instead of doing a second one, and it is the trade + // the duplication was paying in full on every floor run. + None => full_index.clone(), Some((entry_index, prefixes, module_seeds)) => { let started = std::time::Instant::now(); // THE CLOSURE IS THE LOADER'S BOTH-CLOSURE, NOT THE IMPORT HEADERS. A module in @@ -40946,8 +40985,9 @@ pub fn assemble_prepared_subject_closure( full_index.len() ); full_index - .into_iter() - .filter(|(m, _)| keep.contains(m)) + .iter() + .filter(|(m, _)| keep.contains(*m)) + .map(|(m, sf)| (m.clone(), sf.clone())) .collect() } }; @@ -41090,7 +41130,18 @@ pub fn prepare_repository_closure( exclude_substrings: &[String], closure: Option<(&MultiEntryIndex, &[String], &[String])>, ) -> Result<(PreparedRepository, Vec), String> { - let subject = assemble_prepared_subject_closure(source_roots, exclude_substrings, closure)?; + let corpus = read_source_corpus_once(source_roots); + prepare_repository_from_corpus(&corpus, exclude_substrings, closure) +} + +/// The repository fold over a corpus the CALLER read; see `read_source_corpus_once` for why the +/// read is a value rather than something each prepare does for itself. +pub fn prepare_repository_from_corpus( + corpus: &SourceCorpusRead, + exclude_substrings: &[String], + closure: Option<(&MultiEntryIndex, &[String], &[String])>, +) -> Result<(PreparedRepository, Vec), String> { + let subject = assemble_prepared_subject_from_corpus(corpus, exclude_substrings, closure)?; // THE SUBJECT IS STATED BY THE REFUSAL ITSELF, not only by the success path. // // The digest and the two counts are computed above, BEFORE the gate that can reject. diff --git a/src/v1/stage0/src/cli_run/required_floor_runner.rs b/src/v1/stage0/src/cli_run/required_floor_runner.rs index c4c0fd7b1a6..35dab81b402 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -3969,10 +3969,21 @@ pub struct RequiredFloorNominalSubjectSeeds { pub fn required_floor_nominal_subject_seeds( source_roots: &[String], gate_entry_index: &MultiEntryIndex, +) -> Result { + let corpus = crate::cli_run::read_source_corpus_once(source_roots); + required_floor_nominal_subject_seeds_from_corpus(&corpus, gate_entry_index) +} + +/// The seed fold over a corpus the CALLER read. `run_required_floor` is the least common ancestor +/// of this prepare and the gate prepare below it, so it reads once and lends to both; the wrapper +/// above stays for callers with only one demand (the lane resolution census). +pub fn required_floor_nominal_subject_seeds_from_corpus( + corpus: &crate::cli_run::SourceCorpusRead, + gate_entry_index: &MultiEntryIndex, ) -> Result { let policy_seed = [REQUIRED_FLOOR_POLICY_MODULE.to_string()]; - let (policy_prepared, _) = prepare_repository_closure( - source_roots, + let (policy_prepared, _) = crate::cli_run::prepare_repository_from_corpus( + corpus, &floor_prepared_subject_exclusions(), Some((gate_entry_index, &[], &policy_seed)), )?; @@ -5732,6 +5743,21 @@ pub fn run_required_floor( // 4,260-module corpus, measured 2026-08-29), so it is built once here and lent to the // policy-closure prepare and the gate-closure prepare alike. let gate_entry_index = build_multi_entry_index(source_roots); + // ONE CORPUS READ FOR BOTH PREPARES, CARRIED FROM THE ANCESTOR THAT OWNS BOTH DEMANDS. + // + // This function prepares TWO subjects -- the policy closure through + // `required_floor_nominal_subject_seeds_from_corpus`, then the gate closure below -- and both + // call sites pass identical source roots, identical exclusions and this same + // `gate_entry_index`, differing ONLY in their closure seeds. Each prepare used to begin with + // its own `build_module_index(source_roots)`, which is not memoised: it walked every root and + // `read_to_string`d every `.dag` file in the corpus. So the floor read and indexed the whole + // corpus twice, on every run, for two questions that differ in their seeds and in nothing else. + // + // That is DESIGN §2's authored duplication rather than a cache obligation, and §2 names the + // repair: when several demands share a least common ancestor, CARRY the first value. This is + // that ancestor. The entry index one line above was already shared for exactly this reason -- + // the corpus read simply never was. + let floor_corpus = crate::cli_run::read_source_corpus_once(source_roots); // ONE DERIVATION, CONSUMED FOUR WAYS. The same diff observation supplies changed-witness // identities, newly enrolled identities, the compile-subject modules of // `touched_entry_files`, and the match-bearing consumers of every coproduct whose arm set @@ -5787,7 +5813,7 @@ pub fn run_required_floor( required_gate_prefixes, required_gate_authored_modules, local_repo_wet_schedule_rows, - } = required_floor_nominal_subject_seeds(source_roots, &gate_entry_index)?; + } = required_floor_nominal_subject_seeds_from_corpus(&floor_corpus, &gate_entry_index)?; // THE FLOOR'S OWN AUTHORITIES ARE ALWAYS IN THE SUBJECT: the floor evaluates its rosters // (expected red, route gap, cost debt, the gate itself) in a frame over the prepared graph, // and a gate roster that happened not to reach `v2.workflow.required_floor` refused with @@ -5937,8 +5963,8 @@ pub fn run_required_floor( ) .chain(arm_set_consumer_seeds.iter().cloned()) .collect(); - let (mut prepared, prepared_sources) = prepare_repository_closure( - source_roots, + let (mut prepared, prepared_sources) = crate::cli_run::prepare_repository_from_corpus( + &floor_corpus, &floor_prepared_subject_exclusions(), Some(( &gate_entry_index,