Two closure gaps.
1. Both suites asserted only `cases.len() == 45`, so the exact
45-entries-over-43-distinct-inputs regression could recur unnoticed
--- the one where X3 collapsed into 1/E/empty and X4 into
0/V/safe/bare, leaving two framing-specified cases silently
unexercised. shared_cases() now asserts uniqueness over
(status, stdout, stderr), inside the generator so no consumer can
forget it. Verified by reverting both payloads to the sentinel: it
fails naming X3.
2. Comments and ledger still said every stub emits the sentinel, which
the explicit X3/X4 payloads had made false. They now say the
BRANCH-DISCRIMINATING cases carry it while X3 and X4 deliberately
carry their own --- X3 the canonical ignored wording with no token,
X4 noise --- and that this is what makes them distinct inputs. The
duplicated `self::`/`super::` explanation left over from the nesting
fix is reduced to the correct one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Five findings. The first was red CI that my local gate could not have
caught.
1. `crate::common` cannot resolve when gpu_invocation_acceptance.rs is
compiled as a nested module of gpu_initial_target_acceptance.rs,
where `crate::` is the outer test crate. Now `super::common`, which
resolves in both modes --- verified by compiling each target
explicitly. Clippy's `(Some(1 | 2), true)` folding applied too.
The reason this shipped: plain `./scripts/gate` omits sweep-crdt,
the only stage that compiles the nested target under crdt, while
04-lib-crdt builds the lib alone. This lane gates with `--protocol`,
and the ledger now says so.
2. X3 and X4 had stopped being the cases the framing specifies:
stub_script() gave every case the same sentinel stderr, so X3 lacked
the canonical ignored text and X4 was byte-identical to
0/V/safe/bare --- 45 entries, 43 distinct inputs. Case now carries an
explicit stderr payload; X3 emits the canonical wording with no
token, and both consumers assert they never repeat it.
3. The capture-creation-failure row asserted exit, wording and stage
output but not residue. It now inspects the temporary root before
its RAII drop and requires it empty.
4. The exact-token test covered safe and error but not ignored, despite
the ledger claiming all three. The ignored arm now asserts its exact
stdout, driven through a SIGINT-ignoring shell.
5. The ledger's claim that the status-2 mutation is caught only by the
dedicated row is superseded --- the sentinel matrix catches it --- and
the self-referential "this commit" is replaced by bc7d776.
Also records 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 and fail identically at 72da24a, so they are not this
lane's, and no cause is claimed for them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Four acceptance gaps, all upheld.
1. Neither suite distinguished a validated refusal from a boundary
error. Both exit 2 (and both produce Err in Rust), so comparing exit
codes or is_ok() let a validator that accepts EVERY status-2 pair
pass the whole matrix --- the precise defect A6c exists to catch.
Every stub now emits a sentinel on stderr, and an Outcome enum
(Safe / ValidatedIgnored / ValidatedError / Boundary) is asserted
branch-exact: a validated verdict must surface the sentinel, a
boundary failure must withhold it. Verified: mutating the gate to
accept any status 2 now fails the MATRIX, where before it only
failed a dedicated row. Each helper arm's exact stdout token is
asserted as well.
2. The 45-case set was duplicated in both suites and could drift while
both still reported length 45. It now lives in
tests/common/sigint_conformance.rs and both validators consume the
same vectors.
3. A8 was incomplete --- nothing forced capture-directory creation to
fail. A bounded row points TMPDIR at a missing directory so
`mktemp -d` fails, asserting boundary error 2, no stage execution and
no residue; mutating the failure branch to fall through makes it
fail. Temporary directories are RAII throughout, replacing the
keep()-plus-manual-cleanup shape.
4. The R-d comment still claimed a shared helper means the consumers
"can never disagree" and described status-only behaviour. Both were
withdrawn by revision 13; the comment now points at the shared matrix
as what actually keeps them in step.
36 gate rows, 16 GPU rows, clippy clean, full gate green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai