Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions dag/gunbc/regen_affected_set.dag
Original file line number Diff line number Diff line change
@@ -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> = [
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<String> = ["v1.compiler.", "extdeps.languages."]

type AffectedSetBound
= AffectedMirrors { edited: List<String>, mirrors: List<String>, bootstrap_products: List<String> }
| WholePopulation { edited: List<String>, generation_inputs: List<String> }
| EditedSetUnlocatable { unlocatable: List<String>, reason: String }

fn regen_list_has(items: List<String>, 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<String>, edges: List<DependencyEdge>) -> List<String> {
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<String>, edges: List<DependencyEdge>, modules: List<String>) -> List<String> {
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<String>) -> List<String> {
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<String>) -> List<String> {
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<String>,
unlocatable: List<String>,
edges: List<DependencyEdge>,
compared: List<MirrorRow>,
modules: List<String>
) -> 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<String> {
match bound {
AffectedMirrors { edited: e, mirrors: m, bootstrap_products: b } => concat(m, b)
WholePopulation { edited: e, generation_inputs: g } => []
EditedSetUnlocatable { unlocatable: u, reason: r } => []
}
}
4 changes: 2 additions & 2 deletions dag/gunbc/regen_round_cost.dag
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,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
Expand Down
96 changes: 96 additions & 0 deletions dag/test/claim/self_host_regen_affected_set_witness_test.dag
Original file line number Diff line number Diff line change
@@ -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> = [
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<String> = ["std.a", "std.b", "gunbc.c", "gunbc.d", "std.x", "v1.compiler.emit_rust", "v1.compiler.runtime_rust"]

data fixture_compared: List<MirrorRow> = [
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<String>) -> AffectedSetBound {
regen_affected_set(edited: edited, unlocatable: [], edges: fixture_edges, compared: fixture_compared, modules: fixture_modules)
}

fn same_members(a: List<String>, b: List<String>) -> Bool {
count(a) == count(b) && (a |> all(x => b |> any(y => y == x)))
}

fn mirrors_of(bound: AffectedSetBound) -> List<String> {
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"
}
24 changes: 24 additions & 0 deletions src/v1/stage0/src/bin/claim_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ fn run() -> Result<ExitCode, ExitCode> {
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();

Expand Down Expand Up @@ -234,6 +235,12 @@ fn run() -> Result<ExitCode, ExitCode> {
"--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")?;
Expand Down Expand Up @@ -842,6 +849,23 @@ fn run() -> Result<ExitCode, ExitCode> {
// 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(
&regen_candidate_dir,
Expand Down
7 changes: 7 additions & 0 deletions src/v1/stage0/src/cli_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RegenAffectedSetOutcome, String> {
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,
Expand Down
Loading
Loading