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