docs(active-work): synchronize the reap-ledger lane with revision 2

The revision-2 commit shipped the framing alone. Its ledger edit was
generated by a script that aborted on a line-wrap mismatch before
writing, so `b881bd4` left the lane entry describing revision 1 — three
silent failures instead of four, no in-drain twin, no site-directed
seam, and the withdrawn "Bet 1 ships alone" staging.

Synchronized now: four sites including `final_drain_runtime` and its
distinct reader-cancellation consequence; journey step 9 rather than 8;
the `any_running()` precondition on the shutdown coupling; and the
first PR scoped as seam plus tests.

The failure mode is worth naming, because a partial write is exactly the
shape that leaves a document confidently wrong: the commit succeeded and
reported one file changed, which is easy to read as success when two
were intended.
This commit is contained in:
Levi Neuwirth 2026-07-30 16:22:43 -04:00
parent b881bd4c4e
commit b27b377f67
1 changed files with 22 additions and 9 deletions

View File

@ -333,29 +333,38 @@ compatible.
githubsucks/test-ambient-config-isolation
```
## Reap-ledger silent failures — FRAMING OPEN, revision 1
## Reap-ledger silent failures — FRAMING OPEN, revision 2
- **Branch `reap-ledger-silent-failures`**, worktree
`../pmacs-reap-ledger`, based on `githubsucks/main` @ `22df6ab`.
**Framing only; no code, no PR yet.**
`docs/reap-ledger-silent-failures-framing.md`, revision 1.
`docs/reap-ledger-silent-failures-framing.md`, revision 2; one review
round closed (three blocking, two major, all accepted).
- **Unparked from PR #200's §5.** #200 retired the premise that
justified the ledger's leniency and deliberately changed no
disposition; this lane owns what it refused.
- **Three silent failures, not the two #200 named.** A probe error of
any errno drops the entry and cancels escalation; a failed escalating
- **Four sites, not the two #200 named.** In the persistent ledger: a
probe error of any errno drops the entry and cancels escalation; a failed escalating
`SIGKILL` is marked as succeeded and never retried; and `shutdown()`
discards its own force-kill result the same way — on the path that
exists specifically to stop a leak at editor exit.
exists specifically to stop a leak at editor exit. **Plus the in-drain
twin** `final_drain_runtime`, which collapses every errno to "dead"
while no tick runs; a false "dead" there cancels the readers, so its
failure mode is truncated output rather than a leaked process.
- **The blast radius is exactly what the ledger exists for:** a
TERM-ignoring descendant that outlived its leader with output
redirected. Neither leader state nor reader state can see it; only
group liveness can. A silent drop leaks the one process nothing else
is watching.
is watching. **Journey step 9 (build/test), not step 8** — the ledger
arms only for `spec.group`, which spawn rejects for PTY mode, so no
terminal reaches it; compile mode is the only production caller.
- **`shutdown()`'s final loop terminates when the ledger empties**,
which happens via the same silent drop — so the probe error that hides
a leak can also end the cleanup loop early. That coupling is why the
probe cannot be made strict on its own.
probe cannot be made strict on its own. **Its precondition is
`any_running()` already false**, so the fixture must be a leader that
exited leaving a survivor; any other shape tests the other arm of the
disjunction.
- **None of the three has been observed.** #200 saw an explicit
`SIGTERM` fail in `signal()`, not a ledger call. The premise is
falsified and the path exposed; the occurrence is not evidence these
@ -363,8 +372,12 @@ compatible.
- **They are also untestable today**: `tick_reap_ledger` and
`shutdown()` call `nix` directly and consult no injection seam, unlike
`signal()`'s `forced_kill_errno`. All five existing ledger tests
exercise the success path only. **Bet 1 ships the seam alone and
first** and is worth landing even if the rest is abandoned.
exercise the success path only. The seam must be **site-directed and
multi-outcome**: `shutdown()` calls `self.signal()` before its ledger
force-kill, so a single global slot would be consumed by the wrong
call, and the coupling test needs two pending outcomes at once. The
first PR is the seam **plus** the tests that exercise it — a seam
without tests does not show it reaches the intended calls.
- **Diagnosis first, no disposition change proposed.** Stage A of the
signal lane had three tolerance rules rejected across three revisions
for the same shape of error on the same data structure.