From 53559068ea2d6e8fc9745501ea8ff7b97ec7930b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 05:03:18 +0000 Subject: [PATCH 01/24] route-gap finding: 541/546 enrollments dormant on required runs; drop points and (a)/(b)/(c) split --- docs/plans/route-gap-dormant-observation.md | 100 ++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/plans/route-gap-dormant-observation.md diff --git a/docs/plans/route-gap-dormant-observation.md b/docs/plans/route-gap-dormant-observation.md new file mode 100644 index 00000000000..2f3ce9120f6 --- /dev/null +++ b/docs/plans/route-gap-dormant-observation.md @@ -0,0 +1,100 @@ +# Where the route-gap population drops out of the required run — finding, then proposed repair + +Lane: calm-koi-257 (claim-execution-route). Status: finding complete; repair **proposed, not built** — it +touches `required_floor_runner.rs` (the selection/admission path), which waits for coordinator ack. + +## The population and the run + +`v2.workflow.floor_route_gap.floor_route_gap_roster` (authority: `src/v2/workflow/floor_route_gap.dag`) +enrolls identities the floor executes and that reach a host effect the hermetic route has no arm for +(`NoMockResponse` 420, `FilesystemRemoval` 10, `UnpublishedMockCase` 8 among the 425 typed rows; 100 more +as bare identity strings in chunks 00–05). 525 distinct identities across 146 modules; every one currently +resolves against the tree (verified per identity: module present, `test fn` tail present — zero stale rows +on current main; an earlier count of 2 stale rows was an artifact of my worktree lagging main and was +withdrawn). + +On required run 37236808750 (merge_group, pr-13305, floor job 111537440982): + + [floor-cost-debt] floor_route_gap: 5 enrolled identity(ies) suppressed (cost-debt roster withholds them) + [floor-required-gate] floor_route_gap: 536 enrolled identity(ies) suppressed (module outside the required gate, never loaded) + [floor-route-gap] roster carries 5 enrolled identity(ies) + [floor-route-gap] 5 enrolled identity(ies) held as route-gapped; 0 unenrolled route gap(s) reported + +Arithmetic: 546 decoded = 536 outside-gate + 5 cost-debt-withheld + 5 carried. The source on the run's merge +commit carried 421 typed rows + 120 legacy strings; current main carries 425 + 120. + +## The chain, and the three drop points + +For an enrolled identity the required run promises (route-gap .dag header): execute → gap → held; or execute +→ pass → stale-row red; or enrolled → did not execute → red. What actually happens for 541 of 546: + +1. **Discovery/import** — preparation ranges over the gate closure. The gate (`required_gate_prefixes`, 11 + prefixes + seed modules, cut 2026-08-29, rung drop "Required gate reduced to the compiler floor") admits + only 5 of the 146 modules carrying enrollments. 536 rows sit in modules never loaded: no plan, no + execution, no receipt. *Declared* — the bankruptcy names the population per run in + `required_floor_disposition.tsv` (`declined_outside_gate_closure=26844`, `declined_outside_required_gate=696` + on this run; summary line `declared=28274 offered=1430 routed=704`). +2. **Selection/admission** — the only mechanism asserting "enrolled ⇒ executed" is the route-gap join + (`required_floor_runner.rs`). `suppress_withheld` removes the 541 from the roster *before* the join, so the + join's universe is the 5 gate-inside rows. The suppressed list is produced **per identity with grounds** — + and then discarded: route-gap publishes only the two aggregate count lines above. Expected-red got the + per-identity treatment on 2026-09-01 (`v1_compiler_expected_red_roster_join.rs`: "THE DENOMINATOR IS THE + ENROLLED ROSTER, NOT THE SURVIVORS", suppressed rows recorded with their ground); route-gap never did. +3. **Decision** — the run greens with "5 held; 0 unenrolled gaps". 541 enrollments are in no ledger the run + publishes: the claim-cost TSV carries only executed identities (704 rows, 5 `host_effect_refused` — the + carried ones: `test.claim.parse_test.parse_witness_floor_holds`, `...parse_witness_perf_holds`, + `test.claim.namespace_import_closure_witness.namespace_import_closure_receipt_holds`, + `test.claim.namespace_structural_root_exposure_generated_witness_test.namespace_structural_root_exposure_generated_witness_holds`, + `test.claim.v1_dag_parse_witness.v1_dag_parse_witnesses`); the measurement receipt carries only + `standing` + `blockers`; the disposition/join TSVs are written by the floor job but **not uploaded**. + +## The (a)/(b)/(c) split (sampled, identity grain) + +- **(a) Deliberately outside the supported guarantee** — the wet/service families: machine intake + (`test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.*`, 44 rows), fabric (31), spark + serving (74), runner (13), codex supervised turn (21), workspace storage — claims whose execution needs + service fabric the hermetic required run does not attach by design. Their route-gap rows were enrolled from + the pre-bankruptcy era; the bankruptcy (declared 2026-08-29) is the standing disposition for the family, + and `DeclinedNoCiWetLane`/`DeclinedOutsideRequiredGate` type the planning-level fact per identity. For + these, a per-identity suppressed record on a required run is **measurement, not closure** — closure for + them is the gate/wet-lane decision already declared in the rung drop. +- **(b) Meant to support, unobserved** — the hermetic-adjacent families whose route gap is exactly the + missing mock arm the register exists to demand: filesystem/store mock arms (`artifact_store_fs_witness`, + `durable_exclusive_hold_file_store`, `materialization_store_local`, `effect_plan_bash`: operations Dir 127, + DirWithTemplate 130, DigestStdin 42, Run 46, Check 26) and the withdrawn `v2.test.execution.emit_on_demand_*` + family (probed 2026-10-01, amendment names the restoration trigger: "the restoration trigger above stands + whole"). The register's demand semantics — "a row leaves when a route is supplied" — require observation; + with no observing run, no arm gets built and the trigger cannot fire. The suppression comment's promise + ("observable again ... in the whole-corpus receipts run") is **a promise with no executor**: no workflow in + `.github/workflows/` (8 files, none scheduled for a corpus pass) runs it, and the rung-drop authority + itself rules the escape hatch out ("a receipt-only run outside the required path does NOT retire the row"). + So for (b) the absence of that run is not merely a gap — it is the defect: the demand register's only + claimed observation point does not exist. +- **(c) Defect (structural, live today at zero count)** — the class "enrollment the tree no longer declares". + The mechanism cannot see it: suppression counts it with the (a)/(b) rows, no arm refuses it, and the + roster header itself warns "an enrollment nothing observes is a row that can never ask to be removed". + Zero live rows today (audited); the class is one rename away, and nothing would notice. Also (c): the + route-gap contract's four-arm sentence still claims whole-roster observability and was never amended when + gate-bounded suppression landed (2026-08-29) — two authorities now disagree silently. + +## Proposed repair (one partition in the existing admission path) + +In `suppress_withheld` + the route-gap join in `required_floor_runner.rs`, partition the roster at identity +grain — the cost-debt partition precedent (gunbc#9684) and the expected-red join-report precedent +(2026-09-01), no new system, no new CI job: + +1. **Declared + gate-inside** → observed today, join as now (held / stale-red). +2. **Declared + outside-gate (or cost-debt withheld)** → typed, located, per-identity suppressed row carried + into the outcome (like the expected-red join report) and named per identity on the existing stderr + channel (pattern: the cost-debt "kept as record" line), each with its ground + (`OutsideRequiredGate` / `CostDebtWithheld`). Label: for (a) this is measurement; for (b) it is the + explicit disposition that replaces the green absence. +3. **Undeclared** (module absent from the discovery roots / identity tail absent) → **typed refusal**, red, + naming identity and roster. Fires on zero rows today; keeps it that way. +4. **Text amendments**: the route-gap header's four-arm sentence and the suppression comment's + "whole-corpus receipts run" promise are amended to state the real standing (dormant until the gate + widens; no receipts run exists and none retires rows). + +Acceptance path: author a fresh claim in an out-of-gate module, enroll it in `floor_route_gap`, and the +required run records it per identity with ground `OutsideRequiredGate` — or, if misnamed, refuses. Heavy +runs remote/CI only. From 0a5ae75cabebe852d604d6ec7b24739584b33084 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 07:32:46 +0000 Subject: [PATCH 02/24] route-gap admission partition: undeclared enrollment refuses, suppressed enrollments named per identity; .dag contract amended; admission-control fixture --- .../floor/floor_route_gap_seed_growth.dag | 5 +- .../claim/route_gap_dormancy_receipt_test.dag | 13 ++ docs/plans/route-gap-dormant-observation.md | 36 ++++ .../src/cli_run/required_floor_runner.rs | 180 +++++++++++++++++- src/v2/workflow/floor_route_gap.dag | 31 +++ 5 files changed, 258 insertions(+), 7 deletions(-) create mode 100644 dag/test/claim/route_gap_dormancy_receipt_test.dag diff --git a/dag/gunbc/floor/floor_route_gap_seed_growth.dag b/dag/gunbc/floor/floor_route_gap_seed_growth.dag index e61f79d976b..9e9704b1b3c 100644 --- a/dag/gunbc/floor/floor_route_gap_seed_growth.dag +++ b/dag/gunbc/floor/floor_route_gap_seed_growth.dag @@ -11,9 +11,10 @@ data floor_route_gap_seed_growth_justification: SeedGrowthJustification = SeedGr hand_authored_declarations: [ DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "FloorRouteGapExpectedGround", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "FloorRouteGapExpectation", field: WholeDeclaration }, - DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "floor_route_gap_expectation_mismatch", field: WholeDeclaration } + DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "floor_route_gap_expectation_mismatch", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "route_gap_suppressed_undeclared", field: WholeDeclaration } ], - reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +3, exactly the closed ground mirror, the decoded expectation record, and the pure mismatch classifier enumerated above. All other Rust edits are inside existing declarations and are ExistingSeedItemModified.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run.rs +223/-49 and src/v1/stage0/src/bin/claim_executor.rs +5/-5 against origin/main. The latter adds no declaration. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", + reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +4, exactly the closed ground mirror, the decoded expectation record, the pure mismatch classifier, and the admission-partition classifier (route_gap_suppressed_undeclared: which suppressed enrollments the tree does not declare — the refusal arm of the route-gap admission partition, mirroring partition_cost_debt_roster, which also lives in seed Rust; the route-gap .dag cannot express it because the roster decodes in a hermetic frame that never loads out-of-gate modules, so declaredness is knowledge only the discovery walk has). All other Rust edits are inside existing declarations and are ExistingSeedItemModified.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run.rs +223/-49 and src/v1/stage0/src/bin/claim_executor.rs +5/-5 against origin/main. The latter adds no declaration. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, trigger: "Delete the three seed declarations when the self-emitted claim executor executes the required floor and consumes v2.workflow.floor_route_gap FloorRouteGapExpectation directly; the modeled expectation then remains the sole authority and the hand-written decode/classifier disappears with the v1 floor bridge.", current_boundary: "v2.workflow.floor_route_gap FloorRouteGapExpectation -> v1_compiler.cli_run FloorRouteGapExpectation -> v1_compiler.cli_run floor_route_gap_expectation_mismatch -> v1_compiler.cli_run run_required_floor" diff --git a/dag/test/claim/route_gap_dormancy_receipt_test.dag b/dag/test/claim/route_gap_dormancy_receipt_test.dag new file mode 100644 index 00000000000..4e55e962b1c --- /dev/null +++ b/dag/test/claim/route_gap_dormancy_receipt_test.dag @@ -0,0 +1,13 @@ +module test.claim.route_gap_dormancy_receipt_test + + +// THE ACCEPTANCE CONTROL for the route-gap admission partition (docs/plans/ +// route-gap-dormant-observation.md): a freshly authored claim shaped exactly like the dormant +// population — its module sits outside the required gate, and the identity below is enrolled +// in `v2.workflow.floor_route_gap` — must surface on a required run as a typed, located +// disposition at identity grain (suppressed, ground=outside_required_gate, MEASUREMENT) and +// never as a bare count. The body is never executed: the gate-bounded fold never loads this +// module, which is precisely the point the control witnesses. +test fn route_gap_dormancy_receipt_holds() -> Bool { + 1 == 1 +} diff --git a/docs/plans/route-gap-dormant-observation.md b/docs/plans/route-gap-dormant-observation.md index 2f3ce9120f6..efd2efa6f96 100644 --- a/docs/plans/route-gap-dormant-observation.md +++ b/docs/plans/route-gap-dormant-observation.md @@ -98,3 +98,39 @@ grain — the cost-debt partition precedent (gunbc#9684) and the expected-red jo Acceptance path: author a fresh claim in an out-of-gate module, enroll it in `floor_route_gap`, and the required run records it per identity with ground `OutsideRequiredGate` — or, if misnamed, refuses. Heavy runs remote/CI only. + +## Repair as approved and built (coordinator ack, four conditions) + +Built in the coordinator's order; the Rust delta mirrors `partition_cost_debt_roster`, which also lives in +seed Rust (`cli_run.rs`) — stated here and in the PR body because the route-gap .dag **cannot** express the +refuse-if-undeclared decision: the roster decodes in a hermetic frame that never loads out-of-gate modules, +so declaredness is knowledge only the discovery walk has. The .dag authority owns what the arms MEAN (its +header contract is amended below); Rust executes them. + +1. **The wall (closure)**: `route_gap_suppressed_undeclared` + the refusal at the route-gap suppression + site — an enrollment the tree does not declare (module or tail absent from the discovery roots; the + disposition index covers every declared witness identity, gunbc#9684) refuses the run with + `cause=RouteGapEnrollmentUndeclared`, naming the identities. Fresh-recurrence control: a newly misnamed + enrollment refuses (`route_gap_admission_partition_tests`, three tests, including the declared-dormancy + negative control). +2. **Single authority**: the route-gap .dag header contract now carries the gate-bounded amendment (the + two further arms: suppressed-dormant with ground, undeclared-refuses) beside the original four; the + `suppress_withheld` stderr line no longer promises a whole-corpus receipts run — it states the true + standing: no other observation point exists, and consuming rosters record suppressed identities per + identity, never as a bare count. +3. **Measurement (labeled)**: the route-gap suppression site prints every suppressed identity with its + ground (`suppression_ground_label`), one line per ground, headed MEASUREMENT — for the (a) families this + records declared dormancy and closes nothing. +4. **NOT closed here — class (b)**: an out-of-gate enrollment still demands the route it names, and a + per-identity suppressed row for the `artifact_store_fs` / `effect_plan_bash` / `emit_on_demand` families + remains a green absence of something meant to be observed. What would actually observe them, with its + trigger: **(i)** gate admission of those modules — the bankruptcy's own restoration trigger, "a required + lane resolves every module"; each readmitted module brings its enrollments straight back under the + four-arm join; or **(ii)** a dispatch instrument row that schedules a receipts run over the named + families with the same join armed — which would need a required lane to hang it on, because the rung + drop rules receipt-only runs out as a retirement path. Neither is built in this PR. + +The acceptance fixture: `dag/test/claim/route_gap_dormancy_receipt_test.dag` (module outside the gate) + +one typed enrollment at the head of `floor_route_gap_expectation_chunk_00`, labeled FIXTURE in both files; +on the PR's required run the floor names that identity with ground `outside_required_gate` — and if the +identity ever stops resolving, the run refuses instead. 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 a0c4aef0ce1..63b77712f66 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -269,6 +269,22 @@ pub(crate) fn floor_route_gap_expectation_mismatch( } } +/// THE ROUTE-GAP ADMISSION PARTITION's one testable decision: which suppressed enrollments the +/// tree does not declare. The disposition index covers every declared witness identity over the +/// discovery roots (gunbc#9684), so absence there is absence from the tree. A suppressed +/// enrollment that misses it is a row suppression hides from the reverse join — the stale arm +/// can never fire for it — so the route-gap call site refuses instead of counting it dormant. +pub(crate) fn route_gap_suppressed_undeclared( + suppressed: &[(String, SuppressionGround)], + disposition_index: &HashMap, +) -> Vec<(String, SuppressionGround)> { + suppressed + .iter() + .filter(|(identity, _)| !disposition_index.contains_key(identity)) + .cloned() + .collect() +} + /// `v2.workflow.required_floor`'s claims execute Hermetic (pure in-process evaluation), so /// CPU is the judged basis. A lane that later admits an execution mode whose purpose is /// external or blocking interaction picks wall instead — but the choice is made here, by @@ -8417,7 +8433,10 @@ pub fn run_required_floor( "[floor-required-gate] {name}: {outside_gate} enrolled identity(ies) suppressed \ because their module is outside the required gate and was never loaded; their \ enrollment is dormant, not deleted, and becomes observable again when the gate \ - roster admits the module or in the whole-corpus receipts run" + roster admits the module. There is no other observation point: no whole-corpus \ + receipts run exists or is scheduled, and the rung-drop authority rules a \ + receipt-only run outside the required path out as a retirement path — so the \ + consuming rosters record these identities per identity, never as a bare count" ); } removed @@ -9216,11 +9235,73 @@ pub fn run_required_floor( out }; let mut route_gap_roster = route_gap_roster; - let _ = suppress_withheld(&mut route_gap_roster, "floor_route_gap"); - let _ = suppress_declined_no_ci_wet_lane(&mut route_gap_roster, "floor_route_gap"); + // THE ADMISSION PARTITION IS THE RECEIPT. `suppress_withheld` returns exactly which + // identities it removed and why; this was the one call site that dropped the list + // (`let _ =`), so the reverse join below decided only over the identities a gate-bounded + // run can observe while every other enrollment sat in no ledger the run publishes. + // Measured on required run 37236808750: 546 decoded enrollments = 536 outside-gate + + // 5 cost-debt withheld + 5 carried, and the carried 5 were the join's entire universe. + let mut route_gap_suppressed = suppress_withheld(&mut route_gap_roster, "floor_route_gap"); + route_gap_suppressed.extend(suppress_declined_no_ci_wet_lane( + &mut route_gap_roster, + "floor_route_gap", + )); + // THE WALL: an enrollment the tree does not declare refuses. The disposition index covers + // EVERY declared witness identity over the discovery roots (gunbc#9684), so absence there + // is absence from the tree. This is the one class suppression cannot carry as dormant: a + // renamed, deleted, or fabricated enrollment is removed BEFORE the reverse join, so the + // stale arm can never fire for it, and the cheapest way to fake a green run — enrolling an + // identity that does not exist — would otherwise cost a count, not a refusal. + let route_gap_undeclared = + route_gap_suppressed_undeclared(&route_gap_suppressed, &cost_debt_disposition_index); + if !route_gap_undeclared.is_empty() { + return Err(format!( + "REQUIRED-FLOOR REFUSAL cause=RouteGapEnrollmentUndeclared — the route-gap roster \ + enrolls identity(ies) the tree does not declare. Suppression removes them before \ + the reverse join, so no other guard can ever see them and a row that cannot be \ + observed can never ask to be removed. Delete the enrollment or restore the \ + identity: [{}]", + route_gap_undeclared + .iter() + .map(|(identity, _)| identity.as_str()) + .collect::>() + .join(", ") + )); + } + // MEASUREMENT, NOT CLOSURE: every suppressed enrollment is named at identity grain with + // the ground that removed it, on the roster's own channel. For identities whose module the + // 2026-08-29 gate cut withdrew, this line records a declared dormancy — the gate decision + // owns it, and this record closes nothing for them. + if !route_gap_suppressed.is_empty() { + route_gap_suppressed.sort(); + for ground in [ + SuppressionGround::WithheldCostDebt, + SuppressionGround::OutsideRequiredGate, + SuppressionGround::DeclinedNoCiWetLane, + ] { + let named: Vec<&str> = route_gap_suppressed + .iter() + .filter(|(_, g)| *g == ground) + .map(|(identity, _)| identity.as_str()) + .collect(); + if named.is_empty() { + continue; + } + eprintln!( + "[floor-route-gap] {} enrolled identity(ies) suppressed, kept as record and NOT \ + held as agreement (dormant, not deleted; MEASUREMENT — the {} arm is owned by \ + the gate cut of 2026-08-29 and the cost-debt roster, not by this check): {}", + named.len(), + suppression_ground_label(ground.clone()), + named.join(", ") + ); + } + } eprintln!( - "[floor-route-gap] roster carries {} enrolled identity(ies)", - route_gap_roster.len() + "[floor-route-gap] roster carries {} enrolled identity(ies) after admission; {} \ + suppressed with ground and named above", + route_gap_roster.len(), + route_gap_suppressed.len() ); // New enrollments carry the operation and the closed remedy-ground observed at the @@ -16214,6 +16295,95 @@ mod expected_red_roster_join_suppression_tests { } } +#[cfg(test)] +mod route_gap_admission_partition_tests { + use super::*; + + /// THE FRESH-RECURRENCE CONTROL, at the wall: a NEWLY MISNAMED enrollment suppresses with + /// the rest, and suppression removes it BEFORE the reverse join, so the stale arm can + /// never fire for it. The classifier must return it — the call site turns that into + /// `cause=RouteGapEnrollmentUndeclared` and reds the run — because an enrollment nothing + /// can observe can never ask to be removed. Before this partition, this row cost a count + /// on one stderr line and nothing else. + #[test] + fn a_misnamed_enrollment_is_returned_as_undeclared() { + let suppressed = vec![ + ( + "test.claim.renamed_away_test.old_witness_name".to_string(), + SuppressionGround::OutsideRequiredGate, + ), + ( + "test.claim.fabricated_module_test.never_authored".to_string(), + SuppressionGround::WithheldCostDebt, + ), + ]; + let disposition_index = HashMap::new(); + let undeclared = route_gap_suppressed_undeclared(&suppressed, &disposition_index); + assert_eq!( + undeclared.len(), + 2, + "both rows miss every declared identity" + ); + assert_eq!( + undeclared[0].0, + "test.claim.renamed_away_test.old_witness_name" + ); + } + + /// DECLARED DORMANCY IS NOT REFUSED: the 2026-08-29 gate cut owns the outside-gate arm and + /// the cost-debt roster owns the withheld arm, so an enrollment the tree declares comes + /// back from the classifier clean and is carried to the per-identity measurement record + /// instead. Refusing declared rows here would red every run on 536 pre-existing rows. + #[test] + fn a_declared_suppressed_enrollment_is_not_refused() { + let suppressed = vec![ + ( + "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds" + .to_string(), + SuppressionGround::OutsideRequiredGate, + ), + ( + "test.claim.parse_test.parse_witness_floor_holds".to_string(), + SuppressionGround::WithheldCostDebt, + ), + ]; + let disposition_index: HashMap = [ + ( + "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds" + .to_string(), + RequiredFloorDisposition::DeclinedOutsideGateClosure, + ), + ( + "test.claim.parse_test.parse_witness_floor_holds".to_string(), + RequiredFloorDisposition::DeclinedCostDebt, + ), + ] + .into_iter() + .collect(); + let undeclared = route_gap_suppressed_undeclared(&suppressed, &disposition_index); + assert!( + undeclared.is_empty(), + "declared dormancy is a record, not a refusal" + ); + } + + /// THE GROUND TRAVELS WITH THE ROW, so the printed record names the arm that owns the + /// dormancy rather than one lumped cause for two different owners. + #[test] + fn the_ground_of_each_row_is_carried_through() { + let suppressed = vec![( + "test.claim.missing_test.gone".to_string(), + SuppressionGround::OutsideRequiredGate, + )]; + let undeclared = route_gap_suppressed_undeclared(&suppressed, &HashMap::new()); + assert_eq!(undeclared.len(), 1); + assert!(matches!( + undeclared[0].1, + SuppressionGround::OutsideRequiredGate + )); + } +} + #[cfg(test)] mod pure_producer_share_refused_carrier_overlap_tests { use super::*; diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index 5d98c20e7ac..eb4e6bde1ec 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -63,6 +63,27 @@ type FloorRouteGapExpectation = { // - Enrolled, and the identity did not execute at all: reds the build. The reverse join is // what keeps a renamed or deleted identity from sitting here forever, unobserved. // +// THE GATE-BOUNDED AMENDMENT (2026-10, declared when the required floor's admission partition +// started recording it per identity; the gate cut itself is 2026-08-29). A gate-bounded fold +// loads only the gate closure, so two further arms exist and the four above decide only over +// the gate-inside remainder: +// +// - Enrolled, and the identity's module is outside the required gate (or the identity is +// withheld by the cost-debt roster, or declined as no-CI-wet-lane): the run cannot observe +// it, and the required floor names it per identity with that ground and holds it as NEITHER +// agreement NOR stale — dormancy is the gate cut's declared decision, and this roster does +// not own it. Observability returns exactly when the gate roster admits the module; there +// is no other observation point (no whole-corpus receipts run exists, and a receipt-only +// run outside the required path does not retire rows). +// - Enrolled, and the tree declares no such identity: REFUSES the required run. Suppression +// removes the row before the reverse join, so the stale arm above can never fire for it; +// an enrollment nothing can observe must refuse rather than count as dormant. +// +// WHAT ADMISSION DOES NOT OWN: an out-of-gate enrollment still demands the route it names. +// The register is the demand list for hermetic route arms, and unobserved demand is what a +// rung drop must name — a module the gate readmits brings its enrollments straight back under +// the four arms above. +// // THE ORACLE THIS ROSTER IS ALLOWED TO BE (DESIGN §5, the 2026-08-01 ruling on numeric // literals). It is a monotone debt contract, and it is legitimate only because all four of that // ruling's conditions hold: the subject universe is INDEPENDENTLY DISCOVERED (the floor's own @@ -455,6 +476,16 @@ fn floor_route_gap_chunk_03() -> List { fn floor_route_gap_expectation_chunk_00() -> List { Cons { + // THE ADMISSION CONTROL (route-gap dormancy receipt, docs/plans/ + // route-gap-dormant-observation.md): this row is a FIXTURE, not a measured gap. Its + // module sits outside the required gate, so the gate-bounded fold never loads it; the + // required floor must surface it per identity with ground=outside_required_gate as a + // typed, located disposition (MEASUREMENT) instead of a bare count, and refuse the run + // outright if the identity ever stops resolving. It rides the roster only while the + // dormancy follow-up named in the finding is open; if the gate ever admits the module + // first, this row reds as stale, which is the register working as designed. + head: FloorRouteGapExpectation { identity: "test.claim.route_gap_dormancy_receipt_test.route_gap_dormancy_receipt_holds", operation: "Write", ground: NoMockResponse {} }, + tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.artifact_store_fs_witness.artifact_fs_roundtrip_holds", operation: "Write", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.artifact_store_fs_witness.artifact_fs_delete_then_misses_holds", operation: "Write", ground: NoMockResponse {} }, From 7c76e109fa8881d5ed4d25970ef6ab71def385b6 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 07:39:14 +0000 Subject: [PATCH 03/24] route-gap partition: qualify suppression_ground_label path --- src/v1/stage0/src/cli_run/required_floor_runner.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 63b77712f66..d7904a12316 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -9292,7 +9292,9 @@ pub fn run_required_floor( held as agreement (dormant, not deleted; MEASUREMENT — the {} arm is owned by \ the gate cut of 2026-08-29 and the cost-debt roster, not by this check): {}", named.len(), - suppression_ground_label(ground.clone()), + crate::v1_compiler_expected_red_roster_join::suppression_ground_label( + ground.clone() + ), named.join(", ") ); } From ea5cfb3c47c9828e12cf9bf7245fea6da6ab2897 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 08:23:35 +0000 Subject: [PATCH 04/24] route-gap admission fixture: balance chunk_00 Cons nesting --- src/v2/workflow/floor_route_gap.dag | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index eb4e6bde1ec..d31f7a92adb 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -552,6 +552,7 @@ fn floor_route_gap_expectation_chunk_00() -> List { } } } + } } fn floor_route_gap_expectation_chunk_01() -> List { From 881c99c6ec895ef0294e95cb9021b2cdd0e27b0d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 08:49:42 +0000 Subject: [PATCH 05/24] route-gap partition: drop clone on Copy ground --- src/v1/stage0/src/cli_run/required_floor_runner.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 d7904a12316..b7978b0ebb5 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -9292,9 +9292,7 @@ pub fn run_required_floor( held as agreement (dormant, not deleted; MEASUREMENT — the {} arm is owned by \ the gate cut of 2026-08-29 and the cost-debt roster, not by this check): {}", named.len(), - crate::v1_compiler_expected_red_roster_join::suppression_ground_label( - ground.clone() - ), + crate::v1_compiler_expected_red_roster_join::suppression_ground_label(*ground), named.join(", ") ); } From 9f66118ce5c35996495763771334d8e9599b716f Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 08:53:43 +0000 Subject: [PATCH 06/24] route-gap partition: pass ground by value --- src/v1/stage0/src/cli_run/required_floor_runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b7978b0ebb5..de6186b9275 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -9292,7 +9292,7 @@ pub fn run_required_floor( held as agreement (dormant, not deleted; MEASUREMENT — the {} arm is owned by \ the gate cut of 2026-08-29 and the cost-debt roster, not by this check): {}", named.len(), - crate::v1_compiler_expected_red_roster_join::suppression_ground_label(*ground), + crate::v1_compiler_expected_red_roster_join::suppression_ground_label(ground), named.join(", ") ); } From 3db613a10d8b521e9eadd55cff11f9268ff108e9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 09:47:07 +0000 Subject: [PATCH 07/24] route-gap: remove shipped fixture row and module per review 38602; seed receipt trigger/boundary cover the fourth declaration --- dag/gunbc/floor/floor_route_gap_seed_growth.dag | 13 +++++++------ dag/test/claim/route_gap_dormancy_receipt_test.dag | 13 ------------- src/v2/workflow/floor_route_gap.dag | 11 ----------- 3 files changed, 7 insertions(+), 30 deletions(-) delete mode 100644 dag/test/claim/route_gap_dormancy_receipt_test.dag diff --git a/dag/gunbc/floor/floor_route_gap_seed_growth.dag b/dag/gunbc/floor/floor_route_gap_seed_growth.dag index 9e9704b1b3c..7dec6f50fce 100644 --- a/dag/gunbc/floor/floor_route_gap_seed_growth.dag +++ b/dag/gunbc/floor/floor_route_gap_seed_growth.dag @@ -4,9 +4,10 @@ import gunbc.roadmap_model { RoadmapNodeId } import gunbc.seed_growth { SeedGrowthJustification } import std.decl_ref { DeclarationRef, WholeDeclaration } -// FORWARD-FREEZE RECEIPT for the typed route-gap expectation consumed by the required floor. -// The modeled authority is v2.workflow.floor_route_gap FloorRouteGapExpectation; these three -// declarations are its seed-side realization at the interpreter boundary, not a second policy. +// FORWARD-FREEZE RECEIPT for the typed route-gap expectation and the admission partition +// consumed by the required floor. The modeled authority is v2.workflow.floor_route_gap +// FloorRouteGapExpectation; these four declarations are its seed-side realization at the +// interpreter boundary, not a second policy. data floor_route_gap_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "FloorRouteGapExpectedGround", field: WholeDeclaration }, @@ -14,8 +15,8 @@ data floor_route_gap_seed_growth_justification: SeedGrowthJustification = SeedGr DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "floor_route_gap_expectation_mismatch", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "route_gap_suppressed_undeclared", field: WholeDeclaration } ], - reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +4, exactly the closed ground mirror, the decoded expectation record, the pure mismatch classifier, and the admission-partition classifier (route_gap_suppressed_undeclared: which suppressed enrollments the tree does not declare — the refusal arm of the route-gap admission partition, mirroring partition_cost_debt_roster, which also lives in seed Rust; the route-gap .dag cannot express it because the roster decodes in a hermetic frame that never loads out-of-gate modules, so declaredness is knowledge only the discovery walk has). All other Rust edits are inside existing declarations and are ExistingSeedItemModified.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run.rs +223/-49 and src/v1/stage0/src/bin/claim_executor.rs +5/-5 against origin/main. The latter adds no declaration. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", + reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY THE ADMISSION PARTITION CANNOT LIVE IN THE .dag AUTHORITY: v2.workflow.floor_route_gap owns the register (the roster, the arm semantics, the ground vocabulary), but the refusal arm needs to know whether the tree still declares an enrolled identity, and that knowledge exists only in the discovery walk — the roster decodes inside a hermetic frame whose subject is the gate closure, so out-of-gate modules are never loaded there and the .dag cannot ask the tree anything about them. The disposition index (gunbc#9684) is built by the same Rust discovery loop from the --source-roots. What is missing for .dag expressibility is a modeled declared-identity projection the regen lane would maintain; building one is new tracking machinery this repair deliberately did not add, so the partition decision lives beside its precedent partition_cost_debt_roster, which also lives in seed Rust (v1_compiler.cli_run).\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement, and closes its undeclared-enrollment hole. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +4, exactly the closed ground mirror, the decoded expectation record, the pure mismatch classifier, and the admission-partition classifier (route_gap_suppressed_undeclared: which suppressed enrollments the tree does not declare — the refusal arm of the route-gap admission partition). All other Rust edits are inside existing declarations and are ExistingSeedItemModified.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run/required_floor_runner.rs (call-site partition, per-identity measurement record, refusal, tests) and src/v2/workflow/floor_route_gap.dag (contract amendment) against the pre-repair main. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, - trigger: "Delete the three seed declarations when the self-emitted claim executor executes the required floor and consumes v2.workflow.floor_route_gap FloorRouteGapExpectation directly; the modeled expectation then remains the sole authority and the hand-written decode/classifier disappears with the v1 floor bridge.", - current_boundary: "v2.workflow.floor_route_gap FloorRouteGapExpectation -> v1_compiler.cli_run FloorRouteGapExpectation -> v1_compiler.cli_run floor_route_gap_expectation_mismatch -> v1_compiler.cli_run run_required_floor" + trigger: "Delete the four seed declarations when the self-emitted claim executor executes the required floor and consumes v2.workflow.floor_route_gap FloorRouteGapExpectation directly — including the discovery walk's declared-identity index, without which the refusal arm (route_gap_suppressed_undeclared) cannot decide; the modeled expectation then remains the sole authority and the hand-written decode/classifiers disappear with the v1 floor bridge.", + current_boundary: "v2.workflow.floor_route_gap FloorRouteGapExpectation -> v1_compiler.cli_run FloorRouteGapExpectation -> v1_compiler.cli_run floor_route_gap_expectation_mismatch -> v1_compiler.cli_run route_gap_suppressed_undeclared -> v1_compiler.cli_run run_required_floor" } diff --git a/dag/test/claim/route_gap_dormancy_receipt_test.dag b/dag/test/claim/route_gap_dormancy_receipt_test.dag deleted file mode 100644 index 4e55e962b1c..00000000000 --- a/dag/test/claim/route_gap_dormancy_receipt_test.dag +++ /dev/null @@ -1,13 +0,0 @@ -module test.claim.route_gap_dormancy_receipt_test - - -// THE ACCEPTANCE CONTROL for the route-gap admission partition (docs/plans/ -// route-gap-dormant-observation.md): a freshly authored claim shaped exactly like the dormant -// population — its module sits outside the required gate, and the identity below is enrolled -// in `v2.workflow.floor_route_gap` — must surface on a required run as a typed, located -// disposition at identity grain (suppressed, ground=outside_required_gate, MEASUREMENT) and -// never as a bare count. The body is never executed: the gate-bounded fold never loads this -// module, which is precisely the point the control witnesses. -test fn route_gap_dormancy_receipt_holds() -> Bool { - 1 == 1 -} diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index d31f7a92adb..a9b78642890 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -476,16 +476,6 @@ fn floor_route_gap_chunk_03() -> List { fn floor_route_gap_expectation_chunk_00() -> List { Cons { - // THE ADMISSION CONTROL (route-gap dormancy receipt, docs/plans/ - // route-gap-dormant-observation.md): this row is a FIXTURE, not a measured gap. Its - // module sits outside the required gate, so the gate-bounded fold never loads it; the - // required floor must surface it per identity with ground=outside_required_gate as a - // typed, located disposition (MEASUREMENT) instead of a bare count, and refuse the run - // outright if the identity ever stops resolving. It rides the roster only while the - // dormancy follow-up named in the finding is open; if the gate ever admits the module - // first, this row reds as stale, which is the register working as designed. - head: FloorRouteGapExpectation { identity: "test.claim.route_gap_dormancy_receipt_test.route_gap_dormancy_receipt_holds", operation: "Write", ground: NoMockResponse {} }, - tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.artifact_store_fs_witness.artifact_fs_roundtrip_holds", operation: "Write", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.artifact_store_fs_witness.artifact_fs_delete_then_misses_holds", operation: "Write", ground: NoMockResponse {} }, @@ -552,7 +542,6 @@ fn floor_route_gap_expectation_chunk_00() -> List { } } } - } } fn floor_route_gap_expectation_chunk_01() -> List { From 7d4c3df14234e1bd90786a27717303cc56efb512 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 10:09:01 +0000 Subject: [PATCH 08/24] route-gap: name the instrument, never transcribe its output (DESIGN 6) --- src/v1/stage0/src/cli_run/required_floor_runner.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 407736e6a35..3dee6a8f474 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -9583,9 +9583,9 @@ pub fn run_required_floor( // THE ADMISSION PARTITION IS THE RECEIPT. `suppress_withheld` returns exactly which // identities it removed and why; this was the one call site that dropped the list // (`let _ =`), so the reverse join below decided only over the identities a gate-bounded - // run can observe while every other enrollment sat in no ledger the run publishes. - // Measured on required run 37236808750: 546 decoded enrollments = 536 outside-gate + - // 5 cost-debt withheld + 5 carried, and the carried 5 were the join's entire universe. + // run can observe while every other enrollment sat in no ledger the run publishes. The + // two `[floor-route-gap]` suppressed lines below are the instrument that re-derives the + // split at identity grain on every run — cite them, never a copied count. let mut route_gap_suppressed = suppress_withheld(&mut route_gap_roster, "floor_route_gap"); route_gap_suppressed.extend(suppress_declined_no_ci_wet_lane( &mut route_gap_roster, @@ -16377,7 +16377,9 @@ mod route_gap_admission_partition_tests { /// DECLARED DORMANCY IS NOT REFUSED: the 2026-08-29 gate cut owns the outside-gate arm and /// the cost-debt roster owns the withheld arm, so an enrollment the tree declares comes /// back from the classifier clean and is carried to the per-identity measurement record - /// instead. Refusing declared rows here would red every run on 536 pre-existing rows. + /// instead. Refusing declared rows here would red every required run on the gate-cut + /// population — name the instrument for its size, never a copied count: the + /// `[floor-route-gap]` suppressed lines this repair added re-derive it per run. #[test] fn a_declared_suppressed_enrollment_is_not_refused() { let suppressed = vec![ From 3835dde093918e635cd7073c5aefd81acc719bfc Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 10:53:33 +0000 Subject: [PATCH 09/24] route-gap doc: declare the wall's real standing (unit-authored red, no CI path); fix stale header --- docs/plans/route-gap-dormant-observation.md | 26 ++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/plans/route-gap-dormant-observation.md b/docs/plans/route-gap-dormant-observation.md index efd2efa6f96..aa78f03fef0 100644 --- a/docs/plans/route-gap-dormant-observation.md +++ b/docs/plans/route-gap-dormant-observation.md @@ -1,7 +1,7 @@ # Where the route-gap population drops out of the required run — finding, then proposed repair -Lane: calm-koi-257 (claim-execution-route). Status: finding complete; repair **proposed, not built** — it -touches `required_floor_runner.rs` (the selection/admission path), which waits for coordinator ack. +Lane: calm-koi-257 (claim-execution-route). Status: finding complete; repair BUILT and under review on +PR #13376 (coordinator ack covered all four conditions; reviews 38602/76419/76431 addressed). ## The population and the run @@ -130,7 +130,21 @@ header contract is amended below); Rust executes them. families with the same join armed — which would need a required lane to hang it on, because the rung drop rules receipt-only runs out as a retirement path. Neither is built in this PR. -The acceptance fixture: `dag/test/claim/route_gap_dormancy_receipt_test.dag` (module outside the gate) + -one typed enrollment at the head of `floor_route_gap_expectation_chunk_00`, labeled FIXTURE in both files; -on the PR's required run the floor names that identity with ground `outside_required_gate` — and if the -identity ever stops resolving, the run refuses instead. +**The wall's real standing (corrected per review 76431 — an earlier revision of this paragraph claimed a +shipped fixture; the fixture is gone, per review 38602, and this is what actually holds):** + +- **Green by execution on the required path:** the partition IS the run's own admission step — on every + required run it executes over the real roster, names every suppressed enrollment per identity with its + ground (the `[floor-route-gap]` suppressed lines), and the reverse join decides over the observable + remainder. The consumer is the fold itself; nothing about the green arm is test-only. A freshly authored + claim shaped like the dropped population (declared, out-of-gate, enrolled) gets the same typed, located + disposition on that run as the 536 do. +- **Discriminating red: unit-authored only, no CI path.** The misnamed-enrollment control lives in + `route_gap_admission_partition_tests` and runs via `cargo test --release -p v1-compiler --lib`, which + `docs/onboarding.md` (line 175) says runs on NO required step — a unit-test red does not block a merge. + The refusal arm's red has no CI author today, and shipping one would mean shipping a misnamed enrollment + to the production roster — rejected by review 38602, because it would red main forever and fabricate a + gap the §5 oracle forbids. The named follow-up that could author it on a required lane is the same (b) + follow-up above: a dispatch instrument row that runs a probe roster with the join armed. Until then, this + PR does not claim a CI-authored red for the wall; it claims the typed refusal, green by execution, with + the discriminator at unit grain. From 0f449f9810c5a14557077f476767b35d7eb6e138 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 15:02:31 +0000 Subject: [PATCH 10/24] route-gap partition: model the relation on the .dag authority; Rust marshals only; classifier deleted; fixture + floor route witness + gate row --- .../floor/floor_route_gap_seed_growth.dag | 16 +- .../route_gap_partition_witness_test.dag | 103 ++++ docs/plans/route-gap-dormant-observation.md | 71 +-- .../src/cli_run/required_floor_runner.rs | 506 +++++++++++++----- .../fixture/route_gap_admission_partition.dag | 54 ++ src/v2/workflow/floor_route_gap.dag | 42 ++ src/v2/workflow/required_floor.dag | 6 + 7 files changed, 618 insertions(+), 180 deletions(-) create mode 100644 dag/test/claim/route_gap_partition_witness_test.dag create mode 100644 src/v2/test/fixture/route_gap_admission_partition.dag diff --git a/dag/gunbc/floor/floor_route_gap_seed_growth.dag b/dag/gunbc/floor/floor_route_gap_seed_growth.dag index 7dec6f50fce..da8883e24e5 100644 --- a/dag/gunbc/floor/floor_route_gap_seed_growth.dag +++ b/dag/gunbc/floor/floor_route_gap_seed_growth.dag @@ -4,19 +4,17 @@ import gunbc.roadmap_model { RoadmapNodeId } import gunbc.seed_growth { SeedGrowthJustification } import std.decl_ref { DeclarationRef, WholeDeclaration } -// FORWARD-FREEZE RECEIPT for the typed route-gap expectation and the admission partition -// consumed by the required floor. The modeled authority is v2.workflow.floor_route_gap -// FloorRouteGapExpectation; these four declarations are its seed-side realization at the -// interpreter boundary, not a second policy. +// FORWARD-FREEZE RECEIPT for the typed route-gap expectation consumed by the required floor. +// The modeled authority is v2.workflow.floor_route_gap FloorRouteGapExpectation; these three +// declarations are its seed-side realization at the interpreter boundary, not a second policy. data floor_route_gap_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "FloorRouteGapExpectedGround", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "FloorRouteGapExpectation", field: WholeDeclaration }, - DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "floor_route_gap_expectation_mismatch", field: WholeDeclaration }, - DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "route_gap_suppressed_undeclared", field: WholeDeclaration } + DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "floor_route_gap_expectation_mismatch", field: WholeDeclaration } ], - reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY THE ADMISSION PARTITION CANNOT LIVE IN THE .dag AUTHORITY: v2.workflow.floor_route_gap owns the register (the roster, the arm semantics, the ground vocabulary), but the refusal arm needs to know whether the tree still declares an enrolled identity, and that knowledge exists only in the discovery walk — the roster decodes inside a hermetic frame whose subject is the gate closure, so out-of-gate modules are never loaded there and the .dag cannot ask the tree anything about them. The disposition index (gunbc#9684) is built by the same Rust discovery loop from the --source-roots. What is missing for .dag expressibility is a modeled declared-identity projection the regen lane would maintain; building one is new tracking machinery this repair deliberately did not add, so the partition decision lives beside its precedent partition_cost_debt_roster, which also lives in seed Rust (v1_compiler.cli_run).\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement, and closes its undeclared-enrollment hole. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +4, exactly the closed ground mirror, the decoded expectation record, the pure mismatch classifier, and the admission-partition classifier (route_gap_suppressed_undeclared: which suppressed enrollments the tree does not declare — the refusal arm of the route-gap admission partition). All other Rust edits are inside existing declarations and are ExistingSeedItemModified.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run/required_floor_runner.rs (call-site partition, per-identity measurement record, refusal, tests) and src/v2/workflow/floor_route_gap.dag (contract amendment) against the pre-repair main. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", + reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY THE ADMISSION PARTITION IS NOT A FOURTH DECLARATION: the partition decision is modeled on the authority itself — v2.workflow.floor_route_gap.floor_route_gap_admission_partition, the single implementation of the route-gap admission decision — and the runner marshals the run's real values (the suppressed identities with their ground labels, and the discovery walk's declared-identity index) into it through run_in_context_with_args. An earlier revision carried a Rust classifier (route_gap_suppressed_undeclared) beside the modeled relation; it was DELETED rather than kept beside the .dag call, so the membership test has one implementation. What remains in Rust is marshaling and the refusal raise, inside existing declarations (ExistingSeedItemModified) — a net reduction of seed decision surface. The run-time declared index is still discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the gate closure, so out-of-gate modules are never loaded there); a modeled declared-identity projection the regen lane would maintain is what would move even the marshal into the .dag, and that projection was deliberately not built here.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement, and closes its undeclared-enrollment hole. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +3, exactly the closed ground mirror, the decoded expectation record, and the pure mismatch classifier enumerated above. All other Rust edits are inside existing declarations and are ExistingSeedItemModified.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run/required_floor_runner.rs (call-site marshal + modeled-partition call, per-identity measurement record, refusal, pairing witness through run_in_context_with_args, tests), src/v2/workflow/floor_route_gap.dag (contract amendment + the modeled partition), src/v2/workflow/required_floor.dag (one exact-grain authored-module row for test.claim.route_gap_partition_witness), src/v2/test/fixture/route_gap_admission_partition.dag and dag/test/claim/route_gap_partition_witness_test.dag (the shared fixture and the floor-side route witness) against the pre-repair main. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, - trigger: "Delete the four seed declarations when the self-emitted claim executor executes the required floor and consumes v2.workflow.floor_route_gap FloorRouteGapExpectation directly — including the discovery walk's declared-identity index, without which the refusal arm (route_gap_suppressed_undeclared) cannot decide; the modeled expectation then remains the sole authority and the hand-written decode/classifiers disappear with the v1 floor bridge.", - current_boundary: "v2.workflow.floor_route_gap FloorRouteGapExpectation -> v1_compiler.cli_run FloorRouteGapExpectation -> v1_compiler.cli_run floor_route_gap_expectation_mismatch -> v1_compiler.cli_run route_gap_suppressed_undeclared -> v1_compiler.cli_run run_required_floor" + trigger: "Delete the three seed declarations when the self-emitted claim executor executes the required floor and consumes v2.workflow.floor_route_gap FloorRouteGapExpectation directly; the modeled expectation and the modeled admission partition then remain the sole authorities and the hand-written decode/classifier disappears with the v1 floor bridge.", + current_boundary: "v2.workflow.floor_route_gap FloorRouteGapExpectation and floor_route_gap_admission_partition -> v1_compiler.cli_run FloorRouteGapExpectation -> v1_compiler.cli_run.required_floor_runner floor_route_gap_expectation_mismatch and the run-site marshal -> v1_compiler.cli_run run_required_floor" } diff --git a/dag/test/claim/route_gap_partition_witness_test.dag b/dag/test/claim/route_gap_partition_witness_test.dag new file mode 100644 index 00000000000..eab21e5400c --- /dev/null +++ b/dag/test/claim/route_gap_partition_witness_test.dag @@ -0,0 +1,103 @@ +module test.claim.route_gap_partition_witness + +import std.types { List, Bool, Int } +import v2.std.integer { int_add } +import v2.workflow.floor_route_gap { + FloorRouteGapAdmissionRow, + FloorRouteGapAdmissionMeasuredRow, + FloorRouteGapAdmissionRefusedRow, + floor_route_gap_admission_partition, +} +import v2.test.fixture.route_gap_admission_partition { + route_gap_partition_fixture_suppressed, + route_gap_partition_fixture_declared, +} + +// THE ROUTE WITNESS for the route-gap admission partition (docs/plans/ +// route-gap-dormant-observation.md). The required floor's runner marshals the run's real values +// -- the identities suppression removed with the ground that removed each, and the discovery +// walk's declared-identity index -- into `v2.workflow.floor_route_gap` +// `.floor_route_gap_admission_partition`, the modeled relation on the authority that owns the +// register; the runner-side classifier this relation replaces was deleted, so this surface is +// the run site's only route to the answer. This claim drives that surface over the shared +// fixture (src/v2/test/fixture/route_gap_admission_partition.dag) and asserts both arms at +// identity grain on the floor: a declared suppressed row comes back MEASURED with its ground +// kept, and a misnamed enrollment -- renamed, deleted, or fabricated -- comes back REFUSED, the +// wall's discriminating arm. Substrate inputs only: constructed lists through the modeled +// relation; no host effect, no service, no wet lane. The seed's pairing witness in +// `required_floor_runner` evaluates the same fixture rows through `run_in_context_with_args`, +// the real call path the run site uses. +fn measured_row_holds(row: FloorRouteGapAdmissionRow, identity: String, ground: String) -> Bool { + match row { + FloorRouteGapAdmissionMeasuredRow { identity: i, ground: g } => i == identity && g == ground + FloorRouteGapAdmissionRefusedRow { identity: _ } => false + } +} + +fn refused_row_holds(row: FloorRouteGapAdmissionRow, identity: String) -> Bool { + match row { + FloorRouteGapAdmissionMeasuredRow { identity: _, ground: _ } => false + FloorRouteGapAdmissionRefusedRow { identity: i } => i == identity + } +} + +fn count_measured_holding( + rows: List, + identity: String, + ground: String, +) -> Int { + fold(rows, init: 0, f: fn(acc, r) { + if measured_row_holds(row: r, identity: identity, ground: ground) { + int_add(a: acc, b: 1) + } else { + acc + } + }) +} + +fn count_refused_naming(rows: List, identity: String) -> Int { + fold(rows, init: 0, f: fn(acc, r) { + if refused_row_holds(row: r, identity: identity) { + int_add(a: acc, b: 1) + } else { + acc + } + }) +} + +// THE MEASUREMENT ARM, AT IDENTITY GRAIN: a declared suppressed row keeps its ground and is +// named, never absorbed into a count. For identities whose module the 2026-08-29 gate cut +// withdrew this record closes nothing -- the gate decision owns their dormancy. +test fn the_partition_measures_a_declared_suppressed_row_with_its_ground_kept() -> Bool { + let decided = floor_route_gap_admission_partition( + suppressed: route_gap_partition_fixture_suppressed, + declared: route_gap_partition_fixture_declared + ) + count(decided) == 3 + && count_measured_holding( + rows: decided, + identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", + ground: "outside_required_gate" + ) == 1 + && count_measured_holding( + rows: decided, + identity: "test.claim.parse_test.parse_witness_floor_holds", + ground: "withheld_cost_debt" + ) == 1 +} + +// THE WALL'S DISCRIMINATING ARM: an enrollment the tree does not declare refuses. Suppression +// removes such a row before the reverse join, so no other guard can ever see it; the cheapest +// way to fake a green run -- enrolling an identity that does not exist -- must come back as a +// refusal, never as a count. +test fn the_partition_refuses_an_enrollment_the_tree_does_not_declare() -> Bool { + let decided = floor_route_gap_admission_partition( + suppressed: route_gap_partition_fixture_suppressed, + declared: route_gap_partition_fixture_declared + ) + count(decided) == 3 + && count_refused_naming( + rows: decided, + identity: "test.claim.renamed_away_witness_test.old_witness_name" + ) == 1 +} diff --git a/docs/plans/route-gap-dormant-observation.md b/docs/plans/route-gap-dormant-observation.md index aa78f03fef0..d1093e2ade6 100644 --- a/docs/plans/route-gap-dormant-observation.md +++ b/docs/plans/route-gap-dormant-observation.md @@ -99,36 +99,47 @@ Acceptance path: author a fresh claim in an out-of-gate module, enroll it in `fl required run records it per identity with ground `OutsideRequiredGate` — or, if misnamed, refuses. Heavy runs remote/CI only. -## Repair as approved and built (coordinator ack, four conditions) - -Built in the coordinator's order; the Rust delta mirrors `partition_cost_debt_roster`, which also lives in -seed Rust (`cli_run.rs`) — stated here and in the PR body because the route-gap .dag **cannot** express the -refuse-if-undeclared decision: the roster decodes in a hermetic frame that never loads out-of-gate modules, -so declaredness is knowledge only the discovery walk has. The .dag authority owns what the arms MEAN (its -header contract is amended below); Rust executes them. - -1. **The wall (closure)**: `route_gap_suppressed_undeclared` + the refusal at the route-gap suppression - site — an enrollment the tree does not declare (module or tail absent from the discovery roots; the - disposition index covers every declared witness identity, gunbc#9684) refuses the run with - `cause=RouteGapEnrollmentUndeclared`, naming the identities. Fresh-recurrence control: a newly misnamed - enrollment refuses (`route_gap_admission_partition_tests`, three tests, including the declared-dormancy - negative control). -2. **Single authority**: the route-gap .dag header contract now carries the gate-bounded amendment (the - two further arms: suppressed-dormant with ground, undeclared-refuses) beside the original four; the - `suppress_withheld` stderr line no longer promises a whole-corpus receipts run — it states the true - standing: no other observation point exists, and consuming rosters record suppressed identities per - identity, never as a bare count. -3. **Measurement (labeled)**: the route-gap suppression site prints every suppressed identity with its - ground (`suppression_ground_label`), one line per ground, headed MEASUREMENT — for the (a) families this - records declared dormancy and closes nothing. -4. **NOT closed here — class (b)**: an out-of-gate enrollment still demands the route it names, and a - per-identity suppressed row for the `artifact_store_fs` / `effect_plan_bash` / `emit_on_demand` families - remains a green absence of something meant to be observed. What would actually observe them, with its - trigger: **(i)** gate admission of those modules — the bankruptcy's own restoration trigger, "a required - lane resolves every module"; each readmitted module brings its enrollments straight back under the - four-arm join; or **(ii)** a dispatch instrument row that schedules a receipts run over the named - families with the same join armed — which would need a required lane to hang it on, because the rung - drop rules receipt-only runs out as a retirement path. Neither is built in this PR. +## Repair as approved and built (coordinator ack, conditions + single-implementation round) + +The partition decision is now **modeled on the .dag authority** — +`v2.workflow.floor_route_gap.floor_route_gap_admission_partition` is the single implementation of the +route-gap admission decision; the Rust call site marshals the run's real values (suppressed identities +with their ground labels, and the discovery walk's declared-identity index) into it through +`run_in_context_with_args`, and an earlier Rust classifier (`route_gap_suppressed_undeclared`) was +**deleted** rather than kept beside the call — one implementation, a net reduction of seed decision +surface; the seed receipt is back to +3 hand items. What would move even the marshal into the .dag is a +modeled declared-identity projection the regen lane would maintain; deliberately not built. The +refuse-if-undeclared decision could not have been expressed in .dag on its own because the run-time +declared index is discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the +gate closure); modeling the RELATION and marshaling the index in is the resolution of that. + +1. **The wall (closure)**: a refused row names an enrollment the tree does not declare (module or tail + absent from the discovery roots; the disposition index covers every declared witness identity, + gunbc#9684) and the run refuses with `cause=RouteGapEnrollmentUndeclared`, naming the identities and + the modeled entry that decided. +2. **Single authority**: the route-gap .dag header contract states the gate-bounded amendment (the two + further arms) beside the original four; the `suppress_withheld` stderr line no longer promises a + whole-corpus receipts run — it states the true standing; and the membership test has exactly one + implementation, on the authority. +3. **Measurement (labeled)**: every suppressed identity is named with the ground the modeled partition + returned for it, headed MEASUREMENT — for the (a) families this records declared dormancy and closes + nothing. +4. **Not closed here — class (b)**: unchanged from the section above; the named follow-ups are gate + admission of those modules, or a dispatch instrument row running a probe roster with the join armed. + +**Evidence, pinned on both sides of the boundary:** +- **Floor-side route witness** — `test.claim.route_gap_partition_witness`, gate-admitted at exact module + grain in `required_gate_authored_modules` (the same mechanism and operator ruling as + `test.claim.discovery_census_witness` and `test.claim.seed_growth_admission_witness`): drives the + modeled relation over the shared fixture `src/v2/test/fixture/route_gap_admission_partition.dag` and + asserts both arms on the floor — the route, executed by a required run. Substrate inputs only + (constructed lists; no host effect, no service). +- **Seed-side pairing witness** — `route_gap_admission_partition_tests` in `required_floor_runner.rs` + drives the SAME entry by the SAME constant through `run_in_context_with_args` (the real call path the + run site uses) and asserts both arms at identity grain; a second test pins the marshal shape (identity + and ground label, nothing else). Local diligence, not a CI path (`docs/onboarding.md` line 175). +- **One implementation**: with the Rust classifier deleted, the run site's route to the answer is the + modeled entry by construction; a run-path refusal names the entry that decided it. **The wall's real standing (corrected per review 76431 — an earlier revision of this paragraph claimed a shipped fixture; the fixture is gone, per review 38602, and this is what actually holds):** 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 3dee6a8f474..bfd9402ba48 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -269,20 +269,33 @@ pub(crate) fn floor_route_gap_expectation_mismatch( } } -/// THE ROUTE-GAP ADMISSION PARTITION's one testable decision: which suppressed enrollments the -/// tree does not declare. The disposition index covers every declared witness identity over the -/// discovery roots (gunbc#9684), so absence there is absence from the tree. A suppressed -/// enrollment that misses it is a row suppression hides from the reverse join — the stale arm -/// can never fire for it — so the route-gap call site refuses instead of counting it dormant. -pub(crate) fn route_gap_suppressed_undeclared( - suppressed: &[(String, SuppressionGround)], - disposition_index: &HashMap, -) -> Vec<(String, SuppressionGround)> { - suppressed - .iter() - .filter(|(identity, _)| !disposition_index.contains_key(identity)) - .cloned() - .collect() +/// THE ROUTE the run site takes to the admission decision. The relation it names is modeled on +/// `v2.workflow.floor_route_gap` — the authority that owns the register — and there is no Rust +/// copy of the membership test: this constant is the single route, and the pairing witness +/// drives the same name through `run_in_context_with_args` so a rename here breaks the witness +/// instead of silently forking the decision. +pub(crate) const ROUTE_GAP_ADMISSION_PARTITION_ENTRY: &str = + "v2.workflow.floor_route_gap.floor_route_gap_admission_partition"; + +/// THE MARSHAL the run site and the pairing witness share: a suppressed row reaches the modeled +/// relation as a `FloorRouteGapSuppressedRow` record carrying its identity and the label of the +/// ground that removed it, and nothing else. One marshal, so the shape cannot fork between the +/// real roster and the fixture. +pub(crate) fn route_gap_suppressed_rows_value( + ctx: &v1_interpreter::InterpContext, + rows: &[(String, String)], +) -> v1_interpreter::Value { + v1_interpreter::list_value( + rows.iter() + .map(|(identity, ground)| v1_interpreter::Value::Record { + type_name: ctx.sym("FloorRouteGapSuppressedRow"), + fields: Rc::new(vec![ + (ctx.sym("identity"), v1_interpreter::str_value(identity)), + (ctx.sym("ground"), v1_interpreter::str_value(ground)), + ]), + }) + .collect::>(), + ) } /// `v2.workflow.required_floor`'s claims execute Hermetic (pure in-process evaluation), so @@ -9580,73 +9593,146 @@ pub fn run_required_floor( out }; let mut route_gap_roster = route_gap_roster; - // THE ADMISSION PARTITION IS THE RECEIPT. `suppress_withheld` returns exactly which - // identities it removed and why; this was the one call site that dropped the list - // (`let _ =`), so the reverse join below decided only over the identities a gate-bounded - // run can observe while every other enrollment sat in no ledger the run publishes. The - // two `[floor-route-gap]` suppressed lines below are the instrument that re-derives the - // split at identity grain on every run — cite them, never a copied count. + // THE ADMISSION PARTITION IS THE MODELED RELATION, and the relation is the single + // implementation. `suppress_withheld` returns exactly which identities it removed and why; + // this was the one call site that dropped the list (`let _ =`), so the reverse join below + // decided only over the identities a gate-bounded run can observe while every other + // enrollment sat in no ledger the run publishes. The runner now marshals the run's real + // values -- the suppressed identities with the label of the ground that removed each, and + // the discovery walk's declared-identity index -- into `v2.workflow.floor_route_gap` + // `.floor_route_gap_admission_partition` through the hermetic frame the manifest was folded + // in. There is no Rust copy of the membership test to rot apart from the roster it joins; + // the arms and their meaning live on the authority's contract, and the pairing witness + // (`test.claim.route_gap_partition_witness`, floor-side) drives the same surface over the + // shared fixture. let mut route_gap_suppressed = suppress_withheld(&mut route_gap_roster, "floor_route_gap"); route_gap_suppressed.extend(suppress_declined_no_ci_wet_lane( &mut route_gap_roster, "floor_route_gap", )); - // THE WALL: an enrollment the tree does not declare refuses. The disposition index covers - // EVERY declared witness identity over the discovery roots (gunbc#9684), so absence there - // is absence from the tree. This is the one class suppression cannot carry as dormant: a - // renamed, deleted, or fabricated enrollment is removed BEFORE the reverse join, so the - // stale arm can never fire for it, and the cheapest way to fake a green run — enrolling an - // identity that does not exist — would otherwise cost a count, not a refusal. - let route_gap_undeclared = - route_gap_suppressed_undeclared(&route_gap_suppressed, &cost_debt_disposition_index); - if !route_gap_undeclared.is_empty() { + route_gap_suppressed.sort(); + // + // THE WALL: a refused row names an enrollment the tree does not declare. The disposition + // index covers EVERY declared witness identity over the discovery roots (gunbc#9684), so + // absence there is absence from the tree — and suppression removed the row BEFORE the + // reverse join, so the stale arm can never fire for it. The cheapest way to fake a green + // run, enrolling an identity that does not exist, costs a refusal, never a count. + let suppressed_rows_value = route_gap_suppressed_rows_value( + &hermetic, + &route_gap_suppressed + .iter() + .map(|(identity, ground)| { + ( + identity.clone(), + crate::v1_compiler_expected_red_roster_join::suppression_ground_label(*ground), + ) + }) + .collect::>(), + ); + let declared_value = v1_interpreter::list_value( + cost_debt_disposition_index + .keys() + .map(|k| v1_interpreter::str_value(k)) + .collect::>(), + ); + let route_gap_decided = v1_interpreter::run_in_context_with_args( + &hermetic, + ROUTE_GAP_ADMISSION_PARTITION_ENTRY, + &[ + (Some("suppressed".to_string()), suppressed_rows_value), + (Some("declared".to_string()), declared_value), + ], + false, + ) + .map_err(|e| format!("route_gap_admission_partition: {e}"))?; + let decided_rows = floor_decode_list(&hermetic, Some(&route_gap_decided)) + .map_err(|e| format!("route_gap_admission_partition: {e}"))?; + let mut route_gap_refused: Vec = Vec::new(); + let mut route_gap_measured: Vec<(String, String)> = Vec::new(); + for row in decided_rows { + let v1_interpreter::Value::Variant { + variant_name, + fields, + .. + } = row + else { + return Err(format!( + "route_gap_admission_partition: malformed admission row, observed {}", + floor_value_shape(Some(row)) + )); + }; + let field_str = |name: &str| -> Option { + let value = hermetic.field(fields, name)?; + match value { + v1_interpreter::Value::Str(s) => Some(s.to_string()), + _ => None, + } + }; + match hermetic.resolve(*variant_name).as_str() { + "FloorRouteGapAdmissionRefusedRow" => match field_str("identity") { + Some(identity) => route_gap_refused.push(identity), + None => { + return Err( + "route_gap_admission_partition: a refused row carries no identity" + .to_string(), + ) + } + }, + "FloorRouteGapAdmissionMeasuredRow" => { + match (field_str("identity"), field_str("ground")) { + (Some(identity), Some(ground)) => route_gap_measured.push((identity, ground)), + _ => { + return Err( + "route_gap_admission_partition: a measured row carries no identity or ground" + .to_string(), + ) + } + } + } + other => { + return Err(format!( + "route_gap_admission_partition: unknown admission arm {other}" + )) + } + } + } + if !route_gap_refused.is_empty() { return Err(format!( "REQUIRED-FLOOR REFUSAL cause=RouteGapEnrollmentUndeclared — the route-gap roster \ - enrolls identity(ies) the tree does not declare. Suppression removes them before \ + enrolls identity(ies) the tree does not declare, refused by the modeled admission \ + partition ({ROUTE_GAP_ADMISSION_PARTITION_ENTRY}). Suppression removes them before \ the reverse join, so no other guard can ever see them and a row that cannot be \ observed can never ask to be removed. Delete the enrollment or restore the \ identity: [{}]", - route_gap_undeclared - .iter() - .map(|(identity, _)| identity.as_str()) - .collect::>() - .join(", ") + route_gap_refused.join(", ") )); } - // MEASUREMENT, NOT CLOSURE: every suppressed enrollment is named at identity grain with - // the ground that removed it, on the roster's own channel. For identities whose module the - // 2026-08-29 gate cut withdrew, this line records a declared dormancy — the gate decision - // owns it, and this record closes nothing for them. - if !route_gap_suppressed.is_empty() { - route_gap_suppressed.sort(); - for ground in [ - SuppressionGround::WithheldCostDebt, - SuppressionGround::OutsideRequiredGate, - SuppressionGround::DeclinedNoCiWetLane, - ] { - let named: Vec<&str> = route_gap_suppressed - .iter() - .filter(|(_, g)| *g == ground) - .map(|(identity, _)| identity.as_str()) - .collect(); - if named.is_empty() { - continue; - } - eprintln!( - "[floor-route-gap] {} enrolled identity(ies) suppressed, kept as record and NOT \ - held as agreement (dormant, not deleted; MEASUREMENT — the {} arm is owned by \ - the gate cut of 2026-08-29 and the cost-debt roster, not by this check): {}", - named.len(), - crate::v1_compiler_expected_red_roster_join::suppression_ground_label(ground), - named.join(", ") - ); + // MEASUREMENT, NOT CLOSURE: every suppressed enrollment is named at identity grain with the + // ground the modeled partition returned for it, on the roster's own channel. For identities + // whose module the 2026-08-29 gate cut withdrew, this line records a declared dormancy — + // the gate decision owns it, and this record closes nothing for them. + let mut measured_by_ground: Vec<(String, Vec)> = Vec::new(); + for (identity, ground) in &route_gap_measured { + match measured_by_ground.iter_mut().find(|(g, _)| g == ground) { + Some((_, named)) => named.push(identity.clone()), + None => measured_by_ground.push((ground.clone(), vec![identity.clone()])), } } + measured_by_ground.sort(); + for (ground, named) in &measured_by_ground { + eprintln!( + "[floor-route-gap] {} enrolled identity(ies) suppressed, kept as record and NOT \ + held as agreement (dormant, not deleted; MEASUREMENT — the {ground} arm is owned by \ + the gate cut of 2026-08-29 and the cost-debt roster, not by this check): {}", + named.len(), + named.join(", ") + ); + } eprintln!( "[floor-route-gap] roster carries {} enrolled identity(ies) after admission; {} \ suppressed with ground and named above", route_gap_roster.len(), - route_gap_suppressed.len() + route_gap_measured.len() ); // New enrollments carry the operation and the closed remedy-ground observed at the @@ -16343,90 +16429,228 @@ mod expected_red_roster_join_suppression_tests { mod route_gap_admission_partition_tests { use super::*; - /// THE FRESH-RECURRENCE CONTROL, at the wall: a NEWLY MISNAMED enrollment suppresses with - /// the rest, and suppression removes it BEFORE the reverse join, so the stale arm can - /// never fire for it. The classifier must return it — the call site turns that into - /// `cause=RouteGapEnrollmentUndeclared` and reds the run — because an enrollment nothing - /// can observe can never ask to be removed. Before this partition, this row cost a count - /// on one stderr line and nothing else. - #[test] - fn a_misnamed_enrollment_is_returned_as_undeclared() { - let suppressed = vec![ - ( - "test.claim.renamed_away_test.old_witness_name".to_string(), - SuppressionGround::OutsideRequiredGate, - ), - ( - "test.claim.fabricated_module_test.never_authored".to_string(), - SuppressionGround::WithheldCostDebt, - ), - ]; - let disposition_index = HashMap::new(); - let undeclared = route_gap_suppressed_undeclared(&suppressed, &disposition_index); - assert_eq!( - undeclared.len(), - 2, - "both rows miss every declared identity" - ); - assert_eq!( - undeclared[0].0, - "test.claim.renamed_away_test.old_witness_name" - ); + fn string_list(value: &Value, what: &str) -> Vec { + let Value::List(items) = value else { + panic!("{what} is not a List"); + }; + items + .iter() + .map(|item| match item { + Value::Str(s) => s.to_string(), + _ => panic!("{what} holds a non-String element"), + }) + .collect() } - /// DECLARED DORMANCY IS NOT REFUSED: the 2026-08-29 gate cut owns the outside-gate arm and - /// the cost-debt roster owns the withheld arm, so an enrollment the tree declares comes - /// back from the classifier clean and is carried to the per-identity measurement record - /// instead. Refusing declared rows here would red every required run on the gate-cut - /// population — name the instrument for its size, never a copied count: the - /// `[floor-route-gap]` suppressed lines this repair added re-derive it per run. + fn string_list_value(xs: &[String]) -> Value { + Value::List(Rc::new( + xs.iter() + .map(v1_interpreter::str_value) + .collect::>() + .into(), + )) + } + + /// THE FIXTURE'S SUPPRESSED ROWS, decoded to (identity, ground-label) pairs — the shape the + /// shared marshal re-encodes. + fn suppressed_row_pairs( + ctx: &v1_interpreter::InterpContext, + value: &Value, + ) -> Vec<(String, String)> { + let Value::List(items) = value else { + panic!("the fixture suppressed value is not a List"); + }; + items + .iter() + .map(|item| { + let Value::Record { fields, .. } = item else { + panic!("a fixture suppressed row is not a Record"); + }; + let Some(Value::Str(identity)) = ctx.field(fields, "identity") else { + panic!("a fixture suppressed row has no String identity"); + }; + let Some(Value::Str(ground)) = ctx.field(fields, "ground") else { + panic!("a fixture suppressed row has no String ground"); + }; + (identity.to_string(), ground.to_string()) + }) + .collect() + } + + /// THE MODELED PARTITION'S RESULT, decoded to (identity, ground-label, refused) triples — + /// one arm per suppressed row, at identity grain. + fn admission_rows( + ctx: &v1_interpreter::InterpContext, + value: &Value, + ) -> Vec<(String, String, bool)> { + let Value::List(rows) = value else { + panic!("the modeled partition did not return a List"); + }; + rows.iter() + .map(|row| { + let Value::Variant { + variant_name, + fields, + .. + } = row + else { + panic!( + "an admission row is not a Variant, observed {}", + floor_value_shape(Some(row)) + ); + }; + let str_field = |name: &str| -> String { + let Some(Value::Str(s)) = ctx.field(fields, name) else { + panic!("an admission row has no String {name}"); + }; + s.to_string() + }; + match ctx.resolve(*variant_name).as_str() { + "FloorRouteGapAdmissionMeasuredRow" => { + (str_field("identity"), str_field("ground"), false) + } + "FloorRouteGapAdmissionRefusedRow" => { + (str_field("identity"), String::new(), true) + } + other => panic!("unknown admission arm {other}"), + } + }) + .collect() + } + + /// THE PAIRING WITNESS, THROUGH THE REAL CALL PATH. The run site marshals the run's real + /// values into `v2.workflow.floor_route_gap.floor_route_gap_admission_partition` via + /// `run_in_context_with_args`; this witness drives the SAME entry, by the SAME constant, + /// over the SHARED fixture (src/v2/test/fixture/route_gap_admission_partition.dag), so a + /// rename of the entry or a fork of the marshal shape breaks here instead of forking the + /// decision. It asserts both arms at identity grain: a declared suppressed row comes back + /// MEASURED with its ground kept, and an enrollment the tree does not declare -- renamed, + /// deleted, or fabricated -- comes back REFUSED, the wall's discriminating arm. The + /// floor-side route witness (`test.claim.route_gap_partition_witness`) asserts the same + /// fixture rows on the floor, so the answer and the route are pinned on both sides. #[test] - fn a_declared_suppressed_enrollment_is_not_refused() { - let suppressed = vec![ - ( - "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds" - .to_string(), - SuppressionGround::OutsideRequiredGate, - ), - ( - "test.claim.parse_test.parse_witness_floor_holds".to_string(), - SuppressionGround::WithheldCostDebt, - ), - ]; - let disposition_index: HashMap = [ - ( - "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds" - .to_string(), - RequiredFloorDisposition::DeclinedOutsideGateClosure, - ), - ( - "test.claim.parse_test.parse_witness_floor_holds".to_string(), - RequiredFloorDisposition::DeclinedCostDebt, - ), - ] - .into_iter() - .collect(); - let undeclared = route_gap_suppressed_undeclared(&suppressed, &disposition_index); - assert!( - undeclared.is_empty(), - "declared dormancy is a record, not a refusal" - ); + fn the_partition_route_decides_the_shared_fixture_at_identity_grain() { + crate::cli_run::on_live_pool_thread(|| { + let root = process_workspace_root(); + let roots: Vec = ["dag", "src/v2"] + .iter() + .map(|r| root.join(r).to_string_lossy().to_string()) + .collect(); + let entry = root + .join("src/v2/test/fixture/route_gap_admission_partition.dag") + .to_string_lossy() + .to_string(); + let index = crate::cli_run::process_shared_index(&roots); + let (graph, indices) = crate::cli_run::resolve_entry_with_index(&index, &entry) + .expect("the shared fixture resolves"); + let ctx = crate::cli_run::make_eval_context(&graph, indices, ExecutionMode::Hermetic); + let read = |name: &str| { + v1_interpreter::with_active_context(&ctx, || { + v1_interpreter::run_in_context( + &ctx, + &format!("v2.test.fixture.route_gap_admission_partition.{name}"), + false, + ) + }) + .unwrap_or_else(|e| panic!("{name} does not evaluate: {e}")) + }; + let suppressed = + suppressed_row_pairs(&ctx, &read("route_gap_partition_fixture_suppressed")); + let declared = string_list(&read("route_gap_partition_fixture_declared"), "declared"); + assert_eq!( + suppressed.len(), + 3, + "the fixture carries both arms and both grounds" + ); + let args = [ + ( + Some("suppressed".to_string()), + route_gap_suppressed_rows_value(&ctx, &suppressed), + ), + (Some("declared".to_string()), string_list_value(&declared)), + ]; + let decided = v1_interpreter::with_active_context(&ctx, || { + v1_interpreter::run_in_context_with_args( + &ctx, + ROUTE_GAP_ADMISSION_PARTITION_ENTRY, + &args, + false, + ) + }) + .expect("the modeled admission partition evaluates over the fixture"); + let rows = admission_rows(&ctx, &decided); + assert_eq!( + rows.len(), + 3, + "every suppressed row lands in exactly one arm" + ); + assert_eq!( + rows.iter() + .filter(|(i, g, refused)| !refused + && i == "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds" + && g == "outside_required_gate") + .count(), + 1, + "the declared suppressed row is MEASURED with its ground kept" + ); + assert_eq!( + rows.iter() + .filter(|(i, g, refused)| !refused + && i == "test.claim.parse_test.parse_witness_floor_holds" + && g == "withheld_cost_debt") + .count(), + 1, + "the cost-debt withheld declared row is MEASURED with its ground kept" + ); + assert_eq!( + rows.iter() + .filter(|(i, _, refused)| *refused + && i == "test.claim.renamed_away_witness_test.old_witness_name") + .count(), + 1, + "the enrollment the tree does not declare is REFUSED -- the wall's discriminating arm" + ); + }); } - /// THE GROUND TRAVELS WITH THE ROW, so the printed record names the arm that owns the - /// dormancy rather than one lumped cause for two different owners. + /// THE MARSHAL SHAPE the run site owes, pinned beside the fixture: a suppressed row carries + /// its identity and the label of the ground that removed it, and nothing else. #[test] - fn the_ground_of_each_row_is_carried_through() { - let suppressed = vec![( - "test.claim.missing_test.gone".to_string(), - SuppressionGround::OutsideRequiredGate, - )]; - let undeclared = route_gap_suppressed_undeclared(&suppressed, &HashMap::new()); - assert_eq!(undeclared.len(), 1); - assert!(matches!( - undeclared[0].1, - SuppressionGround::OutsideRequiredGate - )); + fn the_marshal_shape_names_identity_and_ground_only() { + crate::cli_run::on_live_pool_thread(|| { + let root = process_workspace_root(); + let roots: Vec = ["src/v2"] + .iter() + .map(|r| root.join(r).to_string_lossy().to_string()) + .collect(); + let entry = root + .join("src/v2/test/fixture/route_gap_admission_partition.dag") + .to_string_lossy() + .to_string(); + let index = crate::cli_run::process_shared_index(&roots); + let (graph, indices) = crate::cli_run::resolve_entry_with_index(&index, &entry) + .expect("the shared fixture resolves"); + let ctx = crate::cli_run::make_eval_context(&graph, indices, ExecutionMode::Hermetic); + let value = route_gap_suppressed_rows_value( + &ctx, + &[( + "test.claim.any_witness_test.any_holds".to_string(), + "suppressed_outside_required_gate".to_string(), + )], + ); + let v1_interpreter::Value::List(items) = &value else { + panic!("the marshal produces a List"); + }; + assert_eq!(items.len(), 1); + let v1_interpreter::Value::Record { type_name, fields } = &items[0] else { + panic!("the marshal produces a suppressed-row Record"); + }; + assert_eq!( + ctx.resolve(*type_name).as_str(), + "FloorRouteGapSuppressedRow" + ); + assert_eq!(fields.len(), 2, "identity and ground, nothing else"); + }); } } diff --git a/src/v2/test/fixture/route_gap_admission_partition.dag b/src/v2/test/fixture/route_gap_admission_partition.dag new file mode 100644 index 00000000000..81267097d62 --- /dev/null +++ b/src/v2/test/fixture/route_gap_admission_partition.dag @@ -0,0 +1,54 @@ +module v2.test.fixture.route_gap_admission_partition + +import std.types { List } +import v2.workflow.floor_route_gap { + FloorRouteGapSuppressedRow, + FloorRouteGapAdmissionRow, + FloorRouteGapAdmissionMeasuredRow, + FloorRouteGapAdmissionRefusedRow, + floor_route_gap_admission_partition, +} + +// ONE FIXTURE, READ BY ALL THREE SIDES. `test.claim.route_gap_partition_witness` asserts the +// modeled partition over these rows on the floor (the route: the same surface the run site +// marshals into). The seed's `required_floor_runner` pairing witness evaluates the same rows +// through `run_in_context_with_args` -- the real call path the run site uses -- and asserts both +// arms at identity grain. The rows span both arms and both grounds the runner produces: a +// declared suppressed row whose module the gate cut withdrew (measured, ground kept), a +// cost-debt-withheld row the tree declares (measured, ground kept), and an enrollment the tree +// does not declare -- renamed, deleted, or fabricated (refused; the wall). +data route_gap_partition_fixture_suppressed: List = [ + FloorRouteGapSuppressedRow { identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", ground: "outside_required_gate" }, + FloorRouteGapSuppressedRow { identity: "test.claim.parse_test.parse_witness_floor_holds", ground: "withheld_cost_debt" }, + FloorRouteGapSuppressedRow { identity: "test.claim.renamed_away_witness_test.old_witness_name", ground: "outside_required_gate" } +] + +data route_gap_partition_fixture_declared: List = [ + "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", + "test.claim.parse_test.parse_witness_floor_holds" +] + +data route_gap_partition_fixture_expected: List = [ + FloorRouteGapAdmissionMeasuredRow { identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", ground: "outside_required_gate" }, + FloorRouteGapAdmissionMeasuredRow { identity: "test.claim.parse_test.parse_witness_floor_holds", ground: "withheld_cost_debt" }, + FloorRouteGapAdmissionRefusedRow { identity: "test.claim.renamed_away_witness_test.old_witness_name" } +] + +fn partition_fixture_suppressed() -> List { + route_gap_partition_fixture_suppressed +} + +fn partition_fixture_declared() -> List { + route_gap_partition_fixture_declared +} + +fn partition_fixture_expected() -> List { + route_gap_partition_fixture_expected +} + +fn partition_fixture_decided() -> List { + floor_route_gap_admission_partition( + suppressed: route_gap_partition_fixture_suppressed, + declared: route_gap_partition_fixture_declared, + ) +} diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index 99d11f26a92..5a80506b615 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -2250,3 +2250,45 @@ fn floor_route_gap_expectation_chunk_38() -> List { tail: Empty {} } } + +// THE ADMISSION PARTITION, MODELED ON THE AUTHORITY THAT OWNS THE REGISTER. This is the single +// implementation of the route-gap admission decision: the required floor's runner marshals the +// run's real values -- the identities suppression removed, each with the label of the ground +// that removed it, and the discovery walk's declared-identity index -- into this relation +// through the hermetic frame it already holds. Nothing re-derives the membership test in Rust: +// a runner-side copy would be a second authority over one decision, the shape the changed- +// selection capability documented at v2.workflow.required_floor's dissolve-on note retires. +// +// COST, NAMED: one frame call per required run, linear in suppressed x declared. The +// changed-selection capability measured per-probe re-folds at roughly 38,000 eval steps and +// retired the mirror for it; the route-gap suppressed population is smaller than the corpus +// census roster and this call is not per claim. If the roster or the gate grows the join back +// into a cost, the corpus census's served-once shape (floor_pure_producer_share) is the named +// retirement. +type FloorRouteGapSuppressedRow = { identity: String, ground: String } + +// THE TWO ARMS AT IDENTITY GRAIN. A declared suppressed row is MEASUREMENT: it keeps its ground +// and the run names it, and for identities whose module the 2026-08-29 gate cut withdrew that +// record closes nothing. An undeclared row REFUSES: suppression removed it before the reverse +// join, so no other guard can see it, and an enrollment nothing can observe must never pass as +// a count. +type FloorRouteGapAdmissionRow = + FloorRouteGapAdmissionMeasuredRow { identity: String, ground: String } + | FloorRouteGapAdmissionRefusedRow { identity: String } + +fn floor_route_gap_string_eq(left: String, right: String) -> Bool { + left == right +} + +fn floor_route_gap_admission_partition( + suppressed: List, + declared: List, +) -> List { + map( + suppressed, + row => match contains(xs: declared, item: row.identity, eq: floor_route_gap_string_eq) { + true => FloorRouteGapAdmissionMeasuredRow { identity: row.identity, ground: row.ground } + false => FloorRouteGapAdmissionRefusedRow { identity: row.identity } + }, + ) +} diff --git a/src/v2/workflow/required_floor.dag b/src/v2/workflow/required_floor.dag index 3bffc515987..f2b06ecc371 100644 --- a/src/v2/workflow/required_floor.dag +++ b/src/v2/workflow/required_floor.dag @@ -903,6 +903,11 @@ fn required_gate_prefixes() -> List { // duplicate-key and well-formed checks and the addition/staleness join; it matched no family prefix, // so those reds executed on no merge lane. Admitted at exact module grain for the same reason as the // test-reference wall: a family prefix would silently widen to any future sibling. +// THE ROUTE-GAP PARTITION WITNESS (same reason, same grain). `test.claim.route_gap_partition_witness` +// carries the route-gap admission partition's discriminating red -- an enrollment the tree does not +// declare refuses -- through the modeled relation on `v2.workflow.floor_route_gap`, over substrate +// inputs only (constructed lists; no host effect, no service). It matched no family prefix, so the +// wall's red would execute on no merge lane without this row. fn required_gate_authored_modules() -> List { [ "test.claim.runner_capacity_plan_witness", @@ -921,6 +926,7 @@ fn required_gate_authored_modules() -> List { "v2.test.claim.effect_demand.effect_demand_floor_join_test", "test.claim.test_reference_wall_witness", "test.claim.seed_growth_admission_witness", + "test.claim.route_gap_partition_witness", ] } From c6133844456989d05db0402f832659419b2c0ece Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 15:16:37 +0000 Subject: [PATCH 11/24] route-gap pairing witness: qualify ExecutionMode --- .../src/cli_run/required_floor_runner.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) 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 bfd9402ba48..3ec3bfe7881 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -16543,7 +16543,11 @@ mod route_gap_admission_partition_tests { let index = crate::cli_run::process_shared_index(&roots); let (graph, indices) = crate::cli_run::resolve_entry_with_index(&index, &entry) .expect("the shared fixture resolves"); - let ctx = crate::cli_run::make_eval_context(&graph, indices, ExecutionMode::Hermetic); + let ctx = crate::cli_run::make_eval_context( + &graph, + indices, + v1_interpreter::ExecutionMode::Hermetic, + ); let read = |name: &str| { v1_interpreter::with_active_context(&ctx, || { v1_interpreter::run_in_context( @@ -16630,7 +16634,11 @@ mod route_gap_admission_partition_tests { let index = crate::cli_run::process_shared_index(&roots); let (graph, indices) = crate::cli_run::resolve_entry_with_index(&index, &entry) .expect("the shared fixture resolves"); - let ctx = crate::cli_run::make_eval_context(&graph, indices, ExecutionMode::Hermetic); + let ctx = crate::cli_run::make_eval_context( + &graph, + indices, + v1_interpreter::ExecutionMode::Hermetic, + ); let value = route_gap_suppressed_rows_value( &ctx, &[( @@ -17336,7 +17344,11 @@ mod changed_selections_outside_discovery_mirror_tests { let index = crate::cli_run::process_shared_index(&roots); let (graph, indices) = crate::cli_run::resolve_entry_with_index(&index, &entry) .expect("the shared fixture resolves"); - let ctx = crate::cli_run::make_eval_context(&graph, indices, ExecutionMode::Hermetic); + let ctx = crate::cli_run::make_eval_context( + &graph, + indices, + v1_interpreter::ExecutionMode::Hermetic, + ); let read = |name: &str| { v1_interpreter::with_active_context(&ctx, || { v1_interpreter::run_in_context(&ctx, &format!("{FIXTURE}.{name}"), false) From 8dc7d4ce873f771ce20d7422bf3b23e366091b43 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 16:33:30 +0000 Subject: [PATCH 12/24] Route-gap ground as declared coproduct; seed receipt counts the two marshal items (+5) with a before/after diff census Round 3, addressing review 76626: - v2.workflow.floor_route_gap declares FloorRouteGapSuppressionGround (OutsideRequiredGate | WithheldCostDebt | DeclinedNoCiWetLane) and both suppressed and measured rows carry it instead of a free String label; the arms are spelled arm-for-arm after the floor's suppression enum as the seed realizes it (v1.expected_red_roster_join's ground, generated into the runner as a Rust enum) because the eval universe's discovery roots are dag + src/v2, so the seed-side type cannot be imported. - The shared marshal decodes the runner's enum into the declared arm; the run site decodes the returned arm back and refuses an arm the enum does not declare (fail-closed, cause named with the arm). - Fixture and the floor-side witness construct the declared arms, bound by the field's declared type. - Seed receipt: the two new module-scope marshal items are counted (delta +5), the trigger says five, and a before/after diff census replaces label-only prose. --- .../floor/floor_route_gap_seed_growth.dag | 12 +- .../route_gap_partition_witness_test.dag | 18 ++- docs/plans/route-gap-dormant-observation.md | 17 ++- .../src/cli_run/required_floor_runner.rs | 128 +++++++++++++----- .../fixture/route_gap_admission_partition.dag | 14 +- src/v2/workflow/floor_route_gap.dag | 20 ++- 6 files changed, 152 insertions(+), 57 deletions(-) diff --git a/dag/gunbc/floor/floor_route_gap_seed_growth.dag b/dag/gunbc/floor/floor_route_gap_seed_growth.dag index da8883e24e5..b98ea164276 100644 --- a/dag/gunbc/floor/floor_route_gap_seed_growth.dag +++ b/dag/gunbc/floor/floor_route_gap_seed_growth.dag @@ -5,16 +5,20 @@ import gunbc.seed_growth { SeedGrowthJustification } import std.decl_ref { DeclarationRef, WholeDeclaration } // FORWARD-FREEZE RECEIPT for the typed route-gap expectation consumed by the required floor. -// The modeled authority is v2.workflow.floor_route_gap FloorRouteGapExpectation; these three +// The modeled authority is v2.workflow.floor_route_gap FloorRouteGapExpectation; these five // declarations are its seed-side realization at the interpreter boundary, not a second policy. data floor_route_gap_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "FloorRouteGapExpectedGround", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "FloorRouteGapExpectation", field: WholeDeclaration }, - DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "floor_route_gap_expectation_mismatch", field: WholeDeclaration } + DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "floor_route_gap_expectation_mismatch", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "ROUTE_GAP_ADMISSION_PARTITION_ENTRY", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "route_gap_suppressed_rows_value", field: WholeDeclaration } ], - reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY THE ADMISSION PARTITION IS NOT A FOURTH DECLARATION: the partition decision is modeled on the authority itself — v2.workflow.floor_route_gap.floor_route_gap_admission_partition, the single implementation of the route-gap admission decision — and the runner marshals the run's real values (the suppressed identities with their ground labels, and the discovery walk's declared-identity index) into it through run_in_context_with_args. An earlier revision carried a Rust classifier (route_gap_suppressed_undeclared) beside the modeled relation; it was DELETED rather than kept beside the .dag call, so the membership test has one implementation. What remains in Rust is marshaling and the refusal raise, inside existing declarations (ExistingSeedItemModified) — a net reduction of seed decision surface. The run-time declared index is still discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the gate closure, so out-of-gate modules are never loaded there); a modeled declared-identity projection the regen lane would maintain is what would move even the marshal into the .dag, and that projection was deliberately not built here.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement, and closes its undeclared-enrollment hole. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +3, exactly the closed ground mirror, the decoded expectation record, and the pure mismatch classifier enumerated above. All other Rust edits are inside existing declarations and are ExistingSeedItemModified.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run/required_floor_runner.rs (call-site marshal + modeled-partition call, per-identity measurement record, refusal, pairing witness through run_in_context_with_args, tests), src/v2/workflow/floor_route_gap.dag (contract amendment + the modeled partition), src/v2/workflow/required_floor.dag (one exact-grain authored-module row for test.claim.route_gap_partition_witness), src/v2/test/fixture/route_gap_admission_partition.dag and dag/test/claim/route_gap_partition_witness_test.dag (the shared fixture and the floor-side route witness) against the pre-repair main. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", + reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY THE ADMISSION PARTITION IS MODELED, WITH TWO COUNTED MARSHALING ITEMS: the partition decision is modeled on the authority itself — v2.workflow.floor_route_gap.floor_route_gap_admission_partition, the single implementation of the route-gap admission decision — and the runner marshals the run's real values (the suppressed identities with their declared ground arms, and the discovery walk's declared-identity index) into it through run_in_context_with_args. An earlier revision carried a Rust classifier (route_gap_suppressed_undeclared) beside the modeled relation; it was DELETED rather than kept beside the .dag call, so the membership test has one implementation and the seed decision surface shrank by that classifier. What feeds the relation could not vanish with it: the entry name the run site and the pairing witness share (ROUTE_GAP_ADMISSION_PARTITION_ENTRY) and the one shared row marshal (route_gap_suppressed_rows_value) are two new module-scope seed items, counted in the delta below. Their ground decoding is the arm-for-arm spelling after v2.workflow.floor_route_gap FloorRouteGapSuppressionGround, so a rename on either side breaks the pairing instead of forking the vocabulary. The run-time declared index is still discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the gate closure, so out-of-gate modules are never loaded there); a modeled declared-identity projection the regen lane would maintain is what would move even the marshal into the .dag, and that projection was deliberately not built here.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement, and closes its undeclared-enrollment hole. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +5, exactly the three declarations the floor expectation needs (the closed ground mirror, the decoded expectation record, the pure mismatch classifier) plus the two items the modeled partition's marshal owes (the shared entry name and the shared suppressed-row marshal), all enumerated above. All other Rust edits are inside existing declarations and are ExistingSeedItemModified. + +HAND-LOC CENSUS, BEFORE AND AFTER, AT THIS RECEIPT: against the pre-PR main, `git diff --stat main..HEAD` over the six files named in HAND-LOC DELTA AT THIS RECEIPT below re-derives 643 insertions and 9 deletions; the seed realization's own line count in v1_compiler.cli_run/required_floor_runner.rs moves by +420 of those, of which the pairing witness and its tests carry the discriminating inputs. The item observation producer is currently absent, so this diff-derived census remains review evidence rather than a mechanically joined admission.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run/required_floor_runner.rs (call-site marshal + modeled-partition call, per-identity measurement record, refusal, pairing witness through run_in_context_with_args, tests), src/v2/workflow/floor_route_gap.dag (contract amendment + the modeled partition), src/v2/workflow/required_floor.dag (one exact-grain authored-module row for test.claim.route_gap_partition_witness), src/v2/test/fixture/route_gap_admission_partition.dag and dag/test/claim/route_gap_partition_witness_test.dag (the shared fixture and the floor-side route witness) against the pre-repair main. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, - trigger: "Delete the three seed declarations when the self-emitted claim executor executes the required floor and consumes v2.workflow.floor_route_gap FloorRouteGapExpectation directly; the modeled expectation and the modeled admission partition then remain the sole authorities and the hand-written decode/classifier disappears with the v1 floor bridge.", + trigger: "Delete the five seed declarations when the self-emitted claim executor executes the required floor and consumes v2.workflow.floor_route_gap FloorRouteGapExpectation directly; the modeled expectation, the modeled admission partition, and its declared ground coproduct then remain the sole authorities and the hand-written decode/classifier disappears with the v1 floor bridge.", current_boundary: "v2.workflow.floor_route_gap FloorRouteGapExpectation and floor_route_gap_admission_partition -> v1_compiler.cli_run FloorRouteGapExpectation -> v1_compiler.cli_run.required_floor_runner floor_route_gap_expectation_mismatch and the run-site marshal -> v1_compiler.cli_run run_required_floor" } diff --git a/dag/test/claim/route_gap_partition_witness_test.dag b/dag/test/claim/route_gap_partition_witness_test.dag index eab21e5400c..69ef5b84da9 100644 --- a/dag/test/claim/route_gap_partition_witness_test.dag +++ b/dag/test/claim/route_gap_partition_witness_test.dag @@ -6,6 +6,7 @@ import v2.workflow.floor_route_gap { FloorRouteGapAdmissionRow, FloorRouteGapAdmissionMeasuredRow, FloorRouteGapAdmissionRefusedRow, + FloorRouteGapSuppressionGround, floor_route_gap_admission_partition, } import v2.test.fixture.route_gap_admission_partition { @@ -22,12 +23,17 @@ import v2.test.fixture.route_gap_admission_partition { // the run site's only route to the answer. This claim drives that surface over the shared // fixture (src/v2/test/fixture/route_gap_admission_partition.dag) and asserts both arms at // identity grain on the floor: a declared suppressed row comes back MEASURED with its ground -// kept, and a misnamed enrollment -- renamed, deleted, or fabricated -- comes back REFUSED, the -// wall's discriminating arm. Substrate inputs only: constructed lists through the modeled +// kept (the declared `FloorRouteGapSuppressionGround` arms, not free labels), and a misnamed +// enrollment -- renamed, deleted, or fabricated -- comes back REFUSED, the wall's discriminating +// arm. Substrate inputs only: constructed lists through the modeled // relation; no host effect, no service, no wet lane. The seed's pairing witness in // `required_floor_runner` evaluates the same fixture rows through `run_in_context_with_args`, // the real call path the run site uses. -fn measured_row_holds(row: FloorRouteGapAdmissionRow, identity: String, ground: String) -> Bool { +fn measured_row_holds( + row: FloorRouteGapAdmissionRow, + identity: String, + ground: FloorRouteGapSuppressionGround +) -> Bool { match row { FloorRouteGapAdmissionMeasuredRow { identity: i, ground: g } => i == identity && g == ground FloorRouteGapAdmissionRefusedRow { identity: _ } => false @@ -44,7 +50,7 @@ fn refused_row_holds(row: FloorRouteGapAdmissionRow, identity: String) -> Bool { fn count_measured_holding( rows: List, identity: String, - ground: String, + ground: FloorRouteGapSuppressionGround, ) -> Int { fold(rows, init: 0, f: fn(acc, r) { if measured_row_holds(row: r, identity: identity, ground: ground) { @@ -77,12 +83,12 @@ test fn the_partition_measures_a_declared_suppressed_row_with_its_ground_kept() && count_measured_holding( rows: decided, identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", - ground: "outside_required_gate" + ground: OutsideRequiredGate ) == 1 && count_measured_holding( rows: decided, identity: "test.claim.parse_test.parse_witness_floor_holds", - ground: "withheld_cost_debt" + ground: WithheldCostDebt ) == 1 } diff --git a/docs/plans/route-gap-dormant-observation.md b/docs/plans/route-gap-dormant-observation.md index d1093e2ade6..34e2aff0159 100644 --- a/docs/plans/route-gap-dormant-observation.md +++ b/docs/plans/route-gap-dormant-observation.md @@ -104,10 +104,12 @@ runs remote/CI only. The partition decision is now **modeled on the .dag authority** — `v2.workflow.floor_route_gap.floor_route_gap_admission_partition` is the single implementation of the route-gap admission decision; the Rust call site marshals the run's real values (suppressed identities -with their ground labels, and the discovery walk's declared-identity index) into it through +with their declared ground arms, and the discovery walk's declared-identity index) into it through `run_in_context_with_args`, and an earlier Rust classifier (`route_gap_suppressed_undeclared`) was **deleted** rather than kept beside the call — one implementation, a net reduction of seed decision -surface; the seed receipt is back to +3 hand items. What would move even the marshal into the .dag is a +surface; the seed receipt counts +5 hand items (three for the floor expectation, two for the modeled +partition's shared marshal: the entry name and the suppressed-row marshal). What would move even the +marshal into the .dag is a modeled declared-identity projection the regen lane would maintain; deliberately not built. The refuse-if-undeclared decision could not have been expressed in .dag on its own because the run-time declared index is discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the @@ -123,7 +125,14 @@ gate closure); modeling the RELATION and marshaling the index in is the resoluti implementation, on the authority. 3. **Measurement (labeled)**: every suppressed identity is named with the ground the modeled partition returned for it, headed MEASUREMENT — for the (a) families this records declared dormancy and closes - nothing. + nothing. The ground itself is a **declared coproduct** (round 3, per review 76626): `ground` carries + `v2.workflow.floor_route_gap`'s `FloorRouteGapSuppressionGround` (`OutsideRequiredGate | + WithheldCostDebt | DeclinedNoCiWetLane`, spelled arm-for-arm after the floor's suppression enum as + the seed realizes it — `v1.expected_red_roster_join`'s suppression ground, generated into the runner + as a Rust enum) instead of a free label; the marshal decodes the enum into the declared arm and the + run site refuses an arm the enum does not declare, so a fabricated ground can never pass as a label. + The arms cannot be shared by import because the eval universe's discovery roots are `dag` + `src/v2` + (no `src/v1`), so name-alignment is what keeps the two spellings one vocabulary. 4. **Not closed here — class (b)**: unchanged from the section above; the named follow-ups are gate admission of those modules, or a dispatch instrument row running a probe roster with the join armed. @@ -137,7 +146,7 @@ gate closure); modeling the RELATION and marshaling the index in is the resoluti - **Seed-side pairing witness** — `route_gap_admission_partition_tests` in `required_floor_runner.rs` drives the SAME entry by the SAME constant through `run_in_context_with_args` (the real call path the run site uses) and asserts both arms at identity grain; a second test pins the marshal shape (identity - and ground label, nothing else). Local diligence, not a CI path (`docs/onboarding.md` line 175). + and declared ground arm, nothing else). Local diligence, not a CI path (`docs/onboarding.md` line 175). - **One implementation**: with the Rust classifier deleted, the run site's route to the answer is the modeled entry by construction; a run-path refusal names the entry that decided it. 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 ce1fd4590d9..8bc8db60d9b 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -278,20 +278,38 @@ pub(crate) const ROUTE_GAP_ADMISSION_PARTITION_ENTRY: &str = "v2.workflow.floor_route_gap.floor_route_gap_admission_partition"; /// THE MARSHAL the run site and the pairing witness share: a suppressed row reaches the modeled -/// relation as a `FloorRouteGapSuppressedRow` record carrying its identity and the label of the -/// ground that removed it, and nothing else. One marshal, so the shape cannot fork between the -/// real roster and the fixture. +/// relation as a `FloorRouteGapSuppressedRow` record carrying its identity and its ground — the +/// runner's suppression enum decoded into the relation's declared coproduct +/// (`v2.workflow.floor_route_gap`'s `FloorRouteGapSuppressionGround`), arm-for-arm — and +/// nothing else. One marshal, so the shape cannot fork between the real roster and the fixture. pub(crate) fn route_gap_suppressed_rows_value( ctx: &v1_interpreter::InterpContext, - rows: &[(String, String)], + rows: &[(String, SuppressionGround)], ) -> v1_interpreter::Value { + // The declared arms are spelled arm-for-arm after the runner's suppression enum, so this + // decode is the spelling's only authority: a rename on either side breaks this match or the + // fixture, never forks silently. Nested so the marshal stays one counted seed item. + fn arm(ground: SuppressionGround) -> &'static str { + match ground { + SuppressionGround::OutsideRequiredGate => "OutsideRequiredGate", + SuppressionGround::WithheldCostDebt => "WithheldCostDebt", + SuppressionGround::DeclinedNoCiWetLane => "DeclinedNoCiWetLane", + } + } v1_interpreter::list_value( rows.iter() .map(|(identity, ground)| v1_interpreter::Value::Record { type_name: ctx.sym("FloorRouteGapSuppressedRow"), fields: Rc::new(vec![ (ctx.sym("identity"), v1_interpreter::str_value(identity)), - (ctx.sym("ground"), v1_interpreter::str_value(ground)), + ( + ctx.sym("ground"), + v1_interpreter::Value::Variant { + type_name: ctx.sym("FloorRouteGapSuppressionGround"), + variant_name: ctx.sym(arm(*ground)), + fields: Rc::new(vec![]), + }, + ), ]), }) .collect::>(), @@ -9887,8 +9905,9 @@ pub fn run_required_floor( // this was the one call site that dropped the list (`let _ =`), so the reverse join below // decided only over the identities a gate-bounded run can observe while every other // enrollment sat in no ledger the run publishes. The runner now marshals the run's real - // values -- the suppressed identities with the label of the ground that removed each, and - // the discovery walk's declared-identity index -- into `v2.workflow.floor_route_gap` + // values -- the suppressed identities with the ground that removed each, decoded into the + // relation's declared coproduct, and the discovery walk's declared-identity index -- into + // `v2.workflow.floor_route_gap` // `.floor_route_gap_admission_partition` through the hermetic frame the manifest was folded // in. There is no Rust copy of the membership test to rot apart from the roster it joins; // the arms and their meaning live on the authority's contract, and the pairing witness @@ -9906,18 +9925,7 @@ pub fn run_required_floor( // absence there is absence from the tree — and suppression removed the row BEFORE the // reverse join, so the stale arm can never fire for it. The cheapest way to fake a green // run, enrolling an identity that does not exist, costs a refusal, never a count. - let suppressed_rows_value = route_gap_suppressed_rows_value( - &hermetic, - &route_gap_suppressed - .iter() - .map(|(identity, ground)| { - ( - identity.clone(), - crate::v1_compiler_expected_red_roster_join::suppression_ground_label(*ground), - ) - }) - .collect::>(), - ); + let suppressed_rows_value = route_gap_suppressed_rows_value(&hermetic, &route_gap_suppressed); let declared_value = v1_interpreter::list_value( cost_debt_disposition_index .keys() @@ -9957,6 +9965,15 @@ pub fn run_required_floor( _ => None, } }; + let ground_arm = |name: &str| -> Option { + let value = hermetic.field(fields, name)?; + match value { + v1_interpreter::Value::Variant { variant_name, .. } => { + Some(hermetic.resolve(*variant_name).as_str().to_string()) + } + _ => None, + } + }; match hermetic.resolve(*variant_name).as_str() { "FloorRouteGapAdmissionRefusedRow" => match field_str("identity") { Some(identity) => route_gap_refused.push(identity), @@ -9968,8 +9985,29 @@ pub fn run_required_floor( } }, "FloorRouteGapAdmissionMeasuredRow" => { - match (field_str("identity"), field_str("ground")) { - (Some(identity), Some(ground)) => route_gap_measured.push((identity, ground)), + match (field_str("identity"), ground_arm("ground")) { + (Some(identity), Some(arm)) => { + // The relation returns the declared arm; decode it back to the runner's + // suppression enum by the same arm-for-arm spelling the marshal used, and + // refuse an arm the enum does not declare — a fabricated ground can never + // pass as a label. + let ground = match arm.as_str() { + "OutsideRequiredGate" => SuppressionGround::OutsideRequiredGate, + "WithheldCostDebt" => SuppressionGround::WithheldCostDebt, + "DeclinedNoCiWetLane" => SuppressionGround::DeclinedNoCiWetLane, + other => { + return Err(format!( + "route_gap_admission_partition: a measured row carries unknown suppression ground arm {other}" + )) + } + }; + route_gap_measured.push(( + identity, + crate::v1_compiler_expected_red_roster_join::suppression_ground_label( + ground, + ), + )); + } _ => { return Err( "route_gap_admission_partition: a measured row carries no identity or ground" @@ -16979,8 +17017,8 @@ mod route_gap_admission_partition_tests { )) } - /// THE FIXTURE'S SUPPRESSED ROWS, decoded to (identity, ground-label) pairs — the shape the - /// shared marshal re-encodes. + /// THE FIXTURE'S SUPPRESSED ROWS, decoded to (identity, declared-arm) pairs — the shape the + /// shared marshal re-encodes into the relation's coproduct. fn suppressed_row_pairs( ctx: &v1_interpreter::InterpContext, value: &Value, @@ -16997,15 +17035,29 @@ mod route_gap_admission_partition_tests { let Some(Value::Str(identity)) = ctx.field(fields, "identity") else { panic!("a fixture suppressed row has no String identity"); }; - let Some(Value::Str(ground)) = ctx.field(fields, "ground") else { - panic!("a fixture suppressed row has no String ground"); + let Some(Value::Variant { variant_name, .. }) = ctx.field(fields, "ground") else { + panic!("a fixture suppressed row has no declared ground arm"); }; - (identity.to_string(), ground.to_string()) + (identity.to_string(), ctx.resolve(*variant_name).to_string()) }) .collect() } - /// THE MODELED PARTITION'S RESULT, decoded to (identity, ground-label, refused) triples — + /// THE MARSHAL'S INPUT, decoded back to the runner's suppression enum by the arm-for-arm + /// spelling the marshal owns. A fixture arm the enum does not declare panics here instead of + /// passing as a label. + fn ground_arm_enum(arm: &str) -> SuppressionGround { + match arm { + "OutsideRequiredGate" => SuppressionGround::OutsideRequiredGate, + "WithheldCostDebt" => SuppressionGround::WithheldCostDebt, + "DeclinedNoCiWetLane" => SuppressionGround::DeclinedNoCiWetLane, + other => panic!( + "the fixture names a suppression arm the runner's enum does not declare: {other}" + ), + } + } + + /// THE MODELED PARTITION'S RESULT, decoded to (identity, declared-arm, refused) triples — /// one arm per suppressed row, at identity grain. fn admission_rows( ctx: &v1_interpreter::InterpContext, @@ -17033,9 +17085,15 @@ mod route_gap_admission_partition_tests { }; s.to_string() }; + let arm_field = |name: &str| -> String { + let Some(Value::Variant { variant_name, .. }) = ctx.field(fields, name) else { + panic!("an admission row has no declared {name} arm"); + }; + ctx.resolve(*variant_name).to_string() + }; match ctx.resolve(*variant_name).as_str() { "FloorRouteGapAdmissionMeasuredRow" => { - (str_field("identity"), str_field("ground"), false) + (str_field("identity"), arm_field("ground"), false) } "FloorRouteGapAdmissionRefusedRow" => { (str_field("identity"), String::new(), true) @@ -17094,10 +17152,14 @@ mod route_gap_admission_partition_tests { 3, "the fixture carries both arms and both grounds" ); + let marshal_input = suppressed + .iter() + .map(|(identity, arm)| (identity.clone(), ground_arm_enum(arm))) + .collect::>(); let args = [ ( Some("suppressed".to_string()), - route_gap_suppressed_rows_value(&ctx, &suppressed), + route_gap_suppressed_rows_value(&ctx, marshal_input.as_slice()), ), (Some("declared".to_string()), string_list_value(&declared)), ]; @@ -17120,7 +17182,7 @@ mod route_gap_admission_partition_tests { rows.iter() .filter(|(i, g, refused)| !refused && i == "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds" - && g == "outside_required_gate") + && g == "OutsideRequiredGate") .count(), 1, "the declared suppressed row is MEASURED with its ground kept" @@ -17129,7 +17191,7 @@ mod route_gap_admission_partition_tests { rows.iter() .filter(|(i, g, refused)| !refused && i == "test.claim.parse_test.parse_witness_floor_holds" - && g == "withheld_cost_debt") + && g == "WithheldCostDebt") .count(), 1, "the cost-debt withheld declared row is MEASURED with its ground kept" @@ -17146,7 +17208,7 @@ mod route_gap_admission_partition_tests { } /// THE MARSHAL SHAPE the run site owes, pinned beside the fixture: a suppressed row carries - /// its identity and the label of the ground that removed it, and nothing else. + /// its identity and its ground as the declared coproduct arm, and nothing else. #[test] fn the_marshal_shape_names_identity_and_ground_only() { crate::cli_run::on_live_pool_thread(|| { @@ -17171,7 +17233,7 @@ mod route_gap_admission_partition_tests { &ctx, &[( "test.claim.any_witness_test.any_holds".to_string(), - "suppressed_outside_required_gate".to_string(), + SuppressionGround::OutsideRequiredGate, )], ); let v1_interpreter::Value::List(items) = &value else { diff --git a/src/v2/test/fixture/route_gap_admission_partition.dag b/src/v2/test/fixture/route_gap_admission_partition.dag index 81267097d62..15ad7f2a3c1 100644 --- a/src/v2/test/fixture/route_gap_admission_partition.dag +++ b/src/v2/test/fixture/route_gap_admission_partition.dag @@ -16,11 +16,13 @@ import v2.workflow.floor_route_gap { // arms at identity grain. The rows span both arms and both grounds the runner produces: a // declared suppressed row whose module the gate cut withdrew (measured, ground kept), a // cost-debt-withheld row the tree declares (measured, ground kept), and an enrollment the tree -// does not declare -- renamed, deleted, or fabricated (refused; the wall). +// does not declare -- renamed, deleted, or fabricated (refused; the wall). Grounds are the +// declared coproduct arms (`v2.workflow.floor_route_gap`'s `FloorRouteGapSuppressionGround`), +// bound by the field's declared type -- the same construction route the runner's marshal takes. data route_gap_partition_fixture_suppressed: List = [ - FloorRouteGapSuppressedRow { identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", ground: "outside_required_gate" }, - FloorRouteGapSuppressedRow { identity: "test.claim.parse_test.parse_witness_floor_holds", ground: "withheld_cost_debt" }, - FloorRouteGapSuppressedRow { identity: "test.claim.renamed_away_witness_test.old_witness_name", ground: "outside_required_gate" } + FloorRouteGapSuppressedRow { identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", ground: OutsideRequiredGate }, + FloorRouteGapSuppressedRow { identity: "test.claim.parse_test.parse_witness_floor_holds", ground: WithheldCostDebt }, + FloorRouteGapSuppressedRow { identity: "test.claim.renamed_away_witness_test.old_witness_name", ground: OutsideRequiredGate } ] data route_gap_partition_fixture_declared: List = [ @@ -29,8 +31,8 @@ data route_gap_partition_fixture_declared: List = [ ] data route_gap_partition_fixture_expected: List = [ - FloorRouteGapAdmissionMeasuredRow { identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", ground: "outside_required_gate" }, - FloorRouteGapAdmissionMeasuredRow { identity: "test.claim.parse_test.parse_witness_floor_holds", ground: "withheld_cost_debt" }, + FloorRouteGapAdmissionMeasuredRow { identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", ground: OutsideRequiredGate }, + FloorRouteGapAdmissionMeasuredRow { identity: "test.claim.parse_test.parse_witness_floor_holds", ground: WithheldCostDebt }, FloorRouteGapAdmissionRefusedRow { identity: "test.claim.renamed_away_witness_test.old_witness_name" } ] diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index 5a80506b615..c556f216d42 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -2253,8 +2253,8 @@ fn floor_route_gap_expectation_chunk_38() -> List { // THE ADMISSION PARTITION, MODELED ON THE AUTHORITY THAT OWNS THE REGISTER. This is the single // implementation of the route-gap admission decision: the required floor's runner marshals the -// run's real values -- the identities suppression removed, each with the label of the ground -// that removed it, and the discovery walk's declared-identity index -- into this relation +// run's real values -- the identities suppression removed, each with the ground that removed +// it, and the discovery walk's declared-identity index -- into this relation // through the hermetic frame it already holds. Nothing re-derives the membership test in Rust: // a runner-side copy would be a second authority over one decision, the shape the changed- // selection capability documented at v2.workflow.required_floor's dissolve-on note retires. @@ -2265,7 +2265,19 @@ fn floor_route_gap_expectation_chunk_38() -> List { // census roster and this call is not per claim. If the roster or the gate grows the join back // into a cost, the corpus census's served-once shape (floor_pure_producer_share) is the named // retirement. -type FloorRouteGapSuppressedRow = { identity: String, ground: String } +// THE GROUND IS A DECLARED COPRODUCT, NOT A LABEL. Suppression grounds are a closed set -- the +// floor removes a row as outside the required gate, as withheld cost debt, or as declined for +// want of a CI wet lane -- so the field carries that set, and a fabricated ground is a +// construction of a type the runner cannot produce, not a free string. The arms are spelled +// arm-for-arm after the floor's suppression vocabulary as the seed realizes it +// (`v1.expected_red_roster_join`'s suppression ground, generated into the runner as a Rust enum +// whose marshal decodes it here by spelling): the eval universe's discovery roots are `dag` + +// `src/v2`, so the seed-side type cannot be imported here, and name-alignment is what keeps the +// two spellings one vocabulary -- a rename on either side breaks the pairing witness +// (src/v2/test/fixture/route_gap_admission_partition.dag) instead of forking silently. +type FloorRouteGapSuppressionGround = OutsideRequiredGate | WithheldCostDebt | DeclinedNoCiWetLane + +type FloorRouteGapSuppressedRow = { identity: String, ground: FloorRouteGapSuppressionGround } // THE TWO ARMS AT IDENTITY GRAIN. A declared suppressed row is MEASUREMENT: it keeps its ground // and the run names it, and for identities whose module the 2026-08-29 gate cut withdrew that @@ -2273,7 +2285,7 @@ type FloorRouteGapSuppressedRow = { identity: String, ground: String } // join, so no other guard can see it, and an enrollment nothing can observe must never pass as // a count. type FloorRouteGapAdmissionRow = - FloorRouteGapAdmissionMeasuredRow { identity: String, ground: String } + FloorRouteGapAdmissionMeasuredRow { identity: String, ground: FloorRouteGapSuppressionGround } | FloorRouteGapAdmissionRefusedRow { identity: String } fn floor_route_gap_string_eq(left: String, right: String) -> Bool { From 9f408da43b29fdba318222f4f34f8c17a32981e1 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 17:40:19 +0000 Subject: [PATCH 13/24] Pairing pool: fold the required floor's own manifest roots (dag + src/v2), matching the changed-selection mirror seam --- src/v1/stage0/src/cli_run/required_floor_runner.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 8bc8db60d9b..2769237dac7 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -17213,7 +17213,7 @@ mod route_gap_admission_partition_tests { fn the_marshal_shape_names_identity_and_ground_only() { crate::cli_run::on_live_pool_thread(|| { let root = process_workspace_root(); - let roots: Vec = ["src/v2"] + let roots: Vec = ["dag", "src/v2"] .iter() .map(|r| root.join(r).to_string_lossy().to_string()) .collect(); @@ -17224,11 +17224,7 @@ mod route_gap_admission_partition_tests { let index = crate::cli_run::process_shared_index(&roots); let (graph, indices) = crate::cli_run::resolve_entry_with_index(&index, &entry) .expect("the shared fixture resolves"); - let ctx = crate::cli_run::make_eval_context( - &graph, - indices, - v1_interpreter::ExecutionMode::Hermetic, - ); + let ctx = make_eval_context(&graph, indices, v1_interpreter::ExecutionMode::Hermetic); let value = route_gap_suppressed_rows_value( &ctx, &[( From 78534c19c8de54e3d71fadff12ac2d4308bf7e87 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 18:02:57 +0000 Subject: [PATCH 14/24] Marshal call site: declare the discovered index through the value producer directly (clippy redundant closure) --- src/v1/stage0/src/cli_run/required_floor_runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2769237dac7..fbd9a53afed 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -9929,7 +9929,7 @@ pub fn run_required_floor( let declared_value = v1_interpreter::list_value( cost_debt_disposition_index .keys() - .map(|k| v1_interpreter::str_value(k)) + .map(v1_interpreter::str_value) .collect::>(), ); let route_gap_decided = v1_interpreter::run_in_context_with_args( From e5872591e2b72f0829d0d8497134f20824567e0d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 20:27:20 +0000 Subject: [PATCH 15/24] Route-gap partition: import v2.std.algebra contains (list membership); bare name resolved to the v1_rt string builtin in emission --- src/v2/workflow/floor_route_gap.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index f26c6c1b6b5..a5c7d833bc9 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -1,7 +1,7 @@ module v2.workflow.floor_route_gap import std.types { List } -import v2.std.algebra { Cons, Empty, list_flat_map } +import v2.std.algebra { Cons, Empty, contains, list_flat_map } // THE EXPECTATION IS TYPED AT THE TWO FACTS THE INTERPRETER OBSERVES. An identity-only row // says merely that some route gap once existed; it cannot object when the witness reaches a From 6c59f397ae9a041e4e61ad849be9dd0ac4aa6ce1 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 22:29:43 +0000 Subject: [PATCH 16/24] Repair the mangled merge: restore main's floor work (reach differential, explicit-witness admission, comment-only-edit charging) via 3-way union; cost-shape fix: membership per row from the keyed index; file the unimported-bare-name divergence row --- .../floor/floor_route_gap_seed_growth.dag | 2 +- ...name_binds_differently_per_realization.dag | 24 + .../route_gap_partition_witness_test.dag | 38 +- docs/plans/route-gap-dormant-observation.md | 20 +- .../src/cli_run/required_floor_runner.rs | 652 +++++++++++++++++- .../fixture/route_gap_admission_partition.dag | 43 +- src/v2/workflow/floor_route_gap.dag | 30 +- src/v2/workflow/required_floor.dag | 280 +++++++- 8 files changed, 1011 insertions(+), 78 deletions(-) create mode 100644 dag/gunbc/recurring_failure_mode/an_unimported_bare_name_binds_differently_per_realization.dag diff --git a/dag/gunbc/floor/floor_route_gap_seed_growth.dag b/dag/gunbc/floor/floor_route_gap_seed_growth.dag index b98ea164276..fd184fbc6b9 100644 --- a/dag/gunbc/floor/floor_route_gap_seed_growth.dag +++ b/dag/gunbc/floor/floor_route_gap_seed_growth.dag @@ -15,7 +15,7 @@ data floor_route_gap_seed_growth_justification: SeedGrowthJustification = SeedGr DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "ROUTE_GAP_ADMISSION_PARTITION_ENTRY", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.cli_run.required_floor_runner", decl_name: "route_gap_suppressed_rows_value", field: WholeDeclaration } ], - reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY THE ADMISSION PARTITION IS MODELED, WITH TWO COUNTED MARSHALING ITEMS: the partition decision is modeled on the authority itself — v2.workflow.floor_route_gap.floor_route_gap_admission_partition, the single implementation of the route-gap admission decision — and the runner marshals the run's real values (the suppressed identities with their declared ground arms, and the discovery walk's declared-identity index) into it through run_in_context_with_args. An earlier revision carried a Rust classifier (route_gap_suppressed_undeclared) beside the modeled relation; it was DELETED rather than kept beside the .dag call, so the membership test has one implementation and the seed decision surface shrank by that classifier. What feeds the relation could not vanish with it: the entry name the run site and the pairing witness share (ROUTE_GAP_ADMISSION_PARTITION_ENTRY) and the one shared row marshal (route_gap_suppressed_rows_value) are two new module-scope seed items, counted in the delta below. Their ground decoding is the arm-for-arm spelling after v2.workflow.floor_route_gap FloorRouteGapSuppressionGround, so a rename on either side breaks the pairing instead of forking the vocabulary. The run-time declared index is still discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the gate closure, so out-of-gate modules are never loaded there); a modeled declared-identity projection the regen lane would maintain is what would move even the marshal into the .dag, and that projection was deliberately not built here.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement, and closes its undeclared-enrollment hole. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +5, exactly the three declarations the floor expectation needs (the closed ground mirror, the decoded expectation record, the pure mismatch classifier) plus the two items the modeled partition's marshal owes (the shared entry name and the shared suppressed-row marshal), all enumerated above. All other Rust edits are inside existing declarations and are ExistingSeedItemModified. + reason: "WHY RUST IS STILL NEEDED: the required floor executes in the seed, and v1_compiler.cli_run is the boundary that receives v1_interpreter.HermeticEffectGround after a claim executes. v2.workflow.floor_route_gap owns the expectation vocabulary and population; the Rust realization decodes that authority and refuses when the observed operation or closed ground differs, instead of letting an identity-only enrollment absorb changed evidence. A modeled row without this consumer cannot constrain the host terminal ledger.\n\nWHY THE ADMISSION PARTITION IS MODELED, WITH TWO COUNTED MARSHALING ITEMS: the partition decision is modeled on the authority itself — v2.workflow.floor_route_gap.floor_route_gap_admission_partition, the single implementation of the route-gap admission decision — and the runner marshals the run's real values into it (the suppressed identities with their declared ground arms and their per-row membership bit, read O(1) from the keyed disposition index) through run_in_context_with_args. Membership travels per row as a Boolean, so the relation never re-scans the corpus-sized declared list; flattening that list for the .dag to linearly re-scan (541 suppressed x 28,274 declared ≈ 15M interpreted string comparisons per required run) was the cost-shape defect this shape retires. An earlier revision carried a Rust classifier (route_gap_suppressed_undeclared) beside the modeled relation; it was DELETED rather than kept beside the .dag call, so the membership test has one implementation and the seed decision surface shrank by that classifier. What feeds the relation could not vanish with it: the entry name the run site and the pairing witness share (ROUTE_GAP_ADMISSION_PARTITION_ENTRY) and the one shared row marshal (route_gap_suppressed_rows_value) are two new module-scope seed items, counted in the delta below. Their ground decoding is the arm-for-arm spelling after v2.workflow.floor_route_gap FloorRouteGapSuppressionGround, so a rename on either side breaks the pairing instead of forking the vocabulary. The membership bit is still discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the gate closure, so out-of-gate modules are never loaded there); a modeled declared-identity projection the regen lane would maintain is what would move even the marshal into the .dag, and that projection was deliberately not built here.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE: gunbc.v1_maintenance_standing v1_seed_standing admits work serving the v2 self-host program. The required floor is the instrument guarding that program, and this change strengthens a pre-existing route-gap debt roster from identity-only agreement to operation-and-ground agreement, and closes its undeclared-enrollment hole. It adds no language behavior, compatibility route, escape hatch, seed feature, or emitted public surface.\n\nHAND-ITEM DELTA: +5, exactly the three declarations the floor expectation needs (the closed ground mirror, the decoded expectation record, the pure mismatch classifier) plus the two items the modeled partition's marshal owes (the shared entry name and the shared suppressed-row marshal), all enumerated above. All other Rust edits are inside existing declarations and are ExistingSeedItemModified. HAND-LOC CENSUS, BEFORE AND AFTER, AT THIS RECEIPT: against the pre-PR main, `git diff --stat main..HEAD` over the six files named in HAND-LOC DELTA AT THIS RECEIPT below re-derives 643 insertions and 9 deletions; the seed realization's own line count in v1_compiler.cli_run/required_floor_runner.rs moves by +420 of those, of which the pairing witness and its tests carry the discriminating inputs. The item observation producer is currently absent, so this diff-derived census remains review evidence rather than a mechanically joined admission.\n\nHAND-LOC DELTA AT THIS RECEIPT: src/v1/stage0/src/cli_run/required_floor_runner.rs (call-site marshal + modeled-partition call, per-identity measurement record, refusal, pairing witness through run_in_context_with_args, tests), src/v2/workflow/floor_route_gap.dag (contract amendment + the modeled partition), src/v2/workflow/required_floor.dag (one exact-grain authored-module row for test.claim.route_gap_partition_witness), src/v2/test/fixture/route_gap_admission_partition.dag and dag/test/claim/route_gap_partition_witness_test.dag (the shared fixture and the floor-side route witness) against the pre-repair main. The item observation producer is currently absent, so these diff-derived figures remain review evidence rather than a mechanically joined admission.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, diff --git a/dag/gunbc/recurring_failure_mode/an_unimported_bare_name_binds_differently_per_realization.dag b/dag/gunbc/recurring_failure_mode/an_unimported_bare_name_binds_differently_per_realization.dag new file mode 100644 index 00000000000..109029fae10 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/an_unimported_bare_name_binds_differently_per_realization.dag @@ -0,0 +1,24 @@ +module gunbc.recurring_failure_mode.an_unimported_bare_name_binds_differently_per_realization + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +// AN UNIMPORTED BARE VALUE NAME IS RESOLVED TWICE -- once per realization of the language -- and +// the two resolutions have different fallback rules, so one name can mean two functions. The +// interpreter's search over declaring modules and the emitter's mapping to runtime builtins are +// two realizations of one resolution decision, and nothing forces them to agree: where the +// arities happen to differ the emitted build refuses (loud, costly, caught late), and where they +// happen to match the program compiles and SILENTLY runs the wrong function in one realization. +// The wall is the authored import: an imported name has one binding by construction, while a +// bare name with no import anywhere is a resolution the author never made. +data an_unimported_bare_name_binds_differently_per_realization: RecurringFailureMode = RecurringFailureMode { + identity: "an_unimported_bare_name_binds_differently_per_realization" as NonEmptyStr, + receipts: [ + "SPECIMEN, FOUND 2026-10-05. `v2.workflow.floor_route_gap.floor_route_gap_admission_partition` (PR #13376) called `contains(xs: declared, item: row.identity, eq: floor_route_gap_string_eq)` WITHOUT importing `contains`. The interpreter resolved the bare name to `v2.std.algebra.contains` (the generic list-membership function, src/v2/std/algebra.dag:201) and every .dag-level claim and pairing lib test evaluated green. The emitter resolved the same bare name to `v1_rt::contains` -- the 2-parameter String substring builtin (src/v1_rt.rs:332) -- and lowered the call as `v1_rt::contains(declared.clone(), row.identity.clone(), floor_route_gap_string_eq)`, failing the emitted build with error[E0061] 'this function takes 2 arguments but 3 arguments were supplied' at emitted src/v2_workflow_floor_route_gap.rs:2139:106; the String/Rc> type note was the tell that the bound function was the substring builtin, not list membership.", + "WHY THE LOCAL BATTERY COULD NOT SEE IT: the interpreter's resolution is the only resolution every .dag-level check exercises, so a bare name that binds differently per realization evaluates consistently green on every interpreted lane; emission is the only lane that runs the emitter's fallback, which made CI's emit-build the sole discriminator. A green interpreter run is not evidence that a name means the same thing in the other realization.", + "WHY THE CORPUS DID NOT CATCH IT AT REVIEW: the corpus always imports this name (src/v2/lens/coverage.dag:3, src/v2/workflow/vocab.dag), so the unimported form contradicted the convention but enforced nothing -- a reader who assumed the convention was a rule saw nothing wrong in the module text.", + "REMEDY AT THE SOURCE: author the import so the name has one binding (`import v2.std.algebra { contains }`); in the specimen's final shape the call itself was retired by the membership-per-row cost fix (review 76768), which is why the class guard is import discipline plus a resolution-time refusal, not any one call site. The class guard this row asks for: an unimported bare VALUE name refuses at resolution time with a typed, located cause -- 'bare name X is not imported' -- instead of binding per-realization fallbacks; the existing ambiguity wall (claim_scope_for's AmbiguousBareNameRead) fires only when two declarers are visible, and an unimported name with one declarer visible per realization is not ambiguous, just different.", + "NEXT-RUNG TRIGGER: the loader's bare-channel decision carried as a substrate fact -- per declined bare name, the declining arm and the declaring module -- is the observation that would make 'resolved by fallback' readable at one authority (the bare_reference_channel_outcome_seed_growth trigger); the resolution-time refusal above is what closes the class." + ], + evidence: [], +} diff --git a/dag/test/claim/route_gap_partition_witness_test.dag b/dag/test/claim/route_gap_partition_witness_test.dag index 69ef5b84da9..a6598734b05 100644 --- a/dag/test/claim/route_gap_partition_witness_test.dag +++ b/dag/test/claim/route_gap_partition_witness_test.dag @@ -11,13 +11,14 @@ import v2.workflow.floor_route_gap { } import v2.test.fixture.route_gap_admission_partition { route_gap_partition_fixture_suppressed, - route_gap_partition_fixture_declared, + route_gap_partition_fixture_expected, + partition_fixture_decided, } // THE ROUTE WITNESS for the route-gap admission partition (docs/plans/ // route-gap-dormant-observation.md). The required floor's runner marshals the run's real values -// -- the identities suppression removed with the ground that removed each, and the discovery -// walk's declared-identity index -- into `v2.workflow.floor_route_gap` +// -- the identities suppression removed with the ground that removed each and the per-row +// membership bit read from its keyed disposition index -- into `v2.workflow.floor_route_gap` // `.floor_route_gap_admission_partition`, the modeled relation on the authority that owns the // register; the runner-side classifier this relation replaces was deleted, so this surface is // the run site's only route to the answer. This claim drives that surface over the shared @@ -73,11 +74,12 @@ fn count_refused_naming(rows: List, identity: String) // THE MEASUREMENT ARM, AT IDENTITY GRAIN: a declared suppressed row keeps its ground and is // named, never absorbed into a count. For identities whose module the 2026-08-29 gate cut -// withdrew this record closes nothing -- the gate decision owns their dormancy. +// withdrew this record closes nothing -- the gate decision owns their dormancy. Membership now +// travels per row (the runner reads it from its keyed index), so the relation decides from the +// row itself. test fn the_partition_measures_a_declared_suppressed_row_with_its_ground_kept() -> Bool { let decided = floor_route_gap_admission_partition( - suppressed: route_gap_partition_fixture_suppressed, - declared: route_gap_partition_fixture_declared + suppressed: route_gap_partition_fixture_suppressed ) count(decided) == 3 && count_measured_holding( @@ -98,8 +100,7 @@ test fn the_partition_measures_a_declared_suppressed_row_with_its_ground_kept() // refusal, never as a count. test fn the_partition_refuses_an_enrollment_the_tree_does_not_declare() -> Bool { let decided = floor_route_gap_admission_partition( - suppressed: route_gap_partition_fixture_suppressed, - declared: route_gap_partition_fixture_declared + suppressed: route_gap_partition_fixture_suppressed ) count(decided) == 3 && count_refused_naming( @@ -107,3 +108,24 @@ test fn the_partition_refuses_an_enrollment_the_tree_does_not_declare() -> Bool identity: "test.claim.renamed_away_witness_test.old_witness_name" ) == 1 } + +// THE ANSWER MATCHES THE EXPECTED LIST AT IDENTITY GRAIN: the fixture's expected rows are +// consumed by the route the run site takes -- decided rows and expected rows name the same +// identities in the same arms with the same grounds, and the decided list carries nothing +// extra -- so "read by all three sides" is a claim the fixture itself discharges. +test fn the_decided_rows_agree_with_the_expected_rows_at_identity_grain() -> Bool { + let decided = partition_fixture_decided() + count(decided) == count(route_gap_partition_fixture_expected) + && fold( + route_gap_partition_fixture_expected, + init: true, + f: fn(acc, exp) { + acc && match exp { + FloorRouteGapAdmissionMeasuredRow { identity: i, ground: g } => + count_measured_holding(rows: decided, identity: i, ground: g) == 1 + FloorRouteGapAdmissionRefusedRow { identity: i } => + count_refused_naming(rows: decided, identity: i) == 1 + } + } + ) +} diff --git a/docs/plans/route-gap-dormant-observation.md b/docs/plans/route-gap-dormant-observation.md index 34e2aff0159..508f92d0063 100644 --- a/docs/plans/route-gap-dormant-observation.md +++ b/docs/plans/route-gap-dormant-observation.md @@ -104,16 +104,20 @@ runs remote/CI only. The partition decision is now **modeled on the .dag authority** — `v2.workflow.floor_route_gap.floor_route_gap_admission_partition` is the single implementation of the route-gap admission decision; the Rust call site marshals the run's real values (suppressed identities -with their declared ground arms, and the discovery walk's declared-identity index) into it through -`run_in_context_with_args`, and an earlier Rust classifier (`route_gap_suppressed_undeclared`) was -**deleted** rather than kept beside the call — one implementation, a net reduction of seed decision -surface; the seed receipt counts +5 hand items (three for the floor expectation, two for the modeled -partition's shared marshal: the entry name and the suppressed-row marshal). What would move even the +with their declared ground arms and their per-row membership bit, read O(1) from the keyed disposition +index) into it through `run_in_context_with_args`, and an earlier Rust classifier +(`route_gap_suppressed_undeclared`) was **deleted** rather than kept beside the call — one implementation, +a net reduction of seed decision surface; the seed receipt counts +5 hand items (three for the floor +expectation, two for the modeled partition's shared marshal: the entry name and the suppressed-row +marshal). Membership travels per row as a Boolean so the relation never re-scans a corpus-sized list: an +earlier shape flattened the 28,274-identity declared index for the .dag to linearly re-scan per suppressed +row (541 x 28,274 ≈ 15M interpreted string comparisons per required run) — a cost-shape defect, fixed per +DESIGN §6 rather than retired on "n is small here". What would move even the marshal into the .dag is a modeled declared-identity projection the regen lane would maintain; deliberately not built. The -refuse-if-undeclared decision could not have been expressed in .dag on its own because the run-time -declared index is discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the -gate closure); modeling the RELATION and marshaling the index in is the resolution of that. +refuse-if-undeclared decision could not have been expressed in .dag on its own because the +declared universe is discovery-walk knowledge (the roster decodes in a hermetic frame whose subject is the +gate closure); modeling the RELATION and marshaling the membership bit per row is the resolution of that. 1. **The wall (closure)**: a refused row names an enrollment the tree does not declare (module or tail absent from the discovery roots; the disposition index covers every declared witness identity, 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 d122fcc1cc5..60faddf2667 100644 --- a/src/v1/stage0/src/cli_run/required_floor_runner.rs +++ b/src/v1/stage0/src/cli_run/required_floor_runner.rs @@ -708,6 +708,7 @@ pub(crate) fn floor_diff_comparison_readout() -> Result = index + .source_files + .values() + .map(|sf| workspace_relative_repo_path(&sf.path)) + .collect(); refusals.extend(unimported_bare_provider_standing_refusals( ROUTE, index, &head, - changed_paths, + &pool_files, )?); eprintln!( - "[floor-phase] phase=unimported-bare-provider-gate touched_paths={} refusals={}", + "[floor-phase] phase=unimported-bare-provider-gate touched_paths={} judged_files={} standing_ms={} refusals={}", changed_paths.len(), + pool_files.len(), + standing_started.elapsed().as_millis(), refusals.len() ); if refusals.is_empty() { @@ -3697,7 +3715,8 @@ pub(crate) fn enrolment_margin_standing_for( // block exists to repair, and asking the cost population first rebuilds it one gate over. match enrolment_gate_execution_disposition(identity, dispositions) { Some(crate::cli_run::RequiredFloorDisposition::Planned) - | Some(crate::cli_run::RequiredFloorDisposition::PlannedAsChangedWitness) => {} + | Some(crate::cli_run::RequiredFloorDisposition::PlannedAsChangedWitness) + | Some(crate::cli_run::RequiredFloorDisposition::PlannedAsReachConsumer) => {} // Named, not caught: a new arm must state whether the margin gate runs for it. Some( other @ (crate::cli_run::RequiredFloorDisposition::DeclinedLongModule { .. } @@ -4134,7 +4153,8 @@ pub(crate) fn changed_witness_projection_rows( }, Some( RequiredFloorDisposition::Planned - | RequiredFloorDisposition::PlannedAsChangedWitness, + | RequiredFloorDisposition::PlannedAsChangedWitness + | RequiredFloorDisposition::PlannedAsReachConsumer, ) => { let outcome = outcomes.get(identity.as_str()).copied(); // THE COST POLICY THIS IDENTITY EXECUTED UNDER, and the measurement published @@ -4418,7 +4438,7 @@ fn local_repo_wet_observed_from(outcome: &crate::cli_run::ClaimOutcome) -> Local // THE ROUTE OR THE PROGRAM REFUSED. Each is a located typed refusal in its own right; the // lane keeps the class and hands the reader that diagnostic. O::NotBool { got } => LocalRepoWetObserved::Refused(format!("not a Bool: {got}")), - O::RuntimeError { cause, message } => { + O::RuntimeError { cause, message, .. } => { LocalRepoWetObserved::Refused(format!("runtime error {cause:?}: {message}")) } O::HostToolUnresolved { name, probed } => LocalRepoWetObserved::Refused(format!( @@ -8244,6 +8264,59 @@ pub fn run_required_floor( } } } + // THE PER-PR v2 DIFFERENTIAL'S POPULATION: reached witness declarations homed under the v2 + // claim root. Which of them are claims is the discovery loop's answer, not this one's. + // PLANNED ON THE MERGE GROUP ONLY (operator ruling, 2026-10-01; `v2.workflow.floor_subject_seed` + // `reach_planned_for_event`). The event is read through the authority that chose this run's + // diff window. On any other event no reach consumer is planned, and the floor says so with the + // count it would have reached, so the deferral is announced and never silent. + // AN UNREADABLE EVENT REFUSES ON CI: read as "not a merge group" it would silently defer the + // differential on the one event that must run it. A local run has no CI event and is a + // pull-request-shaped run for this purpose. + let reach_event = match floor_diff_baseline_readout() { + Ok((_, event)) => event, + Err(e) if commit != "local" && !commit.is_empty() => { + return Err(format!( + "REQUIRED-FLOOR REFUSAL cause=ReachEventUnreadable {e} -- whether this run is the \ + merge group's, and so whether it judges the reach differential, is unknown" + )) + } + Err(_) => String::new(), + }; + let reach_planned = + crate::cli_run::reach_base_standings::reach_planned_for_event(source_roots, &reach_event)?; + let reached_v2_witness_declarations = compile_subject + .iter() + .filter_map(|subject| match &subject.interface_consumers { + InterfaceConsumerPlanning::Selected { body_reach, .. } => Some(body_reach), + _ => None, + }) + .flat_map(|reach| reach.reached.iter()) + .filter(|r| r.witness_carrier && r.rel_path.starts_with("src/v2/")) + .count(); + if !reach_planned { + eprintln!( + "{}", + crate::cli_run::reach_base_standings::reach_deferred_line( + &reach_event, + reached_v2_witness_declarations + ) + ); + } + let reach_consumer_candidates: HashSet = compile_subject + .iter() + .filter_map(|subject| match &subject.interface_consumers { + InterfaceConsumerPlanning::Selected { body_reach, .. } => Some(body_reach), + _ => None, + }) + .flat_map(|reach| reach.reached.iter()) + .filter(|r| reach_planned && r.witness_carrier && r.rel_path.starts_with("src/v2/")) + .map(|r| format!("{}.{}", r.module_path, r.declaration)) + .collect(); + let reach_consumer_module_seeds: BTreeSet = reach_consumer_candidates + .iter() + .filter_map(|identity| identity.rsplit_once('.').map(|(m, _)| m.to_string())) + .collect(); // A ROW-ONLY ROSTER EDIT names subjects the diff never touched: the typed non-fold-residue // check can only judge a row whose subject module is PREPARED, so the subjects of rows the diff // added or deleted become seeds here (read at the floor's own diff base; an unreadable base @@ -8363,6 +8436,7 @@ pub fn run_required_floor( .flat_map(|subject| subject.touched_modules.iter().cloned()), ) .chain(interface_consumer_seeds.iter().cloned()) + .chain(reach_consumer_module_seeds.iter().cloned()) .chain(nfr_row_subject_modules.iter().cloned()) .collect(); // The strict resolve's graph is attributed by bytes where the floor frees it (entry_resolve @@ -9577,6 +9651,7 @@ pub fn run_required_floor( // read off this set rather than maintained beside it: a count and a population kept in step // by hand are two computations of one fact, and the count is the weaker one. let mut declared_identity_set: HashSet = HashSet::new(); + let mut reach_consumer_planned: HashSet = HashSet::new(); let mut sites_offered = 0usize; let mut disposition_rows: Vec = Vec::new(); let mut storage_agreement_rows: Vec = Vec::new(); @@ -9599,6 +9674,8 @@ pub fn run_required_floor( } else { Vec::new() }; + // The control for the one-judgment-per-file rule: judgments (each lexing base and head once) + // must equal changed files, not changed identities. for file in files { let matched_prefix = long_home_prefixes .iter() @@ -9852,13 +9929,23 @@ pub fn run_required_floor( } // THE FOURTH DECLINE, AFTER COST DEBT so a rostered identity outside the gate still // enters `cost_debt_seen` and the roster's staleness check keeps its meaning. - if !inside_required_gate { + // A REACH CONSUMER REPLACES ONLY THIS DECLINE. Inside the gate a claim keeps its + // absolute verdict (a differential would weaken it); a changed witness keeps its own + // arm above; a long-home, fixture or cost-debt decline is a separate authority and + // stands. What changes is that an out-of-gate v2 claim whose evaluation this diff can + // move is run, and judged by whether its verdict moved. + let reach_consumer = + !inside_required_gate && reach_consumer_candidates.contains(&identity); + if !inside_required_gate && !reach_consumer { disposition_rows.push(RequiredFloorDispositionRow { identity, disposition: RequiredFloorDisposition::DeclinedOutsideRequiredGate, }); continue; } + if reach_consumer { + reach_consumer_planned.insert(identity.clone()); + } // NO SECOND DUPLICATE WALL LIVES HERE. This arm used to re-test uniqueness over the // PLANNED identities only, which is the same invariant the offered-side insert above // now establishes over the whole discovered population — strictly wider, and reached @@ -9869,7 +9956,11 @@ pub fn run_required_floor( planned_identities.insert(identity.clone()); disposition_rows.push(RequiredFloorDispositionRow { identity: identity.clone(), - disposition: RequiredFloorDisposition::Planned, + disposition: if reach_consumer { + RequiredFloorDisposition::PlannedAsReachConsumer + } else { + RequiredFloorDisposition::Planned + }, }); // THE TIER, DERIVED FROM ROSTER MEMBERSHIP AND FROM NOTHING ELSE -- after the corpus-census // arm, whose members are judged by their evaluated allowance alone. @@ -10087,7 +10178,8 @@ pub fn run_required_floor( for row in &disposition_rows { match &row.disposition { RequiredFloorDisposition::Planned - | RequiredFloorDisposition::PlannedAsChangedWitness => {} + | RequiredFloorDisposition::PlannedAsChangedWitness + | RequiredFloorDisposition::PlannedAsReachConsumer => {} RequiredFloorDisposition::DeclinedLongModule { .. } => long_declined += 1, RequiredFloorDisposition::DeclinedFixtureMember { .. } => fixture_declined += 1, RequiredFloorDisposition::DeclinedOutsideRequiredGate => outside_gate_declined += 1, @@ -10877,6 +10969,22 @@ pub fn run_required_floor( // over a folded manifest plausibly does. Whether that is what this recovers is exactly what // the next run says, and if the step survives then the cost is elsewhere and this was still // correct — an unread value held across the longest phase of the program has no defence. + // THE REACH DIFFERENTIAL'S STANDING is read here, while the policy frame is alive, because + // the frame is released on the next line and the differential is decided after the fold. + let reach_blocking_budget_ms = match v1_interpreter::run_in_context( + &hermetic, + "v2.workflow.required_floor.reach_differential_blocking_budget", + false, + ) { + Ok(v1_interpreter::Value::Int(n)) if n >= 0 => n as u64, + other => { + return Err(format!( + "REQUIRED-FLOOR REFUSAL cause=ReachDifferentialStandingUnreadable \ + reach_differential_blocking_budget (Milliseconds) returned {}", + floor_value_shape(other.as_ref().ok()) + )) + } + }; drop(hermetic); drop(policy_scope); @@ -10916,6 +11024,7 @@ pub fn run_required_floor( modules_resolved: prepared.modules_resolved, modules_excluded: prepared.modules_excluded, sites_offered, + reach_differential_blocking: Vec::new(), declined_long_module: long_declined, declined_fixture_member: fixture_declined, declined_outside_required_gate: outside_gate_declined, @@ -11027,6 +11136,7 @@ pub fn run_required_floor( // `Some(identity)` exactly when a claim's evaluation unwound and stopped the fold. let mut halted_by: Option = None; let mut known_red_held: usize = 0; + let mut reach_head_standings: Vec<(String, String)> = Vec::new(); let mut known_red_now_passing: usize = 0; let mut known_red_budget_refused: usize = 0; let mut known_red_passed_over_budget: usize = 0; @@ -11389,6 +11499,7 @@ pub fn run_required_floor( &result, expected_red_roster.contains(claim.qualified.as_str()), ), + &result, ); // THE EXPECTED-RED JOIN. A quarantined identity is one this branch KNOWS fails; it is // enrolled by exact qualified name in `v2.workflow.floor_expected_red`, and the @@ -11469,6 +11580,23 @@ pub fn run_required_floor( outcome: result.clone(), }); let passed = matches!(result, ClaimOutcome::Pass); + // A REACH CONSUMER'S VERDICT IS A HEAD STANDING, NOT A PASS/FAIL OF THIS FLOOR. It was + // never gated, so main may already carry it red; whether THIS change moved it is the + // differential's question (`v2.workflow.required_floor` `claim_differential`), answered + // against the base standing. It leaves the fold here, after its terminal row, so no + // arm below can count it as a failure, a route gap or a budget refusal. + if reach_consumer_planned.contains(&claim.qualified) { + // THE HEAD STANDING USES THE BASE ARM'S OWN CLASSIFIER, so one function decides what + // is a verdict on both sides: a non-verdict at head (a wall interruption under host + // load, say) is not_measured, never failed. Read as failed it would make a passing + // base a regression and block on load (neat-boar-16, srv1 rerun, 2026-10-01). + let head_name = match crate::cli_run::reach_base_standings::base_standing_of(&result) { + Ok(standing) => standing.name().to_string(), + Err(_) => "not_measured".to_string(), + }; + reach_head_standings.push((claim.qualified.clone(), head_name)); + continue; + } if expected_red { // ONE DISPATCH. Every arm does its own work here rather than classifying once and // re-deriving the answer below: two dispatches over one value agree only as long @@ -11672,7 +11800,7 @@ pub fn run_required_floor( // population. The comment it replaced described the key as normalizing away // per-row identities; it did the opposite. let detail = match &result { - ClaimOutcome::RuntimeError { cause, message } => { + ClaimOutcome::RuntimeError { cause, message, .. } => { *known_red_runtime_error_causes .entry(cause.token()) .or_insert(0) += 1; @@ -12310,6 +12438,237 @@ pub fn run_required_floor( ); } outcome.known_red_held = known_red_held; + // THE HEAD SIDE OF THE PER-PR v2 DIFFERENTIAL. Observed and published; the base side and + // the blocking join arrive with the main-sha baseline (adhoc-be476b8f-943 follow-up). + reach_head_standings.sort(); + // THE BASE SIDE AND THE VERDICT. Head standings were observed above. The base standing for + // exactly these identities comes from a separate process at the diff base + // (`reach_base_standings`), and each verdict, and whether it blocks, is + // `v2.workflow.required_floor` `reach_claim_verdict`, which delegates to `claim_differential` + // and `claim_differential_blocks`. This function decides neither (review 72143). + if !reach_head_standings.is_empty() { + let diff_base: Option = compile_subject.as_ref().and_then(|subject| match &subject + .interface_consumers + { + InterfaceConsumerPlanning::Selected { base, .. } => Some(base.clone()), + _ => None, + }); + let blocking_budget_ms = reach_blocking_budget_ms; + // A FRAME OVER THE DIFFERENTIAL'S OWN AUTHORITY, built only when there are reached + // claims: the policy frame was released before the fold (see the drop above). + let verdict_frame = { + let entry = process_workspace_root().join("src/v2/workflow/required_floor.dag"); + let (graph, indices) = + resolve_entry_graph_shared(source_roots, &entry.to_string_lossy()) + .map_err(|e| format!("reach differential authority resolve: {e}"))?; + make_eval_context(&graph, indices, v1_interpreter::ExecutionMode::Hermetic) + }; + // ONLY A CLAIM THAT CAN BLOCK NEEDS A BASE. `reach_head_cannot_block` is derived from + // claim_differential_blocks over every base arm; a claim it exempts is never run at base + // and never blocks. That is 60 of 1481 for a one-line v2.std.node edit (srv1, 2026-10-01). + let (identities, head_cannot_block) = + reach_base_identities(&verdict_frame, &reach_head_standings)?; + // A claim's entry FILE, which the explicit-witness-admission roster keys on, from the + // corpus index this floor already read; an unindexed module answers "" and so matches no + // roster row rather than a guessed one. + let reach_claim_entry = |identity: &str| -> String { + identity + .rsplit_once('.') + .and_then(|(module, _)| floor_corpus.index.get(module)) + .map(|source| source.path.clone()) + .unwrap_or_default() + }; + // REPORT-ONLY DOES NOT PAY FOR THE BASE ARM: running it without blocking would charge + // the run that cost while deciding nothing. So the run names what it did not do. + let base_arm = if blocking_budget_ms == 0 { + Err("BaseArmNotRun reach_differential_standing is DifferentialReportOnly".to_string()) + } else { + match &diff_base { + Some(_) if identities.is_empty() => Ok(HashMap::new()), + Some(base) => run_reach_base_arm( + source_roots, + base, + &identities, + claim_wall_safety_limit_ms, + blocking_budget_ms, + ), + None => { + Err("no diff base: the interface-consumer planning did not select".to_string()) + } + } + }; + let base_sha = diff_base.clone().unwrap_or_default(); + // THE BASELINE ARM THE RUN REPORTS IS THE ONE THAT HAPPENED: not-measured when the base + // side was not run, ran-at-merge-base when it was (review 73484). + let baseline_line = reach_baseline_line(&verdict_frame, blocking_budget_ms, &base_sha)?; + match base_arm { + Err(cause) => { + // THE HEAD SIDE IS STILL REPORTED, CLAIM BY CLAIM. These claims were executed at + // head by the fold above; without a base there is no verdict, but a reached claim + // that FAILS at head is a typed, counted finding the queue must see, never silence + // (the gunbc#12582 shape: five reached claims failing with nothing printed). + let head_failed: Vec<&String> = reach_head_standings + .iter() + .filter(|(_, head)| head == "failed") + .map(|(identity, _)| identity) + .collect(); + for (identity, head) in &reach_head_standings { + eprintln!( + "[floor-reach-differential] identity={identity} head={head} base=not-measured" + ); + } + for identity in &head_failed { + eprintln!( + "[floor-reach-finding] ReachedClaimFailedAtHead identity={identity} -- \ + reached by this change's body edits and failing at its head; whether it \ + passed at base was not measured" + ); + } + let failure = format!( + "REACH-DIFFERENTIAL REFUSAL cause=BaseArmRefused {cause} -- the base side of \ + {} reached claims was not measured, so no verdict is read and none is \ + assumed; head_failed={}", + identities.len(), + head_failed.len() + ); + eprintln!("[floor-phase] phase=reach-differential {baseline_line} {failure}"); + if blocking_budget_ms > 0 { + // EVERY CLAIM THE REFUSED ARM LEFT UNJUDGED, BY NAME: the arm refused as a + // whole, so each identity it would have judged blocks as base_arm_refused. + outcome.reach_differential_blocking.extend( + identities + .iter() + .map(|identity| (identity.clone(), "base_arm_refused".to_string())), + ); + } + } + Ok(base) => { + let mut blocking: Vec<(String, String)> = Vec::new(); + let mut counts: BTreeMap = BTreeMap::new(); + for (identity, head_name) in &reach_head_standings { + if head_cannot_block.contains(identity) { + *counts.entry("head_cannot_block".to_string()).or_default() += 1; + eprintln!( + "[floor-reach-differential] identity={identity} base=not-run \ + head={head_name} differential=head_cannot_block blocks=false" + ); + continue; + } + let base_name = base.get(identity).map(String::as_str).unwrap_or("missing"); + let verdict = v1_interpreter::run_in_context_with_args( + &verdict_frame, + "v2.workflow.required_floor.reach_claim_verdict", + &[ + ( + Some("identity".to_string()), + v1_interpreter::str_value(identity), + ), + ( + Some("entry".to_string()), + v1_interpreter::str_value(reach_claim_entry(identity)), + ), + ( + Some("function".to_string()), + v1_interpreter::str_value( + identity.rsplit_once('.').map(|(_, f)| f).unwrap_or(""), + ), + ), + ( + Some("base".to_string()), + v1_interpreter::str_value(base_name), + ), + ( + Some("head".to_string()), + v1_interpreter::str_value(head_name), + ), + ], + false, + ) + .map_err(|e| format!("reach_claim_verdict({identity}): {e}"))?; + let (differential, blocks) = match &verdict { + v1_interpreter::Value::Variant { + variant_name, + fields, + .. + } if verdict_frame.sym_eq(*variant_name, "ReachVerdict") => { + match ( + verdict_frame.field(fields, "differential"), + verdict_frame.field(fields, "blocks"), + ) { + ( + Some(v1_interpreter::Value::Str(d)), + Some(v1_interpreter::Value::Bool(b)), + ) => (d.to_string(), *b), + _ => { + return Err(format!( + "reach_claim_verdict({identity}): malformed ReachVerdict" + )) + } + } + } + v1_interpreter::Value::Variant { + variant_name, + fields, + .. + } if verdict_frame.sym_eq(*variant_name, "ReachVerdictRefused") => { + let reason = match verdict_frame.field(fields, "reason") { + Some(v1_interpreter::Value::Str(r)) => r.to_string(), + _ => String::new(), + }; + ("refused".to_string(), { + blocking.push((identity.clone(), format!("refused: {reason}"))); + true + }) + } + other => { + return Err(format!( + "reach_claim_verdict({identity}) returned an arm this host does \ + not know: {}", + verdict_frame.format_value(other) + )) + } + }; + *counts.entry(differential.clone()).or_default() += 1; + eprintln!( + "[floor-reach-differential] identity={identity} base={base_name} \ + head={head_name} differential={differential} blocks={blocks}" + ); + if blocks && differential != "refused" { + blocking.push((identity.clone(), differential.clone())); + // THE DEQUEUED AUTHOR'S RECEIPT: each identity this change newly broke, by + // name, so nobody has to rerun the queue to learn what failed. + if differential == "regressed" || differential == "new_claim" { + eprintln!( + "[floor-reach-finding] NewlyFailedAtHead identity={identity} \ + base={base_name} head={head_name} differential={differential}" + ); + } + } + } + let mode = if blocking_budget_ms > 0 { + "blocking" + } else { + "report_only" + }; + eprintln!( + "[floor-phase] phase=reach-differential {baseline_line} mode={mode} \ + planned={} verdicts={counts:?} {}={}", + reach_head_standings.len(), + if blocking_budget_ms > 0 { + "blocking" + } else { + "would_block" + }, + blocking.len() + ); + if blocking_budget_ms > 0 { + outcome.reach_differential_blocking.extend(blocking); + } + } + } + } else { + eprintln!("[floor-phase] phase=reach-differential planned=0"); + } outcome.route_gap_held = route_gap_held; outcome.known_red_now_passing = known_red_now_passing; outcome.known_red_budget_refused = known_red_budget_refused; @@ -13305,6 +13664,7 @@ pub(crate) fn required_floor_disposition_label( match disposition { RequiredFloorDisposition::Planned => "planned", RequiredFloorDisposition::PlannedAsChangedWitness => "planned_as_changed_witness", + RequiredFloorDisposition::PlannedAsReachConsumer => "planned_as_reach_consumer", RequiredFloorDisposition::DeclinedLongModule { .. } => "declined_long_module", RequiredFloorDisposition::DeclinedFixtureMember { .. } => "declined_fixture_member", RequiredFloorDisposition::DeclinedOutsideRequiredGate => "declined_outside_required_gate", @@ -13343,6 +13703,7 @@ pub(crate) fn required_floor_disposition_matched_prefix( RequiredFloorDisposition::DeclinedNoCiWetLane { pattern } => pattern, RequiredFloorDisposition::Planned | RequiredFloorDisposition::PlannedAsChangedWitness + | RequiredFloorDisposition::PlannedAsReachConsumer | RequiredFloorDisposition::DeclinedOutsideRequiredGate | RequiredFloorDisposition::DeclinedOutsideGateClosure | RequiredFloorDisposition::DeclinedCostDebt => "", @@ -14522,6 +14883,7 @@ mod changed_witness_projection_tests { known_red_passed_over_budget: 0, known_red_host_tool_unresolved_held: 0, known_red_host_effect_refused: 0, + reach_differential_blocking: Vec::new(), stale_quarantine: Vec::new(), interrupted_before_verdict: Vec::new(), completed_over_cost_requirement: Vec::new(), @@ -18467,6 +18829,210 @@ fn declared_no_ci_wet_lane_population() -> &'static std::collections::HashSet Result<(Vec, HashSet), String> { + let mut exempt: HashSet = HashSet::new(); + let mut needs_base: Vec = Vec::new(); + for (identity, head_name) in head_standings { + match v1_interpreter::run_in_context_with_args( + frame, + "v2.workflow.required_floor.reach_head_cannot_block", + &[( + Some("head".to_string()), + v1_interpreter::str_value(head_name), + )], + false, + ) { + Ok(v1_interpreter::Value::Bool(true)) => { + exempt.insert(identity.clone()); + } + Ok(v1_interpreter::Value::Bool(false)) => needs_base.push(identity.clone()), + other => { + return Err(format!( + "reach_head_cannot_block({identity}) returned {}", + floor_value_shape(other.as_ref().ok()) + )) + } + } + } + Ok((needs_base, exempt)) +} + +/// THE BASELINE ARM THE RUN REPORTS IS THE ONE THAT HAPPENED (review 73484): `not-measured` +/// when the base side is not run (report-only), `ran-at-merge-base` when it is. Rendered by +/// `v2.workflow.required_floor` `baseline_standing_line`; this only chooses which arm happened. +pub(crate) fn reach_baseline_line( + frame: &v1_interpreter::InterpContext, + blocking_budget_ms: u64, + base_sha: &str, +) -> Result { + let (function, args) = if blocking_budget_ms == 0 { + ( + "v2.workflow.required_floor.reach_baseline_not_measured_line", + vec![( + Some("cause".to_string()), + v1_interpreter::str_value("reach_differential_standing is DifferentialReportOnly"), + )], + ) + } else { + ( + "v2.workflow.required_floor.reach_baseline_ran_at_merge_base_line", + vec![ + ( + Some("main_sha".to_string()), + v1_interpreter::str_value(base_sha), + ), + ( + Some("cause".to_string()), + v1_interpreter::str_value("no per-landing baseline store exists yet"), + ), + ], + ) + }; + // Matched by reference: `Value` implements Drop (#12886), so a pattern may not move out of it. + let result = v1_interpreter::run_in_context_with_args(frame, function, &args, false); + match &result { + Ok(v1_interpreter::Value::Str(line)) => Ok(line.to_string()), + other => Err(format!( + "{function} returned {}", + floor_value_shape(other.as_ref().ok()) + )), + } +} + +/// Run the base side of the reach differential: a detached worktree at `base`, this binary in +/// `--reach-base-standings` mode with its working directory there, and the standings it prints. +/// Every failure to produce a COMPLETE set of standings is an `Err` the caller reports by name; +/// no identity gets a default standing. With a positive `budget_ms` the whole arm is bounded by +/// it and an arm over it refuses (`BaseArmOverBudget`), never truncating to the claims that +/// finished. +fn run_reach_base_arm( + source_roots: &[String], + base: &str, + identities: &[String], + claim_wall_limit_ms: u64, + budget_ms: u64, +) -> Result, String> { + let root = process_workspace_root(); + let scratch = root.join(format!("target/reach_base_{}", std::process::id())); + let _ = std::fs::remove_dir_all(&scratch); + let worktree = scratch.join("tree"); + std::fs::create_dir_all(&scratch).map_err(|e| format!("reach base scratch: {e}"))?; + let identities_file = scratch.join("identities.txt"); + std::fs::write(&identities_file, identities.join("\n")) + .map_err(|e| format!("reach base identities: {e}"))?; + let added = std::process::Command::new("git") + .current_dir(&root) + .args(["worktree", "add", "--detach", "--quiet"]) + .arg(&worktree) + .arg(base) + .status() + .map_err(|e| format!("git worktree add: {e}"))?; + if !added.success() { + return Err(format!("git worktree add at {base} exited {added}")); + } + let exe = std::env::current_exe().map_err(|e| format!("current_exe: {e}"))?; + let mut command = std::process::Command::new(exe); + command.current_dir(&worktree); + for r in source_roots { + let rel = std::path::Path::new(r) + .strip_prefix(&root) + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_else(|_| r.clone()); + command.arg("--source-root").arg(rel); + } + command + .arg("--reach-base-standings") + .arg(&identities_file) + .arg("--claim-wall-limit-ms") + .arg(claim_wall_limit_ms.to_string()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::inherit()); + let started = std::time::Instant::now(); + let mut child = command + .spawn() + .map_err(|e| format!("spawn base arm: {e}"))?; + let status = loop { + if let Some(status) = child + .try_wait() + .map_err(|e| format!("base arm wait: {e}"))? + { + break status; + } + if budget_ms > 0 && started.elapsed().as_millis() as u64 > budget_ms { + let _ = child.kill(); + let _ = child.wait(); + remove_reach_base_worktree(&root, &worktree, &scratch); + return Err(format!( + "BaseArmOverBudget wall_ms>{budget_ms} identities={}", + identities.len() + )); + } + std::thread::sleep(std::time::Duration::from_millis(200)); + }; + let mut stdout = String::new(); + if let Some(mut out) = child.stdout.take() { + use std::io::Read; + let _ = out.read_to_string(&mut stdout); + } + let wall_ms = started.elapsed().as_millis(); + remove_reach_base_worktree(&root, &worktree, &scratch); + eprintln!( + "[floor-phase] phase=reach-base-arm base={base} identities={} wall_ms={wall_ms} exit={status}", + identities.len() + ); + if let Some(line) = stdout + .lines() + .find(|l| l.starts_with("reach-base-refused ")) + { + return Err(format!("BaseArmNotAVerdict {line}")); + } + if !status.success() { + return Err(format!("BaseArmFailed exit={status}")); + } + let mut standings: HashMap = HashMap::new(); + for line in stdout.lines() { + let Some(rest) = line.strip_prefix("reach-base identity=") else { + continue; + }; + let Some((identity, standing)) = rest.split_once(" standing=") else { + return Err(format!("BaseArmUnparseable {line:?}")); + }; + standings.insert(identity.to_string(), standing.to_string()); + } + let missing: Vec<&String> = identities + .iter() + .filter(|i| !standings.contains_key(*i)) + .collect(); + if !missing.is_empty() { + return Err(format!( + "BaseArmIncomplete {} of {} identities have no standing, first {:?}", + missing.len(), + identities.len(), + missing.first() + )); + } + Ok(standings) +} + +fn remove_reach_base_worktree( + root: &std::path::Path, + worktree: &std::path::Path, + scratch: &std::path::Path, +) { + let _ = std::process::Command::new("git") + .current_dir(root) + .args(["worktree", "remove", "--force"]) + .arg(worktree) + .status(); + let _ = std::fs::remove_dir_all(scratch); +} /// WHICH DISPOSITIONS DECIDE A CHANGED-WITNESS SELECTION, as an EXHAUSTIVE match: a new /// `RequiredFloorDisposition` arm does not compile here until it states whether it counts, which /// is the structural ceiling of `gunbc.recurring_failure_mode.a_new_decision_arm_the_downstream_join_does_not_admit` @@ -18476,6 +19042,7 @@ fn decides_a_changed_selection(disposition: &RequiredFloorDisposition) -> bool { RequiredFloorDisposition::PlannedAsChangedWitness | RequiredFloorDisposition::DeclinedNoCiWetLane { .. } => true, RequiredFloorDisposition::Planned + | RequiredFloorDisposition::PlannedAsReachConsumer | RequiredFloorDisposition::DeclinedLongModule { .. } | RequiredFloorDisposition::DeclinedFixtureMember { .. } | RequiredFloorDisposition::DeclinedOutsideRequiredGate @@ -18497,6 +19064,7 @@ fn suppresses_a_changed_witness_enrollment(disposition: &RequiredFloorDispositio RequiredFloorDisposition::DeclinedNoCiWetLane { .. } => true, RequiredFloorDisposition::Planned | RequiredFloorDisposition::PlannedAsChangedWitness + | RequiredFloorDisposition::PlannedAsReachConsumer | RequiredFloorDisposition::DeclinedLongModule { .. } | RequiredFloorDisposition::DeclinedFixtureMember { .. } | RequiredFloorDisposition::DeclinedOutsideRequiredGate @@ -18582,6 +19150,24 @@ mod changed_witness_sublane_join_tests { .expect("a declined wet selection is decided"); } + /// A reach consumer is NOT a changed-witness selection: its row does not satisfy a selection + /// (selected_without_disposition) and, unselected, is not foreign (the join stays clean). + #[test] + fn a_planned_as_reach_consumer_row_is_not_a_changed_selection() { + assert!(!decides_a_changed_selection( + &RequiredFloorDisposition::PlannedAsReachConsumer + )); + let rows = vec![row("m.r", RequiredFloorDisposition::PlannedAsReachConsumer)]; + changed_witness_sublane_join(&HashSet::new(), &rows) + .expect("an unselected reach consumer is not foreign to the join"); + let expected: HashSet = ["m.r"].iter().map(|s| s.to_string()).collect(); + let missing = changed_witness_sublane_join(&expected, &rows).unwrap_err(); + assert!( + missing.contains("selected_without_disposition=[m.r]"), + "{missing}" + ); + } + /// The join is still exact: a selection with no deciding row refuses, and a deciding row with no /// selection refuses, whichever arm decided it. #[test] @@ -18643,10 +19229,44 @@ mod changed_selections_outside_discovery_mirror_tests { .collect() } + /// A `List` value in either realization the interpreter produces: the host vector, or the + /// free-monoid `Cons`/`Empty` chain a `.dag` fold builds (`v2.std.algebra` `list_reverse` + /// returns one since gunbc#13138). Anything else is not a list and panics with its own name. + fn list_items(ctx: &v1_interpreter::InterpContext, value: &Value) -> Vec { + let mut out = Vec::new(); + let mut cursor = value.clone(); + loop { + let next = match &cursor { + Value::List(items) => { + out.extend(items.iter().cloned()); + return out; + } + Value::Variant { + variant_name, + fields, + .. + } if ctx.sym_eq(*variant_name, "Empty") && fields.is_empty() => return out, + Value::Variant { + variant_name, + fields, + .. + } if ctx.sym_eq(*variant_name, "Cons") => { + let (Some(head), Some(tail)) = + (ctx.field(fields, "head"), ctx.field(fields, "tail")) + else { + panic!("a Cons cell of the .dag decider's list lacks head or tail"); + }; + out.push(head.clone()); + tail.clone() + } + _ => panic!("the .dag decider did not return a List"), + }; + cursor = next; + } + } + fn dag_rows(ctx: &v1_interpreter::InterpContext, value: &Value) -> BTreeMap { - let Value::List(rows) = value else { - panic!("the .dag decider did not return a List"); - }; + let rows = list_items(ctx, value); let mut out = BTreeMap::new(); for row in rows.iter() { let Value::Record { fields, .. } = row else { diff --git a/src/v2/test/fixture/route_gap_admission_partition.dag b/src/v2/test/fixture/route_gap_admission_partition.dag index 15ad7f2a3c1..b70440ea17a 100644 --- a/src/v2/test/fixture/route_gap_admission_partition.dag +++ b/src/v2/test/fixture/route_gap_admission_partition.dag @@ -13,21 +13,18 @@ import v2.workflow.floor_route_gap { // modeled partition over these rows on the floor (the route: the same surface the run site // marshals into). The seed's `required_floor_runner` pairing witness evaluates the same rows // through `run_in_context_with_args` -- the real call path the run site uses -- and asserts both -// arms at identity grain. The rows span both arms and both grounds the runner produces: a -// declared suppressed row whose module the gate cut withdrew (measured, ground kept), a -// cost-debt-withheld row the tree declares (measured, ground kept), and an enrollment the tree -// does not declare -- renamed, deleted, or fabricated (refused; the wall). Grounds are the -// declared coproduct arms (`v2.workflow.floor_route_gap`'s `FloorRouteGapSuppressionGround`), +// arms at identity grain. Membership is carried PER ROW as a Boolean (the runner reads it from +// its keyed disposition index), so the relation decides each arm from the row itself and never +// scans a corpus-sized declared list. The rows span both arms and both grounds the runner +// produces: a declared suppressed row whose module the gate cut withdrew (measured, ground +// kept), a cost-debt-withheld row the tree declares (measured, ground kept), and an enrollment +// the tree does not declare -- renamed, deleted, or fabricated (refused; the wall). Grounds are +// the declared coproduct arms (`v2.workflow.floor_route_gap`'s `FloorRouteGapSuppressionGround`), // bound by the field's declared type -- the same construction route the runner's marshal takes. data route_gap_partition_fixture_suppressed: List = [ - FloorRouteGapSuppressedRow { identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", ground: OutsideRequiredGate }, - FloorRouteGapSuppressedRow { identity: "test.claim.parse_test.parse_witness_floor_holds", ground: WithheldCostDebt }, - FloorRouteGapSuppressedRow { identity: "test.claim.renamed_away_witness_test.old_witness_name", ground: OutsideRequiredGate } -] - -data route_gap_partition_fixture_declared: List = [ - "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", - "test.claim.parse_test.parse_witness_floor_holds" + FloorRouteGapSuppressedRow { identity: "test.claim.machine_intake.mtcollins1_kvm_observer_protocol_wet_witness.a_holds", ground: OutsideRequiredGate, declared: true }, + FloorRouteGapSuppressedRow { identity: "test.claim.parse_test.parse_witness_floor_holds", ground: WithheldCostDebt, declared: true }, + FloorRouteGapSuppressedRow { identity: "test.claim.renamed_away_witness_test.old_witness_name", ground: OutsideRequiredGate, declared: false } ] data route_gap_partition_fixture_expected: List = [ @@ -36,21 +33,9 @@ data route_gap_partition_fixture_expected: List = [ FloorRouteGapAdmissionRefusedRow { identity: "test.claim.renamed_away_witness_test.old_witness_name" } ] -fn partition_fixture_suppressed() -> List { - route_gap_partition_fixture_suppressed -} - -fn partition_fixture_declared() -> List { - route_gap_partition_fixture_declared -} - -fn partition_fixture_expected() -> List { - route_gap_partition_fixture_expected -} - +// THE DECIDED LIST IS THE RELATION'S ANSWER OVER THE FIXTURE, and both witnesses check it +// against `route_gap_partition_fixture_expected` at identity grain: measured rows one-for-one +// with the same identity and ground, refused rows one-for-one by identity. fn partition_fixture_decided() -> List { - floor_route_gap_admission_partition( - suppressed: route_gap_partition_fixture_suppressed, - declared: route_gap_partition_fixture_declared, - ) + floor_route_gap_admission_partition(suppressed: route_gap_partition_fixture_suppressed) } diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index a5c7d833bc9..c847b8e55d9 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -1,7 +1,7 @@ module v2.workflow.floor_route_gap import std.types { List } -import v2.std.algebra { Cons, Empty, contains, list_flat_map } +import v2.std.algebra { Cons, Empty, list_flat_map } // THE EXPECTATION IS TYPED AT THE TWO FACTS THE INTERPRETER OBSERVES. An identity-only row // says merely that some route gap once existed; it cannot object when the witness reaches a @@ -2346,12 +2346,15 @@ fn floor_route_gap_expectation_chunk_38() -> List { // a runner-side copy would be a second authority over one decision, the shape the changed- // selection capability documented at v2.workflow.required_floor's dissolve-on note retires. // -// COST, NAMED: one frame call per required run, linear in suppressed x declared. The -// changed-selection capability measured per-probe re-folds at roughly 38,000 eval steps and -// retired the mirror for it; the route-gap suppressed population is smaller than the corpus -// census roster and this call is not per claim. If the roster or the gate grows the join back -// into a cost, the corpus census's served-once shape (floor_pure_producer_share) is the named -// retirement. +// COST, NAMED, AND SHAPE-FIXED: the relation is one call per required run, LINEAR in the +// suppressed population, and it never scans a corpus-sized list. Membership is marshaled per +// suppressed row as a Boolean the runner reads from its keyed disposition index +// (cost_debt_disposition_index.contains_key, O(1) per identity) -- the declared-identity +// universe is the discovery walk's own knowledge and stays on the walk's side of the marshal; +// flattening it into a list the .dag re-scans was the corpus-sized join this shape retires +// (541 suppressed x 28,274 declared on run 37236808750 would have been ~15M interpreted +// comparisons per required run). The arm decision itself stays here: membership is an input +// fact, what each arm MEANS is the authority's. // THE GROUND IS A DECLARED COPRODUCT, NOT A LABEL. Suppression grounds are a closed set -- the // floor removes a row as outside the required gate, as withheld cost debt, or as declined for // want of a CI wet lane -- so the field carries that set, and a fabricated ground is a @@ -2364,7 +2367,11 @@ fn floor_route_gap_expectation_chunk_38() -> List { // (src/v2/test/fixture/route_gap_admission_partition.dag) instead of forking silently. type FloorRouteGapSuppressionGround = OutsideRequiredGate | WithheldCostDebt | DeclinedNoCiWetLane -type FloorRouteGapSuppressedRow = { identity: String, ground: FloorRouteGapSuppressionGround } +type FloorRouteGapSuppressedRow = { + identity: String, + ground: FloorRouteGapSuppressionGround, + declared: Bool, +} // THE TWO ARMS AT IDENTITY GRAIN. A declared suppressed row is MEASUREMENT: it keeps its ground // and the run names it, and for identities whose module the 2026-08-29 gate cut withdrew that @@ -2375,17 +2382,12 @@ type FloorRouteGapAdmissionRow = FloorRouteGapAdmissionMeasuredRow { identity: String, ground: FloorRouteGapSuppressionGround } | FloorRouteGapAdmissionRefusedRow { identity: String } -fn floor_route_gap_string_eq(left: String, right: String) -> Bool { - left == right -} - fn floor_route_gap_admission_partition( suppressed: List, - declared: List, ) -> List { map( suppressed, - row => match contains(xs: declared, item: row.identity, eq: floor_route_gap_string_eq) { + row => match row.declared { true => FloorRouteGapAdmissionMeasuredRow { identity: row.identity, ground: row.ground } false => FloorRouteGapAdmissionRefusedRow { identity: row.identity } }, diff --git a/src/v2/workflow/required_floor.dag b/src/v2/workflow/required_floor.dag index 1496176e6b3..1285f493d97 100644 --- a/src/v2/workflow/required_floor.dag +++ b/src/v2/workflow/required_floor.dag @@ -1,6 +1,7 @@ module v2.workflow.required_floor -import v2.std.algebra { Cons, Empty, contains, list_reverse } +import v2.std.algebra { Cons, Empty, contains, any, list_reverse } +import gunbc.explicit_witness_admission { explicit_witness_admissions, explicit_witness_admission_is_known_red } import v2.std.optional { Optional, Present, Absent } import v2.std.integer { Int, int_gt, int_add, int_mul } import v2.workflow.floor_expected_red { floor_expected_red_roster } @@ -42,7 +43,7 @@ import std.measure { eval_step_count, eval_step_count_value, } -import std.types { NonEmptyStr, List } +import std.types { NonEmptyStr, List, Milliseconds } import extdeps.languages.yaml.ingest { ingest_yaml_source, IngestedYaml, YamlIngestRejected } import extdeps.languages.yaml.types { yaml_entry_count } import std.algebra { Cons, Empty } @@ -118,6 +119,7 @@ import std.algebra { Cons, Empty } type RequiredFloorDisposition = Planned | PlannedAsChangedWitness + | PlannedAsReachConsumer | DeclinedLongModule { matched_prefix: String } | DeclinedFixtureMember { matched_prefix: String } | DeclinedOutsideRequiredGate @@ -138,6 +140,7 @@ fn required_floor_disposition_name(d: RequiredFloorDisposition) -> String { match d { Planned => "planned", PlannedAsChangedWitness => "planned_as_changed_witness", + PlannedAsReachConsumer => "planned_as_reach_consumer", DeclinedLongModule { matched_prefix } => "declined_long_module", DeclinedFixtureMember { matched_prefix } => "declined_fixture_member", DeclinedOutsideRequiredGate => "declined_outside_required_gate", @@ -385,6 +388,7 @@ fn cost_debt_roster_standing(reading: CostDebtDispositionReading) -> CostDebtRos DeclinedNoCiWetLane { pattern: _ } => CostDebtOutsideThisRunsUniverse {} DeclinedChangedWitnessOutsideDiscovery { module_path: _ } => CostDebtOutsideThisRunsUniverse {} Planned => CostDebtDeclaredButNotWithheld {} + PlannedAsReachConsumer => CostDebtDeclaredButNotWithheld {} DeclinedLongModule { matched_prefix: _ } => CostDebtDeclaredButNotWithheld {} DeclinedFixtureMember { matched_prefix: _ } => CostDebtDeclaredButNotWithheld {} DeclinedOutsideRequiredGate => CostDebtDeclaredButNotWithheld {} @@ -2529,3 +2533,275 @@ fn required_floor_grandfathered_eval_step_budget() -> EvalStepCount { fn required_floor_new_witness_eval_step_budget() -> EvalStepCount { claim_ceiling_eval_step_budget(tier: NewWitnessTier {}) } + +// THE PER-PR v2 CLAIM DIFFERENTIAL (operator ruling 2026-09-27; gunbc.recurring_failure_mode +// green_floor_executes_no_claim_whose_verdict_a_body_change_can_move). The population is the +// execution-grain reach selection intersected with the v2 claim set -- never the whole set, which +// is the absorbing superset DESIGN section 5 forbids. Those claims were never gated, so their +// absolute verdict is not the question: main may already carry reds a PR did not cause. The +// question is whether THIS change moved a verdict, so the verdict is over a PAIR of standings. +type ClaimStanding + = StandingPassed + | StandingFailed + | StandingNotDeclared + +// A claim not declared at base is NEW: it has no prior verdict to regress from, so it is judged +// exactly as today's changed witness is -- it must pass at head. A claim that fails on both sides +// is main's red, counted and not this change's; a repair is reported and never blocks. Only a +// claim that passed at base and does not pass at head is a regression the change caused. +type ClaimDifferential + = DifferentialHeld + | DifferentialRegressed { head: ClaimStanding } + | DifferentialStillRed + | DifferentialRepaired + | DifferentialNewClaim { head: ClaimStanding } + | DifferentialRemoved + | DifferentialUndeclaredAtBothSides + +fn claim_differential(base: ClaimStanding, head: ClaimStanding) -> ClaimDifferential { + match base { + StandingNotDeclared => + match head { + StandingNotDeclared => DifferentialUndeclaredAtBothSides + StandingPassed => DifferentialNewClaim { head: head } + StandingFailed => DifferentialNewClaim { head: head } + } + StandingPassed => + match head { + StandingPassed => DifferentialHeld + StandingFailed => DifferentialRegressed { head: head } + StandingNotDeclared => DifferentialRemoved + } + StandingFailed => + match head { + StandingPassed => DifferentialRepaired + StandingFailed => DifferentialStillRed + StandingNotDeclared => DifferentialRemoved + } + } +} + +fn claim_differential_blocks(d: ClaimDifferential) -> Bool { + match d { + DifferentialRegressed { head } => true + DifferentialNewClaim { head } => + match head { + StandingPassed => false + StandingFailed => true + StandingNotDeclared => true + } + DifferentialHeld => false + DifferentialStillRed => false + DifferentialRepaired => false + DifferentialRemoved => false + DifferentialUndeclaredAtBothSides => true + } +} + +// WHERE THE BASE STANDING COMES FROM. The baseline is produced once per landing, on the merge +// group's composed revision, which is the sha main then carries; a PR reads it at its own merge +// base. Its key is (main sha, claim identity) and that pair is COMPLETE for a hermetic claim: the +// sha fixes the whole tree -- so every declaration in the claim's reach -- and the seed and +// interpreter built from it. A wet claim reads the host, so no sha keys it; wet claims are not in +// the baseline and are not selected for the differential. A missing baseline is not an empty one: +// it is counted and, where the base side runs, it runs at the merge base, loudly, and is never +// widened into running every claim. Where it does NOT run (reach_differential_standing is +// DifferentialReportOnly), the run says BaselineNotMeasured and decides +// no verdict. It never prints a base that was not measured (review 73484). +type BaselineStanding + = BaselineRead { main_sha: String } + | BaselineMissingRanAtMergeBase { main_sha: String, cause: String } + | BaselineNotMeasured { cause: String } + +// THE BASELINE'S READER. Its only producer today is the merge base: no per-landing baseline store +// exists yet, so every run reaches the BaselineMissingRanAtMergeBase arm and says so by name. The +// rendered line is what the floor prints, so a reader of the run sees which arm the base came from +// and, when it was run, why. `BaselineRead` gains its producer with the store; until then it is +// the declared frontier this note names, and its trigger is a baseline written per landing on the +// merge group's composed revision, keyed (main sha, claim identity). +fn baseline_standing_line(b: BaselineStanding) -> String { + match b { + BaselineRead { main_sha: s } => concat("baseline=read main_sha=", s) + BaselineMissingRanAtMergeBase { main_sha: s, cause: c } => + concat(concat(concat("baseline=missing-ran-at-merge-base main_sha=", s), " cause="), c) + BaselineNotMeasured { cause: c } => concat("baseline=not-measured cause=", c) + } +} + +fn reach_baseline_not_measured_line(cause: String) -> String { + baseline_standing_line(b: BaselineNotMeasured { cause: cause }) +} + +fn reach_baseline_ran_at_merge_base_line(main_sha: String, cause: String) -> String { + baseline_standing_line(b: BaselineMissingRanAtMergeBase { main_sha: main_sha, cause: cause }) +} + +// THE HOST WIRE FOR ONE REACHED CLAIM. The host holds two standing NAMES it observed, never a +// verdict: the verdict and whether it blocks are claim_differential and claim_differential_blocks, +// here and nowhere else. A name this module does not know refuses through StandingNotParsed rather +// than defaulting to a standing that could hide a regression. +// NOT MEASURED IS A DECLARED ARM OF THE WIRE, NOT A STRING THE PREDICATES EACH TEST FOR (review +// 76416): a side that produced no verdict (an interruption, a panic, an unresolved host tool) is +// parsed here, once, into StandingNotMeasured, and every reader matches the arm. +type ParsedClaimStanding + = StandingParsed { standing: ClaimStanding } + | StandingNotMeasured + | StandingNotParsed { name: String } + +fn claim_standing_named(name: String) -> ParsedClaimStanding { + if name == "passed" { + StandingParsed { standing: StandingPassed } + } else if name == "failed" { + StandingParsed { standing: StandingFailed } + } else if name == "not_declared" { + StandingParsed { standing: StandingNotDeclared } + } else if name == "not_measured" { + StandingNotMeasured + } else { + StandingNotParsed { name: name } + } +} + +fn claim_differential_name(d: ClaimDifferential) -> String { + match d { + DifferentialHeld => "held" + DifferentialRegressed { head: _ } => "regressed" + DifferentialStillRed => "still_red" + DifferentialRepaired => "repaired" + DifferentialNewClaim { head: _ } => "new_claim" + DifferentialRemoved => "removed" + DifferentialUndeclaredAtBothSides => "undeclared_at_base_and_head" + } +} + +type ReachClaimVerdict + = ReachVerdict { differential: String, blocks: Bool } + | ReachVerdictRefused { reason: String } + +// ONE DIFFERENTIAL, BOTH FIELDS (review 76416). The name and blocks are read from the SAME +// ClaimDifferential value, through this one constructor, so they cannot disagree. The record keeps +// its two flat fields because they are the host wire the seed's floor runner reads +// (v1_compiler.cli_run.required_floor_runner, reach_base_standings); the agreement is held here. +fn reach_verdict_of(d: ClaimDifferential) -> ReachClaimVerdict { + ReachVerdict { differential: claim_differential_name(d: d), blocks: claim_differential_blocks(d: d) } +} + +// A BASE OUTCOME THAT WAS NOT A VERDICT (a budget interruption, a panic, an unresolved host tool or +// effect, a claim not attempted) is reported for THAT claim alone and never read as failed: failed +// would turn a head failure into still_red and hide the regression. The other reached claims keep +// their verdicts (srv1, 2026-10-01: one BudgetInterrupted claim of 60 used to void all 60). +// +// WHETHER IT BLOCKS IS DERIVED, NOT BLANKET (deep-ferret-305, 2026-10-01). A claim on a DECLARED +// main-red roster is main's debt, already declared and owned, so it is a counted +// base_not_measured_rostered finding and does not block. A claim on no roster BLOCKS as +// base_not_measured_unrostered (BaseNotMeasuredUnrostered): the floor cannot show it is not a +// regression, and nothing declared says it was already red. +// +// THE DECLARED MAIN-RED ROSTERS, BOTH, each joined at its own grain: v2.workflow.floor_expected_red +// by qualified identity, and gunbc.explicit_witness_admission's known-red rows by (entry, function) +// through that roster's own explicit_witness_admission_is_known_red. The first cut joined only the +// first, so three known_red_probe rows of map_literal_test blocked as unrostered on the srv1 +// control rerun (neat-boar-16, 2026-10-01). +// NAMED RESIDUAL, stated so the unrostered rule is not over-read. The base arm runs each claim under +// the same per-claim limit the head arm does: no eval-budget deadline, and the 8000 ms wall of +// required_floor_claim_wall_safety_limit_ms. That wall is a HANG GUARD, not the budget (the +// floor's budget verdict is deterministic eval steps, claim_eval_step_budget_for_identity), and it +// is reported as BudgetInterrupted. POPULATION: unrostered reached claims whose base run lands +// within host-load noise of 8 s (the srv1 control: three map_literal claims at 8010 to 8054 ms). +// EFFECT: their dequeue is load-dependent, though loud and named (base_not_measured_unrostered), +// never silent. TRIGGER, NAMING THE CAPABILITY: the hang guard gets its own typed refusal and a +// declared, much larger value through one plumbing for both arms (jolly-boar-500's cost lane, +// after gunbc#12861), sufficient that no claim within its eval-step budget is interrupted by it. +fn claim_on_declared_main_red_roster(identity: String, entry: String, function: String) -> Bool { + contains(xs: floor_expected_red_roster(), item: identity, eq: fn(a, b) { a == b }) + || any(xs: explicit_witness_admissions, predicate: fn(r) { + (r.witness.entry as String) == entry + && r.witness.function == function + && explicit_witness_admission_is_known_red(row: r) + }) +} + +fn base_not_measured_verdict(identity: String, entry: String, function: String) -> ReachClaimVerdict { + if claim_on_declared_main_red_roster(identity: identity, entry: entry, function: function) { + ReachVerdict { differential: "base_not_measured_rostered", blocks: false } + } else { + ReachVerdict { differential: "base_not_measured_unrostered", blocks: true } + } +} + +fn reach_claim_verdict( + identity: String, + entry: String, + function: String, + base: String, + head: String, +) -> ReachClaimVerdict { + match claim_standing_named(name: head) { + StandingNotMeasured => + ReachVerdictRefused { reason: concat("head not measured: a reached claim has no verdict at head ", identity) } + StandingNotParsed { name: h } => ReachVerdictRefused { reason: concat("unknown head standing ", h) } + StandingParsed { standing: hs } => + match claim_standing_named(name: base) { + StandingNotMeasured => base_not_measured_verdict(identity: identity, entry: entry, function: function) + StandingNotParsed { name: b } => ReachVerdictRefused { reason: concat("unknown base standing ", b) } + StandingParsed { standing: bs } => reach_verdict_of(d: claim_differential(base: bs, head: hs)) + } + } +} + +// WHICH REACHED CLAIMS NEED A BASE STANDING AT ALL. A head standing CANNOT BLOCK when no base +// standing makes claim_differential_blocks true for it, and then its base is irrelevant to the +// verdict. That is derived from the verdict function over EVERY base arm, not asserted. Today it +// holds for StandingPassed only: a head NotDeclared blocks when the base is NotDeclared too +// (DifferentialUndeclaredAtBothSides, review 76405), so a removal runs the base arm to prove it was one, +// so the base arm runs only the claims that FAIL at head: 60 of 1481 for a one-line v2.std.node body edit and 18 of 240 for gunbc#12582's change +// (neat-boar-16, srv1, 2026-10-01). An unknown head name answers false, so it still goes to the +// base arm and refuses there rather than being skipped. +fn head_cannot_block(head: ClaimStanding) -> Bool { + !claim_differential_blocks(d: claim_differential(base: StandingPassed, head: head)) + && !claim_differential_blocks(d: claim_differential(base: StandingFailed, head: head)) + && !claim_differential_blocks(d: claim_differential(base: StandingNotDeclared, head: head)) +} + +// A HEAD THAT WAS NOT MEASURED (a non-verdict at head: a wall interruption under host load) needs +// no base, because no base standing can turn ignorance into a verdict: reach_claim_verdict REFUSES it, +// typed and located (review 76405; DESIGN section 5, a failure arm refuses, never widens), rather than +// reporting it as a non-blocking answer and leaning on the interruption rule elsewhere to refuse. +fn reach_head_cannot_block(head: String) -> Bool { + match claim_standing_named(name: head) { + StandingParsed { standing: h } => head_cannot_block(head: h) + StandingNotMeasured => true + StandingNotParsed { name: _ } => false + } +} + +// WHETHER A BLOCKING VERDICT STOPS THE REQUIRED RUN, AND WHETHER THE BASE SIDE RUNS AT ALL. Under +// DifferentialBlocking the base side runs and every reached claim gets a verdict. Under +// DifferentialReportOnly the base side is SKIPPED, no verdict is decided, and the run prints each +// reached claim's head standing with every head failure as a counted ReachedClaimFailedAtHead +// finding (review 73484). THE COST RULING IS MADE (operator, 2026-10-01, option B): the +// differential runs in the merge group only and BLOCKS there; a pull request adds nothing and +// reports deferred_to_merge_group. So the standing is DifferentialBlocking. An arm over its budget +// refuses with a typed cause (BaseArmOverBudget) and never truncates. There is no flag that +// changes this. +type ReachDifferentialStanding + = DifferentialBlocking { base_arm_wall_budget: Milliseconds } + | DifferentialReportOnly { pending: String } + +// THE BUDGET IS A DECLARED POLICY VALUE, GROUNDED IN ITS RECEIPT: neat-boar-16's base-arm runs on +// srv1 (2026-10-01, merge_group event, a loaded host) took 226 s for 60 identities, 260.5 s for 18 +// and 312.6 s for 22. The arm's cost follows per-claim eval more than identity count, so the +// budget is the largest observed arm (312.6 s) times 2 for host-load variance: 625200 ms. A run +// over it is a typed refusal that names the budget, which is the signal to re-measure, not to widen. +data reach_differential_standing: ReachDifferentialStanding = DifferentialBlocking { + base_arm_wall_budget: 625200 +} + +// The host wire: 0 means report-only, and a positive value is the blocking arm's wall budget. Typed +// Milliseconds (std.types) at birth, so the unit is the type's and not the name's (review 73528). +fn reach_differential_blocking_budget() -> Milliseconds { + match reach_differential_standing { + DifferentialBlocking { base_arm_wall_budget: b } => b + DifferentialReportOnly { pending: _ } => 0 + } +} From 677934a20ef8150ddab31efb932d50f5ad4a68b4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 5 Oct 2026 23:10:03 +0000 Subject: [PATCH 17/24] Restore main's tree wholesale from the mangled merge (476 files incl. cli_run definitions, reach_base_standings.rs; drop resurrected rung_drop roster); keep the route-gap admission partition delta --- .github/workflows/witnesses.yml | 64 +- .gitignore | 4 + DESIGN.md | 2 +- dag/extdeps/apple/app_attest.dag | 10 +- dag/extdeps/bmc/redfish_memory_inventory.dag | 6 +- dag/extdeps/bmc/redfish_telemetry.dag | 52 +- dag/extdeps/cloudflare/r2_lifecycle.dag | 8 +- .../container/oci/ctrl_session_witness.dag | 10 +- dag/extdeps/cpu/ampere_altra_package.dag | 57 +- dag/extdeps/deepseek/deepseek_v4_1_flash.dag | 6 +- dag/extdeps/ebay/browse.dag | 6 +- dag/extdeps/firmware/types.dag | 20 +- dag/extdeps/formats/elf/encode.dag | 2 +- .../formats/elf/hello_static_witness.dag | 61 +- dag/extdeps/formats/elf/types.dag | 3 +- dag/extdeps/git/git.dag | 12 +- dag/extdeps/git/gitignore.dag | 2 +- dag/extdeps/git/object_store.dag | 33 +- dag/extdeps/git/versioning.dag | 2 +- dag/extdeps/github/checks.dag | 1 + dag/extdeps/github/expressions.dag | 12 +- dag/extdeps/github/merge_group_event.dag | 145 ++ dag/extdeps/github/organizations.dag | 14 +- dag/extdeps/github/workflows.dag | 1 + dag/extdeps/languages/go/module.dag | 2 +- dag/extdeps/languages/markdown.dag | 10 +- dag/extdeps/languages/yaml/emit.dag | 11 +- dag/extdeps/languages/yaml/ingest.dag | 14 +- dag/extdeps/linux/proc_mountinfo.dag | 14 +- dag/extdeps/llm/anthropic_rest.dag | 1 + dag/extdeps/llm/openai_rest.dag | 1 + dag/extdeps/memory/jedec.dag | 1 - dag/extdeps/mercurial.dag | 6 +- dag/extdeps/ntfy/access.dag | 3 +- dag/extdeps/ocp/mt_jade/memory_mixing.dag | 21 +- dag/extdeps/pricing/object_storage.dag | 6 +- .../ubuntu_seeded_install_media_remaster.dag | 4 +- dag/extdeps/runtime/api/darwin.dag | 2 +- dag/extdeps/runtime/api/windows.dag | 2 +- .../runtime/architecture/aarch64_linux.dag | 2 +- .../runtime/architecture/x86_64_linux.dag | 2 +- dag/extdeps/rust/std_thread_local.dag | 25 + dag/extdeps/sec/facts.dag | 4 +- dag/extdeps/standards/rfc_5280.dag | 12 +- dag/extdeps/standards/rfc_8118.dag | 10 +- dag/extdeps/standards/rfc_8949.dag | 8 +- dag/extdeps/standards/rfc_9052.dag | 12 +- dag/extdeps/standards/x690_der.dag | 33 +- dag/extdeps/tailscale/serve.dag | 2 +- dag/extdeps/test/http_pilot.dag | 1 + dag/extdeps/version/semver.dag | 2 +- .../accelerator_demo_plan.dag | 1 + dag/gunbc/approve_ios_swift_wire.dag | 37 +- dag/gunbc/auth/approval_decision_store.dag | 6 +- .../approval_device_crypto_realization.dag | 1 + dag/gunbc/auth/approval_device_redemption.dag | 18 +- dag/gunbc/auth/approval_device_wire.dag | 8 +- dag/gunbc/auth/approval_gate.dag | 6 +- .../auth/approval_ntfy_access_readback.dag | 20 +- .../auth/approval_ntfy_runtime_observe.dag | 13 +- .../auth/approval_request_submission.dag | 10 +- dag/gunbc/auth/approval_status_wire.dag | 3 +- dag/gunbc/auth/credentials.dag | 1 - dag/gunbc/auth/gcp_iam_converge.dag | 8 +- .../auth/github_app_control_plane_observe.dag | 1 + dag/gunbc/auth/secret_rotation.dag | 3 +- ...mc_fan_program_syntax_boundary_witness.dag | 1 + dag/gunbc/bmc/bmc_health_reader.dag | 15 +- dag/gunbc/bmc_megarac_web_transport.dag | 20 +- .../build_cache_endpoint_observation.dag | 5 +- dag/gunbc/ci/ci_budget_tree.dag | 3 + dag/gunbc/ci/ci_deploy_target_host.dag | 2 +- dag/gunbc/ci/ci_spec.dag | 2 +- dag/gunbc/citation/pdf_safe_profile.dag | 4 +- .../cli_run_floor_lens_oracle_scaffold.dag | 1 + dag/gunbc/cli_run_workspace_root_scaffold.dag | 1 + dag/gunbc/cloudflare/r2_bucket_ensure.dag | 22 +- dag/gunbc/compute/attempt_lifecycle.dag | 4 +- dag/gunbc/compute/test_run.dag | 2 +- dag/gunbc/compute/work_provider_local.dag | 111 +- dag/gunbc/compute/work_request.dag | 2 +- dag/gunbc/decoder_execution_identity.dag | 1 + dag/gunbc/derived_row_roster_seed_growth.dag | 17 +- dag/gunbc/design_document.dag | 2 +- dag/gunbc/diff_baseline.dag | 53 +- dag/gunbc/discovery_census.dag | 16 +- dag/gunbc/econ/free_tier_serving.dag | 2 +- dag/gunbc/econ/knowable_wedge.dag | 8 +- dag/gunbc/econ/margin_envelope.dag | 6 +- dag/gunbc/econ/regional_compute_premium.dag | 8 +- dag/gunbc/econ/scm_serving_model.dag | 26 +- dag/gunbc/emit_diagnostic_observation.dag | 2 +- dag/gunbc/extdeps_scope_frontier.dag | 1 + .../fabric/fabric_required_build_cell.dag | 9 +- dag/gunbc/fabric/fabric_storage_wire.dag | 3 +- dag/gunbc/fleet/fleet_converge_cli.dag | 42 +- dag/gunbc/fleet/fleet_converge_plan.dag | 8 +- dag/gunbc/fleet/fleet_convergence_verdict.dag | 9 +- dag/gunbc/fleet/fleet_health_observe.dag | 26 +- dag/gunbc/fleet/fleet_host_key_enrollment.dag | 2 +- dag/gunbc/fleet/fleet_revision_acceptance.dag | 21 +- dag/gunbc/fleet/fleet_show_effective_read.dag | 38 +- .../host_credential_custody_converge.dag | 79 +- dag/gunbc/floor/floor_cold_build_receipt.dag | 11 +- .../floor_cost_debt_edit_seed_growth.dag | 6 +- dag/gunbc/floor/floor_demand.dag | 6 + .../floor_arm_set_consumer_seed_growth.dag | 30 + dag/gunbc/floor_cost_distribution.dag | 63 +- dag/gunbc/gcp/gcp_estate_adoption.dag | 8 +- dag/gunbc/gcp/gcp_estate_dependency.dag | 18 +- dag/gunbc/githooks/githooks_pre_push_plan.dag | 2 +- .../variant_owner_identity_stall.dag | 4 +- dag/gunbc/harness/harness_backend.dag | 32 +- dag/gunbc/harness/harness_throughput.dag | 14 +- dag/gunbc/harness/harness_tool.dag | 10 +- dag/gunbc/harness/harness_wire.dag | 35 +- dag/gunbc/host/host_identity_access.dag | 1 + dag/gunbc/host/host_identity_observation.dag | 1 + .../instruments/approval_store_live_probe.dag | 8 +- .../instruments/cause_assertion_census.dag | 7 +- .../dag_compile_clean_shard_totality.dag | 25 +- .../emit_copy_qualification_transport.dag | 35 +- dag/gunbc/instruments/emit_host_transport.dag | 9 +- .../fabric_control_plane_live_probe.dag | 94 +- .../instruments/frontier_ingestion_probe.dag | 31 +- dag/gunbc/instruments/json_parse_ladder.dag | 9 +- dag/gunbc/instruments/native_app_attest.dag | 4 +- .../instruments/native_emission_controls.dag | 43 +- .../instruments/publication_publisher.dag | 3 +- .../instruments/source_snapshot_handoff.dag | 84 +- dag/gunbc/json_exact_number_witness.dag | 1 + dag/gunbc/jurisdiction_drop_accounting.dag | 2 +- dag/gunbc/language_source_scaffold_index.dag | 1 + dag/gunbc/live_deploy/apply.dag | 45 +- dag/gunbc/live_deploy/fleet_request.dag | 29 +- dag/gunbc/live_deploy/member_identity.dag | 6 +- dag/gunbc/live_deploy/member_observe.dag | 37 +- .../live_deploy/repository_convergence.dag | 13 +- .../live_deploy/srv1_residue_rehearsal.dag | 3 +- dag/gunbc/live_deploy/unit_standing.dag | 84 +- .../machine_intake/megarac_kvm_still.dag | 172 +-- .../machine_intake/mtcollins1_boot_run.dag | 1 + .../mtjade1_coverage_frontier.dag | 1 + .../namespace/namespace_pool_independence.dag | 12 +- ...ce_reference_derived_closure_admission.dag | 16 +- .../namespace_xl2_rehearsal_census.dag | 25 +- dag/gunbc/non_fold_residue.dag | 646 -------- dag/gunbc/observation_ci_render.dag | 49 + ...able_value_canonical_order_seed_growth.dag | 20 +- dag/gunbc/product/build_fulfillment.dag | 85 +- dag/gunbc/product/capacity/event_json.dag | 10 +- .../product/compute_board/composition.dag | 36 +- dag/gunbc/product/fan_tach.dag | 4 +- dag/gunbc/product/host_health.dag | 18 +- ...ed_by_field_names_guesses_on_ambiguity.dag | 20 + ...collection_reaches_an_emitted_artifact.dag | 8 +- .../interpreter_ignores_match_arm_guards.dag | 7 +- ...ional_but_realized_as_the_bare_element.dag | 4 +- ...interpreter_and_broken_in_emitted_rust.dag | 7 + ...ted_where_a_required_value_is_declared.dag | 4 +- ...able_value_map_order_is_process_random.dag | 2 +- ...l_match_lowering_skips_rc_arm_grouping.dag | 2 + dag/gunbc/refusal_reason_ownership_join.dag | 252 ++++ dag/gunbc/repo/repo_atlas_projection.dag | 4 +- dag/gunbc/repo_workspace.dag | 6 + .../roadmap_dashboard_instance_apply.dag | 144 +- .../roadmap_launch_deployment_receipt.dag | 3 +- .../roadmap/roadmap_publication_helper.dag | 10 +- dag/gunbc/roadmap/roadmap_publish.dag | 78 +- dag/gunbc/roadmap/roadmap_review_function.dag | 9 +- dag/gunbc/roadmap/roadmap_review_role.dag | 44 +- .../roadmap/roadmap_validation_oracle.dag | 30 +- .../roadmap/roadmap_verification_receipt.dag | 86 +- dag/gunbc/rung_drop.dag | 18 +- .../determinism_transitive_reachability.dag | 15 + ...trix_enrolment_dead_band_observed_only.dag | 44 - ...boot_matrix_new_witness_eval_step_cost.dag | 2 +- ...rer_hop_structural_arm_undiscriminated.dag | 45 + dag/gunbc/rung_drop/roster.dag | 256 ---- .../rust_unit_tests_off_the_merge_path.dag | 4 +- dag/gunbc/rung_drop/standing.dag | 22 + .../transitional_admission_exception.dag | 24 +- ...sus_live_population_off_the_merge_path.dag | 24 +- dag/gunbc/runner/runner_attempt_launch.dag | 50 +- dag/gunbc/runner/runner_capacity_plan.dag | 11 +- dag/gunbc/runner/runner_capacity_realize.dag | 6 +- .../runner/runner_microvm_boot_probe.dag | 8 +- .../runner/runner_microvm_footprint_probe.dag | 3 +- .../runner/runner_microvm_host_ready.dag | 42 +- .../runner/runner_microvm_network_apply.dag | 68 +- .../runner/runner_microvm_slot_start.dag | 28 +- dag/gunbc/runner/runner_microvm_slot_unit.dag | 29 +- .../runner/runner_observed_version_check.dag | 7 +- .../runner/runner_registration_labels.dag | 16 +- .../runner_throughput_qualification.dag | 11 +- .../runner_throughput_qualification_route.dag | 29 +- dag/gunbc/runner/runner_unit_live_read.dag | 7 +- dag/gunbc/runner/runner_usage_receipt.dag | 2 +- dag/gunbc/rust_item_scan.dag | 86 +- dag/gunbc/scm/commit_closure_json_v2.dag | 9 +- .../self_host_compile_phase_frontier.dag | 2 +- dag/gunbc/spark/first_party_serving.dag | 8 +- dag/gunbc/spark/host_commitment.dag | 4 +- .../spark/pair_serving_authority_log.dag | 93 +- dag/gunbc/spark/pair_serving_d0.dag | 52 +- dag/gunbc/spark/pair_serving_d0_door.dag | 28 +- dag/gunbc/spark/vllm_observed.dag | 40 +- dag/gunbc/srv3/srv3_boot_once_cd.dag | 3 +- dag/gunbc/srv3/srv3_os_install_actuate.dag | 10 +- .../srv3/srv3_os_install_actuate_workflow.dag | 22 +- dag/gunbc/srv3/srv3_os_install_diagnostic.dag | 114 +- dag/gunbc/srv3/srv3_seeded_install_media.dag | 2 +- dag/gunbc/tools/review.dag | 1 + .../v1/v1_complexity_decl_classification.dag | 5 +- dag/gunbc/v1/v1_witness_census.dag | 2 + dag/gunbc/witness/compiler_gate_workflow.dag | 152 +- dag/gunbc/witness/v2_native_route.dag | 11 +- dag/std/algebra.dag | 61 +- dag/std/cache_interface.dag | 5 +- dag/std/change.dag | 8 +- dag/std/citation.dag | 11 +- dag/std/decision.dag | 5 +- dag/std/effects.dag | 6 +- dag/std/filesystem.dag | 10 +- dag/std/observation.dag | 4 +- dag/std/occurrence_binding_candidates.dag | 25 +- dag/std/orthogonal_topology.dag | 1 + dag/std/predictive_claim.dag | 1 + dag/std/primitive_identity.dag | 13 +- dag/std/primitive_projection.dag | 1 + dag/std/reducible.dag | 7 +- dag/std/state_durability.dag | 9 +- dag/test/claim/budget_tree_witness_test.dag | 1 + dag/test/claim/build_cache_ensure_test.dag | 1 + .../canonical_map_rendering_witness_test.dag | 91 ++ .../canonical_order_enrolled_witness_test.dag | 63 + .../claim/ci/ci_budget_tree_witness_test.dag | 35 +- .../ci/ci_failure_class_witness_test.dag | 1 + .../ci/ci_render_histogram_width_test.dag | 1 + .../ci/ci_render_slowest_hot_style_test.dag | 1 + .../ci/ci_runner_target_witness_test.dag | 1 + .../cli_dispatch_surface_witness_test.dag | 1 + dag/test/claim/compile_pool_ensure_test.dag | 1 + .../claim/design_argument_witness_test.dag | 1 + dag/test/claim/diff_baseline_witness_test.dag | 58 +- .../diff_window_cross_seam_witness_test.dag | 3 +- .../claim/discovery_census_witness_test.dag | 14 +- dag/test/claim/ebay_browse_witness_test.dag | 1 + dag/test/claim/ebay_listing_witness_test.dag | 2 + ...constant_pattern_emission_witness_test.dag | 131 +- .../claim/emit_host_gate_witness_test.dag | 1 + .../claim/fabric/fabric_witness_run_test.dag | 36 +- ...isdiction_drop_accounting_witness_test.dag | 3 +- .../fleet_intent_network_witness_test.dag | 1 + .../fleet_receipt_collector_witness_test.dag | 1 + dag/test/claim/git_ls_remote_witness_test.dag | 1 + ...git_plumbing_ref_mutation_witness_test.dag | 1 + .../github_app_registry_witness_test.dag | 1 + .../go_module_versioning_witness_test.dag | 1 + ...ens_host_bridge_scaffold_watchdog_test.dag | 1 + .../claim/hetzner_cost_quote_witness_test.dag | 1 + .../host/host_phase_status_witness_test.dag | 4 + .../host/host_standup_spine_witness_test.dag | 2 + dag/test/claim/html_emit_witness_test.dag | 1 + .../claim/html_markup_xss_witness_test.dag | 1 + dag/test/claim/html_roundtrip_test.dag | 1 + .../claim/markdown_inline_render_test.dag | 1 + .../claim/markup_medium_convergence_test.dag | 1 + .../claim/markup_serializer_witness_test.dag | 1 + ..._exhaustiveness_coproduct_witness_test.dag | 4 +- .../materialized_secret_witness_test.dag | 1 + .../claim/merge_group_event_witness_test.dag | 58 + .../observation_ci_render_witness_test.dag | 62 + ...onal_at_required_position_witness_test.dag | 39 +- .../orthogonal_topology_witness_test.dag | 1 + dag/test/claim/output_policy_witness_test.dag | 1 + .../realization_reconcile_witness_test.dag | 1 + .../claim/reconstruction_door_rest_probe.dag | 1 + ...sal_reason_ownership_join_witness_test.dag | 236 +++ ...launch_deployment_receipt_witness_test.dag | 1 + ...g_drop_standing_partition_witness_test.dag | 3 +- ...ust_item_host_observation_witness_test.dag | 50 +- .../claim/rust_item_scan_witness_test.dag | 76 +- .../samsung_dram_module_witness_test.dag | 1 + .../sec_edgar_rollins_chemed_witness_test.dag | 1 + dag/test/claim/sec_edgar_witness_test.dag | 1 + ...spark_bootstrap_provision_witness_test.dag | 1 + dag/test/claim/void_element_test.dag | 1 + .../emission_provenance_specimen_red.dag | 2 +- docs/design-rung-drops.md | 514 +++---- docs/plans/canonical-content-order-draft.md | 77 + src/v1/01_tokenize.dag | 328 ++-- src/v1/04_infer.dag | 53 +- src/v1/05_emit_rust.dag | 174 ++- src/v1/compiler_tests_rust.dag | 14 +- src/v1/languages.dag | 4 +- src/v1/runtime_rust.dag | 14 +- src/v1/stage0/src/bin/claim_batch.rs | 2 +- src/v1/stage0/src/bin/claim_executor.rs | 74 + src/v1/stage0/src/cli_run.rs | 311 +++- .../stage0/src/cli_run/derived_row_roster.rs | 392 +++-- src/v1/stage0/src/cli_run/entry_resolve.rs | 8 + .../stage0/src/cli_run/native_lane_runner.rs | 13 +- .../src/cli_run/reach_base_standings.rs | 494 ++++++ .../stage0/src/cli_run/rostered_row_join.rs | 6 +- src/v1/stage0/src/compiler_tests.rs | 14 +- .../src/emitted_closure_compile_host.rs | 349 ++++- ...ace_reference_derived_closure_admission.rs | 332 +---- src/v1/stage0/src/namespace_baseline.rs | 32 +- src/v1/stage0/src/std_algebra.rs | 414 +++-- src/v1/stage0/src/std_effects.rs | 22 +- .../src/std_occurrence_binding_candidates.rs | 54 +- src/v1/stage0/src/std_primitive_projection.rs | 9 + src/v1/stage0/src/target_invocation_host.rs | 25 +- .../stage0/src/v1_compiler_annotation_bind.rs | 2 +- .../src/v1_compiler_compiler_tests_rust.rs | 2 +- src/v1/stage0/src/v1_compiler_complexity.rs | 9 +- .../src/v1_compiler_dag_collect_support.rs | 4 +- src/v1/stage0/src/v1_compiler_emit.rs | 13 +- src/v1/stage0/src/v1_compiler_emit_rust.rs | 641 ++++++-- src/v1/stage0/src/v1_compiler_infer.rs | 77 +- .../stage0/src/v1_compiler_infer_resolve.rs | 2 +- src/v1/stage0/src/v1_compiler_languages.rs | 4 +- src/v1/stage0/src/v1_compiler_parse.rs | 2 +- src/v1/stage0/src/v1_compiler_resolve.rs | 6 +- src/v1/stage0/src/v1_compiler_runtime_rust.rs | 2 +- .../stage0/src/v1_compiler_stage0_crates.rs | 2 +- src/v1/stage0/src/v1_compiler_tokenize.rs | 686 +++++---- .../src/v1_compiler_type_head_exposure.rs | 24 +- src/v1/stage0/src/v1_interpreter.rs | 998 ++++++++++--- src/v1/stage0/src/v1_rt.rs | 21 +- .../v1_tests_claim_generic_identity_census.rs | 4 +- src/v2/compiler/00_compile.dag | 30 +- src/v2/compiler/03_body_producer.dag | 44 +- src/v2/compiler/03_ingest.dag | 14 +- src/v2/compiler/03_name_resolve.dag | 14 +- src/v2/compiler/03_resolve.dag | 70 +- src/v2/compiler/04_infer.dag | 158 +- src/v2/compiler/05_emit_orchestration.dag | 41 +- src/v2/compiler/05_eval.dag | 164 +- src/v2/compiler/06_translate.dag | 51 +- src/v2/compiler/07_target_carriers.dag | 63 +- src/v2/compiler/body_lowering_fold.dag | 92 +- src/v2/compiler/body_producer_forward.dag | 6 +- src/v2/compiler/effect_demand_floor_join.dag | 24 +- src/v2/compiler/emit_host.dag | 34 +- src/v2/compiler/emit_produced.dag | 27 +- src/v2/compiler/emit_semantic_decl.dag | 6 +- src/v2/compiler/ingested_fixture_arrows.dag | 20 +- src/v2/compiler/namespace_graft.dag | 14 +- src/v2/compiler/program_assembly.dag | 12 +- src/v2/compiler/program_partition.dag | 59 +- src/v2/compiler/reference_conservation.dag | 26 +- .../reference_conservation_admission.dag | 8 +- src/v2/compiler/reference_site_collector.dag | 12 +- src/v2/compiler/self_host/frontier_probe.dag | 13 +- .../self_host/frontier_probe_types.dag | 3 +- src/v2/compiler/source_authority.dag | 31 +- src/v2/compiler/source_authority_read.dag | 12 +- src/v2/compiler/symbol_index_fill.dag | 43 +- src/v2/compiler/target_serialize.dag | 132 +- src/v2/compiler/trait_derive_completeness.dag | 21 +- src/v2/compiler/use_site_verdict.dag | 14 +- src/v2/compiler/wrap_decision.dag | 31 +- src/v2/extdeps/runtimes/v2_evaluator.dag | 8 +- .../roster_gate.dag | 1 + src/v2/lens/complexity_linearity_audit.dag | 5 +- src/v2/lens/coverage.dag | 1 + src/v2/lens/discrimination.dag | 1 + src/v2/lens/disposition_redundancy.dag | 1 + src/v2/lens/fact_cardinality.dag | 1 + src/v2/lens/leaf_model_verification.dag | 1 + src/v2/lens/registry/completeness.dag | 2 +- src/v2/lens/undecidable_verdict_collapse.dag | 14 +- src/v2/lens/vacuity.dag | 8 +- src/v2/program.dag | 6 +- src/v2/std/cardinality.dag | 7 +- src/v2/std/compilers/body_lowering.dag | 60 +- src/v2/std/compilers/compilation_unit.dag | 7 +- .../std/compilers/coproduct_variant_shape.dag | 7 +- .../std/compilers/semantic_decl_emission.dag | 221 ++- src/v2/std/compilers/sugar.dag | 44 +- src/v2/std/compilers/target_model.dag | 1326 +++++++++++++---- src/v2/std/concept_index.dag | 6 +- src/v2/std/data_initializer_identity.dag | 7 +- src/v2/std/decl_facts_skeleton.dag | 24 +- src/v2/std/dependency.dag | 7 +- src/v2/std/fold_assembly.dag | 7 +- src/v2/std/grammar.dag | 329 ++-- src/v2/std/inhabitance.dag | 7 +- src/v2/std/integer.dag | 10 +- src/v2/std/lens_verdict.dag | 5 +- src/v2/std/list_introduction.dag | 7 +- src/v2/std/node.dag | 75 +- src/v2/std/node_query.dag | 134 +- src/v2/std/provenance.dag | 5 +- src/v2/std/qualified_name.dag | 56 +- src/v2/std/type_binder.dag | 46 +- .../data_initializer_fn_value_test.dag | 1 + .../declaration_structure_preserved_test.dag | 1 + .../match_as_field_name_test.dag | 1 + .../body_lowering/single_arm_match_test.dag | 1 + .../compiler_materialization_witness_test.dag | 1 + .../effect_demand_floor_join_test.dag | 8 +- ...plemental_bound_requirement_order_test.dag | 91 ++ .../spice_rc_passive_deck_claims_test.dag | 1 + .../corpus/cargo_build_policy_leak_test.dag | 1 + .../corpus/cargo_clippy_dead_param_test.dag | 1 + .../corpus/cargo_doc_dead_param_test.dag | 1 + .../corpus/cargo_doc_policy_leak_test.dag | 1 + .../corpus/cargo_fmt_dead_param_test.dag | 1 + .../corpus/cargo_fmt_policy_leak_test.dag | 1 + .../corpus/cargo_run_dead_param_test.dag | 1 + .../corpus/gcp_login_dead_param_test.dag | 1 + .../corpus/gcp_oauth_fusion_fork_test.dag | 1 + .../corpus/gist_create_policy_leak_test.dag | 1 + .../corpus/git_policy_leak_test.dag | 1 + .../runtime_local_embedded_policy_test.dag | 1 + .../coverage_domain_equivalence_test.dag | 1 + .../lens_unit/clean_gist_create_test.dag | 1 + .../lens_unit/clean_git_diff_test.dag | 1 + .../lens_unit/dead_param_cargo_build_test.dag | 1 + .../dead_param_cargo_clippy_test.dag | 1 + .../lens_unit/dead_param_gcp_login_test.dag | 1 + .../embedded_policy_literal_local_test.dag | 1 + .../lens_unit/module_path_rename_test.dag | 1 + ...rce_nickname_literal_absent_green_test.dag | 1 + ...ame_literal_coverage_domain_green_test.dag | 1 + ...source_nickname_literal_local_red_test.dag | 1 + .../policy_leak_cargo_build_test.dag | 1 + .../lens_unit/policy_leak_cargo_doc_test.dag | 1 + .../lens_unit/policy_leak_cargo_fmt_test.dag | 1 + .../transport_fusion_gcp_oauth_test.dag | 1 + .../lens_unit/synthetic_fork_red_test.dag | 1 + .../generated_conformance_floor_test.dag | 1 + ...gha_workflow_yaml_fold_structural_test.dag | 1 + .../claim/long/cwc_resolution_probe_test.dag | 1 + .../qualified_record_literal_parse_test.dag | 1 + .../manual/medium_node_instantiation_test.dag | 1 + .../medium_source_text_instantiation_test.dag | 1 + .../manual/value_null_split_witness_test.dag | 1 + .../claim/map_carrier_shape_gate_test.dag | 1 + src/v2/test/claim/nat_semiring/rung_5.dag | 1 + src/v2/test/claim/nat_semiring/rung_6.dag | 1 + .../test/claim/source_root_tagging_test.dag | 2 + .../tokenize/annotation_channel_test.dag | 1 + src/v2/test/floor_changed_witness_test.dag | 5 +- src/v2/test/floor_claim_differential_test.dag | 151 ++ src/v2/test/lens_enforcement/gate_test.dag | 3 + .../cron_mock_totality_test.dag | 1 + .../diagnostic_mock_totality_test.dag | 1 + .../filesystem_mock_totality_test.dag | 1 + .../gcp_mock_totality_test.dag | 1 + .../git_mock_totality_test.dag | 1 + .../github_mock_totality_test.dag | 1 + .../http_pilot_mock_totality_test.dag | 1 + .../llm_mock_totality_test.dag | 1 + .../sec_edgar_mock_totality_test.dag | 1 + .../sheets_mock_totality_test.dag | 1 + .../shell_mock_totality_test.dag | 1 + .../lens_mock_totality/witness_template.dag | 1 + .../required_floor_gate_selector_test.dag | 3 +- .../workflow/bash_command_fold_serialize.dag | 32 +- src/v2/workflow/bootstrap.dag | 3 +- src/v2/workflow/emitted_crate_workspace.dag | 3 +- src/v2/workflow/floor_changed_witness.dag | 4 + src/v2/workflow/floor_diff_observe.dag | 49 +- src/v2/workflow/floor_expected_red.dag | 2 +- .../workflow/floor_expected_red_coherence.dag | 6 +- src/v2/workflow/floor_subject_seed.dag | 21 + src/v2/workflow/floor_subject_seed_test.dag | 12 +- ...r_unimported_bare_provider_debt_roster.dag | 110 +- .../workflow/legacy_binding_observation.dag | 2 +- src/v2/workflow/realization_attempt.dag | 26 +- src/v2/workflow/rust_crate_partition.dag | 14 +- src/v2/workflow/witness_admission.dag | 37 +- 476 files changed, 12199 insertions(+), 5078 deletions(-) create mode 100644 dag/extdeps/github/merge_group_event.dag create mode 100644 dag/extdeps/rust/std_thread_local.dag create mode 100644 dag/gunbc/recurring_failure_mode/anonymous_record_resolved_by_field_names_guesses_on_ambiguity.dag create mode 100644 dag/gunbc/refusal_reason_ownership_join.dag delete mode 100644 dag/gunbc/rung_drop/mtcollins1_boot_matrix_enrolment_dead_band_observed_only.dag create mode 100644 dag/gunbc/rung_drop/renderer_hop_structural_arm_undiscriminated.dag delete mode 100644 dag/gunbc/rung_drop/roster.dag create mode 100644 dag/gunbc/rung_drop/standing.dag create mode 100644 dag/test/claim/canonical_map_rendering_witness_test.dag create mode 100644 dag/test/claim/canonical_order_enrolled_witness_test.dag create mode 100644 dag/test/claim/merge_group_event_witness_test.dag create mode 100644 dag/test/claim/refusal_reason_ownership_join_witness_test.dag create mode 100644 docs/plans/canonical-content-order-draft.md create mode 100644 src/v1/stage0/src/cli_run/reach_base_standings.rs create mode 100644 src/v2/test/claim/emit/supplemental_bound_requirement_order_test.dag create mode 100644 src/v2/test/floor_claim_differential_test.dag diff --git a/.github/workflows/witnesses.yml b/.github/workflows/witnesses.yml index 554a9a54c69..f1dd3264be0 100644 --- a/.github/workflows/witnesses.yml +++ b/.github/workflows/witnesses.yml @@ -21,6 +21,62 @@ env: CARGO_TERM_COLOR: always GUNBC_REQUIRED_CI_CONTRACT_EPOCH: 2026-09-01.1 jobs: + rust-unit-tests: + runs-on: [self-hosted, linux, arm64] + timeout-minutes: 50 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + persist-credentials: false + - name: Isolate toolchain homes + run: |- + # dissolve-on: ci_toolchain_home_isolation_script -- orch-emitted foreign-executor prelude step wiping and setting HOME/CARGO_HOME/RUSTUP_HOME under RUNNER_TEMP so concurrent runner slots stop sharing one toolchain; leaf rm/echo strings remain until a typed per-job filesystem-and-environment effect lands on host_effect_apply (shell-to-intent Phase 2). This obligation covers THIS carrier and ci_isolate_toolchain_script, which share that terminal construction; ci_pin_rustup_default_script carries its own obligation because it does not + rm -rf "$RUNNER_TEMP/rustup" "$RUNNER_TEMP/cargo" + echo "HOME=$RUNNER_TEMP" >> "$GITHUB_ENV" + echo "CARGO_HOME=$RUNNER_TEMP/cargo" >> "$GITHUB_ENV" + echo "RUSTUP_HOME=$RUNNER_TEMP/rustup" >> "$GITHUB_ENV" + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 + with: + components: rustfmt + cache: false + rustflags: -D warnings + - name: Pin rustup default (isolated RUSTUP_HOME has no default toolchain) + run: |- + # dissolve-on: ci_pin_rustup_default_script -- orch-emitted foreign-executor step selecting a rustup default toolchain inside an isolated RUSTUP_HOME, which starts with none, and resolving the cargo binary that selection implies. The leaf rustup/command/echo strings remain until a typed TOOLCHAIN-SELECTION effect lands on host_effect_apply -- NOT the filesystem-and-environment effect ci_toolchain_home_isolation_script waits on, which is why this is a separate obligation: that effect landing alone would leave this carrier standing + rustup default "$(rustup show active-toolchain | awk '{print $1; exit}')" + if [ -x "$CARGO_HOME/bin/cargo" ]; then CARGO_BIN="$CARGO_HOME/bin/cargo"; else CARGO_BIN="$(command -v cargo || true)"; fi + if [ -z "$CARGO_BIN" ]; then echo "::error::no cargo binary: neither the isolated $CARGO_HOME/bin/cargo shim nor PATH carries one"; exit 1; fi + echo "CARGO_BIN=$CARGO_BIN" >> "$GITHUB_ENV" + - name: Run the v1 unit tests + id: rust_unit_tests + run: |+ + GUNBC_FLOOR_LOG='gunbc-floor-cmd.log' + 'set' '+e' + 'set' '-o' 'pipefail' + ('sh' '-e' '-c' 'cargo test --release -p v1-compiler --lib') 2>&1 | 'tee' "$GUNBC_FLOOR_LOG" + GUNBC_FLOOR_EXIT="$?" + GUNBC_FLOOR_RECEIPT='gunbc-floor-outcome.txt' + GUNBC_FLOOR_CLASS='structural' + GUNBC_FLOOR_SIGNATURE='' + if '[' "$GUNBC_FLOOR_EXIT" '-eq' '126' ']'; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='CommandInvokedCannotExecute'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' "$GUNBC_FLOOR_EXIT" '-eq' '127' ']'; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='CommandNotFound'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' "$GUNBC_FLOOR_EXIT" '-eq' '0' ']'; then GUNBC_FLOOR_CLASS='none'; GUNBC_FLOOR_SIGNATURE=''; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'MemoryStallRefusedPageThrash' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='MemoryStallRefusedPageThrash'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'The runner has received a shutdown signal' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='RunnerLost'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'sccache: error: failed to execute compile' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheFailedToExecuteCompile'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' '(exit status: 254)' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheRustcWrapperExit254'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'sccache: encountered fatal error' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='SccacheFatalError'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'failed to spawn' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='ProcessSpawnFailure'; fi + if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' && '[' '-f' "$GUNBC_FLOOR_LOG" ']' && 'grep' '-q' 'Resource temporarily unavailable' "$GUNBC_FLOOR_LOG"; then GUNBC_FLOOR_CLASS='infra'; GUNBC_FLOOR_SIGNATURE='ResourceTemporarilyUnavailable'; fi + if (! '[' '-f' "$GUNBC_FLOOR_RECEIPT" ']') || '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']' || ('[' "$GUNBC_FLOOR_CLASS" '=' 'infra' ']' && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=structural' "$GUNBC_FLOOR_RECEIPT"))) || ('[' "$GUNBC_FLOOR_CLASS" '=' 'none' ']' && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=structural' "$GUNBC_FLOOR_RECEIPT")) && (! ('[' '-f' "$GUNBC_FLOOR_RECEIPT" ']' && 'grep' '-q' 'class=infra' "$GUNBC_FLOOR_RECEIPT"))); then 'printf' 'class=%s\nsignature=%s\nexit=%s\n' "$GUNBC_FLOOR_CLASS" "$GUNBC_FLOOR_SIGNATURE" "$GUNBC_FLOOR_EXIT" > "$GUNBC_FLOOR_RECEIPT"; if '[' "$GUNBC_FLOOR_CLASS" '=' 'infra' ']'; then 'echo' '::error title=environment::floor_class='"$GUNBC_FLOOR_CLASS"' signature='"$GUNBC_FLOOR_SIGNATURE"' exit='"$GUNBC_FLOOR_EXIT"'; this is not a verdict about the diff. Attempt receipt: '"$GUNBC_FLOOR_RECEIPT"; fi; if '[' "$GUNBC_FLOOR_CLASS" '=' 'structural' ']'; then 'echo' '::error title=subject::floor_class='"$GUNBC_FLOOR_CLASS"' exit='"$GUNBC_FLOOR_EXIT"'; read the step log for the subject defect'; fi; fi + 'exit' "$GUNBC_FLOOR_EXIT" + + timeout-minutes: 40 emit-build: runs-on: [self-hosted, linux, arm64] timeout-minutes: 90 @@ -573,7 +629,7 @@ jobs: MALLOC_ARENA_MAX: "2" witnesses: runs-on: [self-hosted, linux, arm64] - needs: [emit-build, floor, generated] + needs: [rust-unit-tests, emit-build, floor, generated] timeout-minutes: 5 if: always() permissions: @@ -581,12 +637,14 @@ jobs: steps: - name: Every required lane must have succeeded run: |- - echo "required lanes: emit-build=$EMIT_BUILD floor=$FLOOR generated=$GENERATED (same_repo=$SAME_REPO)" - if [ "$EMIT_BUILD" = failure ] || [ "$FLOOR" = failure ] || [ "$GENERATED" = failure ]; then echo "::error::a required lane concluded failure (emit-build=$EMIT_BUILD floor=$FLOOR generated=$GENERATED) - open that job's log" >&2; exit 1; fi + echo "required lanes: rust-unit-tests=$RUST_UNIT_TESTS emit-build=$EMIT_BUILD floor=$FLOOR generated=$GENERATED (same_repo=$SAME_REPO)" + if [ "$RUST_UNIT_TESTS" = failure ] || [ "$EMIT_BUILD" = failure ] || [ "$FLOOR" = failure ] || [ "$GENERATED" = failure ]; then echo "::error::a required lane concluded failure (rust-unit-tests=$RUST_UNIT_TESTS emit-build=$EMIT_BUILD floor=$FLOOR generated=$GENERATED) - open that job's log" >&2; exit 1; fi + if [ "$RUST_UNIT_TESTS" != success ]; then if [ "$SAME_REPO" = false ] && [ "$RUST_UNIT_TESTS" = skipped ]; then echo "::notice::the v1 unit-test lane is skipped for a fork pull request: it runs on the fleet"; else echo "::error::the v1 unit-test lane produced no success (rust-unit-tests=$RUST_UNIT_TESTS), so this head carries no verdict from that lane"; exit 1; fi; fi if [ "$EMIT_BUILD" != success ]; then if [ "$SAME_REPO" = false ] && [ "$EMIT_BUILD" = skipped ]; then echo "::notice::the emit-build lane is skipped for a fork pull request: no self-host or native-CLI emission verdict for this head"; else echo "::error::the emit-build lane produced no success (emit-build=$EMIT_BUILD), so this head carries no verdict from that lane"; exit 1; fi; fi if [ "$FLOOR" != success ]; then if [ "$SAME_REPO" = false ] && [ "$FLOOR" = skipped ]; then echo "::notice::the fleet lane is skipped for a fork pull request: no witnesses fold and no generated-artifact verdict for this head"; else echo "::error::the fleet lane produced no success (floor=$FLOOR), so this head carries no verdict from that lane"; exit 1; fi; fi if [ "$GENERATED" != success ]; then if [ "$SAME_REPO" = false ] && [ "$GENERATED" = skipped ]; then echo "::notice::the generated-artifact lane is skipped for a fork pull request: no generated-artifact verdict for this head"; else echo "::error::the generated-artifact lane produced no success (generated=$GENERATED), so this head carries no verdict from that lane"; exit 1; fi; fi env: + RUST_UNIT_TESTS: ${{ needs['rust-unit-tests'].result }} EMIT_BUILD: ${{ needs['emit-build'].result }} FLOOR: ${{ needs.floor.result }} GENERATED: ${{ needs.generated.result }} diff --git a/.gitignore b/.gitignore index 858a1f33f68..fd14ca42bc2 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,10 @@ __pycache__/ # regenerable from committed source — membership list derived from sibling row files before compile; not a second authored authority /dag/gunbc/recurring_failure_mode/roster.dag +# --- Derived rung-drop roster [owner: gunbc.rung_drop] --- +# regenerable from committed source — membership list derived from sibling RungDrop row files before compile; not a second authored authority +/dag/gunbc/rung_drop/roster.dag + # --- Generator stamp files [owner: gunbc.generated_artifact] --- # regenerable from committed source — producer-centric generator stamps .*-stamp diff --git a/DESIGN.md b/DESIGN.md index fa8d0779a4d..83e22ee35af 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -261,7 +261,7 @@ One row per class, each carrying its recognition rule and its receipts, as a fil **This section assumes you already have a built `gunbc`, and nothing here tells you how to get one — so it is not the first page for a reader who has just cloned.** That reader wants [docs/onboarding.md](docs/onboarding.md): the ordered path from a bare clone to one landed reviewed change, with every command derived from the authority that owns it (`gunbc.contributor_onboarding_path`), the obstacles attached to the step each one breaks, and a reading order for THIS document saying what is needed before a first build, what is needed before a first commit, and what can wait. That page is a projection of a path that was walked, not a substitute for it: the exit it serves is measured by an outcome, so a step there that does not work is a defect worth reporting as one. -- Three local checks, each named with the CI step that executes it — a check named here with no executing step is a decoration (§4b): `cargo clippy --all-targets -- -D warnings` (`gunbc.repo_self_build` `repo_self_clippy_command`, the only command that compiles the integration-test and example targets, so a red there is invisible to every other step) runs as the lint step of the `generated` job of `gunbc.compiler_gate_workflow` (`compiler_gate_lint_step`, operator ruling 2026-09-28: no lane the required context reads runs on a hosted runner), which runs on every pull request and on every merge_group (the queue's composed revision) and IS a required lane read by the `witnesses` aggregate — so a clippy red blocks a merge; `cargo fmt --all --check` runs in the generated pre-commit hook (`gunbc.githooks_pre_commit_emit`). `cargo test --release -p v1-compiler --lib` (`repo_self_test_command`) runs on NO CI path: the hosted `rust-unit-tests` measurement lane was deleted (operator ruling 2026-09-29) because its continue_on_error step reported green while the population was red on main, so a unit-test red does not block a merge and the declared drop `gunbc.rung_drop` `rust_unit_tests_off_the_merge_path` still stands; the tests return as a blocking fleet lane once the population is green. `cargo test --workspace` is local only: the test targets are compiled by the lint step and run by nobody. +- Three local checks, each named with the CI step that executes it — a check named here with no executing step is a decoration (§4b): `cargo clippy --all-targets -- -D warnings` (`gunbc.repo_self_build` `repo_self_clippy_command`, the only command that compiles the integration-test and example targets, so a red there is invisible to every other step) runs as the lint step of the `generated` job of `gunbc.compiler_gate_workflow` (`compiler_gate_lint_step`, operator ruling 2026-09-28: no lane the required context reads runs on a hosted runner), which runs on every pull request and on every merge_group (the queue's composed revision) and IS a required lane read by the `witnesses` aggregate — so a clippy red blocks a merge; `cargo fmt --all --check` runs in the generated pre-commit hook (`gunbc.githooks_pre_commit_emit`). `cargo test --release -p v1-compiler --lib` (`repo_self_test_command`) runs as the `rust_unit_tests` step of the fleet `rust-unit-tests` job (`gunbc.compiler_gate_workflow` `compiler_gate_unit_test_step`), a REQUIRED lane read by the `witnesses` aggregate on every pull request and merge_group — so a unit-test red blocks a merge. It returned on 2026-09-30 as the blocking fleet lane the 2026-09-29 ruling (which deleted the non-blocking hosted measurement) named, bound by its runner slot's own MemoryMax so the regeneration-host tests read a real budget, and it retired the declared drop `gunbc.rung_drop` `rust_unit_tests_off_the_merge_path`. `cargo test --workspace` is local only: the test targets are compiled by the lint step and run by nobody. - one-time per clone: `git config core.hooksPath .githooks` — the only documented manual seed; generated pre-commit/pre-push hooks then idempotently converge `merge.generated-artifact.driver` and re-assert `core.hooksPath` via argv derived from `gunbc.repo_local_git_config` (clones that skip hooksPath degrade to vanilla text-merge for generated-artifact paths; drift gate still guards at CI). The driver REFUSES rather than answering `true`: git reaches a low-level merge driver only when both sides changed the path since the merge base — measured on a four-case matrix, one-sided and identical changes never reach it — and taking the ours side there dropped the other side's authority-derived bytes with no conflict, twice on #7836 against the stage0 seed. It now leaves the ours side in the worktree with no conflict markers, marks the path unmerged, and prints the regeneration recipe; the class is mechanically preventable, not structural, and its next-rung trigger is the commit-writer binding rows in `gunbc.commit_workflow` - **`gunbc test