docs(framing): rewrite the contract §4c had only contradicted
Four findings on revision 11, all upheld. 1. The operative contract still said the opposite of §4c. Bet 1 read as open; §7 said the mechanism was unknown with D3/D4 pending; §8 kept the old criteria and a conditional A5; §9 claimed a journey-12(a) product repair; the ledger and the revision-10 paragraph still said D1/D2 had not started. Each is now rewritten as executed, withdrawn, discharged or superseded --- §9 in particular now records journey steps touched: NONE, for the stated reason that no product behaviour changes, with gate trustworthiness named as what the lane does affect. 2. The causal evidence is now portable and cleanly reproduced. The first capture came from d12.log, which finished five minutes BEFOREafe3631committed the diagnostic code and ran in the reused d0a-B target --- inadmissible provenance, now marked as the first sighting only. Replaced by controlled arms on committed head38f2af4, dirty=0, in this worktree's own target, with BYTE-IDENTICAL binary hashes across arms (0890b78cca22ac1e, ef6ff1c15e11062a): foreground exit=0 ok=2, background exit=101 failed=2 SigIgn=0x1007. The outer invocation is recorded as a first-class column, since it is the causal variable and every earlier "exact command" omitted it. The historical foreground/background mapping is marked RECONSTRUCTED from the transcript, not captured --- no pre-existing row carries an outer-invocation field, which is precisely why the matrix stayed confounded for nine revisions. 3. D4 was never executed, so bet 1 is WITHDRAWN BY SCOPE rather than falsified, and A5 is RETIRED BY SCOPE rather than struck. Nothing here shows a real wgpu session behaves correctly; what is shown is that no observed evidence of a user-facing defect survives. The lane is now gate/test correctness only. 4. The remedy is not selected. §7b evaluates four candidates --- runner normalisation, an early gate guard, fixture isolation via pre_exec, and a test-local precondition assertion --- with portability as a selection criterion, noting /proc is Linux-only while the suite is cfg(unix) and sigaction querying is unsafe. Likely R-b + R-d, but nothing is chosen or implemented here. Revision 11's leap from "pre_exec is unsafe" to "therefore an assertion" did not follow. Also renames the meaningless african_close() helper (38f2af4). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
38f2af41f7
commit
57d8dae511
|
|
@ -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 — D0a DONE, mechanism unknown
|
||||
## GPU launcher / probe SIGINT teardown — MECHANISM FOUND, remedy not selected
|
||||
|
||||
**Written with the branch's FIRST commit**, per the standing correction
|
||||
from #171 and #215.
|
||||
|
|
@ -282,9 +282,23 @@ from #171 and #215.
|
|||
- **No PR. Framing revision 10 at `docs/gpu-probe-sigint-framing.md`,
|
||||
APPROVED 2026-08-19 at `4fba9f6`** — revision 9 was approved at
|
||||
`15c25ec`, but did **not** cover retiring D0b. Revision 10 does, with
|
||||
the A3 contingency preserved: if the demonstrated D1/D2 mechanism
|
||||
does not account for the subset/full difference, D0b runs before
|
||||
closure. **D1/D2 are authorised but have not started.** Also: **NO
|
||||
the A3 contingency preserved. **D1/D2 HAVE RUN and found the
|
||||
mechanism: `SIGINT` was ignored group-wide (`SigIgn=0x1007`) because
|
||||
the test runner was launched in the background — `SIG_IGN` is
|
||||
inherited across `fork` and survives `exec`, so it reached the
|
||||
launcher and probe, and `kill(-pgid, SIGINT)` was a no-op.**
|
||||
Controlled arms on committed head `38f2af4` with byte-identical
|
||||
binaries: foreground both copies ok, `setsid nohup … &` both FAILED.
|
||||
**I caused this** by adopting background launches on 08-16 to evade
|
||||
the Bash tool's ten-minute cap — that is the "onset", and the
|
||||
subset-vs-full matrix was confounded with it throughout. A3's
|
||||
subset/full obligation is discharged by explanation, so D0b is not
|
||||
needed. **Bet 1 withdrawn by scope and A5 retired by scope — D4 was
|
||||
never executed**, so no claim is made that a real session behaves
|
||||
correctly, only that no evidence of a user-facing defect survives.
|
||||
Framing revision 11 AWAITING APPROVAL; **no remedy selected** (§7b
|
||||
weighs runner normalisation, a gate guard, fixture isolation and a
|
||||
test-local assertion). Also: **NO
|
||||
IMPLEMENTATION and no fix proposed** — the mechanism is not known yet,
|
||||
and the framing says so
|
||||
rather than guessing. Revisions 1, 2 and 3 were each rejected on
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ not extend to revision 10**, because retiring D0b (§7) materially
|
|||
changed the approved diagnostic sequence — the revision 9 text made
|
||||
D0b mandatory before every other diagnostic. Revision 10's approval
|
||||
covers that retirement and the A3 contingency that preserves its
|
||||
obligation. D0a's execution and result (§4b) are reported; D1/D2 are
|
||||
authorised but have not started.
|
||||
obligation. **D1/D2 have since been executed under it and found the
|
||||
mechanism (§4c).**
|
||||
|
||||
Revisions 1 and 2 were each rejected on five findings. Every correction
|
||||
is recorded in place rather than quietly rewritten, because three of
|
||||
|
|
@ -456,23 +456,34 @@ on process age at all.** Key on identity.
|
|||
|
||||
## 6. Bets
|
||||
|
||||
1. **The failure is a real teardown defect** — a user pressing Ctrl-C
|
||||
on `pmacs --gpu` sees the same hang. **This is a bet, not a
|
||||
finding**, and the current witness does not reach the real GUI
|
||||
path: it goes through a wrapper script and `--headless-managed-probe`
|
||||
(`:1090-1093`), not a live wgpu frontend. Confirming or dropping this
|
||||
bet is D4 below.
|
||||
2. It is **not** a timing margin. A green run finishes in 0.15 s against
|
||||
a 5 s deadline — 33×. Margins that large do not erode.
|
||||
3. Therefore **raising the deadline is not a fix** and is out of scope.
|
||||
If the conclusion turns out to be that the deadline is wrong, that
|
||||
needs its own argument and its own approval.
|
||||
1. ~~The failure is a real teardown defect a user meets.~~
|
||||
**WITHDRAWN BY SCOPE — not falsified.** Every observed red run is
|
||||
explained by inherited `SIG_IGN` from a background invocation
|
||||
(§4c), so **no observed evidence of a user-facing defect remains**.
|
||||
That is weaker than proving a real wgpu session is correct, and
|
||||
**D4 was never executed** (§7), so the correct statement is: this
|
||||
lane is now **gate/test correctness only**, and any user-facing
|
||||
claim is out of its scope and unevidenced in both directions.
|
||||
2. **UPHELD.** It is **not** a timing margin — confirmed twice over: a
|
||||
green run finishes in ~0.19 s against a 5 s deadline, and the
|
||||
foreground arm passes while the background arm fails with the same
|
||||
binaries.
|
||||
3. **UPHELD, and now load-bearing.** Raising the deadline is not a fix
|
||||
and remains out of scope: the signal is never delivered, so no
|
||||
deadline is long enough.
|
||||
|
||||
## 7. First step — diagnostics keyed on identity, not age
|
||||
|
||||
No fix is proposed; the mechanism is unknown. The first commit is
|
||||
diagnostic only, and it must **discriminate** the three live candidates:
|
||||
blocked delivery, inherited ignore, and an escaped or wrong process
|
||||
**EXECUTED. The mechanism is known (§4c): inherited `SIG_IGN`.** This
|
||||
section is kept as the record of what was run. D1/D2 discriminated the
|
||||
three candidates — ignored rather than blocked delivery (`SigPnd` and
|
||||
per-thread `SigBlk` all zero), and no escape from the group (shared
|
||||
`pgid`). D3 is discharged by the controlled arms. **D4 was NOT
|
||||
executed**, and bet 1 is withdrawn by scope rather than falsified.
|
||||
|
||||
As written, the step read: the first commit is diagnostic only, and it
|
||||
must **discriminate** the three live candidates: blocked delivery,
|
||||
inherited ignore, and an escaped or wrong process
|
||||
group.
|
||||
|
||||
- **D0a — reproduce the onset endpoints CLEANLY** (§4a): `7599661`
|
||||
|
|
@ -598,9 +609,43 @@ group.
|
|||
before/after pair is what makes the claim provable.
|
||||
- **D3 — run the full sweep under D1/D2 until the failure is captured
|
||||
*with* its diagnostics.** Only then propose a fix.
|
||||
- **D4 — settle bet 1 separately.** Establish whether a real
|
||||
`pmacs --gpu` session, not the wrapper/headless probe, reproduces the
|
||||
hang. The answer decides whether A5 is an obligation or is dropped.
|
||||
- **D4 — NOT EXECUTED.** It would have established whether a real
|
||||
`pmacs --gpu` session, rather than the wrapper/headless probe,
|
||||
reproduces the hang. It is **not run and not needed**, because bet 1
|
||||
is withdrawn by scope: with every observed failure explained by the
|
||||
runner's invocation, there is no user-facing claim left for this lane
|
||||
to make. **A5 is retired by scope, not falsified** — nothing here
|
||||
demonstrates a real session behaves correctly, only that no evidence
|
||||
of the contrary survives.
|
||||
|
||||
## 7b. Remedy options — evaluated, none yet selected
|
||||
|
||||
Revision 11 jumped from "`pre_exec` is `unsafe`" to "therefore a
|
||||
precondition assertion". That does not follow: ruling out one mechanism
|
||||
does not select another. Four candidates, with the trade-off that
|
||||
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-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 |
|
||||
|
||||
**They are not exclusive**, and the likely answer is R-b + R-d: a guard
|
||||
that stops the whole gate from running in a state where several suites
|
||||
are meaningless, plus a test that explains itself if it is ever reached
|
||||
that way. R-a alone is what already failed. R-c is the only one that
|
||||
makes the test genuinely invocation-independent, and it is also the one
|
||||
that touches an `unsafe` prohibition, so it needs a separate decision.
|
||||
|
||||
**Portability is a selection criterion, not an afterthought.** Reading
|
||||
`SigIgn` from `/proc/self/status` is Linux-only. The suite is already
|
||||
`#![cfg(unix)]`, so a `/proc`-based guard would narrow it further to
|
||||
Linux; `sigaction`-based querying would be portable but is `unsafe`.
|
||||
Whichever is chosen must state what it does on a non-`/proc` unix.
|
||||
|
||||
No remedy is implemented, and none is selected here.
|
||||
|
||||
## 8. Acceptance criteria for the eventual fix
|
||||
|
||||
|
|
@ -627,17 +672,23 @@ Written now so the fix cannot quietly become "make the test pass".
|
|||
to obtain green.
|
||||
- **A5.** **Conditional on D4.** If bet 1 holds, this is unconditional:
|
||||
Ctrl-C on a real `pmacs --gpu` session tears down the frontend and
|
||||
leaves the daemon running. If D4 shows the hang is reachable only
|
||||
through the wrapper/headless path, bet 1 is dropped, A5 is struck,
|
||||
and the lane is recorded as gate-correctness only.
|
||||
leaves the daemon running. **RETIRED BY SCOPE**: D4 was not executed,
|
||||
bet 1 is withdrawn, and this lane is recorded as **gate/test
|
||||
correctness only**. A5 is not claimed satisfied and not claimed
|
||||
falsified — it is out of scope, and a user-facing teardown claim would
|
||||
need its own lane and its own evidence.
|
||||
|
||||
## 9. Coherence impact (`COHERENCE.md` §20)
|
||||
|
||||
- **Journey step touched: 12(a), "closing is clean."** Ctrl-C teardown
|
||||
of a GPU session is exactly that step, whether or not its grade
|
||||
moves. **Revision 1 said "journey steps touched: none", which was
|
||||
false** — it reasoned from grade movement, which §20 explicitly warns
|
||||
against.
|
||||
- **Journey steps touched: NONE, as finally established.** Earlier
|
||||
revisions claimed 12(a) "closing is clean", on the premise that this
|
||||
lane repairs Ctrl-C teardown. §4c withdraws that premise: no product
|
||||
behaviour changes, because the failure is an artifact of how the test
|
||||
runner is invoked. Revision 1's "none" reached the right answer by
|
||||
the wrong route (grade movement, which §20 warns against); this is
|
||||
the right answer for the stated reason.
|
||||
- What the lane does touch is **gate trustworthiness**: seven red
|
||||
sweeps that named a product defect and had none.
|
||||
- **Grade movement: none expected.** This restores a property that is
|
||||
supposed to hold, rather than opening a new one.
|
||||
- **Interaction islands: none added.**
|
||||
|
|
|
|||
|
|
@ -157,6 +157,72 @@ source/environment interaction, or a fix before `7599661` followed by a
|
|||
regression before `724b785`. And an ancestor outside the interval is
|
||||
irrelevant to whether the interval contains a regression.
|
||||
|
||||
## D1/D2 — EXECUTED 2026-08-19. The outer invocation is the variable
|
||||
|
||||
**The causal variable is the OUTER invocation of the test runner**, so
|
||||
it is recorded here as a first-class column. Earlier "exact commands"
|
||||
in this file are incomplete for that reason: they gave the inner
|
||||
`cargo` argv and omitted how the runner itself was started.
|
||||
|
||||
### Controlled arms, committed head, worktree-local target
|
||||
|
||||
Head `38f2af4`, `dirty=0`, worktree
|
||||
`/home/jeans/Repos/personal/pmacs-probe-sigint`, target
|
||||
`/home/jeans/build/pmacs-gate-targets/pmacs-probe-sigint-84ed0f9e`,
|
||||
`TMPDIR=/home/jeans/build/pmacs-gate-targets/tmp/arms`.
|
||||
|
||||
Inner command, identical in both arms:
|
||||
|
||||
```
|
||||
cargo test --features crdt --no-fail-fast \
|
||||
--test gpu_invocation_acceptance --test gpu_initial_target_acceptance \
|
||||
-- ctrl_c_on_launcher_group
|
||||
```
|
||||
|
||||
Outer invocation, the only difference:
|
||||
|
||||
```
|
||||
fg: /home/jeans/build/pmacs-gate-targets/d0a/arms.sh fg
|
||||
bg: setsid nohup /home/jeans/build/pmacs-gate-targets/d0a/arms.sh bg > /dev/null 2>&1 & disown
|
||||
```
|
||||
|
||||
| arm | outer | exit | ok | failed | `SigIgn` | binary hashes | log sha256/16 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| fg | foreground | 0 | 2 | 0 | not captured (no failure ⇒ no dump) | `91f51d0b…`=`0890b78cca22ac1e`, `6b4b8223…`=`ef6ff1c15e11062a` | `aaec01673691479a` |
|
||||
| bg | `setsid nohup … &` | 101 | 0 | 2 | `0000000000001007` | **identical**: `0890b78cca22ac1e`, `ef6ff1c15e11062a` | `c744d85a84cb8683` |
|
||||
|
||||
**The binaries are byte-identical across arms** — same hashes, same
|
||||
head, same target directory, `dirty=0` — so nothing but the outer
|
||||
invocation varies. `arms.tsv` holds the raw rows.
|
||||
|
||||
### Disposition, measured directly
|
||||
|
||||
| context | child `SigIgn` | `SIGINT` |
|
||||
|---|---|---|
|
||||
| foreground | `0000000000001000` | bit 12 (SIGPIPE) only — deliverable |
|
||||
| `setsid nohup … &` | `0000000000000007` | SIGHUP, SIGINT, SIGQUIT — ignored |
|
||||
|
||||
### The first D1/D2 capture, and why it is superseded
|
||||
|
||||
The capture quoted in framing §4c came from `d12.log`, which finished
|
||||
14:10 — **five minutes before `afe3631` committed the diagnostic
|
||||
code** — and ran in the reused `d0a-B` target directory rather than
|
||||
this worktree's. Its signal facts agree with the arms above, but it is
|
||||
**not admissible provenance**: uncommitted tree, foreign target. The
|
||||
arms table replaces it, and `d12.log` is retained only as the first
|
||||
sighting.
|
||||
|
||||
### Historical foreground/background mapping — RECONSTRUCTED
|
||||
|
||||
The claim that "every reduction was foreground and every full sweep was
|
||||
backgrounded" is **reconstructed from this session's transcript, not
|
||||
captured at run time**. No run before today recorded its outer
|
||||
invocation, because none of the harnesses knew it mattered. It is
|
||||
consistent with every observation and with the two arms above, but it
|
||||
is inference, and rows R1–R10 and F1–F7 carry **no outer-invocation
|
||||
field**. That gap is the direct cause of nine revisions spent on a
|
||||
confounded matrix.
|
||||
|
||||
## D0a — EXECUTED 2026-08-19. Verdict: difference NOT captured
|
||||
|
||||
Ten runs, counterbalanced `A B B A A B B A A B`, N = 5 per endpoint,
|
||||
|
|
|
|||
Loading…
Reference in New Issue