diff --git a/docs/active-work.md b/docs/active-work.md index 089c0e1..02b033c 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -270,7 +270,7 @@ hazard in a shape that looks committed. **A documented error message that never appears is worse than no documentation**, because the reader waits for a signal that is not coming. -## GPU launcher / probe SIGINT teardown — MECHANISM FOUND, remedy not selected +## GPU launcher / probe SIGINT teardown — MECHANISM FOUND, remedy SELECTED **Written with the branch's FIRST commit**, per the standing correction from #171 and #215. @@ -480,22 +480,29 @@ from #171 and #215. contract below — a bare difference decides nothing. **(a) is DONE.** **(b) is RETIRED as a precondition** (2026-08-19) because D0a yielded a reliable direct reproduction and D1/D2 measure the mechanism - itself; **its obligation survives under A3** — if D1/D2 do not + itself. **Its obligation is now SATISFIED** by §4c's controlled + explanation of the subset/full difference, so **D0b is not owed and + will not run**. As originally written it said: its obligation + survives under A3 — if D1/D2 do not account for the subset-vs-full difference, D0b runs before the lane closes. It read: re-run the matrix at `main` under a harness capturing provenance **and the artifact hashes executed at run time**, since command shape silently changed the binary once already and a hash computed later reflects only what occupies that path now. -- **Coherence: journey step 12(a), "closing is clean", IS touched** — - Ctrl-C teardown of a GPU session is that step, grade movement or not. - Revision 1 claimed no journey step, reasoning from grade movement, - which §20 explicitly warns against. +- **Coherence: journey steps touched: NONE** (framing §9). Earlier + entries here assigned 12(a) "closing is clean" on the premise that + the lane repairs Ctrl-C teardown; §4c withdraws that premise, because + no product behaviour changes. What the lane affects is **gate + trustworthiness**. - **Gates:** `./scripts/gate --protocol --acceptance - gpu_invocation_acceptance` at minimum; A2 requires `sweep-crdt` green - **three consecutive runs on the reviewed fixed head of this branch** - — not "on main", which is unobtainable before approval and merge. - 1/1 is not evidence for a defect that hid from every reduction. + gpu_invocation_acceptance` at minimum. **The old three-consecutive- + run A2 contract is SUPERSEDED** — it was written for a teardown fix + whose flakiness was unexplained. The mechanism is now known and + deterministic, so the acceptance set is framing §8's A1–A7: guard + bite, direct-test diagnosis, unaffected foreground success, mutation, + an otherwise unchanged gate, a distinct `error` outcome, and a + non-Linux-unix statement. ## `scripts/gate` TMPDIR isolation — PR #240 OPEN diff --git a/docs/gpu-probe-sigint-framing.md b/docs/gpu-probe-sigint-framing.md index 2469c0a..d840ee0 100644 --- a/docs/gpu-probe-sigint-framing.md +++ b/docs/gpu-probe-sigint-framing.md @@ -632,7 +632,7 @@ decides each: | # | remedy | effect | cost / risk | |---|---|---|---| | R-a | **Runner normalisation** — never invoke the gate so that `SIGINT` is ignored; if backgrounding is needed, restore the disposition first | removes the cause for every test at once | a *practice*, not a mechanism: nothing enforces it, and this lane exists because I violated it silently | -| R-b | **Early gate guard** — `scripts/gate` refuses to start when `SIGINT` is `SIG_IGN`, naming the reason | enforces R-a mechanically, once, for all suites | refuses runs that would mostly have succeeded; needs an explicit override for deliberate background use | +| R-b | **Early gate guard** — `scripts/gate` refuses to start when `SIGINT` is `SIG_IGN`, naming the reason | enforces R-a mechanically, once, for all suites | refuses runs that would mostly have succeeded. *This row originally added "needs an explicit override for deliberate background use"; §7c rejects that — see there* | | R-c | **Fixture isolation** — the test restores the default disposition in the spawned launcher | fixes the test wherever it runs, background included | `pre_exec` is `unsafe`, and `#![forbid(unsafe_code)]` binds the lib crate; an integration test could technically opt out, but doing so to dodge a project invariant needs its own argument | | R-d | **Test-local precondition assertion** — detect `SIG_IGN` on `SIGINT` and fail with that reason instead of "child did not exit within 5s" | converts nine revisions of misdirection into one accurate line | does not make the test *pass* when backgrounded; it only stops it lying about why it failed | @@ -666,6 +666,27 @@ it exits **23**; if `SIGINT` was inherited as `SIG_IGN` the kill is a no-op and it exits **0**. Verified in both contexts: 23 foreground, 0 under `setsid nohup … &`. +**The raw probe is not a total classifier, and must not be used as +one.** Its `exit 0` is reached both when the `kill` was a no-op *and* +when the `kill` itself failed — a failed `kill` overwrites its own +status and lands on the same exit. Read naively, a broken probe reports +"inherited `SIG_IGN`", which would fail the gate for the wrong reason +and send the next reader down this lane again. + +So the **helper owns the classification**, and returns one of three +outcomes; consumers only consume the verdict and never re-derive it: + +| outcome | meaning | how it is reached | +|---|---|---| +| `safe` | `SIGINT` is deliverable | probe exits 23 | +| `ignored` | `SIGINT` is inherited as `SIG_IGN` | probe exits 0 **and** the `kill` itself reported success | +| `error` | the probe could not decide | `kill` failed, `sh` unavailable, unexpected exit, or a signal other than the trap | + +`error` is **not** treated as `ignored`. It fails the gate too, but with +a different diagnosis, because "your environment ignores SIGINT" and +"the guard could not run" are different problems and conflating them is +what a naive `exit 0` would do. + This is **POSIX shell only** — `trap`, `kill`, `$$` — so it settles the portability question §7b raised: no `/proc`, hence not Linux-only, and no `sigaction`, hence no `unsafe`. @@ -708,7 +729,17 @@ show: in the normal case. - **A4 — mutation.** Removing the probe's `trap`, or treating exit 0 as "deliverable", makes A1 and A2 fail; each mutation is named against - the row it must bite. + the row it must bite. Additionally, collapsing `error` into `ignored` + must fail A6. +- **A6 — the `error` outcome is distinct.** With the probe forced to + fail (e.g. its interpreter made unavailable), the guard reports the + **`error`** diagnosis, not the `ignored` one, and does not claim the + environment ignores `SIGINT`. +- **A7 — a supported non-Linux unix.** The helper is exercised on a + non-`/proc` unix in the project's supported set, or — if none is + reachable — the record states which platforms the guard is *claimed* + to work on and which were actually tried. No unexercised portability + claim ships unqualified. - **A5 — the gate is otherwise unchanged**: a normal foreground run reaches and passes every stage it did before, with no stage added, skipped, reordered, or made conditional. diff --git a/docs/probe-sigint-evidence.md b/docs/probe-sigint-evidence.md index 6c728b8..abf6061 100644 --- a/docs/probe-sigint-evidence.md +++ b/docs/probe-sigint-evidence.md @@ -179,12 +179,11 @@ cargo test --features crdt --no-fail-fast \ -- ctrl_c_on_launcher_group ``` -Outer invocation, the only difference. `arms.sh` is machine-local, so -the commands are given **fully expanded** — a reader elsewhere needs no -access to it: +Outer invocation — the only difference, written out with **no +placeholders**: ``` -# fg arm +# fg arm — run directly in an interactive foreground shell cd /home/jeans/Repos/personal/pmacs-probe-sigint && \ env TMPDIR=/home/jeans/build/pmacs-gate-targets/tmp/arms \ CARGO_TARGET_DIR=/home/jeans/build/pmacs-gate-targets/pmacs-probe-sigint-84ed0f9e \ @@ -192,32 +191,38 @@ env TMPDIR=/home/jeans/build/pmacs-gate-targets/tmp/arms \ --test gpu_invocation_acceptance --test gpu_initial_target_acceptance \ -- ctrl_c_on_launcher_group -# bg arm — byte-identical inner command, wrapped: -setsid nohup sh -c '' > 2>&1 & disown +# bg arm — the identical command, wrapped +setsid nohup sh -c 'cd /home/jeans/Repos/personal/pmacs-probe-sigint && \ +env TMPDIR=/home/jeans/build/pmacs-gate-targets/tmp/arms \ + CARGO_TARGET_DIR=/home/jeans/build/pmacs-gate-targets/pmacs-probe-sigint-84ed0f9e \ + cargo test --features crdt --no-fail-fast \ + --test gpu_invocation_acceptance --test gpu_initial_target_acceptance \ + -- ctrl_c_on_launcher_group' \ + > /home/jeans/build/pmacs-gate-targets/d0a/arm2-bg.log 2>&1 & disown ``` -The wrapper additionally recorded `git rev-parse HEAD`, -`git status --porcelain | wc -l`, the exit status, both copies' -results, the executed suffixes, their hashes, and the log digest. +### Result — head `77b623c`, `dirty=0`, digests captured PER RUN -| arm | outer | exit | ok | failed | `SigIgn` | binary hashes | log sha256/16 | -|---|---|---|---|---|---|---|---| -| fg | foreground | 0 | 2 | 0 | not captured (no failure ⇒ no dump) | `aaec01673691479a…` (prefix) | -| bg | `setsid nohup … &` | 101 | 0 | 2 | `0000000000001007` | `c744d85a84cb8683…` (prefix) | +| arm | outer | exit | ok | failed | `SigIgn` | log sha256 (full) | +|---|---|---|---|---|---|---| +| fg | foreground | 0 | 2 | 0 | not captured (no failure ⇒ no dump) | `b6117619242f3dabadbe5826f59f11cef451cd09a2beb304e9a3dec1a4616465` | +| bg | `setsid nohup … &` | 101 | 0 | 2 | `0000000000001007` | `30ac6568e345c485ddf23734a239d71bd06d4a3c2d549f1b4a8942a930527507` | -Both arms executed the same two binaries, whose **full** SHA-256 are: +Binary digests, **full SHA-256, taken immediately after each run before +anything could rebuild them** — not read later from a reused path: -``` -gpu_initial_target_acceptance-91f51d0b5303ff9f - 0890b78cca22ac1e80b79845f85fb6e88def3330db15ae123a2a672d3084124c -gpu_invocation_acceptance-6b4b8223dea45247 - ef6ff1c15e11062ab53a075763814f32c1bbc9be1b146d068c60e91fa247c696 -``` +| binary | fg arm | bg arm | +|---|---|---| +| `gpu_initial_target_acceptance-91f51d0b5303ff9f` | `0890b78cca22ac1e80b79845f85fb6e88def3330db15ae123a2a672d3084124c` | *(identical)* | +| `gpu_invocation_acceptance-6b4b8223dea45247` | `ef6ff1c15e11062ab53a075763814f32c1bbc9be1b146d068c60e91fa247c696` | *(identical)* | -Same head, same target directory, `dirty=0`, and the binaries were not -rebuilt between arms — so nothing but the outer invocation varies. The -**log** digests above are 16-character **prefixes**, not full values, -and are identifiers only; no claim rests on them. +**These are byte-identical, and that claim is now carried by the +capture rather than by inference.** The earlier arms table recorded only +16-character prefixes at run time and its full values were read +afterwards from reused paths — which is exactly the provenance rule +§7/D0 states, applied against my own record. Those rows are superseded +by the table above; raw rows for both generations are in `arms.tsv` and +`arms2.tsv`. ### Disposition — UNRECORDED CORROBORATION, not a controlled arm