From 886f9988f2ef658f5178014409e0c149196898be Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 2 Sep 2026 08:15:41 +0000 Subject: [PATCH] #9886's second symptom: shell_service_unmodeled_output_key_refuses asserted the pre-wall shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part 1 of the main-is-red item. #9886 produced ONE root cause with TWO symptom classes, and #10017 closed the first. This closes the second. No emitter changes. WHAT WAS RED. `cargo test --release -p v1-compiler --lib` on main: 642 passed, 2 failed. Both failures are in the GENERATED src/v1/stage0/src/compiler_tests.rs. render_rust_applied_type_routes_qualified_base_through_leaf_name -- ALREADY FIXED by #10017, verified here rather than assumed. #9886's stale mirror deleted two lines from the TEST BODY, `env_value.unit_variant_index_observed = true` and the same on populated_env_value. Counting that string across the three refs gives fb481ae0faf=2, 4059156e491=0, 52aac48b67b=2. Without the observed flag the env carries no unit-variant evidence, so render_rust_applied_type CORRECTLY refused with a located compile_error! instead of emitting i64. The emitter was right and the regenerated test was wrong. shell_service_unmodeled_output_key_refuses -- what this commit fixes. It is a test #9886 ADDED, and #10017's mirror repair never touched it, so it is a separate defect rather than a second face of the stale mirror. WHY IT WAS UNSATISFIABLE THE DAY IT LANDED. It did `.find(|f| f.path == "src/probe.rs").expect("service module must emit src/probe.rs")` and then looked for the refusal text INSIDE that file. No such file exists, by construction: v1.compiler.compile emit_artifact returns `EmitResult { files: [], diagnostics: unmodeled_transports }` when a transport diagnostic fires -- the empty file list is PAIRED with a blocking diagnostic, not standing alone. ยง5 was already satisfied; the compile refuses, typed (TransportEmissionNotModeled) and located (span: ch.span, at the FIELD, with the individual key in missing_realization_fact). #9886 wrote the wall and the test in one PR, and the test asks for the shape the wall replaced. THE SHAPE IT ASKED FOR IS A FILED DEFECT CLASS, twice over, which is why the fix is not to make the emitter emit the file. - gunbc.recurring_failure_mode `accepted_source_emits_uncompilable_target`: "the .dag graph is the authority and Rust is one realization, so 'rustc catches it' is exactly the outsourcing this project exists to end." - 05_emit.dag's own annotation records that this exact shape was TRIED and filed as `refusal_deferred_to_emitted_runtime`: "7 files emitted, 0 diagnostics beside a panic!(): the line did not stop, the compile reported success, and the refusal was deferred to a runtime nobody reads until production." WHAT THE TEST NOW ASSERTS -- through the wall, not about the bytes: 1. an error diagnostic of variant TransportEmissionNotModeled exists 2. its rendered message names `not_a_channel` AND `has no modeled channel` 3. no `src/probe.rs` among r.files -- the line STOPPED rather than reported and continued 4. no emitted file anywhere contains `stdout.clone()` -- #9886's own fall-through assertion, widened from one file to all of them Its positive control is the sibling shell_service_output_projection_binds_each_ declared_channel: same fixture with every key modeled, zero diagnostics, src/probe.rs emitted. So "no file" here is the refusal firing and not an emitter that never emits for services. RED CONTROLS, EXECUTED, in an isolated detached worktree so nothing here was edited mid-run. Two mutations of the seed, each rebuilt and run: - delete the wall's diagnostic (unmodeled_shell_transport_diagnostics returns []): conjunct 1 goes RED, "must refuse with TransportEmissionNotModeled. Got: []". - remove only the line-stop in emit_artifact, then neutralise conjuncts 1 and 2 so the mutant is judged by the file conjunct alone: conjunct 3 goes RED with ["Cargo.toml", "src/lib.rs", "src/main.rs", "src/probe.rs", "src/v1_rt.rs", "src/dry_run.rs", "src/emitted_population.rs"] -- seven files emitted, which is the ledger's recorded pre-wall shape reproduced by execution. So neither the typed-refusal half nor the line-stop half is a passenger. AUTHORITY ONLY. The edit is src/v1/compiler_tests_rust.dag; the two .rs files are its regeneration and were installed from the candidate tree, never hand-edited. Regenerated across BOTH generations, since compiler_tests.rs is rendered from the running seed's baked string: regen -> install v1_compiler_compiler_tests_rust.rs -> REBUILD claim_executor (grep of the new binary confirms it bakes the new string) -> regen -> install compiler_tests.rs -> regen, which reports `first_generation_equal=true planned=150 executed=150 adjudicated=150`. The rebuild is the load-bearing step: a regen that greened against the file it just wrote would prove nothing. Neither 05_emit_rust.dag nor 04_resolve.dag is touched, so the two lanes working in those files are unaffected. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VSP89XiSm2YMnUvSwSR1ct --- src/v1/compiler_tests_rust.dag | 68 +++++++++++++++---- src/v1/stage0/src/compiler_tests.rs | 52 ++++++++++---- .../src/v1_compiler_compiler_tests_rust.rs | 2 +- 3 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/v1/compiler_tests_rust.dag b/src/v1/compiler_tests_rust.dag index a563a76a381..939a39d59b6 100644 --- a/src/v1/compiler_tests_rust.dag +++ b/src/v1/compiler_tests_rust.dag @@ -3143,6 +3143,22 @@ fn ct_shell_service_output_projection_test() -> String { // no modeled channel must REFUSE, typed and located, rather than being answered // with stdout. The refusal is minted at the BINDING, before a target is chosen, // so there is no renderer default arm left for it to fall through. +// +// ASSERT THROUGH THE WALL, NOT ABOUT THE BYTES. This test first asked for the +// refusal TEXT inside an emitted src/probe.rs, and it was RED on main for exactly +// as long as that shape stood, because emit_artifact in v1.compiler.compile pairs +// the refusal EXPRESSION with a BLOCKING diagnostic and returns `files: []` -- +// the compile itself stops, so there is no src/probe.rs to read. The weaker claim +// was not merely unsatisfiable here; it was satisfiable by precisely the class the +// wall exists to close, refusal_deferred_to_emitted_runtime: a compile that reports +// success while shipping bytes that carry the refusal to a runtime nobody reads. +// So the assertion is now the compile's own verdict -- a located diagnostic naming +// the key, and the ABSENCE of any emitted body for the refused module. +// +// ITS POSITIVE CONTROL IS THE SIBLING ABOVE. shell_service_output_projection_binds +// _each_declared_channel compiles the same fixture with every key modeled and gets +// src/probe.rs with zero diagnostics, so "no file" here is the refusal firing and +// not an emitter that emits nothing for services. fn ct_shell_service_unmodeled_output_key_refusal_test() -> String { concat( " #[test]\n", @@ -3158,23 +3174,51 @@ fn ct_shell_service_unmodeled_output_key_refusal_test() -> String { " std::rc::Rc::new(im::vector![source]),\n", " crate::v1_compiler_artifact::RenderTarget::Rust,\n", " );\n", - " let emitted = r\n", - " .files\n", + " let errors: Vec<_> = r\n", + " .diagnostics\n", " .iter()\n", - " .find(|f| f.path == \"src/probe.rs\")\n", - " .map(|f| f.content.clone())\n", - " .expect(\"service module must emit src/probe.rs\");\n", + " .filter(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone()))\n", + " .collect();\n", + " // The line stops, and it stops with the TYPED cause -- not merely\n", + " // with some error that happens to be present.\n", " assert!(\n", - " emitted.contains(\"not_a_channel\")\n", - " && emitted.contains(\"has no modeled channel\"),\n", - " \"an unmodeled output key must refuse and name the key. Got:\\n{}\",\n", - " emitted\n", + " errors.iter().any(|d| matches!(\n", + " *d.diagnostic,\n", + " crate::v1_std_core::CompilerDiagnostic::TransportEmissionNotModeled { .. }\n", + " )),\n", + " \"an unmodeled output key must refuse with TransportEmissionNotModeled. \\\n", + " Got: {:?}\",\n", + " r.diagnostics\n", + " );\n", + " // ...and it is LOCATED at the field: the message names the key the\n", + " // author wrote, so an operation with one bad key among several says\n", + " // which one.\n", + " let messages: Vec = errors\n", + " .iter()\n", + " .map(|d| crate::v1_std_core::diagnostic_to_message(d.diagnostic.clone()))\n", + " .collect();\n", + " assert!(\n", + " messages.iter().any(|m| m.contains(\"not_a_channel\")\n", + " && m.contains(\"has no modeled channel\")),\n", + " \"the refusal must name the unmodeled key. Got: {:?}\",\n", + " messages\n", + " );\n", + " // THE DISCRIMINATING HALF. A refusal deferred into emitted bytes\n", + " // would still satisfy both assertions above while leaving a\n", + " // src/probe.rs behind; the wall is that emit returns no files at all.\n", + " let paths: Vec = r.files.iter().map(|f| f.path.clone()).collect();\n", + " assert!(\n", + " !paths.iter().any(|p| p == \"src/probe.rs\"),\n", + " \"a refused operation must not be emitted at all -- the refusal may \\\n", + " not be deferred into a body. Got files: {:?}\",\n", + " paths\n", " );\n", + " // And nothing anywhere fell through to the stdout channel.\n", " assert!(\n", - " !emitted.contains(\"stdout.clone()\"),\n", + " !r.files.iter().any(|f| f.content.contains(\"stdout.clone()\")),\n", " \"a refused operation must not fall through to the stdout channel. \\\n", - " Got:\\n{}\",\n", - " emitted\n", + " Got files: {:?}\",\n", + " paths\n", " );\n", " })\n", " .expect(\"failed to spawn thread\")\n", diff --git a/src/v1/stage0/src/compiler_tests.rs b/src/v1/stage0/src/compiler_tests.rs index 3a56a28bb83..4e4a54e3833 100644 --- a/src/v1/stage0/src/compiler_tests.rs +++ b/src/v1/stage0/src/compiler_tests.rs @@ -1037,23 +1037,51 @@ mod compiler_tests { std::rc::Rc::new(im::vector![source]), crate::v1_compiler_artifact::RenderTarget::Rust, ); - let emitted = r - .files + let errors: Vec<_> = r + .diagnostics .iter() - .find(|f| f.path == "src/probe.rs") - .map(|f| f.content.clone()) - .expect("service module must emit src/probe.rs"); + .filter(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone())) + .collect(); + // The line stops, and it stops with the TYPED cause -- not merely + // with some error that happens to be present. assert!( - emitted.contains("not_a_channel") - && emitted.contains("has no modeled channel"), - "an unmodeled output key must refuse and name the key. Got:\n{}", - emitted + errors.iter().any(|d| matches!( + *d.diagnostic, + crate::v1_std_core::CompilerDiagnostic::TransportEmissionNotModeled { .. } + )), + "an unmodeled output key must refuse with TransportEmissionNotModeled. \ + Got: {:?}", + r.diagnostics + ); + // ...and it is LOCATED at the field: the message names the key the + // author wrote, so an operation with one bad key among several says + // which one. + let messages: Vec = errors + .iter() + .map(|d| crate::v1_std_core::diagnostic_to_message(d.diagnostic.clone())) + .collect(); + assert!( + messages.iter().any(|m| m.contains("not_a_channel") + && m.contains("has no modeled channel")), + "the refusal must name the unmodeled key. Got: {:?}", + messages ); + // THE DISCRIMINATING HALF. A refusal deferred into emitted bytes + // would still satisfy both assertions above while leaving a + // src/probe.rs behind; the wall is that emit returns no files at all. + let paths: Vec = r.files.iter().map(|f| f.path.clone()).collect(); assert!( - !emitted.contains("stdout.clone()"), + !paths.iter().any(|p| p == "src/probe.rs"), + "a refused operation must not be emitted at all -- the refusal may \ + not be deferred into a body. Got files: {:?}", + paths + ); + // And nothing anywhere fell through to the stdout channel. + assert!( + !r.files.iter().any(|f| f.content.contains("stdout.clone()")), "a refused operation must not fall through to the stdout channel. \ - Got:\n{}", - emitted + Got files: {:?}", + paths ); }) .expect("failed to spawn thread") 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 44c37931bd0..37dd22fad44 100644 --- a/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs +++ b/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs @@ -385,7 +385,7 @@ pub fn ct_shell_service_output_projection_test() -> String { } pub fn ct_shell_service_unmodeled_output_key_refusal_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(" #[test]\n".to_string(), " fn shell_service_unmodeled_output_key_refuses() {\n".to_string()), " let result = std::thread::Builder::new()\n".to_string()), " .stack_size(32 * 1024 * 1024)\n".to_string()), " .spawn(|| {\n".to_string()), " let source = std::rc::Rc::new(crate::v1_compiler_compile::SourceFile {\n".to_string()), " path: \"probe.dag\".to_string(),\n".to_string()), " content: \"module probe\\nservice Probe {\\n operation Version {\\n input {}\\n output {\\n first: String from \\\"not_a_channel\\\"\\n out: String from \\\"stdout\\\"\\n }\\n transport shell { argv: [\\\"git\\\", \\\"--version\\\"] }\\n }\\n}\\n\".to_string(),\n".to_string()), " });\n".to_string()), " let r = crate::v1_compiler_compile::compile_sources(\n".to_string()), " std::rc::Rc::new(im::vector![source]),\n".to_string()), " crate::v1_compiler_artifact::RenderTarget::Rust,\n".to_string()), " );\n".to_string()), " let emitted = r\n".to_string()), " .files\n".to_string()), " .iter()\n".to_string()), " .find(|f| f.path == \"src/probe.rs\")\n".to_string()), " .map(|f| f.content.clone())\n".to_string()), " .expect(\"service module must emit src/probe.rs\");\n".to_string()), " assert!(\n".to_string()), " emitted.contains(\"not_a_channel\")\n".to_string()), " && emitted.contains(\"has no modeled channel\"),\n".to_string()), " \"an unmodeled output key must refuse and name the key. Got:\\n{}\",\n".to_string()), " emitted\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " !emitted.contains(\"stdout.clone()\"),\n".to_string()), " \"a refused operation must not fall through to the stdout channel. \\\n".to_string()), " Got:\\n{}\",\n".to_string()), " emitted\n".to_string()), " );\n".to_string()), " })\n".to_string()), " .expect(\"failed to spawn thread\")\n".to_string()), " .join();\n".to_string()), " result.expect(\"shell_service_unmodeled_output_key_refuses panicked\");\n".to_string()), " }\n".to_string()), "\n".to_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(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 shell_service_unmodeled_output_key_refuses() {\n".to_string()), " let result = std::thread::Builder::new()\n".to_string()), " .stack_size(32 * 1024 * 1024)\n".to_string()), " .spawn(|| {\n".to_string()), " let source = std::rc::Rc::new(crate::v1_compiler_compile::SourceFile {\n".to_string()), " path: \"probe.dag\".to_string(),\n".to_string()), " content: \"module probe\\nservice Probe {\\n operation Version {\\n input {}\\n output {\\n first: String from \\\"not_a_channel\\\"\\n out: String from \\\"stdout\\\"\\n }\\n transport shell { argv: [\\\"git\\\", \\\"--version\\\"] }\\n }\\n}\\n\".to_string(),\n".to_string()), " });\n".to_string()), " let r = crate::v1_compiler_compile::compile_sources(\n".to_string()), " std::rc::Rc::new(im::vector![source]),\n".to_string()), " crate::v1_compiler_artifact::RenderTarget::Rust,\n".to_string()), " );\n".to_string()), " let errors: Vec<_> = r\n".to_string()), " .diagnostics\n".to_string()), " .iter()\n".to_string()), " .filter(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone()))\n".to_string()), " .collect();\n".to_string()), " // The line stops, and it stops with the TYPED cause -- not merely\n".to_string()), " // with some error that happens to be present.\n".to_string()), " assert!(\n".to_string()), " errors.iter().any(|d| matches!(\n".to_string()), " *d.diagnostic,\n".to_string()), " crate::v1_std_core::CompilerDiagnostic::TransportEmissionNotModeled { .. }\n".to_string()), " )),\n".to_string()), " \"an unmodeled output key must refuse with TransportEmissionNotModeled. \\\n".to_string()), " Got: {:?}\",\n".to_string()), " r.diagnostics\n".to_string()), " );\n".to_string()), " // ...and it is LOCATED at the field: the message names the key the\n".to_string()), " // author wrote, so an operation with one bad key among several says\n".to_string()), " // which one.\n".to_string()), " let messages: Vec = errors\n".to_string()), " .iter()\n".to_string()), " .map(|d| crate::v1_std_core::diagnostic_to_message(d.diagnostic.clone()))\n".to_string()), " .collect();\n".to_string()), " assert!(\n".to_string()), " messages.iter().any(|m| m.contains(\"not_a_channel\")\n".to_string()), " && m.contains(\"has no modeled channel\")),\n".to_string()), " \"the refusal must name the unmodeled key. Got: {:?}\",\n".to_string()), " messages\n".to_string()), " );\n".to_string()), " // THE DISCRIMINATING HALF. A refusal deferred into emitted bytes\n".to_string()), " // would still satisfy both assertions above while leaving a\n".to_string()), " // src/probe.rs behind; the wall is that emit returns no files at all.\n".to_string()), " let paths: Vec = r.files.iter().map(|f| f.path.clone()).collect();\n".to_string()), " assert!(\n".to_string()), " !paths.iter().any(|p| p == \"src/probe.rs\"),\n".to_string()), " \"a refused operation must not be emitted at all -- the refusal may \\\n".to_string()), " not be deferred into a body. Got files: {:?}\",\n".to_string()), " paths\n".to_string()), " );\n".to_string()), " // And nothing anywhere fell through to the stdout channel.\n".to_string()), " assert!(\n".to_string()), " !r.files.iter().any(|f| f.content.contains(\"stdout.clone()\")),\n".to_string()), " \"a refused operation must not fall through to the stdout channel. \\\n".to_string()), " Got files: {:?}\",\n".to_string()), " paths\n".to_string()), " );\n".to_string()), " })\n".to_string()), " .expect(\"failed to spawn thread\")\n".to_string()), " .join();\n".to_string()), " result.expect(\"shell_service_unmodeled_output_key_refuses panicked\");\n".to_string()), " }\n".to_string()), "\n".to_string()) } pub fn ct_shell_service_exit_error_arm_boxed_test() -> String {