diff --git a/dag/gunbc/emitted_closure_compile_seed_growth.dag b/dag/gunbc/emitted_closure_compile_seed_growth.dag index 3ba6959de97..19a771466ca 100644 --- a/dag/gunbc/emitted_closure_compile_seed_growth.dag +++ b/dag/gunbc/emitted_closure_compile_seed_growth.dag @@ -78,15 +78,154 @@ import gunbc.seed_growth { SeedGrowthJustification } // silently on the next declaration added, and nothing in the required run compares the two. Its // dissolution is the same v1-hand-queue-drain lane this obligation already names. // -// WHAT THE EXECUTED EVIDENCE IS, because the seed's own unit tests are not it. The Rust suite was -// removed from CI on 2026-07-11, so nothing under #[cfg(test)] executes on the merge path and none -// of it may be cited as coverage. The executed evidence is the phase itself: +// WHAT THE EXECUTED EVIDENCE IS, because the seed's own unit tests are not it. THE SENTENCE THAT +// STOOD HERE UNTIL 2026-09-01 SAID THE RUST SUITE WAS REMOVED FROM CI ON 2026-07-11 AND THAT +// NOTHING UNDER #[cfg(test)] EXECUTES ON THE MERGE PATH. THAT IS FALSE AND IS CORRECTED RATHER +// THAN LEFT STANDING: `gunbc.witness_floor_workflow` runs a `rust-unit-tests` job executing +// `repo_self_test_command` (`cargo test --release -p v1-compiler --lib`) on every push and pull +// request. A known-false paragraph does not get to survive because it predates the change that +// noticed it -- it is premise contamination, and a reader planning against it plans against a +// CI shape that has not existed for six weeks. +// +// WHAT IS TRUE, AT THE GRAIN THE DISTINCTION ACTUALLY HAS, because the corrected sentence is +// weaker than it first looks and the weakness is the point. The fixture discriminator +// (`fixture_closure_rustc_discrimination`, authored in `v1.compiler.compiler_tests_rust` and +// emitted into `compiler_tests.rs`) is ENROLLED AND AVAILABLE in `rust-unit-tests` via +// `cargo test --release -p v1-compiler --lib fixture_closure_rustc_discrimination -- --ignored`. +// It DOES NOT EXECUTE BY DEFAULT on push or pull request: it is `#[ignore]`d because it spawns +// cargo and compiles two emitted crates, which is minutes inside a suite that runs on every push. +// And `rust-unit-tests` is not a `needs` of the required aggregate, so even un-ignored a red +// there would be visible and would not block through the required context. THE EVIDENCE IS +// THEREFORE CANDIDATE EVIDENCE -- reviewable on demand, no wall -- and an `#[ignore]` is a COST +// DECISION AND NOT A RUNG. Nothing here may be cited as coverage that executes on the merge path, +// which is what the false sentence above got right for the wrong reason. +// +// The older reason still stands, and it is a different claim from the paragraph above: the +// phase's OWN executed evidence is the phase itself, not any unit test. // establish_discriminating_red injects one type error into one emitted file on EVERY required run, // requires it to fail alone, restores the bytes and requires the green back -- and a mutation that // fails to go red is a PHASE FAILURE, not a note. That is DESIGN 4b's authorable-RED question // answered by execution rather than by inspection, and it is why a green from this phase carries // information that a bare cargo-exit-status phase would not. +// THE SECOND ROUTE INTO THIS HOST: FIXTURE-CALLABLE EMITTED-CRATE COMPILATION (2026-09-01). +// +// WHAT WAS MISSING, AND IT WAS A REACH GAP RATHER THAN A MISSING CHECK. A witness can already +// compile an in-memory .dag source and assert on the emitted TEXT +// (v1_compiler.cli_run.emit_host compile_dag_rust_emit_check: substring includes and excludes). +// That is a SPELLING oracle. A meaning-level emitter defect -- a host representation emitted +// where a structural carrier was declared, a dropped type argument, a reference emitted past its +// resolved binding -- is invisible to it whenever the wrong bytes still contain the right +// substring, and it is exactly what rustc's type checker refuses. Meanwhile the phase above DOES +// reach rustc, but only over the entry files gunbc.ci_layer_roots required_emit_compile_entries +// names, so no fixture-authorable subject could be posed to it at all. Fixture-authorable +// subjects with no compiler behind them on one side; a real compiler with no fixture-authorable +// subjects on the other. +// +// WHAT LANDED: fixture_closure_rustc_verdict, taking the SAME in-memory source shape +// compile_dag_rust_emit_check takes, emitted through the same compile_sources, written by the +// SAME crate writer (write_probe_crate_files, factored out of write_probe_crate for this) and +// handed to the SAME run_cargo. Sharing those three is the whole design: a separately authored +// fixture harness would have made a green here and a green in the required phase two facts about +// two crates, which is the property this file's header already refuses for the emission producer. +// +// GUNBC'S OWN REFUSAL IS A SEPARATE ARM AND NEVER A RUSTC VERDICT. FixtureClosureOutcome +// separates SourceRefused (v1 refused the fixture; nothing was emitted, so rustc had no subject), +// CrateNotWritten and Measured. Collapsing the first into a red would report a fixture gunbc +// rejected as 'rustc refused the emitted bytes' -- a claim about bytes that never existed. +// +// THE ROUTE SHIPS WITH ITS OWN DISCRIMINATING PAIR, run_fixture_closure_discrimination, because a +// route that reaches rustc proves nothing on its own: a green says only that SOME crate compiled. +// The control is a self-contained fixture whose closure must compile. The RED is meaning-level +// and is the fixture gunbc.rung_drop text_boundary_identity_wall names -- a NON-LITERAL +// kernel-String value at an exact v2.std.text.String boundary, admitted by leaf-name compatibility +// with no homomorphism row. That row's temporary rung claims this class is mechanically +// preventable ON THE EMITTED-RUST PATH because such a value 'renders as a Rust type mismatch and +// the mandatory emitted-crate cargo check refuses it' -- a claim about a fixture the entry roster +// cannot reach. This arm is where that claim EXECUTES instead of being asserted. +// +// ATTRIBUTION IS PART OF THE RED. The red fixture's closure is hundreds of modules and a bare +// non-zero status could come from any of them, so the pair passes only when a diagnostic line +// NAMES the fixture's own emitted module; otherwise the report reads 'unattributed' and the pair +// FAILS rather than crediting a red the fixture did not cause. run_cargo's attribution symbol is +// therefore a parameter of the caller rather than the injected mutation's constant: the required +// phase attributes its own fault, the fixture route attributes the fixture. +// +// WHERE IT EXECUTES, STATED HONESTLY BECAUSE IT IS NOT THE MERGE PATH. The pair's ONE consumer is +// `v1.compiler.compiler_tests_rust` `fixture_closure_rustc_discrimination`, emitted into +// `compiler_tests.rs` and run by `repo_self_test_command` -- and it is `#[ignore]`d, so it is +// ENROLLED AND AVAILABLE via `cargo test --release -p v1-compiler --lib +// fixture_closure_rustc_discrimination -- --ignored` and DOES NOT EXECUTE BY DEFAULT on push or +// pull request. `rust-unit-tests` is additionally not a `needs` of the required aggregate, so even +// un-ignored a red there would be visible and would not block through the required context. The +// evidence is therefore CANDIDATE EVIDENCE -- reviewable on demand, NO WALL -- and an `#[ignore]` +// is a cost decision and NOT a rung. It discharges no next-rung trigger naming this capability: +// the capability exists, the evidence does not execute. Nothing here claims a rung for the +// text-boundary class, which stays exactly where its own row puts it. +// +// THE #[ignore] IS A MEASURED DECISION WITH A REVERSAL CONDITION, NOT NEGLECT, and it is written +// down because a decision to NOT RUN something is exactly the kind that goes silently stale -- read +// in six months, an ignore with no reasoning beside it is indistinguishable from an oversight. +// +// THE MEASUREMENT, one BuildBuddy runner, one run, warm target directory, so the two figures are +// comparable: the discriminator alone is 184.65s (`test result: ok`, pair PASSED); the default +// `--lib` suite that runs on every push and pull request is 456.28s. Un-ignoring it is therefore +// roughly +40% on that suite, fleet-wide, per push. NOT MEASURED COLD, and stated rather than +// estimated: a cold runner pays the dependency graph first. WHAT THE 185s IS, so nobody prices a +// black box: two emitted-crate cargo builds -- the control's closure is 6 files, the red's is 17, +// both linking the seed crate. It is rustc doing the work that makes the arm mean anything, not +// test logic, so it cannot be optimised away without removing the subject. +// +// THE ARGUMENT THAT DECIDES IT IS A RUNG ARGUMENT AND NOT A COST ONE. Un-ignoring would move this +// evidence from AVAILABLE to EXECUTES-ON-A-NON-BLOCKING-JOB. That is a real honesty improvement and +// it is still not an acceptance path, because `rust-unit-tests` is not a `needs` of the required +// aggregate. DESIGN 4b(1) establishes a rung by evidence executing on the REAL ACCEPTANCE PATH, so +// BOTH STATES LEAVE A NEXT-RUNG TRIGGER NAMING THIS CAPABILITY UNFIRED. The ignore therefore costs +// NOTHING in rung terms and saves 40% of a per-push suite, which is the whole argument. +// +// THE CONDITION THAT REVERSES IT, named so this is re-decided rather than inherited: when +// `rust-unit-tests` is promoted into the required aggregate. At that point the cost and the WALL +// arrive together -- the evidence would execute on an acceptance path and could block a merge -- +// and the trade this row prices no longer holds. Nothing else reverses it: not the suite getting +// faster, not the roster growing, and not a lane wanting to cite this as coverage. +// +// AN EARLIER REVISION OF THIS PARAGRAPH CITED TWO `claim_executor` MODES THAT NO LONGER EXIST +// (`--fixture-closure-compile`, `--fixture-closure-pair`) AND SAID THE PAIR'S VERDICT JOINED +// `--required-emit-compile`'s EXIT STATUS. Those modes were withdrawn under the approval's +// test-facing-visibility condition before this landed, and the sentence describing them survived +// the code by one revision -- specification pointing at wiring that is not in the tree, found by +// review 58151. Recorded rather than silently rewritten, because this file's whole subject is +// receipts that outlive what they certify. +// +// WHERE THE CONSUMER LIVES, AND A FALSIFIED CLAIM RECORDED RATHER THAN QUIETLY DROPPED. An +// earlier revision of this lane asserted that a cargo-test-hosted consumer DEADLOCKS -- that a +// test binary launched by cargo, spawning cargo at the same target directory, blocks on the outer +// lock. THAT IS FALSE AND WAS NEVER MEASURED: it was inferred from a thirty-minute run that +// produced no output and was terminated. Measured properly, from inside a cargo-launched test +// binary: elapsed 215s, exit status 0, and cargo emitted no `Blocking waiting for file lock` +// line at all. The run completed. What the original silence actually was is an ordinary REBUILD -- +// an inner `cargo build --release` from an outer `cargo test --release` does not share +// fingerprints, because a test build resolves features differently, so the inner invocation +// recompiles part of the dependency graph before it reaches the emitted closure. Two long builds +// in series, working the whole time. The claim is recorded here in the past tense, with what +// replaced it, because a mechanism asserted from a silence is exactly what this file's own +// evidence discipline exists to prevent, and deleting the sentence would leave the next author +// free to infer it again. +// +// HAND-ITEM DELTA FOR THIS LANE: fifteen declarations, every one enumerated in the roster below +// rather than counted -- write_probe_crate_files, FixtureClosureOutcome, fixture_closure_compiled, +// fixture_closure_attributed_line, fixture_closure_summary, fixture_rust_module, +// fixture_closure_rustc_verdict, fixture_arm_diagnostic_lines, FixtureDiscrimination, +// FIXTURE_GREEN_PATH, FIXTURE_RED_PATH, fixture_arm_verdict, run_fixture_closure_discrimination, +// fixture_discrimination_passed, fixture_discrimination_report. run_cargo, write_probe_crate and +// the cli_run re-export list are MODIFIED rather than added, and claim_executor gains argv arms +// and two mode bodies inside an existing function -- ExistingSeedItemModified in both files, so +// listing them would net a modification into an addition census. THE ROSTER IS EXACT AGAINST THE +// FILE AS OF THIS LANE: 75 declarations counted at every indent by the rule stated above, 75 +// rows, zero stale, zero unaccounted (74 at first push; fixture_closure_reached_rustc is the +// fail-closed repair review 58120 required). Re-derive it rather than trusting it; the paragraph above +// records what happens when nobody does. + // HAND-RUST RECEIPT FOR THE REQUIRED-PATH ROOT CORRECTION. Additions: PROBE_ROOT_DIR_NAME, // required_ci_probe_root_from_runner_temp, required_ci_emit_compile_probe_root, // local_emit_compile_probe_root, and the_probe_root_does_not_exist_without_runner_temp -- five @@ -135,6 +274,22 @@ data emitted_closure_compile_seed_growth_justification: SeedGrowthJustification DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "probe_crate_dir", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "write_probe_crate", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "run_cargo", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "write_probe_crate_files", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "FixtureClosureOutcome", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_closure_compiled", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_closure_reached_rustc", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_closure_attributed_line", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_closure_summary", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_rust_module", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_closure_rustc_verdict", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_arm_diagnostic_lines", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "FixtureDiscrimination", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "FIXTURE_GREEN_PATH", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "FIXTURE_RED_PATH", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_arm_verdict", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "run_fixture_closure_discrimination", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_discrimination_passed", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "fixture_discrimination_report", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "closure_modules", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "mutation_subject", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.emitted_closure_compile_host", decl_name: "establish_discriminating_red", field: WholeDeclaration }, @@ -167,5 +322,5 @@ data emitted_closure_compile_seed_growth_justification: SeedGrowthJustification reason: "WHY RUST IS STILL NEEDED, and it is a REACHABILITY limit rather than a modeling gap. The subject is a required CI phase: it must run inside claim_executor, which is the only witness-executing consumer in the tree, and its stages are host effects -- writing an emitted tree to disk and spawning cargo over it. The required floor's hermetic envelope REFUSES host effects during preparation, so a .dag witness whose subject is a subprocess exit status is counted executed while its assertion never runs; DESIGN's Building-&-checks section records that boundary in as many words, and records that mocking the refusal would pass the witness against a fabricated exit status. So the phase has to live where the effects do, exactly as required_regen_host and partition_crate_boundary_host do.\n\nWHY IT IS ADMITTED AGAINST THE v1 FREEZE, ON PRECEDENT RATHER THAN ANALOGY. gunbc.v1_maintenance_standing v1_seed_standing admits a change by PURPOSE -- does it serve the v2 self-host program -- and the purpose test reaches the INSTRUMENT that measures that program, not only the program. This is that instrument in the most direct available sense: the self-host claim IS that the emitted tree compiles and behaves, and until now nothing on the merge path compiled an emitted tree at all. gunbc.floor_non_verdict_enrollment and gunbc.declaration_index_seed_growth are both admitted on that same footing. Classified against the five refused classes: NewLanguageBehavior -- no, the compile pipeline is byte-untouched, no diagnostic is added, moved or removed. NewCompatibilityObligation -- no, nothing is kept working for an old caller. NewEscapeHatchOrAdmissionRow -- no, and the direction is opposite: this adds a wall and adds no flag, env var or mode that skips it. SeedFeatureCompletion -- no, the seed compiler gains no capability; it compiles exactly what it compiled before. PublicSurfaceGrowth -- the nearest, and the test is a diff over THE EMITTED SEED'S EXPORTED DECLARATIONS: the file is wired by #[path] mod inside cli_run.rs exactly as declaration_index.rs, phase_profile.rs and partition_crate_boundary_host.rs are, so it contributes no pub mod line to the emitted lib.rs and its SeedRetainedIntrinsicRegistration carries has_pub_mod: false.\n\nWHAT IS NOT CLAIMED. This phase is not a compile of the corpus and must not be read as one: gunbc.whole_corpus_compile_admission refuses a whole-bundle compile on the default runner and records two EXIT=137 kills behind that refusal, so the subject is a bounded roster of entries in gunbc.ci_layer_roots required_emit_compile_entries. It carries no diagnostic count, no baseline and no ratchet -- a merge-blocking comparison against a population measured on the current tree is the tree-copied oracle DESIGN 5 rejects.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, trigger: "Delete this host when the phase's two host effects are modeled and a modeled actuator can perform them: writing an emitted file set to a directory, and invoking cargo over a manifest with a typed outcome. extdeps.cargo already models the manifest, the dependency rows and the target kinds, and extdeps.filesystem.filesystem_io models the write, so what is missing is the ACTUATION -- a modeled operation the required run can execute without the hermetic envelope refusing it. When that exists, run_emit_compile_entry becomes a .dag fold over those two operations and every item here is deleted, not re-homed. A PARTIAL migration is admissible and is the expected shape: the moment the cargo invocation is a modeled transport, run_cargo and the CargoVerdict vocabulary go first and the emission-to-disk half stays behind. What does NOT dissolve these items is the phase merely changing its roster, and what does NOT dissolve them is a hermetic witness asserting a fabricated exit status -- that would retire the evidence while retiring nothing it guards.", - current_boundary: "src/v1/stage0/src/emitted_closure_compile_host.rs; src/v1/stage0/src/cli_run.rs (the #[path] mod line and the re-export); src/v1/stage0/src/bin/claim_executor.rs (RequiredCiPhase::EmitCompile and its phase body); dag/gunbc/ci/ci_layer_roots.dag required_emit_compile_entries; src/v2/compiler/self_host/stage0_crate_layout.dag; dag/gunbc/emitted_closure_compile_seed_growth.dag" + current_boundary: "src/v1/stage0/src/emitted_closure_compile_host.rs; src/v1/stage0/src/cli_run.rs (the #[path] mod line and the re-export); src/v1/stage0/src/bin/claim_executor.rs (RequiredCiPhase::EmitCompile and its phase body); src/v1/compiler_tests_rust.dag ct_fixture_closure_rustc_discrimination_test and its emitted form in src/v1/stage0/src/compiler_tests.rs, the route's only consumer; fixtures/fixture_closure_rustc/ (the pair's two arms, which are files rather than string constants so that the arm this pair adjudicates is the arm any caller runs); dag/gunbc/ci/ci_layer_roots.dag required_emit_compile_entries; src/v2/compiler/self_host/stage0_crate_layout.dag; dag/gunbc/emitted_closure_compile_seed_growth.dag" } diff --git a/fixtures/fixture_closure_rustc/green_probe.dag b/fixtures/fixture_closure_rustc/green_probe.dag new file mode 100644 index 00000000000..6ba9ba7a4c9 --- /dev/null +++ b/fixtures/fixture_closure_rustc/green_probe.dag @@ -0,0 +1,8 @@ +module fixture.closure.rustc_green_probe + +// The positive control for the fixture-callable emitted-crate compilation route +// (v1_compiler.emitted_closure_compile_host fixture_closure_rustc_verdict). No imports, so its +// emitted closure is its own module plus the runtime -- the smallest subject that still proves +// the crate writer, the manifest and the emitter reached a crate rustc accepts. Without it the +// red arm beside it could pass for a reason that has nothing to do with its fixture. +data rustc_green_probe_value: Int = 424242 diff --git a/fixtures/fixture_closure_rustc/text_nonliteral_probe.dag b/fixtures/fixture_closure_rustc/text_nonliteral_probe.dag new file mode 100644 index 00000000000..676b3b0b634 --- /dev/null +++ b/fixtures/fixture_closure_rustc/text_nonliteral_probe.dag @@ -0,0 +1,26 @@ +module fixture.closure.rustc_text_nonliteral_probe + +import v2.std.text { string_is_empty } + +// The MEANING-LEVEL red arm, and the fixture gunbc.rung_drop text_boundary_identity_wall names. +// `concat` answers the kernel string; the return boundary declares the structural +// v2.std.text.String; no homomorphism row stands between them, and the corpus-wide compatibility +// relation admits the pairing by leaf-name spelling. A substring oracle over the emitted text +// cannot see this -- the emitted bytes contain every substring a spelling assertion would look +// for -- which is why the subject needs rustc rather than a text check. +// +// THE IMPORT ABOVE IS LOAD-BEARING AND MUST NOT BE DELETED AS UNUSED. It binds a name this module +// never mentions again, so every reader -- and review 58161, which is why this note exists -- sees +// dead weight. It is the opposite: `v1_compiler.cli_run` `resolve_virtual_source_with_imports` +// builds this fixture's source set ONLY by following import statements transitively, so the import +// is the single mechanism that puts v2.std.text into the resolved closure at all. Without it the +// qualified return type has nothing to resolve against. +// +// MEASURED RATHER THAN ASSERTED, by running the route on this file with the import stripped: +// WITHOUT IMPORT => SourceRefused hard=1 +// UnresolvedType { name: "v2.std.text.String" } | reached_rustc=false +// So deleting it does not tidy the fixture; it converts the red arm from a RUSTC VERDICT into a +// GUNBC REFUSAL and destroys the discrimination, while looking like cleanup. The pair does go red +// if anyone does it -- `fixture_closure_reached_rustc` refuses to count an arm that never reached +// rustc -- so the wall holds; this note exists so the wall is not hit in the first place. +fn rustc_text_nonliteral_probe() -> v2.std.text.String { concat("x", "y") } diff --git a/src/v1/compiler_tests_rust.dag b/src/v1/compiler_tests_rust.dag index 3a5dd2399eb..857a5f1a1be 100644 --- a/src/v1/compiler_tests_rust.dag +++ b/src/v1/compiler_tests_rust.dag @@ -293,6 +293,49 @@ fn ct_parse_tests() -> String { " }\n\n") } +fn ct_fixture_closure_rustc_discrimination_test() -> String { + concat( + " /// THE EMITTED CLOSURE, HANDED TO RUSTC, OVER FIXTURES A TEST CAN AUTHOR.\n", + " ///\n", + " /// Every other emitted-bytes assertion in this file is a SPELLING oracle: it reads the\n", + " /// emitted text and asserts on substrings. A meaning-level emitter defect is invisible to\n", + " /// that whenever the wrong bytes still contain the right substring -- and it is exactly\n", + " /// what rustc's type checker refuses. This arm is the one that asks rustc.\n", + " ///\n", + " /// THE PAIR IS THE SUBJECT, NOT EITHER ARM. The control must COMPILE, or a red below\n", + " /// proves only that something in the tree is broken. The red must be refused BY RUSTC and\n", + " /// ATTRIBUTED to the fixture's own emitted module, or a non-zero status from any of the\n", + " /// hundreds of modules in its closure would pass for the fixture's own defect.\n", + " ///\n", + " /// #[ignore] AND WHY, STATED RATHER THAN LEFT TO BE DISCOVERED: this arm spawns cargo and\n", + " /// compiles two emitted crates, which is minutes rather than milliseconds, and\n", + " /// `repo_self_test_command` runs the whole --lib suite on every push and pull request. It\n", + " /// is therefore ENROLLED AND OPT-IN: `cargo test --release -p v1-compiler --lib\n", + " /// fixture_closure_rustc_discrimination -- --ignored`. An #[ignore] is a cost decision and\n", + " /// NOT a rung: nothing here may be cited as coverage that executes on the merge path.\n", + " #[test]\n", + " #[ignore]\n", + " fn fixture_closure_rustc_discrimination() {\n", + " let probe_root = crate::cli_run::local_emit_compile_probe_root();\n", + " let pair = crate::cli_run::run_fixture_closure_discrimination(&probe_root);\n", + " for line in crate::cli_run::fixture_discrimination_report(&pair) {\n", + " eprintln!(\"{}\", line);\n", + " }\n", + " assert!(\n", + " crate::cli_run::fixture_closure_reached_rustc(&pair.red),\n", + " \"the red arm never reached a rustc verdict, so nothing about the emitted bytes was measured: {}\",\n", + " crate::cli_run::fixture_closure_summary(&pair.red)\n", + " );\n", + " assert!(\n", + " crate::cli_run::fixture_discrimination_passed(&pair),\n", + " \"the control must compile and the meaning-level red must be refused by rustc and attributed to its own emitted module; control={} red={} attribution={:?}\",\n", + " crate::cli_run::fixture_closure_summary(&pair.green),\n", + " crate::cli_run::fixture_closure_summary(&pair.red),\n", + " crate::cli_run::fixture_closure_attributed_line(&pair.red)\n", + " );\n", + " }\n\n") +} + fn ct_import_lines_follow_resolved_binding_identity_test() -> String { concat( " #[test]\n", @@ -3079,6 +3122,7 @@ fn compiler_tests_source() -> String { ct_parse_tests(), ct_pipeline_test(), ct_import_lines_follow_resolved_binding_identity_test(), + ct_fixture_closure_rustc_discrimination_test(), ct_unlisted_import_use_witness_test(), ct_call_shape_wall_witness_test(), ct_call_deficit_red_witness_test(), diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 5706e63d1ec..e4549ed19cc 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -1893,7 +1893,7 @@ fn collect_module_binding_manifest_rows(source_roots: &[String]) -> Vec, @@ -38762,6 +38762,21 @@ pub use emitted_closure_compile_host::{ EmitCompileSelection, MutationVerdict, }; +/// THE FIXTURE ROUTE IS TEST-FACING ONLY, AND THAT IS WHY IT HAS ITS OWN `use` RATHER THAN A LINE +/// IN THE LIST ABOVE. +/// +/// `pub(crate)` under `#[cfg(test)]`: the generated `compiler_tests` module is a sibling module in +/// this crate, so it needs crate visibility to reach the route and needs nothing wider. Putting +/// these names in the production list would place them on the emitted seed's exported surface -- +/// PublicSurfaceGrowth against a seed frozen for growth. An internal test function does not become +/// public surface merely because Rust needs a path to it. +#[cfg(test)] +pub(crate) use emitted_closure_compile_host::{ + fixture_arm_diagnostic_lines, fixture_closure_attributed_line, fixture_closure_reached_rustc, + fixture_closure_rustc_verdict, fixture_closure_summary, fixture_discrimination_passed, + fixture_discrimination_report, run_fixture_closure_discrimination, FixtureClosureOutcome, +}; + /// The authority's own declared module path, for consumers outside this module. /// /// Exposed rather than re-implemented: a second parser for `module ` would be a second diff --git a/src/v1/stage0/src/compiler_tests.rs b/src/v1/stage0/src/compiler_tests.rs index 32a964a88be..5469b774325 100644 --- a/src/v1/stage0/src/compiler_tests.rs +++ b/src/v1/stage0/src/compiler_tests.rs @@ -459,6 +459,46 @@ mod compiler_tests { result.expect("emit_import_lines_follow_resolved_binding_identity panicked"); } + /// THE EMITTED CLOSURE, HANDED TO RUSTC, OVER FIXTURES A TEST CAN AUTHOR. + /// + /// Every other emitted-bytes assertion in this file is a SPELLING oracle: it reads the + /// emitted text and asserts on substrings. A meaning-level emitter defect is invisible to + /// that whenever the wrong bytes still contain the right substring -- and it is exactly + /// what rustc's type checker refuses. This arm is the one that asks rustc. + /// + /// THE PAIR IS THE SUBJECT, NOT EITHER ARM. The control must COMPILE, or a red below + /// proves only that something in the tree is broken. The red must be refused BY RUSTC and + /// ATTRIBUTED to the fixture's own emitted module, or a non-zero status from any of the + /// hundreds of modules in its closure would pass for the fixture's own defect. + /// + /// #[ignore] AND WHY, STATED RATHER THAN LEFT TO BE DISCOVERED: this arm spawns cargo and + /// compiles two emitted crates, which is minutes rather than milliseconds, and + /// `repo_self_test_command` runs the whole --lib suite on every push and pull request. It + /// is therefore ENROLLED AND OPT-IN: `cargo test --release -p v1-compiler --lib + /// fixture_closure_rustc_discrimination -- --ignored`. An #[ignore] is a cost decision and + /// NOT a rung: nothing here may be cited as coverage that executes on the merge path. + #[test] + #[ignore] + fn fixture_closure_rustc_discrimination() { + let probe_root = crate::cli_run::local_emit_compile_probe_root(); + let pair = crate::cli_run::run_fixture_closure_discrimination(&probe_root); + for line in crate::cli_run::fixture_discrimination_report(&pair) { + eprintln!("{}", line); + } + assert!( + crate::cli_run::fixture_closure_reached_rustc(&pair.red), + "the red arm never reached a rustc verdict, so nothing about the emitted bytes was measured: {}", + crate::cli_run::fixture_closure_summary(&pair.red) + ); + assert!( + crate::cli_run::fixture_discrimination_passed(&pair), + "the control must compile and the meaning-level red must be refused by rustc and attributed to its own emitted module; control={} red={} attribution={:?}", + crate::cli_run::fixture_closure_summary(&pair.green), + crate::cli_run::fixture_closure_summary(&pair.red), + crate::cli_run::fixture_closure_attributed_line(&pair.red) + ); + } + #[test] fn unlisted_import_use_witness() { // Discriminating witness for the selective-import fail-closed mask diff --git a/src/v1/stage0/src/emitted_closure_compile_host.rs b/src/v1/stage0/src/emitted_closure_compile_host.rs index f7edff00ed1..bfa61500d83 100644 --- a/src/v1/stage0/src/emitted_closure_compile_host.rs +++ b/src/v1/stage0/src/emitted_closure_compile_host.rs @@ -24,6 +24,21 @@ //! `run_required_emit_compile`, which stops there and reports every later entry as `NotExecuted` //! rather than measuring through a target directory of unknown state. //! +//! TWO ROUTES REACH RUSTC FROM HERE, AND THE SECOND IS WHY THE HEADER ABOVE IS NOT THE WHOLE +//! FILE. The required phase's subject is a rostered `.dag` ENTRY FILE, so nothing a fixture can +//! author is a subject for it. `fixture_closure_rustc_verdict` is the second route: the same +//! in-memory `.dag` source shape a witness already hands `compile_dag_rust_emit_check`, emitted +//! through the same `compile_sources`, written by the same crate writer, handed to the same cargo +//! invocation. It exists because a substring oracle over emitted TEXT cannot see a meaning-level +//! emitter defect, and rustc can. The two routes share `write_probe_crate_files`, `probe_manifest` +//! and `run_cargo` deliberately: a separately authored fixture harness would make a green here and +//! a green there two facts about two crates. +//! +//! THE FIXTURE ROUTE IS `#[cfg(test)]` AND HAS EXACTLY ONE CONSUMER: the generated +//! `fixture_closure_rustc_discrimination` (authored in `v1.compiler.compiler_tests_rust`), which +//! is `#[ignore]`d and therefore available on demand rather than executing on push. It reaches no +//! CLI flag and no required phase, and it is not on the emitted seed's public surface. +//! //! WHAT THIS PHASE IS NOT. No baseline, no diagnostic count, no ratchet. Cargo's exit status is //! the whole verdict; warnings are not errors here. Pinning a diagnostic population measured on //! the current tree would be the tree-copied oracle DESIGN 5 rejects; an identity-grain debt @@ -105,11 +120,17 @@ pub enum CargoVerdict { DidNotComplete { detail: String }, /// Ran to completion and reported its own exit status. /// - /// `probe_line` is the first diagnostic line naming the injected probe symbol, scanned from - /// the WHOLE stderr, not `stderr_tail`: the tail is the last 20 lines kept for a human - /// reader, and a genuine `E0308` for the injected item can sit above it when other + /// `probe_line` is the first diagnostic line naming the CALLER'S ATTRIBUTION SYMBOL, scanned + /// from the WHOLE stderr, not `stderr_tail`: the tail is the last 20 lines kept for a human + /// reader, and a genuine diagnostic for the attributed item can sit above it when other /// diagnostics follow. Attributing from the tail would fail a run whose fault WAS refused /// because the receipt was short. + /// + /// THE SYMBOL IS THE CALLER'S BECAUSE THE TWO ROUTES ATTRIBUTE DIFFERENT THINGS AND MUST NOT + /// SHARE ONE ANSWER: the required phase attributes its own injected fault + /// (`MUTATION_PROBE_SYMBOL`), while the fixture route attributes a red to the FIXTURE'S OWN + /// emitted module. A red naming neither is a red about something else in the closure, and + /// both callers need to be able to say so. Completed { status: i32, stderr_tail: String, @@ -136,7 +157,7 @@ pub fn cargo_verdict_summary(verdict: &CargoVerdict) -> String { } } -/// The diagnostic line naming the injected probe symbol, if the run produced one. +/// The diagnostic line naming the caller's attribution symbol, if the run produced one. pub fn cargo_verdict_probe_line(verdict: &CargoVerdict) -> Option<&str> { match verdict { CargoVerdict::Completed { probe_line, .. } => probe_line.as_deref(), @@ -544,6 +565,21 @@ fn write_probe_crate( .iter() .find(|emission| emission.target_name == "rust") .ok_or_else(|| "the emission carries no rust target".to_string())?; + write_probe_crate_files(&emission.result.files, probe_root, entry) +} + +/// The crate writer both routes share: emitted files in, a written crate directory out. +/// +/// ONE WRITER, TWO CALLERS, AND THE SHARING IS THE POINT (DESIGN §2). The required phase reaches +/// it through a `CompileRun`'s rust emission; the fixture route reaches it with the files a +/// virtual source's `compile_sources` produced. A second writer beside this one would let the two +/// routes disagree about what "the emitted crate" is -- manifest, stale-tree removal, or the +/// `src/lib.rs` requirement -- so a green on one would stop being evidence about the other. +fn write_probe_crate_files( + files: &im::Vector>, + probe_root: &Path, + entry: &str, +) -> Result<(PathBuf, usize), String> { let dir = probe_crate_dir(probe_root, entry); // A STALE TREE IS NOT A SUBJECT. A previous run's bytes under the same slug would let a module // deleted from the closure keep compiling, so the directory is removed, not written over. @@ -551,7 +587,7 @@ fn write_probe_crate( std::fs::create_dir_all(dir.join("src")) .map_err(|e| format!("creating {}: {e}", dir.display()))?; let mut written = 0usize; - for file in emission.result.files.iter() { + for file in files.iter() { let path = dir.join(&*file.path); if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) @@ -585,7 +621,7 @@ fn write_probe_crate( /// /// Phases within one required run are sequential in one process, so nothing else holds cargo's /// lock on that directory. -fn run_cargo(crate_dir: &Path, workspace: &Path) -> CargoVerdict { +fn run_cargo(crate_dir: &Path, workspace: &Path, attribution_symbol: &str) -> CargoVerdict { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); let mut command = std::process::Command::new(&cargo); command @@ -608,7 +644,7 @@ fn run_cargo(crate_dir: &Path, workspace: &Path) -> CargoVerdict { let tail: Vec<&str> = stderr.lines().rev().take(20).collect(); let probe_line = stderr .lines() - .find(|line| line.contains(MUTATION_PROBE_SYMBOL)) + .find(|line| line.contains(attribution_symbol)) .map(|line| line.trim().to_string()); CargoVerdict::Completed { status, @@ -671,7 +707,7 @@ fn establish_discriminating_red( }; } - let red = run_cargo(crate_dir, workspace); + let red = run_cargo(crate_dir, workspace, MUTATION_PROBE_SYMBOL); // THE RESTORE RUNS WHATEVER THE FAULTED ARM ANSWERED, or the next run's baseline goes red for // a reason unrelated to the corpus. @@ -765,7 +801,7 @@ fn establish_discriminating_red( }; let attributed = attributed.to_string(); - let restored = run_cargo(crate_dir, workspace); + let restored = run_cargo(crate_dir, workspace, MUTATION_PROBE_SYMBOL); if !cargo_verdict_compiled(&restored) { return MutationVerdict::RestoreFailed { detail: format!( @@ -861,7 +897,7 @@ pub fn run_emit_compile_entry( "emit-compile: {entry} emitted {emitted_files} file(s) into {} — cargo baseline", crate_dir.display() ); - let baseline = run_cargo(&crate_dir, &workspace); + let baseline = run_cargo(&crate_dir, &workspace, MUTATION_PROBE_SYMBOL); eprintln!( "emit-compile: {entry} baseline {} — mutation", cargo_verdict_summary(&baseline) @@ -890,6 +926,361 @@ pub fn run_emit_compile_entry( } } +/// WHAT A FIXTURE CAN ASK RUSTC, AND WHY NOTHING COULD ASK IT BEFORE. +/// +/// `compile_dag_rust_emit_check` compiles an in-memory `.dag` source and lets a fixture assert on +/// the emitted TEXT — substring includes and excludes. That is a SPELLING oracle: it answers +/// "does this byte sequence occur", never "does the emitted program MEAN what the source said". +/// The required `emit-compile` phase does reach rustc, but only over the eight `.dag` ENTRY files +/// its roster names, so a fixture cannot pose a subject to it at all. +/// +/// So the two capabilities sat on opposite sides of a gap: fixture-authorable subjects with no +/// compiler behind them, and a real compiler with no fixture-authorable subjects. This function +/// closes it — the SAME in-memory source shape `compile_dag_rust_emit_check` takes, emitted +/// through the same `compile_sources`, written by the same crate writer the required phase uses, +/// and handed to the same cargo invocation. +/// +/// WHY THAT MATTERS FOR SAFETY RATHER THAN CONVENIENCE. A meaning-level emitter defect —— a host +/// representation emitted where a structural carrier was declared, a dropped type argument, a +/// reference emitted past its resolved binding —— is invisible to a substring oracle whenever the +/// wrong bytes happen to contain the right substring, and it is exactly what rustc's type checker +/// refuses. `gunbc.rung_drop` `text_boundary_identity_wall` already CLAIMS that rung ("on the +/// emitted-Rust self-host path the class is mechanically preventable — ... renders as a Rust type +/// mismatch and the mandatory emitted-crate cargo check refuses it") for a fixture the required +/// phase's roster cannot reach. DESIGN §4b(1) says a rung is established by evidence executing on +/// the acceptance path; this is the route that lets such a claim execute over its own fixture +/// instead of being asserted about one. +/// +/// GUNBC'S OWN REFUSAL IS A DISTINCT ARM AND NEVER A RUSTC VERDICT. If the fixture does not reach +/// emission compile-clean, the answer is `SourceRefused` carrying the count and the first hard +/// diagnostic — not a red. Collapsing the two would let a fixture that gunbc rejected be reported +/// as "rustc refused the emitted bytes", which is a claim about bytes that were never emitted. +#[derive(Debug, Clone)] +#[cfg(test)] +pub(crate) enum FixtureClosureOutcome { + /// The v1 compiler refused the fixture itself: emission never happened, so rustc has no + /// subject. `first` names one hard diagnostic so the caller can say WHICH refusal. + SourceRefused { + hard_diagnostics: usize, + first: String, + }, + /// Emission completed and the crate could not be written — a filesystem or closure-shape + /// fact, again not a rustc verdict. + CrateNotWritten { cause: String }, + /// The emitted closure was written and cargo answered over it. + Measured { + crate_dir: String, + emitted_files: usize, + cargo: CargoVerdict, + }, +} + +/// True only when cargo COMPLETED with status zero over the written closure. Every other arm — +/// a gunbc refusal, an unwritten crate, a killed toolchain — answers false, because none of them +/// is evidence the emitted program type-checks. +#[cfg(test)] +pub(crate) fn fixture_closure_compiled(outcome: &FixtureClosureOutcome) -> bool { + match outcome { + FixtureClosureOutcome::Measured { cargo, .. } => cargo_verdict_compiled(cargo), + _ => false, + } +} + +/// DID THIS ARM REACH A RUSTC VERDICT AT ALL -- which is a different question from whether it +/// compiled, and the one a caller must ask before reading a red as evidence. +/// +/// TRUE only for a cargo run that COMPLETED and reported its own exit status. A red (`status` +/// non-zero) is reached: rustc ran and refused, which is half of what a discriminator is for. +/// Everything else answered nothing about the emitted bytes and must not be counted as if it had: +/// +/// - `SourceRefused` -- gunbc refused the fixture, so nothing was emitted and rustc had no subject; +/// - `CrateNotWritten` -- the closure never reached disk; +/// - `NotAttempted` -- the toolchain was never invoked; +/// - `DidNotComplete` -- cargo was killed or failed to spawn, reporting no status of its own. +/// +/// THIS EXISTS BECAUSE A CALL SITE GOT IT WRONG (review 58120). A caller counted only +/// `CrateNotWritten` as unanswered and therefore treated a fixture gunbc REJECTED, and a cargo run +/// that never FINISHED, as successfully measured -- reporting success without reaching rustc, +/// which is the fail-open §5 forbids and which contradicted this module's own outcome separation. +/// That caller was a CLI mode since withdrawn, so the specimen no longer exists in the tree; the +/// predicate stays because the defect was a caller enumerating a SUBSET of the arms that cannot +/// answer, and the next caller would enumerate a different subset. A predicate beside the carrier +/// is what makes that unavailable, rather than a `matches!` repeated at each call site. +#[cfg(test)] +pub(crate) fn fixture_closure_reached_rustc(outcome: &FixtureClosureOutcome) -> bool { + match outcome { + FixtureClosureOutcome::Measured { cargo, .. } => { + matches!(cargo, CargoVerdict::Completed { .. }) + } + FixtureClosureOutcome::SourceRefused { .. } + | FixtureClosureOutcome::CrateNotWritten { .. } => false, + } +} + +/// The diagnostic line attributing a red to the fixture's OWN emitted module, if there is one. +/// +/// A CALLER MUST BE ABLE TO SEPARATE "RUSTC REFUSED THIS FIXTURE" FROM "THE CLOSURE WAS ALREADY +/// RED". The subject is one module inside a closure of hundreds; a bare non-zero status says the +/// crate did not build and says nothing about which member broke. So the red direction is only +/// claimable when a diagnostic line names the fixture's emitted file, which is what this returns. +#[cfg(test)] +pub(crate) fn fixture_closure_attributed_line(outcome: &FixtureClosureOutcome) -> Option<&str> { + match outcome { + FixtureClosureOutcome::Measured { cargo, .. } => cargo_verdict_probe_line(cargo), + _ => None, + } +} + +#[cfg(test)] +pub(crate) fn fixture_closure_summary(outcome: &FixtureClosureOutcome) -> String { + match outcome { + FixtureClosureOutcome::SourceRefused { + hard_diagnostics, + first, + } => format!("SourceRefused hard={hard_diagnostics} first={first}"), + FixtureClosureOutcome::CrateNotWritten { cause } => { + format!("CrateNotWritten cause={cause}") + } + FixtureClosureOutcome::Measured { + crate_dir, + emitted_files, + cargo, + } => format!( + "Measured files={emitted_files} dir={crate_dir} cargo={}", + cargo_verdict_summary(cargo) + ), + } +} + +/// The emitted Rust module basename for a `.dag` module path — `a.b.c` emits `src/a_b_c.rs`. +/// +/// It is the ATTRIBUTION SYMBOL for the fixture route, and it is derived from the fixture's own +/// module line rather than passed in beside it: a caller-supplied symbol could name a module the +/// fixture does not declare, and every red would then be reported as unattributed while the +/// fixture's real diagnostics sat in the same stderr. +#[cfg(test)] +fn fixture_rust_module(source: &str) -> Result { + source + .lines() + .find(|line| line.starts_with("module ")) + .map(|line| line.trim_start_matches("module ").trim().replace('.', "_")) + .ok_or_else(|| "the fixture source declares no module line".to_string()) +} + +/// Emit one in-memory `.dag` fixture's closure and hand it to rustc. +/// +/// The fixture's imports are resolved transitively against the live tree by +/// `resolve_virtual_source_with_imports` — the same resolver `compile_dag_rust_emit_check` uses, +/// so a fixture that compiles under one reaches the same closure under the other. +#[cfg(test)] +pub(crate) fn fixture_closure_rustc_verdict( + source: &str, + probe_root: &Path, +) -> FixtureClosureOutcome { + let rust_module = match fixture_rust_module(source) { + Ok(module) => module, + Err(cause) => return FixtureClosureOutcome::CrateNotWritten { cause }, + }; + eprintln!("fixture-closure: {rust_module} emitting"); + let module_index = crate::cli_run::build_module_path_index_from_witness_roots(); + let sources = + crate::cli_run::resolve_virtual_source_with_imports("fixture.dag", source, &module_index); + let result = crate::v1_compiler_compile::compile_sources( + std::rc::Rc::new(sources.into()), + crate::v1_compiler_artifact::RenderTarget::Rust, + ); + let hard: Vec = result + .diagnostics + .iter() + .filter(|d| crate::cli_run::compile_clean_diagnostic_is_hard(d)) + .map(|d| format!("{d:?}")) + .collect(); + if !hard.is_empty() { + return FixtureClosureOutcome::SourceRefused { + hard_diagnostics: hard.len(), + first: hard[0].chars().take(400).collect(), + }; + } + // The crate slug is the fixture's own emitted module, so two fixtures never share a package + // name in the shared target directory — the fingerprint-aliasing fail-open `probe_package_name` + // records for the entry route applies identically here. + let (crate_dir, emitted_files) = + match write_probe_crate_files(&result.files, probe_root, &rust_module) { + Ok(pair) => pair, + Err(cause) => return FixtureClosureOutcome::CrateNotWritten { cause }, + }; + eprintln!( + "fixture-closure: {rust_module} emitted {emitted_files} file(s) into {} — cargo", + crate_dir.display() + ); + let cargo = run_cargo( + &crate_dir, + &process_workspace_root(), + &format!("{rust_module}.rs"), + ); + eprintln!( + "fixture-closure: {rust_module} {}", + cargo_verdict_summary(&cargo) + ); + FixtureClosureOutcome::Measured { + crate_dir: crate_dir.display().to_string(), + emitted_files, + cargo, + } +} + +/// THE FIXTURE ROUTE'S OWN DISCRIMINATING PAIR, EXECUTED RATHER THAN DESCRIBED. +/// +/// A route that reaches rustc proves nothing on its own: a green says only that SOME crate +/// compiled, and DESIGN §4b calls a check that cannot go red worse than absent because it is +/// cited as coverage. So the route ships with the two arms that make its answer information — +/// run together, in one report, because either alone is satisfiable by an instrument that ignores +/// its input. +/// +/// THE POSITIVE CONTROL is a self-contained fixture whose emitted closure must compile. It fails +/// if the crate writer, the manifest or the emitter regresses, and it is what stops the red arm +/// below from passing for an unrelated reason. +/// +/// THE RED IS MEANING-LEVEL, WHICH IS THE WHOLE POINT OF REACHING RUSTC. Its fixture is the one +/// `gunbc.rung_drop` `text_boundary_identity_wall` names: a NON-LITERAL kernel-String value at an +/// exact `v2.std.text.String` boundary, which the corpus-wide compatibility relation admits by +/// leaf-name spelling and which therefore emits a host representation where the structural +/// carrier was declared. A substring oracle over the emitted text cannot see it — the emitted +/// bytes contain every substring a spelling assertion would look for. rustc's type checker does, +/// and that row's temporary rung ("on the emitted-Rust self-host path the class is mechanically +/// preventable — ... renders as a Rust type mismatch and the mandatory emitted-crate cargo check +/// refuses it") is a claim about exactly this, made about a fixture the required phase's entry +/// roster cannot reach. This arm is where that claim executes. +/// +/// ATTRIBUTION IS PART OF THE RED AND NOT A NICETY. The red fixture's closure is hundreds of +/// modules; a bare non-zero status could come from any of them. So the arm passes only when a +/// diagnostic line NAMES the fixture's own emitted module — otherwise the report says +/// `unattributed` and the pair fails, rather than crediting a red the fixture did not cause. +#[derive(Debug, Clone)] +#[cfg(test)] +pub(crate) struct FixtureDiscrimination { + pub(crate) green: FixtureClosureOutcome, + pub(crate) red: FixtureClosureOutcome, +} + +/// THE TWO ARMS ARE FIXTURE FILES, NOT STRING CONSTANTS IN THIS FILE, and the difference is +/// §3 rather than taste. The same arms are files any caller can hand this route by path; carrying +/// a second copy of their source here would be one fixture with two authorities +/// that drift the first time either is edited, and the arm this pair adjudicates would stop being +/// the arm anyone else runs. +/// +/// The positive control has no imports, so its closure is its own emitted module plus the runtime. +#[cfg(test)] +const FIXTURE_GREEN_PATH: &str = "fixtures/fixture_closure_rustc/green_probe.dag"; + +/// The meaning-level red: `concat` answers the kernel string, the boundary declares the +/// structural `v2.std.text.String`, and no homomorphism row stands between them. +#[cfg(test)] +const FIXTURE_RED_PATH: &str = "fixtures/fixture_closure_rustc/text_nonliteral_probe.dag"; + +/// AN UNREADABLE ARM IS `CrateNotWritten`, NAMING THE PATH -- never an empty source quietly +/// compiled. An empty `.dag` text emits a crate that builds, so substituting one would turn a +/// missing fixture into a GREEN control and a red arm that stopped discriminating. +#[cfg(test)] +fn fixture_arm_verdict(rel_path: &str, probe_root: &Path) -> FixtureClosureOutcome { + let path = process_workspace_root().join(rel_path); + match std::fs::read_to_string(&path) { + Ok(source) => fixture_closure_rustc_verdict(&source, probe_root), + Err(e) => FixtureClosureOutcome::CrateNotWritten { + cause: format!("reading the fixture arm {}: {e}", path.display()), + }, + } +} + +#[cfg(test)] +pub(crate) fn run_fixture_closure_discrimination(probe_root: &Path) -> FixtureDiscrimination { + FixtureDiscrimination { + green: fixture_arm_verdict(FIXTURE_GREEN_PATH, probe_root), + red: fixture_arm_verdict(FIXTURE_RED_PATH, probe_root), + } +} + +/// The pair passes only when BOTH directions hold: the control compiled, and the meaning-level +/// fixture was refused BY RUSTC with a diagnostic naming its own emitted module. +/// +/// A `SourceRefused` red does NOT pass. gunbc refusing the fixture is a different and better +/// outcome — it means the wall climbed — but it is not this pair's subject, and reporting it as +/// a pass here would let the route's evidence green while nothing reached rustc at all. +#[cfg(test)] +pub(crate) fn fixture_discrimination_passed(pair: &FixtureDiscrimination) -> bool { + fixture_closure_compiled(&pair.green) + && fixture_closure_reached_rustc(&pair.red) + && !fixture_closure_compiled(&pair.red) + && fixture_closure_attributed_line(&pair.red).is_some() +} + +/// EACH ARM REPORTS ITS OWN DIAGNOSTIC, NOT A VERDICT ABOUT ITSELF. +/// +/// A pass/fail per arm is not enough for the thing this route exists to carry. A two-arm emitter +/// discriminator separates its arms BY WHAT RUSTC SAID -- one arm `E0308 expected Quantity found +/// Time`, the other `E0573 expected type found variant` -- and a route reporting only that both +/// arms were red cannot tell those two apart, so it cannot discriminate the emitter behaviours +/// that produced them. So every arm's diagnostic lines are reported, red or green, and the +/// summary sits beside them rather than replacing them. +#[cfg(test)] +pub(crate) fn fixture_discrimination_report(pair: &FixtureDiscrimination) -> Vec { + let mut lines = vec![format!( + "fixture-closure: control {}", + fixture_closure_summary(&pair.green) + )]; + lines.extend( + fixture_arm_diagnostic_lines("control", &pair.green) + .into_iter() + .map(|line| format!("fixture-closure: {line}")), + ); + lines.push(format!( + "fixture-closure: red {}", + fixture_closure_summary(&pair.red) + )); + lines.extend( + fixture_arm_diagnostic_lines("red", &pair.red) + .into_iter() + .map(|line| format!("fixture-closure: {line}")), + ); + lines.push(format!( + "fixture-closure: red attribution {}", + fixture_closure_attributed_line(&pair.red).unwrap_or("unattributed") + )); + lines.push(format!( + "fixture-closure: pair {}", + if fixture_discrimination_passed(pair) { + "PASSED" + } else { + "FAILED" + } + )); + lines +} + +/// One arm's diagnostics, whatever arm the outcome took. +/// +/// A gunbc refusal reports the diagnostic gunbc produced; a cargo run reports what cargo said. +/// Neither is silently rendered as the other, because the reader's next move differs: one names +/// a fixture the compiler rejected, the other names emitted bytes rustc rejected. +#[cfg(test)] +pub(crate) fn fixture_arm_diagnostic_lines( + label: &str, + outcome: &FixtureClosureOutcome, +) -> Vec { + match outcome { + FixtureClosureOutcome::SourceRefused { first, .. } => { + vec![format!("{label}| gunbc {first}")] + } + FixtureClosureOutcome::CrateNotWritten { cause } => { + vec![format!("{label}| not-written {cause}")] + } + FixtureClosureOutcome::Measured { cargo, .. } => cargo_verdict_stderr_tail(cargo) + .lines() + .map(|line| format!("{label}| cargo {line}")) + .collect(), + } +} + /// Every configured entry, each run whatever the previous one did -- the stopped-line audit /// shape: report everything, green nothing. /// diff --git a/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs b/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs index 4fec99157be..ecd61324b23 100644 --- a/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs +++ b/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs @@ -52,6 +52,10 @@ pub fn ct_parse_tests() -> String { v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(" #[test]\n".to_string(), " fn parse_trivial_module() {\n".to_string()), " let tokens = tokenize(\n".to_string()), " \"module test\\ntype Foo { x: Int }\\n\".to_string(),\n".to_string()), " \"test.dag\".to_string(),\n".to_string()), " );\n".to_string()), " let result = crate::v1_compiler_parse::parse(tokens, std::rc::Rc::new(im::HashMap::new()));\n".to_string()), " assert!(\n".to_string()), " result.module.is_some(),\n".to_string()), " \"valid module should parse successfully\"\n".to_string()), " );\n".to_string()), " }\n\n".to_string()), " #[test]\n".to_string()), " fn self_parse_tokenize_dag() {\n".to_string()), " let result = std::thread::Builder::new()\n".to_string()), " .stack_size(16 * 1024 * 1024)\n".to_string()), " .spawn(|| {\n".to_string()), " let source = read_dag(\"src/v1/01_tokenize.dag\");\n".to_string()), " let tokens = tokenize(source, \"src/v1/01_tokenize.dag\".to_string());\n\n".to_string()), " assert!(\n".to_string()), " !tokens.is_empty(),\n".to_string()), " \"tokenizing 01_tokenize.dag should produce tokens\"\n".to_string()), " );\n\n".to_string()), " let last = tokens.last().expect(\"should have tokens\");\n".to_string()), " assert!(\n".to_string()), " matches!(last.shape, crate::v1_std_core::TokenShape::ShEof),\n".to_string()), " \"last token should be Eof, got {:?}\",\n".to_string()), " last.shape\n".to_string()), " );\n\n".to_string()), " let result = crate::v1_compiler_parse::parse(tokens, std::rc::Rc::new(im::HashMap::new()));\n\n".to_string()), " assert!(\n".to_string()), " result.module.is_some(),\n".to_string()), " \"parsing 01_tokenize.dag should produce a module\"\n".to_string()), " );\n\n".to_string()), " let module = result.module.as_ref().unwrap();\n".to_string()), " assert_eq!(\n".to_string()), " module.name, \"v1.compiler.tokenize\",\n".to_string()), " \"module name should be v1.compiler.tokenize, got {}\",\n".to_string()), " module.name\n".to_string()), " );\n".to_string()), " })\n".to_string()), " .expect(\"failed to spawn thread\")\n".to_string()), " .join();\n".to_string()), " result.expect(\"self-parse test panicked\");\n".to_string()), " }\n\n".to_string()) } +pub fn ct_fixture_closure_rustc_discrimination_test() -> String { + v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(" /// THE EMITTED CLOSURE, HANDED TO RUSTC, OVER FIXTURES A TEST CAN AUTHOR.\n".to_string(), " ///\n".to_string()), " /// Every other emitted-bytes assertion in this file is a SPELLING oracle: it reads the\n".to_string()), " /// emitted text and asserts on substrings. A meaning-level emitter defect is invisible to\n".to_string()), " /// that whenever the wrong bytes still contain the right substring -- and it is exactly\n".to_string()), " /// what rustc's type checker refuses. This arm is the one that asks rustc.\n".to_string()), " ///\n".to_string()), " /// THE PAIR IS THE SUBJECT, NOT EITHER ARM. The control must COMPILE, or a red below\n".to_string()), " /// proves only that something in the tree is broken. The red must be refused BY RUSTC and\n".to_string()), " /// ATTRIBUTED to the fixture's own emitted module, or a non-zero status from any of the\n".to_string()), " /// hundreds of modules in its closure would pass for the fixture's own defect.\n".to_string()), " ///\n".to_string()), " /// #[ignore] AND WHY, STATED RATHER THAN LEFT TO BE DISCOVERED: this arm spawns cargo and\n".to_string()), " /// compiles two emitted crates, which is minutes rather than milliseconds, and\n".to_string()), " /// `repo_self_test_command` runs the whole --lib suite on every push and pull request. It\n".to_string()), " /// is therefore ENROLLED AND OPT-IN: `cargo test --release -p v1-compiler --lib\n".to_string()), " /// fixture_closure_rustc_discrimination -- --ignored`. An #[ignore] is a cost decision and\n".to_string()), " /// NOT a rung: nothing here may be cited as coverage that executes on the merge path.\n".to_string()), " #[test]\n".to_string()), " #[ignore]\n".to_string()), " fn fixture_closure_rustc_discrimination() {\n".to_string()), " let probe_root = crate::cli_run::local_emit_compile_probe_root();\n".to_string()), " let pair = crate::cli_run::run_fixture_closure_discrimination(&probe_root);\n".to_string()), " for line in crate::cli_run::fixture_discrimination_report(&pair) {\n".to_string()), " eprintln!(\"{}\", line);\n".to_string()), " }\n".to_string()), " assert!(\n".to_string()), " crate::cli_run::fixture_closure_reached_rustc(&pair.red),\n".to_string()), " \"the red arm never reached a rustc verdict, so nothing about the emitted bytes was measured: {}\",\n".to_string()), " crate::cli_run::fixture_closure_summary(&pair.red)\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " crate::cli_run::fixture_discrimination_passed(&pair),\n".to_string()), " \"the control must compile and the meaning-level red must be refused by rustc and attributed to its own emitted module; control={} red={} attribution={:?}\",\n".to_string()), " crate::cli_run::fixture_closure_summary(&pair.green),\n".to_string()), " crate::cli_run::fixture_closure_summary(&pair.red),\n".to_string()), " crate::cli_run::fixture_closure_attributed_line(&pair.red)\n".to_string()), " );\n".to_string()), " }\n\n".to_string()) +} + pub fn ct_import_lines_follow_resolved_binding_identity_test() -> String { v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(" #[test]\n".to_string(), " fn emit_import_lines_follow_resolved_binding_identity() {\n".to_string()), " let result = std::thread::Builder::new()\n".to_string()), " .stack_size(16 * 1024 * 1024)\n".to_string()), " .spawn(|| {\n".to_string()), " let textlike = std::rc::Rc::new(crate::v1_compiler_compile::SourceFile {\n".to_string()), " path: \"probe_textlike.dag\".to_string(),\n".to_string()), " content: \"module probe.textlike\\ntype String = StringLeaf | StringNode { next: String }\\ntype Carrier = CarrierOne | CarrierTwo\\n\".to_string(),\n".to_string()), " });\n".to_string()), " let logiclike = std::rc::Rc::new(crate::v1_compiler_compile::SourceFile {\n".to_string()), " path: \"probe_logiclike.dag\".to_string(),\n".to_string()), " content: \"module probe.logiclike\\ntype Bool = ProbeTrue | ProbeFalse\\n\".to_string(),\n".to_string()), " });\n".to_string()), " let victim = std::rc::Rc::new(crate::v1_compiler_compile::SourceFile {\n".to_string()), " path: \"probe_victim.dag\".to_string(),\n".to_string()), " content: \"module probe.victim\\nimport probe.textlike { String, Carrier }\\nimport probe.logiclike { Bool }\\ntype Holder { tag: String, load: Carrier, flag: Bool }\\nfn keep(h: Holder) -> String { h.tag }\\n\".to_string(),\n".to_string()), " });\n".to_string()), " let result = crate::v1_compiler_compile::compile_sources(std::rc::Rc::new(im::vector![textlike, logiclike, victim]), crate::v1_compiler_artifact::RenderTarget::Rust);\n".to_string()), " let victim_out = result.files.iter().find(|f| f.path.contains(\"probe_victim\")).expect(\"victim module must emit\");\n".to_string()), " assert!(\n".to_string()), " !victim_out.content.lines().any(|l| l.contains(\"pub use\") && l.contains(\"String\")),\n".to_string()), " \"a name the resolver answers with the structureless kernel scalar must not emit a structural use-line shadow (the 139-row std::string::String vs Rc> family); emitted:\\n{}\",\n".to_string()), " victim_out.content\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " victim_out.content.lines().any(|l| l.contains(\"pub use\") && l.contains(\"Carrier\")),\n".to_string()), " \"a non-kernel imported type must keep its use-line -- positive control that the drop is keyed on the resolved kernel identity, not on the import list; emitted:\\n{}\",\n".to_string()), " victim_out.content\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " victim_out.content.lines().any(|l| l.contains(\"pub use\") && l.contains(\"Bool\")),\n".to_string()), " \"a kernel-SPELLED name whose kernel binding is structural (Bool = True | False) must keep its use-line -- the control that stops a blanket kernel-name drop; emitted:\\n{}\",\n".to_string()), " victim_out.content\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " victim_out.content.contains(\"pub tag:\") && victim_out.content.contains(\"pub load:\"),\n".to_string()), " \"the probe fields must still emit at all; emitted:\\n{}\",\n".to_string()), " victim_out.content\n".to_string()), " );\n".to_string()), " })\n".to_string()), " .expect(\"failed to spawn thread\")\n".to_string()), " .join();\n".to_string()), " result.expect(\"emit_import_lines_follow_resolved_binding_identity panicked\");\n".to_string()), " }\n\n".to_string()) } @@ -381,5 +385,5 @@ pub fn ct_shell_service_output_projection_known_hole_probe_test() -> String { } pub fn compiler_tests_source() -> String { - v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(ct_module_header(), ct_workspace_helpers()), ct_file_discovery_helpers()), ct_source_builders()), ct_tokenizer_tests()), ct_parse_tests()), ct_pipeline_test()), ct_import_lines_follow_resolved_binding_identity_test()), ct_unlisted_import_use_witness_test()), ct_call_shape_wall_witness_test()), ct_call_deficit_red_witness_test()), ct_call_shape_duplicate_wall_witness_test()), ct_function_value_named_application_controls_witness_test()), ct_function_value_field_method_known_hole_probe_test()), ct_shell_service_output_projection_known_hole_probe_test()), ct_method_existence_wall_witness_test()), ct_declared_type_conformance_witness_test()), ct_sole_constructor_test()), ct_constructor_call_admission_test()), ct_constructor_call_admission_same_module_sibling_test()), ct_constructor_call_admission_function_value_test()), ct_constructor_call_admission_zero_arity_test()), ct_constructor_call_admission_shadowing_test()), ct_constructor_call_admission_qualified_caller_test()), ct_sole_constructor_fieldless_witness_test()), ct_contracts_sidecar_witness_test()), ct_self_parse_all_test()), ct_self_resolve_test()), ct_self_compile_test()), ct_self_compile_cargo_check_test()), ct_type_size_test()), ct_coercion_tests()), ct_profile_helpers()), ct_profile_self_compile_test()), ct_profile_full_pipeline_test()), ct_profile_reconcile_test()), ct_module_footer()) + v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(ct_module_header(), ct_workspace_helpers()), ct_file_discovery_helpers()), ct_source_builders()), ct_tokenizer_tests()), ct_parse_tests()), ct_pipeline_test()), ct_import_lines_follow_resolved_binding_identity_test()), ct_fixture_closure_rustc_discrimination_test()), ct_unlisted_import_use_witness_test()), ct_call_shape_wall_witness_test()), ct_call_deficit_red_witness_test()), ct_call_shape_duplicate_wall_witness_test()), ct_function_value_named_application_controls_witness_test()), ct_function_value_field_method_known_hole_probe_test()), ct_shell_service_output_projection_known_hole_probe_test()), ct_method_existence_wall_witness_test()), ct_declared_type_conformance_witness_test()), ct_sole_constructor_test()), ct_constructor_call_admission_test()), ct_constructor_call_admission_same_module_sibling_test()), ct_constructor_call_admission_function_value_test()), ct_constructor_call_admission_zero_arity_test()), ct_constructor_call_admission_shadowing_test()), ct_constructor_call_admission_qualified_caller_test()), ct_sole_constructor_fieldless_witness_test()), ct_contracts_sidecar_witness_test()), ct_self_parse_all_test()), ct_self_resolve_test()), ct_self_compile_test()), ct_self_compile_cargo_check_test()), ct_type_size_test()), ct_coercion_tests()), ct_profile_helpers()), ct_profile_self_compile_test()), ct_profile_full_pipeline_test()), ct_profile_reconcile_test()), ct_module_footer()) }