diff --git a/dag/gunbc/gen_one_executable_keep_seed_growth.dag b/dag/gunbc/gen_one_executable_keep_seed_growth.dag new file mode 100644 index 00000000000..1a24996865b --- /dev/null +++ b/dag/gunbc/gen_one_executable_keep_seed_growth.dag @@ -0,0 +1,23 @@ +module gunbc.gen_one_executable_keep_seed_growth + +import gunbc.roadmap_model { RoadmapNodeId } +import gunbc.seed_growth { SeedGrowthJustification } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data gen_one_executable_keep_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { + hand_authored_declarations: [ + DeclarationRef { + module_path: "v1_compiler.cli_run.native_lane_runner", + decl_name: "keep_generation_one_executable", + field: WholeDeclaration + } + ], + + reason: "Generation two needs a surviving generation-one executable. Generation two is the BUILT v2.cli.compile_cli binary emitting the v2.compiler.compile closure, and before this declaration the only place that binary existed was the run's PrivateProbeRoot, which removes itself on drop, so no generation-one artifact outlived the //gunbc/instruments:v2-native-cli run that built it and nothing could invoke it as a program. The declaration copies the executable out of the probe root under its own sha256, re-hashes the copy against the identity the run recorded and refuses on any mismatch, then lets the root drop as designed. The root is not retained: its per-run cargo target dir has no consumer once the copy is verified. It prints the kept path together with the binary, closure and seed identities as one provenance record. It performs no part of the generation-two emission and admits nothing, so the seed still does not stand in for the successor step. WHO CONSUMES THE KEPT EXECUTABLE (DESIGN section 3c), stated as a declared frontier because the consumer does not land in this change: the generation-two emission of the self-host closure frontier lane (dashboard node adhoc-a1bbb2a6-2d3), which invokes the kept file as ` emit --entry v2.compiler.compile --source-root dag --source-root src/v2` and records its termination against the kept path and binary identity this declaration prints. That invocation has already run once by hand on srv1 at gunbc#12182's head and produced the lane's first gen-two refusal, but a manual run is a receipt, not a modeled consumer. TRIGGER FOR THE FRONTIER: it becomes consumed by execution when that lane's door change (the census/closure-scoped ingest split, PR-c of that lane) lands the gen-two step as an instrument that spawns this path. If that lane closes without doing so, this declaration has no consumer and is deleted rather than kept. WHY A THROWAWAY IS ADMITTED (DESIGN section 6 scaffold presumption, section 5 scaffold admission): this is deliberately not terminal. It is admitted under the operator's seed Rust policy for this lane (brief of 2026-09-23: seed Rust only if very urgent and blocking major progress, and paired in the same program with the work to get off it). The admission is recorded outside this diff, in that brief and in the v2 Foundation manager's review of the diff, and not by this row's say-so. The pairing is the trigger below.", + + owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, + + trigger: "Delete this declaration when the v2.cli.compile_cli door writes its own crate, that is, when the generation-one executable can assemble and build an emitted closure as a program, without the seed's write_probe_crate, so that generation one is a named, invocable artifact instead of something recovered from a seed probe root. WHAT DOES NOT RETIRE IT: moving the copy to another host file; keeping the probe root through PrivateProbeRoot retain instead of copying; or the door emitting a closure's text while the seed still assembles the crate. Each of those leaves the seed as the only producer of the generation-one executable.", + + current_boundary: "src/v1/stage0/src/cli_run/native_lane_runner.rs keep_generation_one_executable, called once from run_v2_native_cli after walk_cli_door, behind gunbc test //gunbc/instruments:v2-native-cli" +} diff --git a/dag/gunbc/seed_growth_admission.dag b/dag/gunbc/seed_growth_admission.dag index 2228a99e57c..7ff4f588dda 100644 --- a/dag/gunbc/seed_growth_admission.dag +++ b/dag/gunbc/seed_growth_admission.dag @@ -44,6 +44,7 @@ import gunbc.regen_digest_spelling_seed_growth { regen_digest_spelling_seed_grow import gunbc.qualified_pipe_callee_seed_growth { qualified_pipe_callee_seed_growth_justification } import gunbc.shared_fill_attribution_seed_growth { shared_fill_attribution_seed_growth_justification } import gunbc.source_root_eval_driver_seed_growth { source_root_eval_driver_seed_growth_justification } +import gunbc.gen_one_executable_keep_seed_growth { gen_one_executable_keep_seed_growth_justification } import gunbc.roadmap_authority { observation_scoped_run_seed_growth_justification } import gunbc.target_invocation_seed_growth { target_invocation_seed_growth_justification } import gunbc.bootstrap_operation_seed_growth { @@ -267,6 +268,7 @@ fn seed_growth_justification_roster() -> List { claim_batch_entry_closure_preflight_seed_growth_justification, shared_fill_attribution_seed_growth_justification, source_root_eval_driver_seed_growth_justification, + gen_one_executable_keep_seed_growth_justification, census_memo_seed_growth_justification, kind_reflection_seed_growth_justification, typed_module_store_seed_growth_justification, diff --git a/src/v1/stage0/src/cli_run/native_lane_runner.rs b/src/v1/stage0/src/cli_run/native_lane_runner.rs index e6e783bf2ed..376d648355b 100644 --- a/src/v1/stage0/src/cli_run/native_lane_runner.rs +++ b/src/v1/stage0/src/cli_run/native_lane_runner.rs @@ -1347,6 +1347,9 @@ pub struct V2NativeCliHeld { /// spoke of "the one argument the positive control supplies" -- both wrong since that arm became /// an expecting-red probe, and corrected rather than left standing (review 69621). pub door_refusal_exit_status: i64, + /// Where the generation-one executable was kept (`keep_generation_one_executable`), named by + /// its own sha256, carried so the instrument's receipt names the file generation two invokes. + pub generation_one_executable: String, } /// THE V2-EXCLUSIVE CLI, EMITTED AND BUILT. This is the door the self-host step stops in front of. @@ -1762,6 +1765,54 @@ fn walk_cli_door(binary: &Path, workspace: &Path) -> Result<(i64, usize, i64), S Ok((door_exit_status, emitted_bytes, refusal_status)) } +/// THE GENERATION-ONE EXECUTABLE OUTLIVES THE RUN, because generation two is that executable +/// emitting `v2.compiler.compile` and nothing else may stand in for it. The probe root is NOT +/// retained: once the executable is copied out and its copy re-hashes to the identity the run +/// recorded, the root and its per-run cargo target dir have no further consumer, so it drops as +/// `PrivateProbeRoot` is designed to. The copy is named by its own sha256, so the path names the +/// bytes that ran, and the printed line carries the closure and seed identities beside it so the +/// provenance is one record. Declared at `gunbc.gen_one_executable_keep_seed_growth`; it +/// dissolves when the door writes its own crate. +fn keep_generation_one_executable(prepared: &EmittedPreparation) -> Result { + let base = prepared + ._probe_root + .path() + .parent() + .ok_or_else(|| { + format!( + "V2-NATIVE REFUSAL cause=GenOneExecutableNotKept — probe root {} has no parent", + prepared._probe_root.display() + ) + })? + .to_path_buf(); + let kept = base.join(format!( + "v2-native-cli-gen-one-{}", + prepared.binary_identity + )); + std::fs::copy(&prepared.binary_path, &kept).map_err(|e| { + format!( + "V2-NATIVE REFUSAL cause=GenOneExecutableNotKept — copy {} -> {}: {e}", + prepared.binary_path.display(), + kept.display() + ) + })?; + if sha256_file(&kept)? != prepared.binary_identity { + return Err(format!( + "V2-NATIVE REFUSAL cause=GenOneExecutableNotKept — {} does not hash to {}", + kept.display(), + prepared.binary_identity + )); + } + eprintln!( + "v2-native-cli: generation-one executable kept path={} binary_sha256={} closure_identity={} seed_sha256={}", + kept.display(), + prepared.binary_identity, + prepared.closure_identity, + prepared.seed_identity + ); + Ok(kept) +} + pub fn run_v2_native_cli(source_roots: &[String]) -> Result { let started = std::time::Instant::now(); eprintln!( @@ -1781,6 +1832,7 @@ pub fn run_v2_native_cli(source_roots: &[String]) -> Result Result InvocationOutcome { }, message: format!( "v2-native-cli: closure={} binary={} seed={} exit_status={} warning_count={} \ - door_exit_status={} door_emitted_bytes={} door_refusal_exit_status={}", + door_exit_status={} door_emitted_bytes={} door_refusal_exit_status={} \ + generation_one_executable={}", held.closure_identity, held.binary_identity, held.seed_identity, @@ -1028,6 +1029,7 @@ fn run_v2_native_cli(source_roots: &[String]) -> InvocationOutcome { held.door_exit_status, held.door_emitted_bytes, held.door_refusal_exit_status, + held.generation_one_executable, ), }, Err(cause) => InvocationOutcome {