docs: record Stage 2 in the framing, and fence R3 off from R2's retirement
Revision 4 of `docs/macos-ci-signal-integrity-framing.md` records
implementation findings, not a new design round: §4's acceptance is
unchanged and was approved at revision 3. Two of the four findings
correct this document.
- **§1.3 named the right window and the wrong assertion.**
`leader=exited(signal SIGUSR1)` is rendered only on a FAILED `kill`,
and the USR1 cannot be the call that failed — it is the call that did
the killing. The failing call is the SIGTERM that follows, so
`.expect("TERM delivers")` is what blew up, not the `Running` state
check; `ProcessState` never carries that value and nothing ticks
between the two calls. The row's fragment and mechanism were both
right. Why a group-directed TERM found no group is NOT established
here, and the fix does not depend on it.
- **§1.5 scoped the fix one function too narrowly.**
`wait_for_published_file` gates the real-TUI smoke on the identical
predicate. §1.5's note about the bottom-panel helper is about a
different file and correctly refuses creep there; it does not reach
this one.
- The fixture also had an unnamed second dependency: these signals are
group-directed, so a forked `sleep` is an untrapped group member, and
survival depended on bash and dash suppressing the fork for the last
command of a `-c` script.
- R2's witness could not reproduce the row on Linux, so it widens the
pre-trap window deliberately and proves survival by exit disposition
rather than by an absence observed within a window.
`docs/ci-red-signatures.md` gains the sentence R3 needs most: **R2's
retirement does not touch it and must not be read as touching it.** The
hardening changed a fixture and no product code; the same
group-directed `kill` runs. Because the fixture changes the shape of the
signalled group, a change in how often R3 appears would be evidence
about FREQUENCY, not about cause — and its retirement is still a
diagnosis by the process-signal / reap-ledger lanes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bd532dde6b
commit
2d9c6787e4
|
|
@ -131,6 +131,17 @@ field able to disagree — could not be read at all.
|
|||
**Do not treat a red matching this row as environmental.** A green rerun
|
||||
changes nothing about it.
|
||||
|
||||
**R2's retirement does not touch this row, and must not be read as
|
||||
touching it.** The 2026-08-05 hardening changed that test's *fixture* —
|
||||
a readiness gate and an `exec` — and changed **no product code at all**;
|
||||
the same group-directed `kill` runs against the same supervisor. What
|
||||
the fixture change does do is alter the shape of the group being
|
||||
signalled (one process now, where a forked `sleep` could make two), so
|
||||
**a change in how often this row appears would be evidence about
|
||||
frequency, not about cause**. A red carrying these fragments after that
|
||||
date is this same unresolved row, and its retirement is still a
|
||||
diagnosis by the process-signal / reap-ledger lanes.
|
||||
|
||||
---
|
||||
|
||||
## Retired rows
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
# Framing — macOS CI signal integrity: a signature registry, then hardening
|
||||
|
||||
**Revision 3.** Status: **Stage 1 implemented** on
|
||||
`macos-ci-signal-integrity`, PR #215. Scouted against
|
||||
**Revision 4.** Status: **Stage 1 MERGED** as #215 (`main` @ `12f2970`);
|
||||
**Stage 2 implemented** on `ci-signal-hardening`. Scouted against
|
||||
`githubsucks/main` @ `bfb97c6`.
|
||||
|
||||
**Revision 4 records implementation findings, not a new design round.**
|
||||
The acceptance in §4 is unchanged and was approved at revision 3; §8
|
||||
below is what building Stage 2 established, including two places where
|
||||
this document's own account of a mechanism was imprecise.
|
||||
|
||||
**Revision 2 → 3** exists because the implementation discovered a state
|
||||
the contract did not allow, and the contract — not the implementation —
|
||||
was what needed changing. Acceptance 3 offered a binary: carry an
|
||||
|
|
@ -356,15 +361,80 @@ survives on reputation. Stage 2 adds the repetition sets of acceptance 9.
|
|||
|
||||
---
|
||||
|
||||
## 7. Branch plan
|
||||
## 7. Stage 2 implementation notes (revision 4)
|
||||
|
||||
Two PRs, in this order:
|
||||
Stage 2 landed acceptance 6–9 on `ci-signal-hardening`. Four findings,
|
||||
two of which correct this document.
|
||||
|
||||
### 7.1 §1.3 named the right window and the wrong assertion
|
||||
|
||||
`leader=exited(signal SIGUSR1)` is rendered **only on a failed `kill`**,
|
||||
by `signal_failure_report`. The USR1 cannot be the call that failed — it
|
||||
is the call that did the killing — so the failing call is the **SIGTERM
|
||||
that follows**, and the assertion that blew up is
|
||||
`.expect("TERM delivers")`, not the `Running` state check. §1.3's "the
|
||||
record is `exited(signal SIGUSR1)` instead of `Running`" reads as though
|
||||
`ProcessState` carries that value; it does not, and nothing ticks
|
||||
between the two calls, so the state assertion could not have seen an
|
||||
exit. **The row's fragment and its mechanism were both right.** Why a
|
||||
group-directed TERM then found no group is *not* established, and the
|
||||
fix does not depend on it.
|
||||
|
||||
### 7.2 The fixture had a second dependency nobody had named
|
||||
|
||||
These signals are **group-directed**, so the fixture's `sleep 30` — if
|
||||
the shell *forks* it — is an **untrapped member of the same group**, and
|
||||
the USR1 kills it even when the trap is installed correctly. That the
|
||||
old fixture survived at all depended on bash and dash suppressing the
|
||||
fork for the last command of a `-c` script when no non-ignored trap is
|
||||
set. Verified locally, and the suppression is visible in `ps`: with
|
||||
`trap '' USR1` present, the only member of the group is the **shell's
|
||||
own pid** running `sleep` — the shell replaced itself, so there was
|
||||
never an untrapped child to kill.
|
||||
|
||||
**An optimization is not a guarantee.** The fixture now says `exec`, so
|
||||
the group holds exactly one process and the ignored disposition survives
|
||||
by POSIX. This is a second readiness-shaped defect in the same fixture:
|
||||
the test depended on a state it never established.
|
||||
|
||||
### 7.3 §1.5 scoped the fix one function too narrowly
|
||||
|
||||
`wait_for_published_file` — in the same suite, one function above
|
||||
`wait_for_file` — gates the real-TUI smoke's `assert_eq!(…, b"1")` on
|
||||
the identical "`fs::read` succeeded" predicate. §1.5's note that the
|
||||
*bottom-panel* helper already rejects empty reads is about a different
|
||||
file and correctly refuses scope creep there; it does not cover this
|
||||
one. Fixing only `wait_for_file` would have left R4's mechanism live
|
||||
under a different selector, where the registry would have had to judge
|
||||
the recurrence a **new incident**.
|
||||
|
||||
### 7.4 What a witness had to be, in each case
|
||||
|
||||
- **R4's witness reproduces the row.** The file is created empty and
|
||||
filled later, so the window is certain rather than load-dependent, and
|
||||
with the old predicate restored it fails `left: []`, `right: [49]` —
|
||||
the row's two required fragments, verbatim.
|
||||
- **R2's witness could not reproduce the row on Linux**, because the old
|
||||
fixture passes here; the pre-trap window is real everywhere but only
|
||||
macOS ever reported the failure. So the witness **widens the window
|
||||
deliberately** (the fixture sleeps before `trap`) and proves survival
|
||||
by the child's **exit disposition** — `Signaled { signal: "SIGUSR1" }`
|
||||
versus `"SIGTERM"` — rather than by an absence observed within a
|
||||
window, which would have been another timing assumption in a lane
|
||||
about timing assumptions.
|
||||
- Bet 1 is therefore **supported but not yet resolved**: both mechanisms
|
||||
are gone and both are witnessed, but the bet is falsified only by a
|
||||
recurrence in CI, which only time can supply.
|
||||
|
||||
## 8. Branch plan
|
||||
|
||||
Two PRs, in this order — **both now exist**:
|
||||
|
||||
1. **`macos-ci-signal-integrity`** — the registry, the pointer
|
||||
rewrites, the audit, and the rerun rule. No code.
|
||||
2. **A hardening PR** — the `wait_for_file` predicate and the USR1 trap
|
||||
readiness, each with a repetition set. Signature 1 is referred to the
|
||||
async-runtime design question rather than patched.
|
||||
rewrites, the audit, and the rerun rule. No code. **Merged as #215.**
|
||||
2. **`ci-signal-hardening`** — the `wait_for_file` predicate and the
|
||||
USR1 trap readiness, each with a repetition set. Signature 1 is
|
||||
referred to the async-runtime design question rather than patched.
|
||||
|
||||
Quarantine, if it happens, is a third and is scoped by what hardening
|
||||
fails to fix.
|
||||
|
|
|
|||
Loading…
Reference in New Issue