docs(framing): revision 11 --- the framing afe3631 claimed but did not write

afe3631's message described revision 11 in detail. The commit contains
only the test file: the script that was to write the framing died on a
stale anchor --- the approval commit had reworded the header --- and the
shell chain ran `git commit` regardless of its exit status.

This is the SECOND time in this lane, and I recorded the lesson for it
in ea0f3bf: "asserting the edit is not enough if the commit does not
depend on it". I then repeated it. This commit gates `git commit` behind
the editing script's exit status, which is what the earlier note should
have changed and did not.

The framing is now actually at revision 11, AWAITING APPROVAL, carrying
§4c: SIGINT ignored group-wide (SigIgn=0x1007, signal 2), zero SigPnd
and zero per-thread SigBlk so ignored rather than blocked delivery,
shared pgid so nothing escaped the group; the foreground/background
SigIgn comparison; the controlled two-arm experiment; the invalidation
of the subset-vs-full matrix as confounded with my own invocation
method; and the consequences --- bet 1 falsified, A5 struck, the §7/§8
remedy withdrawn in favour of a runner practice and a precondition
assertion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
Levi Neuwirth 2026-08-19 14:16:48 +02:00
parent afe3631ed6
commit 952f8d5bc2
No known key found for this signature in database
1 changed files with 98 additions and 2 deletions

View File

@ -1,7 +1,14 @@
# GPU launcher / probe SIGINT teardown — framing # GPU launcher / probe SIGINT teardown — framing
Revision 10. Status: **APPROVED 2026-08-19 at `4fba9f6`. No fix Revision 11. Status: **MECHANISM FOUND (§4c). Awaiting approval — the
implemented; mechanism still unknown.** problem statement has changed, so the remedy in §7/§8 no longer
follows.**
Revision 10 was approved 2026-08-19 at `4fba9f6`, authorising
diagnostic-only D1/D2. They ran, and found the mechanism on the first
reproducing sweep. It is not what this document was built around: bet 1
is falsified, A5 is struck, and nothing is wrong with the probe's
shutdown path.
Revision 9 was approved 2026-08-19 at `15c25ec`. **That approval did Revision 9 was approved 2026-08-19 at `15c25ec`. **That approval did
not extend to revision 10**, because retiring D0b (§7) materially not extend to revision 10**, because retiring D0b (§7) materially
@ -325,6 +332,95 @@ difference is **not captured by those two commits**.
Per-run provenance: `docs/probe-sigint-evidence.md` §D0a. Per-run provenance: `docs/probe-sigint-evidence.md` §D0a.
## 4c. D1/D2 RESULT — the mechanism, and it is my own artifact
**`SIGINT` was ignored by every process in the target group, because I
launched the test runner in the background.**
Captured at the moment of the test's own `kill`:
```
test parent pid 8252 SigIgn=0000000000001007
launcher pid 8281 ppid=8252 pgid=8281 SigIgn=0000000000001007
SigCgt=0000000000000440 wchan=do_wait
probe pid 8284 ppid=8281 pgid=8281 SigIgn=0000000000001007
```
`SigIgn=0x1007` is signals 1, 2, 3 and 13 — and **signal 2 is
`SIGINT`**. The launcher's `SigCgt=0x440` is signals 7 and 11 only,
Rust's SIGBUS/SIGSEGV handlers; there is no `SIGINT` handler anywhere.
Every `SigPnd`/`ShdPnd` is zero and every per-thread `SigBlk` is zero,
so this is **ignored** delivery, not **blocked** delivery. Launcher and
probe share `pgid=8281`, so nothing escaped the group either. All three
candidates D1/D2 was built to separate are thereby separated.
`kill(-pgid, SIGINT)` is a **no-op for every member**. The launcher
waits in `do_wait` for a child that was never told to stop, and the 5 s
deadline fires.
### Where the ignore comes from — measured in both directions
| invocation | child's `SigIgn` | `SIGINT` |
|---|---|---|
| foreground | `0000000000001000` | bit 12 only (SIGPIPE) — **deliverable** |
| `setsid nohup … &` | `0000000000000007` | SIGHUP, SIGINT, SIGQUIT — **ignored** |
A shell running a command in the background without job control sets
`SIGINT`/`SIGQUIT` to `SIG_IGN` in the child; `nohup` adds `SIGHUP`.
**`SIG_IGN` is inherited across `fork` and survives `exec`**, so it
propagates shell → `cargo` → test binary → launcher → probe.
### The controlled experiment
Same command, same tree, same target directory, minutes apart — only
the invocation differs:
| arm | invocation | both target copies |
|---|---|---|
| 1 | foreground | **ok** |
| 2 | `setsid nohup … &` | **FAILED** |
### This invalidates most of this lane's investigation, and I caused it
I adopted `setsid nohup … &` on 08-16 to stop the Bash tool's
ten-minute cap truncating gate runs. **That is the "onset".**
- **The subset-vs-full distinction was never real.** Every reduction I
ran was foreground; every full sweep was backgrounded. The two
variables were perfectly confounded, so §4's matrix measured my
invocation method rather than the code, and R9's "paradox" dissolves.
- **The 08-15 → 08-16 window** dates my method change, not the machine
and not the source.
- **D0a's both-uniform-red is consistent and was right** — its harness
backgrounded both arms, so both were red; the cause was invisible to
a comparison in which it did not vary.
- **"Pre-existing on `main`" is true but trivial**: `main` fails the
same way backgrounded and passes foreground.
### Consequences for the contract
- **Bet 1 is FALSIFIED, and A5 is struck** under its own D4 condition.
An interactive terminal does not ignore `SIGINT`, so Ctrl-C on a real
`pmacs --gpu` session behaves correctly. This is not a defect a user
can meet.
- **The §7/§8 remedy no longer follows.** What remains is narrower and
genuinely real:
1. **The gate must not be invoked so that `SIGINT` is ignored** — a
runner practice, and the direct cause of all seven red sweeps.
2. **The test should not fail obscurely when its precondition is
absent.** "child did not exit within 5s" sent this lane chasing a
teardown defect for nine revisions. It should detect an ignored
`SIGINT` and say so. Silently skipping is not acceptable —
`scripts/gate`'s own comments record that self-skipping tests
"void coverage silently".
- **A3's subset/full obligation is discharged by explanation**, not by
D0b: the difference was invocation mode, demonstrated in both
directions.
`#![forbid(unsafe_code)]` rules out `pre_exec`, so remedy 2 is a
precondition assertion rather than a repair — but that is a design
question for the next revision, not a decision taken here.
## 5. Two retracted claims, both mine, kept as warnings ## 5. Two retracted claims, both mine, kept as warnings
**Claim A — "mechanism located".** Reported the launcher blocked in **Claim A — "mechanism located".** Reported the launcher blocked in