scripts/check-sigint-deliverable is the single checked-in helper, to the
ABI revision 12 fixed: exit 0 safe with no diagnostic, exit 1 ignored
with the canonical wording, exit 2 error with a distinct one. The inner
probe's `|| exit 24` arms are the load-bearing part --- without them a
FAILED kill also falls through to exit 0 and gets misread as inherited
SIG_IGN, which is the one wrong answer the helper exists to prevent.
R-b: scripts/gate runs it before any stage and stops on a non-zero
status, surfacing the helper's stderr unchanged and adding only that no
stage ran. It does not re-derive the classification or supply its own
wording. Plan/print modes skip it, since they run nothing. No override.
R-d: the target test calls the same helper first and panics with
"precondition failed --- this is NOT a teardown defect" plus the helper's
own stderr, instead of reaching the misleading "child did not exit
within 5s". The Linux-only /proc D1/D2 instrument is removed now that
its evidence is portable, taking the platform dependency with it.
Witnesses:
A1 backgrounded gate stops before stage 1 with the ignored
diagnosis, exit 1.
A2 backgrounded direct test reports the precondition failure, NOT
the 5s deadline.
A3 foreground: both target copies pass in 0.16s and the guard is
silent.
A4 mutations measured, each biting its named row --- removing the
trap bites A3 (fg 0->2), treating inner 0 as safe bites A1/A2 (bg
1->0), collapsing error into ignored bites A6 (forced 2->1).
A5 the full default gate passes all 8 stages foreground, and
--print-plan is byte-identical to HEAD's: no stage added,
removed, reordered or made conditional.
A6 forced probe failure yields exit 2 and the error wording, not
the ignored wording.
A7 exercised on Linux x86_64 only, all three outcomes; no non-Linux
unix was reachable, so A7 stays OPEN there and the portability
argument is labelled contract-level, not measured.
Also records that this session's tool-level background mode leaves
SIGINT deliverable while setsid nohup ... & does not --- so the construct
that caused this lane was never necessary for long runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The D1/D2 instrument carried a helper called african_close() that
returned ")". The name was meaningless --- it described nothing about
/proc/<pid>/stat --- and the two call sites duplicated an awkward
rsplit/nth chain around it.
Replaced by d12_stat_field_after_comm(pid, n), which says what it reads
and documents the field numbering it anchors: comm is parenthesised and
may contain spaces and parentheses, so the only safe anchor is the last
")", after which 0=state, 1=ppid, 2=pgrp, 3=session.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The instrument is diagnostic-only: keyed on the PID the test already
owns, snapshotting the test parent, launcher and launcher's children
before the SIGINT, 50ms after, and at the deadline, with per-thread
SigBlk/SigPnd, SigIgn/SigCgt, SigPnd/ShdPnd and PID/PPID/PGID/SID.
Nothing it does changes what the test asserts.
It found the mechanism on the first reproducing run, and the answer is
that I caused the failure.
SIGINT was IGNORED by every process in the target group.
SigIgn=0x1007 on the test parent, the launcher and the probe --- signals
1, 2, 3, 13, and signal 2 is SIGINT. All SigPnd/ShdPnd and every
per-thread SigBlk are zero, so this is ignored delivery, not blocked
delivery; launcher and probe share pgid, so nothing escaped the group.
kill(-pgid, SIGINT) is a no-op, the launcher waits in do_wait for a
child never told to stop, and the 5s deadline fires.
The ignore is inherited from the shell. Measured both ways: a foreground
child has SigIgn=0x1000 (SIGPIPE only), a `setsid nohup ... &` child has
SigIgn=0x0007 (SIGHUP|SIGINT|SIGQUIT). SIG_IGN is inherited across fork
AND survives exec, so it reaches the probe. Controlled experiment, same
command and tree minutes apart: foreground both copies ok, backgrounded
both copies FAILED.
I adopted `setsid nohup ... &` on 08-16 to stop the Bash tool's ten
-minute cap truncating gate runs. That is the "onset" this lane spent
nine revisions investigating. The subset-vs-full distinction was never
real --- every reduction ran foreground, every full sweep backgrounded,
perfectly confounded --- so §4's matrix measured my invocation method.
D0a's both-uniform-red is consistent: it backgrounded both arms.
Consequences: bet 1 is falsified and A5 struck, since an interactive
terminal does not ignore SIGINT and Ctrl-C on a real session works. The
probe's shutdown path is not defective. What remains is a runner
practice and a test that fails obscurely when its precondition is
absent. Framing promoted to revision 11, AWAITING APPROVAL, because the
problem statement has changed and the §7/§8 remedy no longer follows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The mechanical half, riding on the census in the previous commit.
* 342 in-process construction sites in 65 files now take
`new_with_roots` / `open_with_roots` with `iso::roots()`. The isolated
base is a pure function of `CARGO_TARGET_TMPDIR` — no counter, no
`OnceLock` — so two copies of the module in one binary agree instead of
racing, and the tree lives somewhere `cargo clean` owns rather than
leaking into `/tmp` once per run. It is shared deliberately:
materialization is content-gated and idempotent, so a per-test
directory would repeat it ~330 times per run for a byte-identical
result.
* `journey_acceptance` keeps the ambient `EditorState::open`, because
proving the production entry point has a caller is the whole of what
that ratchet is for. Rev 2's "isolated by the environment its binary is
launched with" was not a mechanism — cargo launches each test binary
with the caller's environment, and a binary cannot re-point its own
roots before its tests run. Each test is now a thin parent that
re-execs this binary for its own name with controlled roots, and the
child runs the body against production's call. Two pins guard it: the
child asserts all four roots resolve inside the controlled base, and
the suite asserts against its own source that it has not quietly taken
the seam. The parent also asserts the child ran `1 passed` — a stale
`--exact` filter would otherwise hollow the whole thing out silently.
* The shared spawners take all five storage variables.
`spawn_daemon_process_with_env` set `HOME` and `XDG_CONFIG_HOME` only;
`HOME` is a FALLBACK, so it isolates a root only while the matching
`XDG_*` is unset — the harness's apparent adequacy was a property of
one developer's environment. The PTY spawner backfills whichever of the
five its caller did not pin. The 10 direct `Command::new` daemon and
attach spawns get the same treatment.
Three suites had `mod common;` behind `#[cfg(feature = "crdt")]`;
`common::iso` is needed in every build, so those are ungated. Files that
already pull in `common` reach `iso` through a `use` rather than a second
`#[path]` declaration — loading one file as two modules is
`clippy::duplicate_mod`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Bottom-panel Stage 2B-3, part 1 of 3: the compatibility-preserving v21
activation mechanism and the negotiated `panel_capable` flip.
2B-1 reserved the v21 wire and 2B-2 built the daemon projection behind
it, both dark, because the handshake is server-first: the daemon writes
`Hello` before the frontend has said anything, and a frontend rejects a
`protocol_version` outside its supported range *before* it can send
`AttachRequest`. Advertising 21 there is therefore an incompatible act on
its own, independent of whether one new message is ever exchanged.
So the advertised version does not move. `ADVERTISED_PROTOCOL_VERSION`
becomes a permanent compatibility BASELINE, and the session's real
version is settled one message later, by the frontend:
1. the daemon advertises the baseline (20, unchanged);
2. the frontend answers `requested_protocol_version(baseline)` — its
own `PROTOCOL_VERSION` when the baseline is the current one, and a
verbatim echo of anything older;
3. the daemon records `negotiated_session_version(offer)`.
A shipped v20 frontend echoes 20 and gets a v20 session, byte-for-byte
as before — the real-daemon acceptance that emulates its rejection point
still passes untouched. A current frontend offers up and gets v21. The
`Hello` encoding and value are unchanged, which is why the old frontend
never sees a version it must reject.
`peer_declared_panel_support` gains the arm 2B-2 deliberately left off:
a semantic session is panel-capable exactly when it negotiated
`PANEL_MIN_VERSION` or later. The gate is on placement, not only
transport, so a v6-v20 semantic session keeps the Stage 1 fallback.
The GPU client's `server_protocol_version` splits into
`session_protocol_version` (what the session speaks — every wire gate
keys on this) and `baseline_protocol_version` (what `Hello` advertised).
They now differ in the normal case, and that difference IS the
compatibility property, so both headless probe reports emit both keys and
the two ratchets that read them assert both directions: session 21 AND
baseline 20. Asserting only the session version would pass if the
baseline had been bumped too — the exact incompatible change this
mechanism avoids.
Also fixes a pre-existing `unused_mut` in a `crdt`-gated daemon test,
dark to the standard clippy gate because that gate runs without the
feature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Reserve the additive v21 panel schema without advertising it in the
server-first production handshake. Pin a real shipped-v20 client attach,
make the two aggregate-budget ratchets exactly one byte over, and update
the framing, coherence audit, handoff, and volatile lane record.
Capture the managed daemon's lifecycle facts from the ready report before
waiting for the post-quiescence directory snapshot. If that regression
assertion times out, the acceptance harness can now terminate the daemon
instead of leaving it orphaned.
Drive pmacs --gpu . through the root broker and real managed GPU
connector, keep the session alive through the asynchronous dired
replacement, and assert its canonical listing before daemon reuse.
Expose snapshot count and materialized text through the private
display-less acceptance probe so the public path is observable.
Consume the directory session's later replacement snapshot and assert
the canonical dired header plus a known listing entry before checking
daemon reuse.
Correct the bottom-panel revision-5 recovery branch and advance the
durable handoff to the Journey Stage 1a main anchor.
Replace the stale directory-negative in the GPU initial-target suite
with an explicit snapshot-first readiness path, while retaining all
genuinely malformed and unloadable failure cases. Record the portable
side-quest and bottom-panel dependency state.
Shut down bootstrap sockets on every dispatcher-side failure and reject
frontend events whose session state was never installed. This prevents a
lingering failed client from reaching absent render/size state.
Track target-side CRDT upgrades independently from load/create status so a
deduplicated hidden buffer is published to every existing grid replica. Add
real-daemon regressions for both failure containment and replica publication.
Add protocol-v20 semantic bootstrap and readiness result framing so
`pmacs --gpu FILE` opens the requested path before the GPU window becomes
ready. Keep target identity scoped to the authenticated frontend, preserve
legacy/no-target attach behavior, and publish fresh buffers coherently to
existing replicas.
Carry Unix path bytes and launcher cwd through the root broker, resolve paths
lexically in the daemon, reuse or create buffers without ambient-view state,
and preserve the managed daemon lifecycle from #141. Add focused parser,
wire, lifecycle, hook, isolation, and real-connector acceptance coverage.
Throttle the managed probe after its event channel closes, reject option-like
path operands, and document the connector test seam. Strengthen non-CRDT and
Ctrl-C acceptance so socket side effects and a pre-signal surviving frontend
are exercised, while avoiding cleanup signals to already-reaped daemon PIDs.
Buffer attach events until winit state exists, keep spawned daemon ownership
until the reaper handoff, and detach daemon stderr from the launcher terminal.
Tighten direct GPU CLI guidance and sibling discovery. Strengthen managed
connector unit and process acceptance coverage for transient retries, timeout
reporting, hermetic paths, and deterministic loser reaping.
Add the root --gpu broker, strict GPU entry points, daemon connect-or-start orchestration, process-group isolation, bounded retry, named child reaping, and a deterministic headless lifecycle probe. Cover the complete launch matrix with real subprocess acceptance, make root Cargo runs unambiguous, and document the coherent build and one-command workflow.