diff --git a/docs/active-work.md b/docs/active-work.md index f057c4f..56faf09 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -279,7 +279,7 @@ from #171 and #215. **`72da24a`**, worktree `/home/jeans/Repos/personal/pmacs-probe-sigint`. Recover with `git fetch githubsucks && git checkout gpu-probe-sigint-teardown`. -- **REVISION 13 IMPLEMENTED (this commit).** Helper emits the token on +- **REVISION 13 IMPLEMENTED at `bc7d776`.** Helper emits the token on stdout; both consumers validate the `(status, token)` pair. The gate owns a guard-local capture dir, keeps `|| status=$?` under `set -eu`, selects `expected_token` before any `set -u`-sensitive use, compares @@ -313,13 +313,29 @@ from #171 and #215. - **The earlier `…-2647615` run is NOT head-exact evidence**: it finished about 30 seconds before `bc7d776` was committed, so it describes the implementation tree rather than a committed head. -- **A4 mutations, each biting:** accepting any status 2 regardless of - token → `gate_refuses_on_helper_error_…`; surfacing child stderr on a - boundary failure → the conformance row **and** that row; token to - stderr → `sigint_helper_reports_safe_…`. Noted for the record: the - first mutation is caught by the dedicated row rather than by the - conformance set, because the set's boundary cases mostly have empty - stderr and so cannot see which branch produced the exit 2. +- **A4 mutations, each biting the MATRIX now**, not a dedicated row: + accepting any status 2 regardless of token, and surfacing child + stderr on a boundary failure, both fail + `gate_validates_the_whole_shared_conformance_set`; token-to-stderr + fails `sigint_helper_reports_safe_…`. An earlier entry said the + status-2 mutation was caught only by the dedicated row — that was + true of the pre-sentinel matrix and is **superseded**: every stub now + emits a sentinel on stderr, so the matrix can see which branch + produced the exit 2. +- **Two PRE-EXISTING crdt-only failures found while gating properly:** + `m4_24_bare_string_glob_stays_relative` and + `m4_24_d3_fallback_base_is_the_smallest_attachment_dir`. They + reproduce in isolation (so not load) and **fail identically at + `72da24a`**, so they are not this lane's. They are crdt-only — the + plain gate's `05-m4` stage runs without `crdt` and passes. Recorded, + not attributed; whether they are environment-specific is for CI to + say. +- **My local gate did NOT cover what CI covers, and CI caught it.** + Plain `./scripts/gate` omits `sweep-crdt`, which is the only stage + that compiles the nested `gpu_initial_target_acceptance` under + `crdt`; `04-lib-crdt` builds the lib alone. A `crate::common` path + that cannot resolve when nested, and a clippy lint, both shipped + green locally. **This lane gates with `--protocol`.** - **CI ON `916007b`: 12 GREEN, 2 RED — both macOS `Test` jobs**, and it is the **pre-declared A7 portability finding**, not an environment excuse. Exactly one row: diff --git a/tests/common/sigint_conformance.rs b/tests/common/sigint_conformance.rs index 59eaeb6..04644b5 100644 --- a/tests/common/sigint_conformance.rs +++ b/tests/common/sigint_conformance.rs @@ -33,10 +33,20 @@ pub const TOKEN_ERROR: &[u8] = b"pmacs-sigint-v1:error"; /// searched for it: present ⇒ the child's stderr was surfaced. pub const SENTINEL: &str = "PMACS-CONFORMANCE-SENTINEL"; +/// The canonical wording the helper uses for `ignored`. X3 emits it +/// **without** a valid token, so a consumer that surfaced untrusted +/// stderr would repeat it — the defect A6b forbids. +pub const CANONICAL_IGNORED: &str = + "pmacs: SIGINT is ignored; run this command with SIGINT deliverable"; + pub struct Case { pub name: String, pub status: i32, pub stdout: Vec, + /// Exact stderr this stub emits. Most cases use [`SENTINEL`]; X3 + /// and X4 carry their own payloads, which is what makes them + /// distinct inputs rather than duplicates of other rows. + pub stderr: String, pub expect: Outcome, } @@ -49,8 +59,8 @@ pub fn stub_script(case: &Case) -> String { acc }); format!( - "#!/bin/sh\nprintf '{octal}'\necho '{SENTINEL}' >&2\nexit {}\n", - case.status + "#!/bin/sh\nprintf '{octal}'\necho '{}' >&2\nexit {}\n", + case.stderr, case.status ) } @@ -61,6 +71,12 @@ pub fn stub_script(case: &Case) -> String { /// shell boundary cannot represent it, because an `exec` failure there /// becomes a status. Rust exercises it separately. #[must_use] +#[allow( + clippy::too_many_lines, + reason = "the bulk is the generated vector list; splitting it would \ + separate a case from the outcome it encodes, which is the \ + one thing this file exists to keep together" +)] pub fn shared_cases() -> Vec { let toks: [(&str, &[u8]); 3] = [ ("safe", TOKEN_SAFE), @@ -81,12 +97,14 @@ pub fn shared_cases() -> Vec { name: format!("{status}/V/{name}/bare"), status, stdout: correct.to_vec(), + stderr: SENTINEL.to_owned(), expect: diagonal, }); out.push(Case { name: format!("{status}/V/{name}/lf"), status, stdout: lf, + stderr: SENTINEL.to_owned(), expect: diagonal, }); for (other, bytes) in &toks { @@ -99,12 +117,14 @@ pub fn shared_cases() -> Vec { name: format!("{status}/M/{other}/bare"), status, stdout: bytes.to_vec(), + stderr: SENTINEL.to_owned(), expect: Outcome::Boundary, }); out.push(Case { name: format!("{status}/M/{other}/lf"), status, stdout: olf, + stderr: SENTINEL.to_owned(), expect: Outcome::Boundary, }); } @@ -135,6 +155,7 @@ pub fn shared_cases() -> Vec { name: format!("{status}/{cls}"), status, stdout: bytes, + stderr: SENTINEL.to_owned(), expect: Outcome::Boundary, }); } @@ -143,18 +164,25 @@ pub fn shared_cases() -> Vec { name: "X1/status-126".to_owned(), status: 126, stdout: TOKEN_SAFE.to_vec(), + stderr: SENTINEL.to_owned(), expect: Outcome::Boundary, }); out.push(Case { name: "X3/ignored-text-no-token".to_owned(), status: 1, stdout: Vec::new(), + // The canonical ignored wording WITHOUT a token: a consumer + // that surfaced untrusted stderr would repeat it. + stderr: CANONICAL_IGNORED.to_owned(), expect: Outcome::Boundary, }); out.push(Case { name: "X4/stderr-noise".to_owned(), status: 0, stdout: TOKEN_SAFE.to_vec(), + // Noise on stderr must not affect classification --- and this + // payload is what distinguishes X4 from 0/V/safe/bare. + stderr: "unrelated chatter on stderr".to_owned(), expect: Outcome::Safe, }); out diff --git a/tests/gate_script_acceptance.rs b/tests/gate_script_acceptance.rs index fb7772d..8b66dbf 100644 --- a/tests/gate_script_acceptance.rs +++ b/tests/gate_script_acceptance.rs @@ -254,7 +254,9 @@ fn gate_maps_an_unexecutable_helper_to_error_not_ignored() { /// boundary failure must withhold it. #[test] fn gate_validates_the_whole_shared_conformance_set() { - use common::sigint_conformance::{Outcome, SENTINEL, shared_cases, stub_script}; + use common::sigint_conformance::{ + CANONICAL_IGNORED, Outcome, SENTINEL, shared_cases, stub_script, + }; let cases = shared_cases(); assert_eq!(cases.len(), 45, "the shared set is 45 cases"); @@ -307,6 +309,12 @@ fn gate_validates_the_whole_shared_conformance_set() { child's stderr --- this is what separates it from a \ validated error, which shares its exit code: {err}" ); + assert!( + !err.contains(CANONICAL_IGNORED), + "case {name}: and it must never repeat the canonical \ + ignored wording --- X3 emits exactly that on stderr \ + with no token: {err}" + ); } } @@ -348,12 +356,24 @@ fn gate_refuses_when_the_capture_directory_cannot_be_created() { !String::from_utf8_lossy(&out.stdout).contains("[01]"), "no stage may run" ); + // A8 on this path too: the temporary root is inspected BEFORE its + // RAII drop, and must contain nothing the guard left behind. + let residue: Vec<_> = std::fs::read_dir(root.path()) + .expect("read tmpdir") + .filter_map(Result::ok) + .map(|e| e.file_name().to_string_lossy().into_owned()) + .collect(); + assert!( + residue.is_empty(), + "a guard that could not create its capture directory must leave \ + nothing behind: {residue:?}" + ); } /// Each helper arm emits its exact token on stdout. #[test] fn sigint_helper_emits_the_exact_token_for_each_arm() { - use common::sigint_conformance::{TOKEN_ERROR, TOKEN_SAFE}; + use common::sigint_conformance::{TOKEN_ERROR, TOKEN_IGNORED, TOKEN_SAFE}; let safe = Command::new(sigint_helper()).output().expect("run helper"); assert_eq!(safe.status.code(), Some(0)); @@ -369,6 +389,16 @@ fn sigint_helper_emits_the_exact_token_for_each_arm() { .expect("run helper"); assert_eq!(erroring.status.code(), Some(2)); assert_eq!(erroring.stdout, [TOKEN_ERROR, b"\n"].concat()); + + // The ignored arm needs a shell that ignores SIGINT; assert its + // STDOUT, not merely its status and stderr. + let ignored = under_ignored_sigint(&sigint_helper(), &[], &repo_root(), &[]); + assert_eq!(ignored.status.code(), Some(1)); + assert_eq!( + ignored.stdout, + [TOKEN_IGNORED, b"\n"].concat(), + "the ignored arm emits exactly its token plus one LF" + ); } /// §7c: the helper answers `safe` when `SIGINT` is deliverable. diff --git a/tests/gpu_invocation_acceptance.rs b/tests/gpu_invocation_acceptance.rs index 461d3e3..7e86f44 100644 --- a/tests/gpu_invocation_acceptance.rs +++ b/tests/gpu_invocation_acceptance.rs @@ -254,7 +254,7 @@ mod crdt { match (out.status.code(), token_ok) { (Some(0), true) => Ok(()), // A VALIDATED verdict: the helper's stderr is the diagnosis. - (Some(1) | Some(2), true) => Err(format!( + (Some(1 | 2), true) => Err(format!( "precondition failed --- this is NOT a teardown defect. \ (status={status} token={token_state})\n{}", String::from_utf8_lossy(&out.stderr).trim_end() @@ -289,7 +289,15 @@ mod crdt { /// surfaces it, a boundary failure must not. #[test] fn rd_precondition_validates_the_whole_conformance_set() { - use crate::common::sigint_conformance::{Outcome, SENTINEL, shared_cases, stub_script}; + // `self::` and NOT `crate::`: this file is ALSO compiled as a + // nested module of `gpu_initial_target_acceptance.rs`, where + // `crate::` is the outer test crate and has no `common`. + // `super::` and NOT `crate::`: this file is ALSO compiled as a + // nested module of `gpu_initial_target_acceptance.rs`, where + // `crate::` is the outer test crate and has no `common`. + use super::common::sigint_conformance::{ + CANONICAL_IGNORED, Outcome, SENTINEL, shared_cases, stub_script, + }; let dir = tempfile::tempdir().expect("tempdir"); let cases = shared_cases(); @@ -314,6 +322,11 @@ mod crdt { Outcome::Boundary => { let message = got.expect_err("a boundary refusal"); assert!(message.contains("boundary error"), "case {name}: {message}"); + assert!( + !message.contains(CANONICAL_IGNORED), + "case {name}: never repeats the canonical ignored wording \ + --- X3 emits exactly that with no token: {message}" + ); assert!( !message.contains(SENTINEL), "case {name}: a boundary failure must NOT surface the child's \