From 37e9e6550a83e9eaf9e452ac60d668a6471eb2da Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Thu, 27 Aug 2026 18:08:08 +0000 Subject: [PATCH 1/3] Retract a causal story I put on main: the emitter's pub use ordering varies run to run, my preamble-reorder diagnosis was never established, and a two-draw control could not have told the difference #9439 landed an annotation on emit_rust's preamble asserting that factoring the preamble moved emitted bytes, that "the emitter is stable given its source and NOT invariant under this reordering", and that restoring the original order restored byte-identity. THE FIRST HALF OF THAT SENTENCE IS FALSE AND THE REST IS UNSUPPORTED. Prose on main asserting a mechanism nobody established is premise contamination, and the next person to touch that preamble would have found a confident causal story and planned against it. WHAT IS ACTUALLY HAPPENING, one binary compiling one unchanged corpus six consecutive times (scoped emit of src/v2/compiler/00_compile.dag, 175 files): the differing-file count VARIES BY RUN -- 2, 0, 1, 0, 2. Exactly two files ever differ (v2_lens_enforcement_vocab.rs, v2_std_cross_tree_resolution.rs), each with exactly two distinct outputs; sorted lines are IDENTICAL in every differing pair, so this is REORDERING and not value nondeterminism; every changed line is a `pub use` line (2 of 2, 4 of 4); and after rustfmt both files are NORMALIZED-IDENTICAL. That is the known import-set ordering class (#5913; measured again on 03_ingest 2026-08-22), reported independently by another lane on main at 38a127bd60 naming THESE TWO FILES with no contact between lanes. THE CENTRE OF THE REWRITE IS THE LESSON, NOT THE FINDING: a control over a probabilistic subject needs a stated sample size before it concludes anything, and two agreeing draws are not determinism. The same-source control was run twice, agreed twice, and was read as proof of determinism -- against a flip with roughly those odds it agrees about half the time, so it could not have detected the thing it was controlling for. That generalises past this file; the pub use finding does not. TWO CORRECTIONS STATED IN THE TERMS THAT MATTER. The reordering was never shown to move a byte, and was never shown innocent either -- so keeping the original binding order is NOT justified by the specimen given for it, and the annotation says so rather than quietly keeping the conclusion. And the claim that this undermines every byte-comparison gate including regen's fixed point is true in general and FALSE of this mechanism against that gate: the compared population is normalized, and normalization is exactly what removes pure use-statement reordering. WHAT THIS DOES NOT DO: it offers no theory for why a site grounded in June (#5913) varies again in August. That is unexplained, is stated as unexplained, and a correct retraction must not become a second causal story. The contribution is the localisation -- two named files, pure `pub use` order, two outputs each. ALSO IN THIS PR, from review 56672's non-blocking note on #9439: reference_derived_census now counts through one fold that dispatches on the coproduct instead of four filters over the rendered disposition NAME. Adding a fifth arm now breaks this function rather than being silently uncounted -- which, in a change whose subject is a population that goes uncounted in silence, was that defect reintroduced one level up. It also makes `candidates` the sum of the arms by construction, and the witness pins that. VERIFIED: required-regen first_generation_equal=true planned=138 executed=138 with NO drift against the committed mirror; all five witness rows PASS. Co-Authored-By: Claude Opus 5 (1M context) --- src/v1/05_emit_rust.dag | 155 ++++++++++++------ src/v1/stage0/src/v1_compiler_emit_rust.rs | 97 ++++++----- ...derived_disposition_census_witness_test.rs | 6 +- ...erived_disposition_census_witness_test.dag | 1 + 4 files changed, 162 insertions(+), 97 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 49e43408c99..2a3197f13b0 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -2531,51 +2531,69 @@ fn merged_module_source_indices(modules: List) -> Map fold(init: empty_map(), f: (acc, m) => map_merge(acc, m.type_env.source_indices)) } -// THE ORDER OF THE BINDINGS BELOW IS LOAD-BEARING AND IS NOT A TIDY-UP. DO NOT REORDER THEM. +// A CONTROL OVER A PROBABILISTIC SUBJECT NEEDS A STATED SAMPLE SIZE BEFORE IT CONCLUDES ANYTHING. +// TWO AGREEING DRAWS ARE NOT DETERMINISM. That is the durable content of this annotation, it is why +// an earlier revision of it asserted a cause that was never established, and it generalises far past +// this file -- so it is stated first rather than as a lesson appended to a finding. // -// MEASURED, not reasoned. Factoring this preamble out of emit_rust moved workflow_funcs, the two -// diagnostic guards and test_projections to AFTER export_sets and module_index, where they had been -// interleaved before. Nothing about the import decision changed -- same candidates, same survivors, -// same use-line SET -- and the emitted crate still moved: in a scoped emit of -// src/v2/compiler/00_compile.dag (175 files, roots dag + src/v2), exactly one file differed, -// v2_lens_enforcement_vocab.rs, and the whole diff was two `pub use` lines SWAPPED IN ORDER -// (std_realization_schedule::ScheduleWitnessEntry against v2_std_qualified_name::QualifiedName). -// Reproduced across three separate builds, against a same-source control that was byte-identical -- -// so the emitter is stable given its source and NOT invariant under this reordering. Restoring the -// original order restored byte-identity exactly. +// The receipt for it is immediately below: the same binary compiling the same unchanged corpus six +// consecutive times produces a differing-file count of 2, 0, 1, 0, 2. A same-source control run +// TWICE against a flip with roughly those odds agrees about half the time, and agreeing twice was +// read as "the emitter is deterministic". It is not. The control could not have detected the very +// thing it was controlling for, and nothing about running it was wrong except that no sample size +// was stated before it was allowed to conclude. // -// SO EMITTED BYTES DEPEND ON THE EVALUATION ORDER OF INDEPENDENT-LOOKING BINDINGS IN THIS PREAMBLE. -// That is a latent defect with a bounded, reproducible specimen, and it is RECORDED rather than -// chased: the suspect is a shared memo or interner upstream of the import-line stream, which nothing -// here establishes. Its blast radius is every future refactor in this region, each of which will -// silently move bytes and be diagnosed from scratch by whoever hits it. A reader who "simplifies" -// these bindings back into a natural grouping will reproduce it. +// WHAT AN EARLIER REVISION CLAIMED, AND WHAT IS RETRACTED. It said that factoring this preamble -- +// moving workflow_funcs, the two diagnostic guards and test_projections after export_sets and +// module_index -- moved emitted bytes; that "the emitter is stable given its source and NOT +// invariant under this reordering"; and that restoring the original order restored byte-identity. +// The first half of that sentence is FALSE: the emitter is not stable given its source. The rest is +// UNSUPPORTED, because the mechanism measured below fully explains the observation that was +// attributed to the reordering, and nothing run at the time distinguished them. // -// THIS ADMITS NO DEBT AND IS NOT A SCAFFOLD, and the distinction decides who has to approve it. The -// order-dependence is PRE-EXISTING: it is a property of the emitter as it stood before this change, -// not something introduced here, and no artifact is added that must later be deleted. The ordering -// kept below IS the ordering that was already there, so what is preserved is the status quo and what -// is new is only the knowledge that preserving it was necessary. DESIGN's scaffold-admission ruling -// governs CREATING temporary work -- "a dissolution condition describes how admitted debt ends, it -// does not authorize creating the debt" -- and there is no debt here to authorize; an earlier -// revision of this annotation called the kept ordering a "workaround", which invited exactly that -// reading and is corrected rather than defended. DESIGN's workaround rule is about routing AROUND an -// obstacle without diagnosing it, and the opposite happened: the line was stopped, the bytes were -// measured, the cause was located to the preamble, and the original order was restored. +// THE REORDERING WAS NEVER SHOWN TO MOVE A BYTE. It was also never shown innocent, and those are +// different claims -- holding them apart is the whole content of this retraction. So: KEEPING THE +// ORIGINAL BINDING ORDER IS NOT JUSTIFIED BY THE SPECIMEN THAT WAS GIVEN FOR IT. The order below is +// simply the order that was already there. This annotation is not an argument against regrouping +// these bindings; it is a record that the question was asked and answered wrongly once, and that any +// measurement settling it must control for a per-run flip in the two files named below rather than +// diffing a single pair. // -// THE TRIGGER BELOW IS AN OBLIGATION, NOT A PERMISSION. Section 4b(2) requires a discovered class -// below its ceiling to name its next-rung trigger so the stall is tracked rather than silent; -// omitting it would leave a measured defect recorded with no way to tell "cannot climb" from "nobody -// has". NEXT-RUNG TRIGGER: the order-dependence is located and removed -- the emitter made invariant -// under permutation of these bindings -- at which point this annotation and the ordering constraint -// it protects both retire. The discriminating control until then is the one that found it: emit a -// scoped entry before and after, and diff. +// WHAT IS ACTUALLY HAPPENING, measured on one binary compiling one unchanged corpus six consecutive +// times (scoped emit of src/v2/compiler/00_compile.dag, 175 files, roots dag + src/v2): +// - the differing-file count VARIES BY RUN: 2, 0, 1, 0, 2 against the first run; +// - exactly two files ever differ, v2_lens_enforcement_vocab.rs and v2_std_cross_tree_resolution.rs; +// - each has EXACTLY TWO distinct outputs across the six runs; +// - sorted lines are IDENTICAL in every differing pair, so this is REORDERING, not value +// nondeterminism; +// - every changed line is a `pub use` line (2 of 2, and 4 of 4); +// - and after rustfmt both files are NORMALIZED-IDENTICAL. // -// THE STRUCTURAL BYTE-IDENTITY ARGUMENT DID NOT COVER THIS, and that is the reusable lesson. "The -// use-lines ARE the survived arm of the same decision the census counts, so bytes cannot move unless -// the decision moves" is TRUE, and it is silent about evaluation order, memoisation and interning -- -// everything the refactor touched but the decision did not. A valid argument with an unstated scope -// passes review precisely because each half checks out. The measured diff is what caught it. +// So this is the known import-set ordering class: a set or map iterated in nondeterministic order +// inside use-line synthesis. It was characterised and closed once (#5913, which grounded the +// variant-owner pick and the import-set ordering in the .dag authority, taking a corpus-x2 churn of +// 36 files to 0), and measured again on the 03_ingest closure on 2026-08-22 with the same signature. +// It was reported independently by another lane on main at 38a127bd60, naming THESE TWO FILES, with +// no contact between the lanes. +// +// THE CONSEQUENCE FOR THE GATES, corrected in the direction that matters. The retracted revision +// warned that an emitter which does not produce the same bytes twice undermines every byte-comparison +// gate downstream, regen's fixed point included. That is true in general and FALSE OF THIS MECHANISM +// AGAINST THAT GATE: the emitted artifact is stored as a fixed point of the formatter, so the +// compared population is normalized, and normalization is precisely what removes pure use-statement +// reordering -- measured above, not argued. required-regen reported first_generation_equal=true on +// every run throughout. +// +// THE OPEN QUESTION IS NOT "IS THE EMITTER NONDETERMINISTIC", which is answered. It is WHY A SITE +// GROUNDED IN JUNE IS VARYING AGAIN IN AUGUST -- whether that grounding regressed, never covered this +// site, or a second mechanism exists. NOTHING HERE EXPLAINS THAT, and this annotation deliberately +// offers no theory: a correct retraction must not become a second causal story. What it contributes +// is the localisation -- two named files, pure `pub use` order, two outputs each. +// +// A SECOND, OLDER LESSON ALSO STANDS. The structural argument "the use-lines ARE the survived arm of +// the same decision the census counts, so bytes cannot move unless the decision moves" is TRUE, and +// silent about evaluation order, memoisation and interning. A valid argument with an unstated scope +// passes review precisely because each half checks out. // THE INPUTS EVERY MODULE'S EMISSION IS COMPUTED AGAINST, built once and named once. // @@ -3769,14 +3787,57 @@ type ReferenceDerivedCensus { export_proof_failed: Int } +// ONE FOLD THAT DISPATCHES ON THE COPRODUCT, so that adding an arm BREAKS THIS FUNCTION rather than +// being silently uncounted. An earlier cut counted each field with its own filter over the +// disposition's rendered NAME. That kept the arm-naming honest -- reference_derived_disposition_name +// carries no wildcard, so a new arm fails to compile there -- and it left the COUNTERS able to +// compile unchanged while answering for a population they no longer covered: `candidates` would have +// exceeded the sum of the four counts, and nothing would have said so. In a change whose entire +// subject is a population that goes uncounted in silence, that is the defect this file exists to +// close, reintroduced one level up (review 56672). +// +// Counting through the match rather than beside it also makes `candidates` the SUM of the arms by +// construction instead of a second, independently computed total that happens to agree -- so a census +// whose parts do not add up to its whole is unrepresentable rather than merely unlikely. fn reference_derived_census(rows: List) -> ReferenceDerivedCensus { - ReferenceDerivedCensus { - candidates: rows |> count, - survived: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "survived") |> count, - own_module: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "own-module") |> count, - registry_absent: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "registry-absent") |> count, - export_proof_failed: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "export-proof-failed") |> count - } + rows |> fold( + init: ReferenceDerivedCensus { candidates: 0, survived: 0, own_module: 0, registry_absent: 0, export_proof_failed: 0 }, + f: (acc, r) => + match r.disposition { + CandidateSurvived { provider_module: _ } => + ReferenceDerivedCensus { + candidates: acc.candidates + 1, + survived: acc.survived + 1, + own_module: acc.own_module, + registry_absent: acc.registry_absent, + export_proof_failed: acc.export_proof_failed + } + CandidateOwnModule => + ReferenceDerivedCensus { + candidates: acc.candidates + 1, + survived: acc.survived, + own_module: acc.own_module + 1, + registry_absent: acc.registry_absent, + export_proof_failed: acc.export_proof_failed + } + CandidateRegistryAbsent => + ReferenceDerivedCensus { + candidates: acc.candidates + 1, + survived: acc.survived, + own_module: acc.own_module, + registry_absent: acc.registry_absent + 1, + export_proof_failed: acc.export_proof_failed + } + CandidateExportProofFailed { provider_module: _ } => + ReferenceDerivedCensus { + candidates: acc.candidates + 1, + survived: acc.survived, + own_module: acc.own_module, + registry_absent: acc.registry_absent, + export_proof_failed: acc.export_proof_failed + 1 + } + } + ) } // The use-lines and the decisions that produced them, returned together so that no consumer can read diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 35e18cd883f..c546db6eb0d 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -7856,57 +7856,56 @@ pub struct ReferenceDerivedCensus { pub fn reference_derived_census( rows: Rc>>, ) -> ReferenceDerivedCensus { - ReferenceDerivedCensus { - candidates: (rows.clone().len() as i64), - survived: (Rc::new({ - let mut __result = Vec::new(); - for r in rows.iter().cloned() { - if (reference_derived_disposition_name(r.disposition.clone()) - == "survived".to_string()) - { - __result.push(r); - } - } - __result - }) - .len() as i64), - own_module: (Rc::new({ - let mut __result = Vec::new(); - for r in rows.iter().cloned() { - if (reference_derived_disposition_name(r.disposition.clone()) - == "own-module".to_string()) - { - __result.push(r); - } - } - __result - }) - .len() as i64), - registry_absent: (Rc::new({ - let mut __result = Vec::new(); - for r in rows.iter().cloned() { - if (reference_derived_disposition_name(r.disposition.clone()) - == "registry-absent".to_string()) - { - __result.push(r); - } - } - __result - }) - .len() as i64), - export_proof_failed: (Rc::new({ - let mut __result = Vec::new(); - for r in rows.iter().cloned() { - if (reference_derived_disposition_name(r.disposition.clone()) - == "export-proof-failed".to_string()) - { - __result.push(r); + rows.iter().cloned().fold( + ReferenceDerivedCensus { + candidates: 0, + survived: 0, + own_module: 0, + registry_absent: 0, + export_proof_failed: 0, + }, + |acc: ReferenceDerivedCensus, r: Rc| match (*r + .disposition + .clone()) + .clone() + { + ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: _, .. + } => ReferenceDerivedCensus { + candidates: (acc.candidates.clone() + 1), + survived: (acc.survived.clone() + 1), + own_module: acc.own_module.clone(), + registry_absent: acc.registry_absent.clone(), + export_proof_failed: acc.export_proof_failed.clone(), + }, + ReferenceDerivedCandidateDisposition::CandidateOwnModule => ReferenceDerivedCensus { + candidates: (acc.candidates.clone() + 1), + survived: acc.survived.clone(), + own_module: (acc.own_module.clone() + 1), + registry_absent: acc.registry_absent.clone(), + export_proof_failed: acc.export_proof_failed.clone(), + }, + ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent => { + ReferenceDerivedCensus { + candidates: (acc.candidates.clone() + 1), + survived: acc.survived.clone(), + own_module: acc.own_module.clone(), + registry_absent: (acc.registry_absent.clone() + 1), + export_proof_failed: acc.export_proof_failed.clone(), } } - __result - }) - .len() as i64), - } + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: _, + .. + } => ReferenceDerivedCensus { + candidates: (acc.candidates.clone() + 1), + survived: acc.survived.clone(), + own_module: acc.own_module.clone(), + registry_absent: acc.registry_absent.clone(), + export_proof_failed: (acc.export_proof_failed.clone() + 1), + }, + }, + ) } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] diff --git a/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs b/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs index e91a0c67a9a..2773b6dfd62 100644 --- a/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs +++ b/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs @@ -146,9 +146,13 @@ pub fn census_counts_each_arm_separately() -> bool { }), ]); let census = reference_derived_census(rows.clone()); - (((((census.candidates.clone() == 4) && (census.survived.clone() == 1)) + ((((((census.candidates.clone() == 4) && (census.survived.clone() == 1)) && (census.own_module.clone() == 1)) && (census.registry_absent.clone() == 1)) && (census.export_proof_failed.clone() == 1)) + && (census.candidates.clone() + == (((census.survived.clone() + census.own_module.clone()) + + census.registry_absent.clone()) + + census.export_proof_failed.clone()))) } } diff --git a/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag b/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag index 9e8d1b76e1a..6da5a35e990 100644 --- a/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag +++ b/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag @@ -92,4 +92,5 @@ test fn census_counts_each_arm_separately() -> Bool { && census.own_module == 1 && census.registry_absent == 1 && census.export_proof_failed == 1 + && census.candidates == census.survived + census.own_module + census.registry_absent + census.export_proof_failed } From c88942e63e73c0c90153b02c2b734b1028d3c1b4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 28 Aug 2026 00:29:09 +0000 Subject: [PATCH 2/3] Take #9537's repair instead of carrying it here: drop the three mirrors this branch did not originate The merge regenerated five stage0 mirrors -- the two this branch owns, plus v1_compiler_emit_core_support.rs, v1_compiler_emit_go.rs and v1_compiler_emit_python.rs, whose drift a pristine-main control showed to be main's and not this branch's. A dedicated PR (#9537) now carries exactly those four files at the same base, so carrying them here too would be two independent repairs of one drift -- the conflict class this branch spent the evening resolving. Also restores src/v1/stage0/src/bin/claim_executor.rs and src/v1/stage0/src/cli_run.rs to main's bytes: the regeneration dispatch tarred the whole stage0 source directory back from a runner whose checkout predated this merge, so those two hand-maintained files returned as pre-merge copies and silently dropped main's content. --- src/v1/stage0/src/bin/claim_executor.rs | 10 +- src/v1/stage0/src/cli_run.rs | 113 +++++++++++++++++- .../src/v1_compiler_emit_core_support.rs | 4 +- src/v1/stage0/src/v1_compiler_emit_go.rs | 5 +- src/v1/stage0/src/v1_compiler_emit_python.rs | 5 +- 5 files changed, 124 insertions(+), 13 deletions(-) diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index ee319d8ac83..eaa39713166 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -1551,9 +1551,17 @@ fn report_required_floor_outcome(outcome: &v1_compiler::cli_run::RequiredFloorOu // dropped, which is how a roster that narrowed read exactly like one that did // not. The three are printed together so the subtraction is visible rather // than inferable. + // AND THE SENTENCE IS NOW BOUNDED ABOVE, WHICH IT WAS NOT. + // "every discovered site is exactly one of these" is a totality claim over SITES, and it was + // exact and silent at the same time: a `*_test.dag` entry declaring no `test fn` contributes + // no site at all, so it could never appear in any of the four numbers, and the line read as a + // coverage guarantee over a denominator that had already dropped it. It cannot now — a barren + // entry stops the line in `run_required_floor` before this point — so the guarantee is stated + // rather than left for a reader to discover it was never claimed. eprintln!( "required-floor: offered={} routed={} declined_long={} \ - declined_live={} — every discovered site is exactly one of these", + declined_live={} — every discovered site is exactly one of these, and no `*_test.dag` \ + entry offered zero sites (BarrenTestSidecar refuses upstream of this line)", outcome.sites_offered, outcome.claims_planned, outcome.declined_long_module, diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 6881b0feed7..00ee29e135b 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -44120,6 +44120,8 @@ pub struct PreparedRepository { pub modules_excluded: usize, /// The witness sites preparation found, already folded. NOT the corpus bytes. pub witness_files: Vec, + /// Admitted sources carrying zero `test fn` decls. See `PreparedSubject::test_decl_free_paths`. + pub test_decl_free_paths: Vec, } /// Shared view of one admitted source. Holds the same `Rc` preparation already @@ -44308,6 +44310,14 @@ pub struct PreparedSubject { pub sources: Vec>, pub inventory: Vec, pub witness_files: Vec, + /// Admitted sources carrying ZERO `test fn` decls, by repo-relative path. + /// + /// Preparation records the fact and judges nothing with it. Whether a path in here is a + /// VIOLATION is a policy question owned by `v2.workflow.floor_naming_hygiene` + /// (`floor_test_sidecar_suffix` / `floor_entry_is_barren_test_sidecar`), and the floor asks + /// it once, later, against the constant read from that module — so the rule keeps one home + /// and only its consumer moved. + pub test_decl_free_paths: Vec, pub subject_digest: String, pub modules_resolved: usize, pub modules_excluded: usize, @@ -44334,6 +44344,7 @@ pub fn assemble_prepared_subject( let index = build_module_index(source_roots); let total = index.len(); let mut witness_files: Vec = Vec::new(); + let mut test_decl_free_paths: Vec = Vec::new(); let mut sources: Vec> = Vec::with_capacity(total); let mut inventory: Vec = Vec::with_capacity(total); for (module_path, sf) in index.iter() { @@ -44344,8 +44355,42 @@ pub fn assemble_prepared_subject( { continue; } - if let Some(site) = witness_file_from_source(module_path, &sf.path, &sf.content) { - witness_files.push(site); + // THE DROP THAT MADE A WALL UNREACHABLE, NOW A RECORDED FACT. + // + // `witness_file_from_source` answers `None` for a file with no `test fn` decl, and this + // loop used to discard that answer. Since `run_required_floor` builds its whole roster + // from `witness_files`, a `*_test.dag` file declaring no `test fn` was not declined and + // not counted — it left the floor's universe entirely, one level ABOVE the + // `offered = routed + declined_long + declined_live` partition, which is why that line + // can be exact and still say nothing about it. + // + // The `.dag` producer has carried a wall for exactly this since it was written + // (`v2.workflow.floor_naming_hygiene` `floor_entry_is_barren_test_sidecar`, refused as + // `BarrenTestSidecar`), and it never fired, because the required floor does not take the + // path that reaches it — `invoke_floor_discovery_producer` is reachable only through + // `discover_floor_witness_roster`, which `run_required_floor` never calls. + // + // AND THE RECORDED SET USES THE RULE'S OWN VOCABULARY, NOT THIS SCANNER'S. `test data ` + // is a test decl to `floor_discovery_scan_test_decl_names`, which is what + // `floor_entry_is_barren_test_sidecar` composes, so a file declaring only `test data` + // rows is NOT barren under the rule this wall enforces — measured at 3bbd53c05a, 13 such + // files exist and refusing them here would have been this wall over-reaching into a + // different defect. That different defect is real and is declared, not fixed here: + // `witness_file_from_source` recognises `test fn ` and not `test data `, so those rows + // are dropped from the floor's roster whether or not their file carries a `test fn`. + let site = witness_file_from_source(module_path, &sf.path, &sf.content); + let declares_no_test_decl = site.is_none() + && !sf + .content + .lines() + .any(|line| line.starts_with("test data ")); + match site { + Some(site) => witness_files.push(site), + None => { + if declares_no_test_decl { + test_decl_free_paths.push(p.clone()); + } + } } inventory.push(PreparedSourceView { module_path: module_path.clone(), @@ -44358,12 +44403,14 @@ pub fn assemble_prepared_subject( } let modules_excluded = total - sources.len(); witness_files.sort_by(|a, b| a.path.cmp(&b.path)); + test_decl_free_paths.sort(); let subject_digest = subject_digest_for_closure(&sources); let modules_resolved = total - modules_excluded; Ok(PreparedSubject { sources, inventory, witness_files, + test_decl_free_paths, subject_digest, modules_resolved, modules_excluded, @@ -44392,6 +44439,7 @@ pub fn prepare_repository_once( sources, inventory, witness_files, + test_decl_free_paths, subject_digest, modules_resolved, modules_excluded, @@ -44406,6 +44454,7 @@ pub fn prepare_repository_once( modules_resolved, modules_excluded, witness_files, + test_decl_free_paths, }, inventory, )) @@ -45758,6 +45807,51 @@ fn floor_required_int(ctx: &v1_interpreter::InterpContext, func: &str) -> Result } } +/// Read one `String` constant from a `.dag` authority, by qualified name. +/// +/// The barren-sidecar rule's suffix lives in `v2.workflow.floor_naming_hygiene` +/// (`floor_test_sidecar_suffix`), and it stays there: re-spelling `"_test.dag"` in Rust would +/// fork the rule across two representations, which is the defect this whole repair is about. +fn floor_required_string( + ctx: &v1_interpreter::InterpContext, + qualified: &str, +) -> Result { + match v1_interpreter::run_in_context(ctx, qualified, false) { + Ok(v1_interpreter::Value::Str(s)) if !s.is_empty() => Ok(s.to_string()), + Ok(other) => Err(format!( + "{qualified}: expected a non-empty String, got {}", + floor_value_shape(Some(&other)) + )), + Err(e) => Err(format!("{qualified}: {e}")), + } +} + +/// The barren witnesses in the prepared subject, judged by the `.dag` rule rather than by a +/// second Rust spelling of it. +/// +/// Preparation records every admitted source with no `test fn` decl; this asks +/// `floor_naming_hygiene`'s own suffix which of them are `*_test.dag` entries — the same +/// predicate `floor_entry_is_barren_test_sidecar` composes, consumed here because THIS is the +/// path the required floor takes. +fn floor_barren_test_sidecars( + hermetic: &v1_interpreter::InterpContext, + test_decl_free_paths: &[String], +) -> Result, String> { + let suffix = floor_required_string( + hermetic, + "v2.workflow.floor_naming_hygiene.floor_test_sidecar_suffix", + )?; + Ok(test_decl_free_paths + .iter() + .filter(|path| { + path.strip_prefix("./") + .unwrap_or(path.as_str()) + .ends_with(suffix.as_str()) + }) + .cloned() + .collect()) +} + fn floor_decode_list<'a>( ctx: &v1_interpreter::InterpContext, v: Option<&'a v1_interpreter::Value>, @@ -46476,6 +46570,21 @@ pub fn run_required_floor( &hermetic, "v2.workflow.required_floor.long_home_prefixes", )?; + // THE LINE STOPS BEFORE THE PARTITION IS COMPUTED, because a barren entry is invisible to + // the partition by construction — it contributes no SITE, so `offered` cannot report it and + // `offered == routed + declined_long + declined_live` stays exact while saying nothing about + // it. A file that claims the `*_test.dag` place and enrolls nothing is a witness nobody asked + // and everybody reads as covered. + let barren_test_sidecars = + floor_barren_test_sidecars(&hermetic, &prepared.test_decl_free_paths)?; + if !barren_test_sidecars.is_empty() { + return Err(format!( + "REQUIRED-FLOOR REFUSAL cause=BarrenTestSidecar count={} — a `*_test.dag` entry must \ + declare at least one `test fn`; the required floor enrolls nothing from: {}", + barren_test_sidecars.len(), + barren_test_sidecars.join(", ") + )); + } let mut claims: Vec = Vec::new(); let mut planned_identities: HashSet = HashSet::new(); let mut long_declined = 0usize; diff --git a/src/v1/stage0/src/v1_compiler_emit_core_support.rs b/src/v1/stage0/src/v1_compiler_emit_core_support.rs index ac07b01952b..75c92d91332 100644 --- a/src/v1/stage0/src/v1_compiler_emit_core_support.rs +++ b/src/v1/stage0/src/v1_compiler_emit_core_support.rs @@ -95,7 +95,7 @@ pub fn module_filename_collision_diagnostics(typed: Rc) -> Rc, tm: Rc| { - let module_name = crate::v1_std_core::authored_name_at( + let module_name = authored_name_at( tm.type_env.clone().source_indices.clone(), tm.module.clone(), ); @@ -109,7 +109,7 @@ pub fn module_filename_collision_diagnostics(typed: Rc) -> Rc String { pub fn emit_go(typed: Rc) -> Rc { { - let filename_collisions = - crate::v1_compiler_emit_core_support::module_filename_collision_diagnostics( - typed.clone(), - ); + let filename_collisions = module_filename_collision_diagnostics(typed.clone()); if ((filename_collisions.clone().len() as i64) > 0) { return Rc::new(EmitResult { files: Rc::new(vec![]), diff --git a/src/v1/stage0/src/v1_compiler_emit_python.rs b/src/v1/stage0/src/v1_compiler_emit_python.rs index f8f36902791..bd941c59959 100644 --- a/src/v1/stage0/src/v1_compiler_emit_python.rs +++ b/src/v1/stage0/src/v1_compiler_emit_python.rs @@ -100,10 +100,7 @@ use std::rc::Rc; pub fn emit_python(typed: Rc) -> Rc { { - let filename_collisions = - crate::v1_compiler_emit_core_support::module_filename_collision_diagnostics( - typed.clone(), - ); + let filename_collisions = module_filename_collision_diagnostics(typed.clone()); if ((filename_collisions.clone().len() as i64) > 0) { return Rc::new(EmitResult { files: Rc::new(vec![]), From e2cbd7bb04d8ac3f50b57de41a8da008700f7ebe Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 28 Aug 2026 06:35:11 +0000 Subject: [PATCH 3/3] Complete the census sum assertion over all six arms (review 57169) The #9466 merge extended the disposition coproduct to six arms and this file's fixture to six rows, and did not extend the sum clause. It read candidates == survived + own_module + registry_absent + export_proof_failed against a six-row fixture, so it asserted 6 == 4 and evaluated FALSE -- and it asserted the opposite of the property it exists to check, that the two variant arms are not part of the total. Nothing caught it because nothing ran it. The fold compiled, the per-arm equalities were all correct, the mirror regenerated, and required-regen reached first_generation_equal with fixed-point 0 -- six green signals, none of which evaluates a witness assertion. The regen gate proves the mirror matches the authority; it says nothing about whether the authority is right. Verified by execution rather than by inspection this time: all five rows run green against the emitted mirror (2 passed, 0 failed). --- ...claim_reference_derived_disposition_census_witness_test.rs | 4 +++- .../reference_derived_disposition_census_witness_test.dag | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs b/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs index 943e6020e8e..227c7bae45a 100644 --- a/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs +++ b/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs @@ -182,7 +182,9 @@ pub fn census_counts_each_arm_separately() -> bool { && (census.registry_absent.clone() == 1)) && (census.export_proof_failed.clone() == 1)) && (census.candidates.clone() - == (((census.survived.clone() + census.own_module.clone()) + == (((((census.survived.clone() + census.own_module.clone()) + + census.variant_delegated_to_parent.clone()) + + census.variant_parent_unresolved.clone()) + census.registry_absent.clone()) + census.export_proof_failed.clone()))) } diff --git a/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag b/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag index 3b86e277928..5fce048d415 100644 --- a/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag +++ b/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag @@ -107,7 +107,8 @@ test fn census_counts_each_arm_separately() -> Bool { && census.variant_parent_unresolved == 1 && census.registry_absent == 1 && census.export_proof_failed == 1 - && census.candidates == census.survived + census.own_module + census.registry_absent + census.export_proof_failed + && census.candidates == census.survived + census.own_module + census.variant_delegated_to_parent + + census.variant_parent_unresolved + census.registry_absent + census.export_proof_failed } data variant_arm_red_note: String = "THE DISCRIMINATING RED FOR THE FIFTH ARM, and it is the one this file most needs. Before the arm existed a bare variant name reached the registry lookup, missed, and answered registry-absent -- so the fixture below is RED against the four-arm classifier and green against the five-arm one, which is what makes it evidence rather than decoration. The positive control beside it is the census row above: a name that is NOT a known variant still answers registry-absent, so the arm narrows the class rather than emptying it."