Commit Graph

2 Commits

Author SHA1 Message Date
Levi Neuwirth bc7d776569
feat(gate,test): implement revision 13 --- the validated (status, token) pair
The helper now emits its verdict token on stdout with diagnostics on
stderr, and both consumers validate the PAIR rather than the status
alone. This closes the macOS defect CI found: a shell that cannot
execute the helper exits 1, which the status-only ABI read as
`ignored`, so a broken guard told the operator their environment
ignores SIGINT.

Gate (shell consumer):
  - guard-local capture directory, created before the gate's own
    temporary roots exist, with cleanup armed BEFORE the helper runs and
    disarmed on the safe path so the gate's later trap is undisturbed;
  - `|| sigint_status=$?` retained --- a bare invocation dies under
    `set -eu` before the status is read, which was the original bug;
  - `expected_token` selected by an explicit status case before any
    `set -u`-sensitive use, since an out-of-range status has none;
  - byte comparison via `cmp` against both permitted encodings, because
    a shell variable neither preserves NUL nor carries the child status;
  - the helper's stderr is surfaced ONLY for validated verdicts; a
    boundary failure prints the gate's own wording and withholds the
    untrusted child output;
  - every refusing branch prints status= and token=.

R-d (Rust consumer) validates the same pair from Command::output()
bytes. It needs no capture files, and its spawn-error path has no status
at all --- the boundary the shell cannot represent.

Conformance: 45 shared cases generated as a cross-product over token
class, encoding and status, run by BOTH validators so they cannot
diverge, plus Rust's X2 for 46 overall. 34 gate rows, 16 GPU rows, full
gate green.

Mutations, each biting its row: accepting any status 2 regardless of
token; surfacing child stderr on a boundary failure; emitting the token
to stderr. The first is caught by the dedicated error row rather than
the conformance set --- most of the set's boundary cases have empty
stderr, so they cannot tell which branch produced the exit 2 --- and
that limitation is recorded rather than left implicit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
2026-08-19 21:14:30 +02:00
Levi Neuwirth 86ace38ef5
feat(gate,test): implement R-b + R-d --- the SIGINT deliverability guard
scripts/check-sigint-deliverable is the single checked-in helper, to the
ABI revision 12 fixed: exit 0 safe with no diagnostic, exit 1 ignored
with the canonical wording, exit 2 error with a distinct one. The inner
probe's `|| exit 24` arms are the load-bearing part --- without them a
FAILED kill also falls through to exit 0 and gets misread as inherited
SIG_IGN, which is the one wrong answer the helper exists to prevent.

R-b: scripts/gate runs it before any stage and stops on a non-zero
status, surfacing the helper's stderr unchanged and adding only that no
stage ran. It does not re-derive the classification or supply its own
wording. Plan/print modes skip it, since they run nothing. No override.

R-d: the target test calls the same helper first and panics with
"precondition failed --- this is NOT a teardown defect" plus the helper's
own stderr, instead of reaching the misleading "child did not exit
within 5s". The Linux-only /proc D1/D2 instrument is removed now that
its evidence is portable, taking the platform dependency with it.

Witnesses:

  A1  backgrounded gate stops before stage 1 with the ignored
      diagnosis, exit 1.
  A2  backgrounded direct test reports the precondition failure, NOT
      the 5s deadline.
  A3  foreground: both target copies pass in 0.16s and the guard is
      silent.
  A4  mutations measured, each biting its named row --- removing the
      trap bites A3 (fg 0->2), treating inner 0 as safe bites A1/A2 (bg
      1->0), collapsing error into ignored bites A6 (forced 2->1).
  A5  the full default gate passes all 8 stages foreground, and
      --print-plan is byte-identical to HEAD's: no stage added,
      removed, reordered or made conditional.
  A6  forced probe failure yields exit 2 and the error wording, not
      the ignored wording.
  A7  exercised on Linux x86_64 only, all three outcomes; no non-Linux
      unix was reachable, so A7 stays OPEN there and the portability
      argument is labelled contract-level, not measured.

Also records that this session's tool-level background mode leaves
SIGINT deliverable while setsid nohup ... & does not --- so the construct
that caused this lane was never necessary for long runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
2026-08-19 15:46:57 +02:00