From d5fc68134b9efb6428a84404736bbf0ce28489da Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 30 Aug 2026 12:01:36 +0000 Subject: [PATCH 1/2] The affected set of one edit: gunbc.regen_affected_set bounds which mirrors a .dag edit can change -- reverse closure over the regen's own edge index, a declared bootstrap edge for v1_rt.rs, a declared generation-input roster, and a refusal (never a widen) for an unlocatable edited path Realizes regen_round_cost's DerivableNow disposition as an authority a regen can consume. `claim_executor --regen-affected-set` reads the floor's diff range, names each edited .dag as a module, and prints the model's bound; the host's native reverse walk is held to the model's answer on every run. The three measured edits of 2026-08-30 are the live-tree controls. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KGCnzm2zkb37mVHgrGCt9L --- dag/gunbc/regen_affected_set.dag | 167 +++++ dag/gunbc/regen_round_cost.dag | 4 +- ...f_host_regen_affected_set_witness_test.dag | 96 +++ src/v1/stage0/src/bin/claim_executor.rs | 24 + src/v1/stage0/src/cli_run.rs | 7 + src/v1/stage0/src/required_regen_host.rs | 616 ++++++++++++++++++ 6 files changed, 912 insertions(+), 2 deletions(-) create mode 100644 dag/gunbc/regen_affected_set.dag create mode 100644 dag/test/claim/self_host_regen_affected_set_witness_test.dag diff --git a/dag/gunbc/regen_affected_set.dag b/dag/gunbc/regen_affected_set.dag new file mode 100644 index 00000000000..12e549560fe --- /dev/null +++ b/dag/gunbc/regen_affected_set.dag @@ -0,0 +1,167 @@ +module gunbc.regen_affected_set + +import std.types { String, List, Bool } + +// THE AFFECTED SET OF ONE EDIT: which committed mirrors can change when these .dag modules +// change. This is the bound gunbc.regen_round_cost regen_affected_set_disposition scoped +// (DerivableNow, with its residual), made into an authority a regen can consume, and it is a +// BOUND -- a structural over-approximation computed AS the answer (DESIGN section 5) -- never an +// exact set: exactness needs the per-module emit itself. +// +// THREE SOURCES, ALL DECLARED, NONE A FILENAME EXCEPTION. +// +// 1. The dependency closure the regen already builds, read in reverse. The seed's closure +// authority is cli_run extend_sources_to_both_closure_fixpoint over both_closure_edge_index +// (dotted references, which include every `import` line, plus bare references), and the +// affected modules are every module from which an edited module is reachable along those +// edges. Measured 2026-08-30 (tree 677988a2, srv1 and BuildBuddy): a data row added to +// std.content_hash drifted exactly {std_content_hash.rs} against 72 predicted by the import +// lines alone -- contained, over-approximate. +// +// 2. The bootstrap edge. v1_rt.rs is a generated dependent of v1.compiler.runtime_rust that is +// NOT a module's mirror and is NOT reachable through any import: it is the runtime template's +// product, emitted from the string baked into the emitting seed. Measured 2026-08-30 (tree +// 0fe2c517): editing the template drifted {v1_compiler_runtime_rust.rs, v1_rt.rs}, and only the +// first is in the graph. The edge is a declared row here (regen_bootstrap_edges), so a reader +// of the authority sees it and the host cannot carry it as an undocumented special case +// (operator ruling 2026-08-30). +// +// 3. The generation-level input. Every mirror is emitted by a seed compiled FROM the mirrors, so +// an edit to a module the emitter itself is made of changes every emitted byte's producer. No +// reverse closure sees that -- nothing imports the emitter -- so it is a declared prefix roster +// (regen_generation_input_prefixes): an edit under one of them answers WholePopulation, and the +// consumer regenerates everything. This arm is what keeps the bound honest for the edits that +// matter most; it is deliberately wide (a std module the compiler merely calls is NOT under +// it, because the measured std.content_hash edit did not propagate). +// +// REFUSAL, NEVER WIDENING. When an edited path cannot be located as a module -- a departed .dag +// whose module name is no longer readable from the tree, or a .dag under no source root -- the +// answer is EditedSetUnlocatable naming the paths. It does not fall back to WholePopulation: +// "whole rebuild required" and "the selection could not answer" are different states, and +// collapsing them would erase the only signal that the locator has a deficit (DESIGN section 5, +// the absorbing fallback; operator ruling 2026-08-30). + +type DependencyEdge { + from: String + to: String +} + +type MirrorRow { + module: String + basename: String +} + +type BootstrapEdge { + source_module: String + product: String + reason: String +} + +data regen_bootstrap_edges: List = [ + BootstrapEdge { + source_module: "v1.compiler.runtime_rust", + product: "v1_rt.rs", + reason: "the runtime shim is the template's product, emitted from the string the emitting seed carries; it is a compared mirror with no module and no import edge (measured drift on tree 0fe2c517: v1_compiler_runtime_rust.rs and v1_rt.rs)" + } +] + +data regen_generation_input_prefixes: List = ["v1.compiler.", "extdeps.languages."] + +type AffectedSetBound + = AffectedMirrors { edited: List, mirrors: List, bootstrap_products: List } + | WholePopulation { edited: List, generation_inputs: List } + | EditedSetUnlocatable { unlocatable: List, reason: String } + +fn regen_list_has(items: List, item: String) -> Bool { + items |> any(x => x == item) +} + +// ONE STEP of the reverse walk: every module with an edge INTO the current set joins it. +fn regen_reverse_step(reached: List, edges: List) -> List { + fold(edges, init: reached, f: fn(acc, edge) { + if regen_list_has(items: acc, item: edge.to) && !regen_list_has(items: acc, item: edge.from) { + list_push(acc, edge.from) + } else { + acc + } + }) +} + +// THE REVERSE CLOSURE, as a bounded iteration: a path in a graph of n modules has at most n +// edges, so n steps reach the fixpoint by construction (execution is bounded and forward, +// DESIGN section 4). The host realizes the same walk over its edge index; the lockstep test +// holds the two to one answer on a fixture graph. +fn regen_reverse_closure(edited: List, edges: List, modules: List) -> List { + fold(modules, init: edited, f: fn(acc, m) { regen_reverse_step(reached: acc, edges: edges) }) +} + +// A DECLARED BOOTSTRAP SOURCE IS NOT A GENERATION INPUT, and the exclusion is the declaration's +// content: the bootstrap row says, with its measurement, that this module's effect on the +// population is its reverse closure plus its named product -- so it takes the AffectedMirrors +// arm. Without the exclusion the prefix roster would answer WholePopulation for the runtime +// template, against the measured two-file drift. +fn regen_generation_inputs(edited: List) -> List { + edited |> filter(m => + (regen_generation_input_prefixes |> any(prefix => starts_with(s: m, prefix: prefix))) + && !(regen_bootstrap_edges |> any(edge => edge.source_module == m))) +} + +fn regen_bootstrap_products(reached: List) -> List { + regen_bootstrap_edges + |> filter(edge => regen_list_has(items: reached, item: edge.source_module)) + |> map(edge => edge.product) +} + +// THE BOUND. `unlocatable` is the host's list of edited paths it could not name as modules; a +// non-empty list is the refusal arm before any closure is taken. `compared` is the committed +// mirror population with each row's module; `modules` is the closure's module list (the walk's +// step bound). The bootstrap rows are this module's own declaration, read here, never passed: +// a caller that could hand in a different roster would be a second authority for the edge. +fn regen_affected_set( + edited: List, + unlocatable: List, + edges: List, + compared: List, + modules: List +) -> AffectedSetBound { + if count(unlocatable) > 0 { + EditedSetUnlocatable { + unlocatable: unlocatable, + reason: "an edited path could not be named as a module in the tree; the selection cannot answer, and it does not widen to the population" + } + } else { + let generation = regen_generation_inputs(edited: edited) + if count(generation) > 0 { + WholePopulation { edited: edited, generation_inputs: generation } + } else { + let reached = regen_reverse_closure(edited: edited, edges: edges, modules: modules) + AffectedMirrors { + edited: edited, + mirrors: compared |> filter(row => regen_list_has(items: reached, item: row.module)) |> map(row => row.basename), + bootstrap_products: regen_bootstrap_products(reached: reached) + } + } + } +} + +fn regen_affected_set_bound_line(bound: AffectedSetBound) -> String { + match bound { + AffectedMirrors { edited: e, mirrors: m, bootstrap_products: b } => + concat( + concat(concat("regen-affected-set: AffectedMirrors edited=", to_string(count(e))), concat(" mirrors=", to_string(count(m)))), + concat(" bootstrap_products=", to_string(count(b))) + ) + WholePopulation { edited: e, generation_inputs: g } => + concat(concat("regen-affected-set: WholePopulation edited=", to_string(count(e))), concat(" generation_inputs=", to_string(count(g)))) + EditedSetUnlocatable { unlocatable: u, reason: r } => + concat(concat("regen-affected-set: EditedSetUnlocatable unlocatable=", to_string(count(u))), concat(" reason=", r)) + } +} + +fn regen_affected_set_members(bound: AffectedSetBound) -> List { + match bound { + AffectedMirrors { edited: e, mirrors: m, bootstrap_products: b } => concat(m, b) + WholePopulation { edited: e, generation_inputs: g } => [] + EditedSetUnlocatable { unlocatable: u, reason: r } => [] + } +} diff --git a/dag/gunbc/regen_round_cost.dag b/dag/gunbc/regen_round_cost.dag index a302366226f..f2248cd9cfd 100644 --- a/dag/gunbc/regen_round_cost.dag +++ b/dag/gunbc/regen_round_cost.dag @@ -296,8 +296,8 @@ type AffectedSetDisposition // correct over-approximation (DESIGN section 5: a structural over-approximation computed AS // the answer), not an exact set. data regen_affected_set_disposition: AffectedSetDisposition = DerivableNow { - from: "the .dag dependency closure the regen already builds: cli_run regen_input_sources_over_roots / extend_sources_to_both_closure_fixpoint (imports, dotted and bare references), read in reverse from the edited module, restricted to the compared mirror population, plus the runtime template products (v1_rt.rs) whenever v1.compiler.runtime_rust is in the set", - residual: "the emitting seed's own baked mirrors are a generation-level input to every emitted byte (the two-round bootstrap); the reverse closure over-approximates -- 72 predicted against 1 changed for a std.content_hash data row -- and exactness needs the per-module emit itself" + from: "gunbc.regen_affected_set regen_affected_set, whose bound is the .dag dependency closure the regen already builds: cli_run regen_input_sources_over_roots / extend_sources_to_both_closure_fixpoint (imports, dotted and bare references), read in reverse from the edited module, restricted to the compared mirror population, plus the declared bootstrap edge (regen_bootstrap_edges: v1_rt.rs whenever v1.compiler.runtime_rust is in the set); an edited path the tree cannot name refuses (EditedSetUnlocatable) rather than widening", + residual: "the emitting seed's own baked mirrors are a generation-level input to every emitted byte (the two-round bootstrap); the reverse closure over-approximates -- 72 predicted against 1 changed for a std.content_hash data row -- so an edit under regen_generation_input_prefixes answers WholePopulation; the AffectedMirrors arm over-approximates -- 72 predicted against 1 changed for a std.content_hash data row -- and exactness needs the per-module emit itself" } // WHERE THE ROUND'S TIME GOES, read from the instrument on 2026-08-30 (producer: this module's diff --git a/dag/test/claim/self_host_regen_affected_set_witness_test.dag b/dag/test/claim/self_host_regen_affected_set_witness_test.dag new file mode 100644 index 00000000000..903b28e8a77 --- /dev/null +++ b/dag/test/claim/self_host_regen_affected_set_witness_test.dag @@ -0,0 +1,96 @@ +module test.claim.self_host_regen_affected_set_witness + +import std.types { Bool, String, List } +import gunbc.regen_affected_set { + DependencyEdge, MirrorRow, BootstrapEdge, AffectedSetBound, AffectedMirrors, WholePopulation, EditedSetUnlocatable, + regen_affected_set, regen_reverse_closure, regen_affected_set_members, regen_affected_set_bound_line +} + +// WHAT THIS WITNESS COVERS: the bound's three arms and the reverse walk, on a fixture graph +// small enough to read. The fixture is the shape of the measured cases: a leaf std module with +// two dependents, an unrelated module, the declared runtime-template bootstrap edge, and an +// emitter module under the generation-input roster. The live-tree control -- the bound over +// the seed's own edge index containing the drift sets the 2026-08-30 rounds measured -- is the +// Rust test beside required_regen_host run_regen_affected_set, because the edge index is the +// seed's and no hermetic form here can read it. + +data fixture_edges: List = [ + DependencyEdge { from: "std.b", to: "std.a" }, + DependencyEdge { from: "gunbc.c", to: "std.b" }, + DependencyEdge { from: "gunbc.d", to: "std.x" }, + DependencyEdge { from: "v1.compiler.emit_rust", to: "std.a" } +] + +data fixture_modules: List = ["std.a", "std.b", "gunbc.c", "gunbc.d", "std.x", "v1.compiler.emit_rust", "v1.compiler.runtime_rust"] + +data fixture_compared: List = [ + MirrorRow { module: "std.a", basename: "std_a.rs" }, + MirrorRow { module: "std.b", basename: "std_b.rs" }, + MirrorRow { module: "gunbc.c", basename: "gunbc_c.rs" }, + MirrorRow { module: "gunbc.d", basename: "gunbc_d.rs" }, + MirrorRow { module: "v1.compiler.emit_rust", basename: "v1_compiler_emit_rust.rs" }, + MirrorRow { module: "v1.compiler.runtime_rust", basename: "v1_compiler_runtime_rust.rs" } +] + +fn bound_for(edited: List) -> AffectedSetBound { + regen_affected_set(edited: edited, unlocatable: [], edges: fixture_edges, compared: fixture_compared, modules: fixture_modules) +} + +fn same_members(a: List, b: List) -> Bool { + count(a) == count(b) && (a |> all(x => b |> any(y => y == x))) +} + +fn mirrors_of(bound: AffectedSetBound) -> List { + match bound { + AffectedMirrors { edited: e, mirrors: m, bootstrap_products: b } => m + WholePopulation { edited: e, generation_inputs: g } => [] + EditedSetUnlocatable { unlocatable: u, reason: r } => [] + } +} + +// POSITIVE CONTROL: the reverse walk reaches every dependent, transitively, and nothing else. +test fn a_reverse_closure_reaches_transitive_dependents_only() -> Bool { + same_members(a: regen_reverse_closure(edited: ["std.a"], edges: fixture_edges, modules: fixture_modules), b: ["std.a", "std.b", "gunbc.c", "v1.compiler.emit_rust"]) +} + +// The bound for a leaf std edit is its reverse closure restricted to compared mirrors. gunbc.d +// depends on something else and stays out; that is the over-approximation being a bound and not +// the population. +test fn a_leaf_std_edit_bounds_to_its_dependents_mirrors() -> Bool { + same_members(a: mirrors_of(bound: bound_for(edited: ["std.a"])), b: ["std_a.rs", "std_b.rs", "gunbc_c.rs", "v1_compiler_emit_rust.rs"]) +} + +// THE DISCRIMINATING RED for the walk: an edit nobody depends on bounds to itself alone. +test fn w_RED_an_edit_with_no_dependents_bounds_to_itself() -> Bool { + same_members(a: mirrors_of(bound: bound_for(edited: ["gunbc.d"])), b: ["gunbc_d.rs"]) +} + +// THE DECLARED BOOTSTRAP EDGE: editing the runtime template yields its own mirror AND v1_rt.rs, +// which is in no graph -- the measured two-file drift of 2026-08-30. +test fn a_runtime_template_edit_carries_the_declared_bootstrap_product() -> Bool { + same_members(a: regen_affected_set_members(bound: bound_for(edited: ["v1.compiler.runtime_rust"])), b: ["v1_compiler_runtime_rust.rs", "v1_rt.rs"]) +} + +// THE GENERATION-INPUT ARM: an edit to the emitter answers WholePopulation, never a subset. +test fn a_emitter_edit_answers_whole_population() -> Bool { + match bound_for(edited: ["v1.compiler.emit_rust"]) { + WholePopulation { edited: e, generation_inputs: g } => same_members(a: g, b: ["v1.compiler.emit_rust"]) + AffectedMirrors { edited: e, mirrors: m, bootstrap_products: b } => false + EditedSetUnlocatable { unlocatable: u, reason: r } => false + } +} + +// THE REFUSAL, and its RED shape: an unlocatable path refuses BEFORE any closure is taken, and +// the refusal carries no mirrors -- a consumer cannot mistake it for an empty affected set. +test fn w_RED_an_unlocatable_edited_path_refuses_without_widening() -> Bool { + let bound = regen_affected_set(edited: ["std.a"], unlocatable: ["dag/std/gone.dag"], edges: fixture_edges, compared: fixture_compared, modules: fixture_modules) + match bound { + EditedSetUnlocatable { unlocatable: u, reason: r } => same_members(a: u, b: ["dag/std/gone.dag"]) && count(regen_affected_set_members(bound: bound)) == 0 + AffectedMirrors { edited: e, mirrors: m, bootstrap_products: b } => false + WholePopulation { edited: e, generation_inputs: g } => false + } +} + +test fn a_bound_line_names_the_arm_and_the_counts() -> Bool { + regen_affected_set_bound_line(bound: bound_for(edited: ["std.a"])) == "regen-affected-set: AffectedMirrors edited=1 mirrors=4 bootstrap_products=0" +} diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index f2745cfe1c1..35d1ab4f696 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -163,6 +163,7 @@ fn run() -> Result { let mut emit_partition_crates_write = false; let mut required_regen_fixed_point_mode = false; let mut regen_round_cost_mode = false; + let mut regen_affected_set_mode = false; let mut regen_candidate_dir = "target/stage0-regen-candidate".to_string(); let mut regen_receipt_path = "target/stage0-regen-receipt.json".to_string(); @@ -234,6 +235,12 @@ fn run() -> Result { "--regen-round-cost" => { regen_round_cost_mode = true; } + // THE AFFECTED SET OF THE FLOOR'S DIFF RANGE: which committed mirrors can change + // for the .dag modules this edit touched, as `gunbc.regen_affected_set` bounds it. + // Reports; it installs nothing. An edited path the tree cannot name refuses. + "--regen-affected-set" => { + regen_affected_set_mode = true; + } "--regen-candidate-dir" => { i += 1; regen_candidate_dir = require_value(&args, i, "--regen-candidate-dir")?; @@ -842,6 +849,23 @@ fn run() -> Result { // outside `//:required` by construction there -- `gunbc.discovery_census` derives that // aggregate from discovered witness sites, and no fold feeds the instrument population into it. + if regen_affected_set_mode { + return match v1_compiler::cli_run::run_regen_affected_set(&source_roots) { + Ok(outcome) => { + eprint!("{}", outcome.rendered); + if outcome.arm == "EditedSetUnlocatable" { + Err(ExitCode::from(1)) + } else { + Ok(ExitCode::SUCCESS) + } + } + Err(e) => { + eprintln!("regen-affected-set: refused: {e}"); + Err(ExitCode::from(1)) + } + }; + } + if regen_round_cost_mode { return match v1_compiler::cli_run::run_regen_round_cost( ®en_candidate_dir, diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 28cc85df032..cad627a9671 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -38274,8 +38274,15 @@ pub fn run_required_regen_fixed_point( required_regen_host::run_required_regen_fixed_point(receipt_rel, pass1_digest) } +pub use required_regen_host::RegenAffectedSetOutcome; pub use required_regen_host::RegenRoundCostOutcome; +/// The affected-set bound of the floor's diff range — see +/// `required_regen_host::run_regen_affected_set` and `gunbc.regen_affected_set`. +pub fn run_regen_affected_set(source_roots: &[String]) -> Result { + required_regen_host::run_regen_affected_set(source_roots) +} + /// One priced regen round — see `required_regen_host::run_regen_round_cost`. pub fn run_regen_round_cost( candidate_dir_rel: &str, diff --git a/src/v1/stage0/src/required_regen_host.rs b/src/v1/stage0/src/required_regen_host.rs index 0129150e856..8fb98587fc1 100644 --- a/src/v1/stage0/src/required_regen_host.rs +++ b/src/v1/stage0/src/required_regen_host.rs @@ -2569,3 +2569,619 @@ mod regen_round_cost_tests { } } } + +// =========================================================================================== +// THE AFFECTED SET OF ONE EDIT -- host realization of `gunbc.regen_affected_set`. +// +// The host does three things the model cannot: read the edit (the floor's git diff range), read +// the tree (module names from the edited files, the seed's closure edge index, the committed +// mirror population), and take the reverse walk over the full edge index at native cost. The +// VERDICT -- which arm, which members -- is the model's: the host hands `regen_affected_set` the +// edited modules, the unlocatable paths, the edges among the modules its own walk reached, the +// compared rows, and the declared bootstrap rows, and prints what the model answers. The host's +// walk is then held to the model's answer on every run (`lockstep` below): a disagreement is a +// refusal, never a silently preferred side. +// =========================================================================================== + +const REGEN_AFFECTED_SET_PRODUCER: &str = "claim_executor --regen-affected-set"; +const REGEN_AFFECTED_SET_ENTRY_UNDER_ROOT: &str = "gunbc/regen_affected_set.dag"; + +pub struct RegenAffectedSetOutcome { + /// Provenance, the edited population, the bound line, and one `member` line per mirror. + pub rendered: String, + /// The model's arm name (`AffectedMirrors` | `WholePopulation` | `EditedSetUnlocatable`). + pub arm: String, + /// The mirrors the bound names, by committed basename; empty for the two non-selecting arms. + pub members: Vec, +} + +/// The model's answer for one edited population, as the host consumes it. +pub struct AffectedSetBound { + pub line: String, + pub arm: String, + pub members: Vec, +} + +/// The edited population, classified. `unlocatable` is every `.dag` path the diff names that the +/// tree cannot name as a module -- departed, unreadable, or without a `module` line -- and a +/// non-empty list is the model's refusal arm. Paths that are not `.dag` are not the selection's +/// subject (a hand edit to a mirror is what the regen's own diff catches) and are only counted. +#[derive(Debug, PartialEq, Eq)] +pub struct EditedPopulation { + pub edited_modules: Vec, + pub unlocatable: Vec, + pub non_dag_paths: Vec, +} + +pub fn edited_population_from_diff(workspace: &Path, diff_text: &str) -> EditedPopulation { + let departed = super::parse_unified_diff_departed_paths(diff_text); + let mut paths: BTreeSet = super::parse_unified_diff_changed_new_lines(diff_text) + .keys() + .cloned() + .collect(); + paths.extend(super::parse_unified_diff_added_paths(diff_text)); + paths.extend(departed.iter().cloned()); + let mut edited_modules: BTreeSet = BTreeSet::new(); + let mut unlocatable = Vec::new(); + let mut non_dag_paths = Vec::new(); + for path in paths { + if !path.ends_with(".dag") { + non_dag_paths.push(path); + continue; + } + if departed.contains(&path) { + unlocatable.push(format!( + "{path} (departed: no module line remains in the tree)" + )); + continue; + } + match fs::read_to_string(workspace.join(&path)) { + Ok(content) => match super::extract_module_path_public(&content) { + Some(module) => { + edited_modules.insert(module); + } + None => unlocatable.push(format!("{path} (no module line)")), + }, + Err(e) => unlocatable.push(format!("{path} (unreadable: {e})")), + } + } + EditedPopulation { + edited_modules: edited_modules.into_iter().collect(), + unlocatable, + non_dag_paths, + } +} + +/// The seed's closure edges, module to module, off the SAME edge index the regen's closure walk +/// uses (`both_closure_edge_index`: dotted references, which include every import line, plus bare +/// references) -- one authority for "what pulls what", read here in reverse. A file the index +/// names but cannot map to a module is a refusal: an edge dropped silently would shrink the bound. +pub fn regen_module_edges( + workspace: &Path, +) -> Result<(Vec<(String, String)>, Vec), String> { + let abs_roots: Vec = super::regen_source_roots() + .all() + .iter() + .map(|root| { + workspace + .join(root.repo_relative_path()) + .to_string_lossy() + .into_owned() + }) + .collect(); + let index = super::build_multi_entry_index(&abs_roots); + let edge_index = super::both_closure_edge_index(&index)?; + let mut module_of_path: HashMap = HashMap::new(); + let mut modules: BTreeSet = BTreeSet::new(); + for (module, source) in index.source_files.iter() { + module_of_path.insert( + super::workspace_relative_repo_path(&source.path), + module.clone(), + ); + modules.insert(module.clone()); + } + let mut edges: BTreeSet<(String, String)> = BTreeSet::new(); + let mut unmapped: BTreeSet = BTreeSet::new(); + for table in [&edge_index.ref_out, &edge_index.bare_out] { + for (from_path, to_paths) in table { + let from_key = super::workspace_relative_repo_path(from_path); + let Some(from) = module_of_path.get(&from_key) else { + unmapped.insert(from_key); + continue; + }; + for to_path in to_paths { + let to_key = super::workspace_relative_repo_path(to_path); + match module_of_path.get(&to_key) { + Some(to) if to != from => { + edges.insert((from.clone(), to.clone())); + } + Some(_) => {} + None => { + unmapped.insert(to_key); + } + } + } + } + } + if !unmapped.is_empty() { + return Err(format!( + "refusal: {} closure edge endpoint(s) name no module in the index, so the reverse \ + walk would be missing edges: {:?}", + unmapped.len(), + unmapped.iter().take(8).collect::>() + )); + } + Ok((edges.into_iter().collect(), modules.into_iter().collect())) +} + +/// The host's reverse walk: every module from which an edited module is reachable. The model's +/// `regen_reverse_closure` is the same relation as a bounded fold; `lockstep` holds them equal. +pub fn regen_reverse_closure_host( + edited: &[String], + edges: &[(String, String)], +) -> BTreeSet { + let mut dependents_of: HashMap<&str, Vec<&str>> = HashMap::new(); + for (from, to) in edges { + dependents_of + .entry(to.as_str()) + .or_default() + .push(from.as_str()); + } + let mut reached: BTreeSet = edited.iter().cloned().collect(); + let mut frontier: Vec = edited.to_vec(); + while let Some(module) = frontier.pop() { + if let Some(dependents) = dependents_of.get(module.as_str()) { + for dependent in dependents { + if reached.insert((*dependent).to_string()) { + frontier.push((*dependent).to_string()); + } + } + } + } + reached +} + +/// The committed mirror population as `(module, basename)` rows: a module whose mirror basename +/// (`a.b.c` -> `a_b_c.rs`) is a committed generated file. Rows are the join of two authorities the +/// regen already owns -- the module index and the committed generated population -- never a list. +pub fn compared_mirror_rows( + stage0_src: &Path, + modules: &[String], +) -> Result, String> { + let committed: BTreeSet = committed_generated_basenames(stage0_src)? + .into_iter() + .collect(); + Ok(modules + .iter() + .filter_map(|module| { + let basename = format!("{}.rs", module.replace('.', "_")); + committed + .contains(&basename) + .then(|| (module.clone(), basename)) + }) + .collect()) +} + +fn affected_set_entry(source_roots: &[String]) -> Result { + source_roots + .iter() + .map(|root| Path::new(root).join(REGEN_AFFECTED_SET_ENTRY_UNDER_ROOT)) + .find(|candidate| candidate.is_file()) + .map(|found| found.to_string_lossy().into_owned()) + .ok_or_else(|| { + format!( + "refusal: {REGEN_AFFECTED_SET_ENTRY_UNDER_ROOT} is not under any declared source \ + root {source_roots:?}, so the bound has no authority to answer from" + ) + }) +} + +/// Ask the model. The edges handed over are those whose target the host's walk reached -- the +/// subgraph on which the model's bounded fold re-derives the same closure at interpreter cost -- +/// and `modules` is that reached set, which bounds the fold (a path among n modules has at most +/// n-1 edges). The bootstrap rows are the model's own declaration; nothing is passed for them. +pub fn render_affected_set_bound( + source_roots: &[String], + edited: &[String], + unlocatable: &[String], + edges: &[(String, String)], + compared: &[(String, String)], +) -> Result { + use crate::v1_interpreter::{self, str_value, ExecutionMode, Value}; + let entry = affected_set_entry(source_roots)?; + let index = super::process_shared_index(source_roots); + let (graph, indices) = super::resolve_entry_with_index_for_discovery_corpus(&index, &entry) + .map_err(|e| { + format!("refusal: {entry} did not resolve, so the bound cannot answer: {e}") + })?; + let ctx = super::make_eval_context(&graph, indices, ExecutionMode::Hermetic); + + let reached = regen_reverse_closure_host(edited, edges); + let edge_values: Vec = edges + .iter() + .filter(|(_, to)| reached.contains(to)) + .map(|(from, to)| Value::Record { + type_name: ctx.sym("DependencyEdge"), + fields: Rc::new(vec![ + (ctx.sym("from"), str_value(from.clone())), + (ctx.sym("to"), str_value(to.clone())), + ]), + }) + .collect(); + let compared_values: Vec = compared + .iter() + .map(|(module, basename)| Value::Record { + type_name: ctx.sym("MirrorRow"), + fields: Rc::new(vec![ + (ctx.sym("module"), str_value(module.clone())), + (ctx.sym("basename"), str_value(basename.clone())), + ]), + }) + .collect(); + let strs = |items: &[String]| { + let values: Vec = items.iter().map(str_value).collect(); + Value::List(Rc::new(values.into())) + }; + let reached_list: Vec = reached.iter().cloned().collect(); + let args = vec![ + (Some("edited".to_string()), strs(edited)), + (Some("unlocatable".to_string()), strs(unlocatable)), + ( + Some("edges".to_string()), + Value::List(Rc::new(edge_values.into())), + ), + ( + Some("compared".to_string()), + Value::List(Rc::new(compared_values.into())), + ), + (Some("modules".to_string()), strs(&reached_list)), + ]; + let bound = v1_interpreter::with_active_context(&ctx, || { + v1_interpreter::run_in_context_with_args(&ctx, "regen_affected_set", &args, false) + }) + .map_err(|e| format!("refusal: regen_affected_set did not answer: {e}"))?; + let bound_arg = vec![(Some("bound".to_string()), bound)]; + let line = match v1_interpreter::with_active_context(&ctx, || { + v1_interpreter::run_in_context_with_args( + &ctx, + "regen_affected_set_bound_line", + &bound_arg, + false, + ) + }) + .map_err(|e| format!("refusal: regen_affected_set_bound_line did not render: {e}"))? + { + Value::Str(s) => s.to_string(), + other => { + return Err(format!( + "refusal: regen_affected_set_bound_line returned {} where a String was expected", + other.type_label_public() + )) + } + }; + let members: Vec = match v1_interpreter::with_active_context(&ctx, || { + v1_interpreter::run_in_context_with_args( + &ctx, + "regen_affected_set_members", + &bound_arg, + false, + ) + }) + .map_err(|e| format!("refusal: regen_affected_set_members did not answer: {e}"))? + { + Value::List(items) => items + .iter() + .map(|item| match item { + Value::Str(s) => Ok(s.to_string()), + other => Err(format!( + "refusal: regen_affected_set_members holds a {} where a String was expected", + other.type_label_public() + )), + }) + .collect::, _>>()?, + other => { + return Err(format!( + "refusal: regen_affected_set_members returned {} where a List was expected", + other.type_label_public() + )) + } + }; + let arm = line + .strip_prefix("regen-affected-set: ") + .and_then(|rest| rest.split_whitespace().next()) + .ok_or_else(|| format!("refusal: bound line has no arm: {line}"))? + .to_string(); + // LOCKSTEP, every run: on the selecting arm the model's mirrors must be exactly the host's + // reached modules joined to the compared rows. Either side alone could be wrong; agreement is + // the evidence, and disagreement stops the line rather than electing a side. + if arm == "AffectedMirrors" { + let host_mirrors: BTreeSet = compared + .iter() + .filter(|(module, _)| reached.contains(module)) + .map(|(_, basename)| basename.clone()) + .collect(); + let model_mirrors: BTreeSet = members + .iter() + .filter(|m| host_mirrors.contains(*m) || compared.iter().any(|(_, b)| b == *m)) + .cloned() + .collect(); + if host_mirrors != model_mirrors { + return Err(format!( + "refusal: lockstep disagreement -- host reverse walk names {} mirror(s), the model's \ + regen_affected_set names {}; host-only {:?}, model-only {:?}", + host_mirrors.len(), + model_mirrors.len(), + host_mirrors.difference(&model_mirrors).take(8).collect::>(), + model_mirrors.difference(&host_mirrors).take(8).collect::>() + )); + } + } + Ok(AffectedSetBound { line, arm, members }) +} + +/// The bound for an edited population against the live tree: edges and compared rows from the +/// tree, verdict from the model. +pub fn affected_set_bound_for( + workspace: &Path, + source_roots: &[String], + edited: &[String], + unlocatable: &[String], +) -> Result { + let (edges, modules) = regen_module_edges(workspace)?; + let compared = compared_mirror_rows(&workspace.join("src/v1/stage0/src"), &modules)?; + render_affected_set_bound(source_roots, edited, unlocatable, &edges, &compared) +} + +/// `claim_executor --regen-affected-set`: the edited population is the floor's own diff range +/// (the same "what changed" the required floor selects witnesses from), so the selection and the +/// gate read one edit. +pub fn run_regen_affected_set(source_roots: &[String]) -> Result { + let workspace = workspace_root(); + let tree = git_head_sha(&workspace)?; + let tree_dirty = git_tree_dirty(&workspace)?; + let diff_text = super::required_floor_runner::floor_git_diff_range()?; + let population = edited_population_from_diff(&workspace, &diff_text); + let bound = affected_set_bound_for( + &workspace, + source_roots, + &population.edited_modules, + &population.unlocatable, + )?; + let mut rendered = format!( + "regen-affected-set: producer={REGEN_AFFECTED_SET_PRODUCER} host={} tree={tree} tree_dirty={tree_dirty}\n", + host_name() + ); + for module in &population.edited_modules { + rendered.push_str(&format!("regen-affected-set: edited {module}\n")); + } + for path in &population.unlocatable { + rendered.push_str(&format!("regen-affected-set: unlocatable {path}\n")); + } + rendered.push_str(&format!( + "regen-affected-set: non_dag_paths={}\n", + population.non_dag_paths.len() + )); + rendered.push_str(&bound.line); + rendered.push('\n'); + for member in &bound.members { + rendered.push_str(&format!("regen-affected-set: member {member}\n")); + } + Ok(RegenAffectedSetOutcome { + rendered, + arm: bound.arm, + members: bound.members, + }) +} + +#[cfg(test)] +mod regen_affected_set_tests { + use super::*; + + fn roots() -> Vec { + ["dag", "src/v2"] + .iter() + .map(|r| workspace_root().join(r).to_string_lossy().into_owned()) + .collect() + } + + fn s(x: &str) -> String { + x.to_string() + } + + /// The witness's fixture graph, so the host walk and the model fold are held to one answer on + /// the same edges the .dag witness asserts against. + fn fixture_edges() -> Vec<(String, String)> { + vec![ + (s("std.b"), s("std.a")), + (s("gunbc.c"), s("std.b")), + (s("gunbc.d"), s("std.x")), + (s("v1.compiler.emit_rust"), s("std.a")), + ] + } + + fn fixture_compared() -> Vec<(String, String)> { + [ + "std.a", + "std.b", + "gunbc.c", + "gunbc.d", + "std.x", + "v1.compiler.emit_rust", + ] + .iter() + .map(|m| (s(m), format!("{}.rs", m.replace('.', "_")))) + .collect() + } + + #[test] + fn host_walk_and_model_fold_agree_on_the_fixture_graph() { + let host = regen_reverse_closure_host(&[s("std.a")], &fixture_edges()); + assert_eq!( + host, + ["std.a", "std.b", "gunbc.c", "v1.compiler.emit_rust"] + .iter() + .map(|m| s(m)) + .collect::>() + ); + let bound = render_affected_set_bound( + &roots(), + &[s("std.a")], + &[], + &fixture_edges(), + &fixture_compared(), + ) + .expect("the model answers on the fixture"); + assert_eq!(bound.arm, "AffectedMirrors"); + assert_eq!( + bound.line, + "regen-affected-set: AffectedMirrors edited=1 mirrors=4 bootstrap_products=0" + ); + let members: BTreeSet = bound.members.into_iter().collect(); + assert_eq!( + members, + [ + "std_a.rs", + "std_b.rs", + "gunbc_c.rs", + "v1_compiler_emit_rust.rs" + ] + .iter() + .map(|m| s(m)) + .collect() + ); + } + + /// RED CONTROL: an unlocatable path refuses with no members, and does not widen to the + /// population -- the arm is the refusal, and the edited module beside it is not walked. + #[test] + fn an_unlocatable_edited_path_refuses_and_selects_nothing() { + let bound = render_affected_set_bound( + &roots(), + &[s("std.a")], + &[s( + "dag/std/gone.dag (departed: no module line remains in the tree)", + )], + &fixture_edges(), + &fixture_compared(), + ) + .expect("the refusal is an arm, not an error"); + assert_eq!(bound.arm, "EditedSetUnlocatable"); + assert!(bound.members.is_empty()); + assert!(bound + .line + .starts_with("regen-affected-set: EditedSetUnlocatable unlocatable=1 reason=")); + } + + /// The edit reader on a synthetic diff: an existing module is named, a departed `.dag` and an + /// added `.dag` that is not in the tree are unlocatable, and a `.rs` is only counted. + #[test] + fn edited_population_classifies_named_departed_and_missing_paths() { + let diff = "\ +diff --git a/dag/std/content_hash.dag b/dag/std/content_hash.dag +--- a/dag/std/content_hash.dag ++++ b/dag/std/content_hash.dag +@@ -1,1 +1,2 @@ + module std.content_hash ++data planted: Int = 1 +diff --git a/dag/std/gone.dag b/dag/std/gone.dag +deleted file mode 100644 +--- a/dag/std/gone.dag ++++ /dev/null +@@ -1,1 +0,0 @@ +-module std.gone +diff --git a/dag/std/never_written.dag b/dag/std/never_written.dag +new file mode 100644 +--- /dev/null ++++ b/dag/std/never_written.dag +@@ -0,0 +1,1 @@ ++module std.never_written +diff --git a/src/v1/stage0/src/v1_rt.rs b/src/v1/stage0/src/v1_rt.rs +--- a/src/v1/stage0/src/v1_rt.rs ++++ b/src/v1/stage0/src/v1_rt.rs +@@ -1,1 +1,2 @@ + // x ++// y +"; + let population = edited_population_from_diff(&workspace_root(), diff); + assert_eq!(population.edited_modules, vec![s("std.content_hash")]); + assert_eq!( + population.unlocatable.len(), + 2, + "{:?}", + population.unlocatable + ); + assert!(population.unlocatable[0].starts_with("dag/std/gone.dag (departed")); + assert!(population.unlocatable[1].starts_with("dag/std/never_written.dag (unreadable")); + assert_eq!( + population.non_dag_paths, + vec![s("src/v1/stage0/src/v1_rt.rs")] + ); + } + + /// THE LIVE-TREE CONTROLS, one index build for all three: the three measured edits of + /// 2026-08-30 (tree 677988a2 / 0fe2c517) each land on the arm and members the measurement + /// drifted. A leaf std edit names its own mirror and not the runtime shim; the runtime + /// template names its mirror AND the shim through the declared bootstrap edge; an emitter + /// edit is the whole population. + #[test] + fn live_tree_controls_land_on_the_measured_arms() { + let workspace = workspace_root(); + let (edges, modules) = regen_module_edges(&workspace).expect("the closure edge index maps"); + let compared = compared_mirror_rows(&workspace.join("src/v1/stage0/src"), &modules) + .expect("committed population"); + assert!(compared.iter().any(|(m, _)| m == "std.content_hash")); + + let leaf = + render_affected_set_bound(&roots(), &[s("std.content_hash")], &[], &edges, &compared) + .expect("leaf edit answers"); + assert_eq!(leaf.arm, "AffectedMirrors", "{}", leaf.line); + assert!( + leaf.members.iter().any(|m| m == "std_content_hash.rs"), + "{:?}", + leaf.members + ); + assert!( + !leaf.members.iter().any(|m| m == "v1_rt.rs"), + "{:?}", + leaf.members + ); + assert!( + leaf.members.len() < compared.len(), + "the leaf bound is a proper subset" + ); + + let template = render_affected_set_bound( + &roots(), + &[s("v1.compiler.runtime_rust")], + &[], + &edges, + &compared, + ) + .expect("template edit answers"); + assert_eq!(template.arm, "AffectedMirrors", "{}", template.line); + assert!( + template + .members + .iter() + .any(|m| m == "v1_compiler_runtime_rust.rs"), + "{:?}", + template.members + ); + assert!( + template.members.iter().any(|m| m == "v1_rt.rs"), + "{:?}", + template.members + ); + + let emitter = render_affected_set_bound( + &roots(), + &[s("v1.compiler.emit_rust")], + &[], + &edges, + &compared, + ) + .expect("emitter edit answers"); + assert_eq!(emitter.arm, "WholePopulation", "{}", emitter.line); + assert!(emitter.members.is_empty()); + } +} From 5f315ec2a5e7be587971af5dadf03b28f7426c2a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 30 Aug 2026 12:03:18 +0000 Subject: [PATCH 2/2] RUSTFMT_SPAWNS counts normalize spawns, not the version probe (review 57579 on #9738) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KGCnzm2zkb37mVHgrGCt9L --- src/v1/stage0/src/required_regen_host.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/required_regen_host.rs b/src/v1/stage0/src/required_regen_host.rs index 851736a3b6c..e144551fa81 100644 --- a/src/v1/stage0/src/required_regen_host.rs +++ b/src/v1/stage0/src/required_regen_host.rs @@ -1515,7 +1515,8 @@ pub struct ResolvedFormatter { disk_cache: Option, } -/// Every rustfmt spawn this process has made. Read by `--regen-round-cost` before and after +/// Every NORMALIZE spawn this process has made -- the `--version` probe in `with_normalize_cache` +/// is not counted, since the fixed-point claim is about normalizations. Read by `--regen-round-cost` before and after /// the round so the receipt carries the count -- the fixed-point control's claim is that it /// is ~0, and a claim about a count is checked against the count. static RUSTFMT_SPAWNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);