Not a new design round. Every bet resolved, and one acceptance turned
out to be satisfiable vacuously.
Acceptance 2's in-drain clause said "the live descendant's named late
output absent" without saying how the descendant stays live. It does
not, by default: `poll_one` TERMs the whole group on leader exit. The
first fixture's marker was therefore absent on both paths, and the pin
would have stayed green with the collapse fixed.
Bets 1 and 2 hold — every site took a directed outcome without
restructuring, and every consequence is reachable, so the lane does not
shrink. Bet 3 resolves the shutdown coupling as real and measured. Bet
4 is falsified exactly as its own clause anticipated: `ProcessEvent` is
keyed by `ProcessId` while the ledger is keyed by pgid and is
deliberately independent of managed records, every production consumer
polls per known id, and `take_all_events` has no production consumer at
all. Q#RL3 is answered accordingly and reporting is parked as its own
lane.
Also records the four sites and the two reusable lessons in
`docs/agent-handoff.md` — that a seam for a background loop has to be
directed, and that an absence assertion is only as good as its
fixture's ability to produce the thing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Refine the framing's in-drain test seam: a one-shot probe error cannot
survive the 50 ms quiescence interval, so the test-only override lasts
for one drain context and remains per-supervisor. Name the in-drain
SIGKILL as a non-observable local fact rather than adding an untestable
seam, and synchronize the revision-three ledger record.
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.
Unparked from PR #200's §5, which retired the premise justifying the
ledger's leniency and deliberately changed no disposition.
Scouting found three silent failures where #200 named two. A probe error
of any errno drops the entry and cancels escalation; a failed escalating
SIGKILL is recorded as a success and never retried; and `shutdown()`
discards its own force-kill result identically — on the path written
specifically to stop a leak at editor exit.
Two facts shape the lane. `shutdown()`'s final loop terminates when the
ledger empties, which happens through the same silent drop, so the probe
error that hides a leak can also end the cleanup loop early — the probe
cannot be made strict in isolation. And all three paths are untestable
today: `tick_reap_ledger` and `shutdown()` call nix directly and consult
no injection seam, unlike `signal()`'s `forced_kill_errno`, so every one
of the five existing ledger tests exercises only the success path.
Diagnosis first; no disposition change is proposed. Stage A of the signal
lane had three tolerance rules rejected across three revisions, each for
concluding something about one entity from something about another, on
this same data structure.
The framing states plainly that none of the three has been observed:
#200 saw an explicit SIGTERM fail in `signal()`, not a ledger call.
Framing only. No code.
`docs/active-work.md` auto-merged without conflict — this branch's lane
entry sits above the folding lane, clear of the six blocks #199 removed.
Verified rather than assumed: all lane headers checked afterwards.
The merge surfaced a stale statement on `main`. The process-signal lane
still read "PR #200 open, four review rounds closed, held for review",
which stopped being true when #200 merged as `a2a92bb`. Its arc is
complete — Stage A #176 and Stage B #200 both landed, and the items its
§5 parked become their own lanes — so under the rule #199 established
(a lane retires when its ARC is done, not when a PR merges) it is
removed and recorded under "Closed since the last snapshot".
Doing that here rather than deferring it: this PR already edits the
file, and leaving a known-false statement on `main` to keep a PR
single-purpose is the wrong trade. The alternative was letting it stand
until the reap-ledger lane, which is scoped next and inherits from the
very §5 list being retired.
Review round 4: three blocking, one major. All accepted.
**`measured_group` was sampled after the failure.** It ran inside
`signal_failure_report`, after the `kill` and after `observe_leader`,
while the framing and the function's own doc both said before. A
concurrent group change would have made the diagnostic report
post-failure state as evidence about the attempted target. It is now
sampled in `signal` before the kill and passed into the report, so the
field describes the target that was attempted rather than the state the
failure left behind.
**The Linux corroboration did not exercise the production lookup.** Its
helper read `portable_pty::process_group_leader` — the accessor this
lane stopped using on the signal path — so `pty_foreground_group` could
have fallen back on every call with every test still green. Forcing it
to always fall back demonstrates the hole exactly: the corroboration
fails, and the injected pin PASSES, because the injected tests supply
the group themselves and structurally cannot detect a broken lookup.
The helper now calls the production lookup, and the corroboration forces
only the kill so the report is built from a real terminal read. The
residual limitation is recorded rather than left to the green: on macOS
`pty_foreground_group` has no end-to-end coverage, because the platform
cannot produce the precondition.
**The framing did not update its own acceptance contract.** Revision 5
recorded Bet 1's falsification in the revision history and in the bet,
and left the normative criterion demanding the real-shell rewrite — the
"implementation quietly diverges from the contract" shape this project
already recorded as a lesson on #191/#188. Acceptance 1 now describes
the injected pin, 1a adds the corroboration and its macOS limitation,
and 4 states the sampling order. The ledger is synchronized: revision 6,
four commits, 4,471 tests, bash armed on Linux only.
**`TargetSource`'s doc had the wrong classification.** Two of the four
variants target the leader pid, not one, and the pid-versus-group split
does not line up with PTY-versus-pipe — which is why the fallback needed
its own variant rather than reusing `LeaderPid`.
Gates: 11 gates, 4,471 tests, zero failures.
One blocking and two major findings, all accepted.
- The isolated gate still inherited `PMACS_STATE_HOME`, which wins over
`XDG_STATE_HOME` in `user_state_dir` (`src/state.rs:47-68`), so
controlling the four XDG storage variables did not isolate state on a
machine that exports it. The contract now names the exact five
variables rather than counting roots.
- Q#TI5 still called self-spawn the durable regression guard after
revision 3 had accepted that it proves behaviour, not continued
adoption. It now names both guards and their different jobs: self-spawn
is the behavioural proof, the checked source inventory is the adoption
proof.
- The active-work lane still said revision 2 and still prescribed an
isolated `XDG_CONFIG_HOME` alone; both are synchronized, and the stale
config-only gating note elsewhere in the ledger is corrected to say it
isolates the observed symptom rather than the gate.
Framing only. No code.
`docs/active-work.md`'s snapshot header was the only conflict; main's
absorption version supersedes this branch's. `docs/agent-handoff.md`
auto-merged, and both sides were verified present afterwards rather than
assumed: the absorption's `4cd4a7b` anchor and its new landed-lane
entries, and this branch's macOS occurrence with its run link.
This lane's ledger entry is updated from "revision 4 awaiting review" to
its actual state — implemented, gated, PR open — since the framing was
approved and both implementation commits have landed on the branch.
The full gate suite is re-run on the merged tree rather than inherited
from the pre-merge head. The absorption is docs-only, so a clean merge
is very likely correct — which is exactly when inheriting a green would
be most tempting and least justified.
Three blocking and two major findings, all accepted. Two were verified
by running the thing rather than reading it.
- **Journey isolation had no executable mechanism.** Cargo launches each
integration-test binary with the caller's environment, and a binary
cannot re-point its own roots before its tests run — the `set_var`
prohibition applies to itself. Revision 2's "isolated by its launched
environment" therefore assumed the external wrapper this lane exists
to delete. Now named and pinned: each journey test re-execs
`current_exe()` with `--exact`, a marker, and controlled roots; the
child runs the real body against the ambient `EditorState::open`, so
the production-entry-point ratchet is untouched while the roots are
contained. The same helper serves the hostile-environment check.
- **One self-spawning test is not a ratchet.** It proves the seam works
and cannot notice a raw `EditorState::new()` added to another binary
later. A checked source inventory, falsifiable by adding an ambient
constructor, is now acceptance 12.
- **The root list and the gates disagreed with the audit.** The scope is
now explicit — bootstrap STORAGE roots only (config, data, state,
cache). `HOME`'s non-storage semantics are excluded by decision, not
omission: `expand_tilde` resolves user-entered `~` and
`find_file_acceptance` pins it deliberately, so redirecting `HOME`
would retarget a user-facing feature. `XDG_RUNTIME_DIR` addresses
sockets, not stored data.
- **The gate instruction itself was insufficient**, and this is the
finding with immediate consequences: isolating only `XDG_CONFIG_HOME`
stops the reads and leaves the write path open. Every local gate run
in this repository today had that hole.
- **The count was one high and the ledger overstated it further.** 65
files call the constructor; 66 mention it. The 66th, `m5_6_acceptance`,
mentions it only to say it deliberately does not use it — making it
the third place in the suite documenting the `cfg(test)` gap. The
ledger's "all 96 test files load the real config" was false.
- **The recovery command did not work**, verified by running it:
`git worktree add <path> <remote-only-branch>` fails with `fatal:
invalid reference` after a bare fetch. Replaced with the explicit
tracking-branch form.
Framing only. No code.
Four blocking and two major findings, all accepted, all verified in the
code before acceptance. The lane's scope changes: it is about ambient
roots, not about init.lua.
- The read-only assumption was already false. `EditorState::new`
materializes bundled packages unconditionally and before config
loading, into `XDG_DATA_HOME` or `$HOME/.local/share`, and
`materialize_all` creates directories. Confirmed on the development
machine: `~/.local/share/pmacs/builtin-packages/` exists with v0.1.0
and v1.0.0. This upgrades the lane from "local gates lie" to "tests
write into real user data".
- The population count was wrong and its stated method did not match the
command that produced it: 18 from a grep for `Editor::new`, which does
not match the real constructor `EditorState::new`. 66 of 96 files
construct an editor directly.
- File-level classification cannot work: 5 files are both in-process and
spawned. Classification moves to construction sites.
- The seam must cover `EditorState::open`, which calls `Self::new()`
directly, while `journey_acceptance` requires that exact public entry
point to avoid a dead-production-path test. Resolved by isolating
journey through its launched environment rather than a different call.
- Isolated construction must still return `is_init_complete() == true`.
Config loading and `set_init_complete()` share one block, and
`m8_2_acceptance.rs:75` documents its dependence on integration-test
construction being init-complete — the `cfg(test)` gap is load-bearing
in that one respect.
- Revision 1 both proposed and parked a hostile-config CI leg. Resolved
in favour of a test-binary self-spawn, which travels with the test
rather than the workflow file, and which now also asserts the hostile
root is unmodified afterwards.
- The lane is recorded in `docs/active-work.md`, which revision 1 omitted
despite the volatile-work protocol requiring it.
Also documents six ambient roots where the shared daemon harness sets
two, and why setting HOME only isolates a root whose XDG variable is
unset — the harness's apparent adequacy is a property of one developer's
environment.
Framing only. No code.
Eight PRs merged on 2026-07-29/30 (#188, #190, #191, #194, #195, #196,
#197, #198) and `docs/active-work.md` had drifted to 1,854 lines, most of
it describing work already on `main`. Rule 4 permits removal once a
lane's facts reach `docs/agent-handoff.md`; this does both halves in one
commit so the precondition is satisfied rather than deferred.
Handoff §1: the head-of-`main` anchor moves from `6c9e765` to `4cd4a7b`
and its date from 2026-07-28 to 2026-07-30. New entries for the three
implementation PRs that had no record — #190's pre-filesystem delete
refusal, #191's Stage 1 adoption of the generated-buffer contract, and
#196's dired Stage 2a — plus #188 recorded as landed rather than
proposed.
Ledger: six merged lanes removed (781 lines). Two lanes are NOT removed
and are rewritten to their remaining plan instead, because their arcs are
unfinished: generated-buffer immutability (Stage 1 merged, Stage 2 not
started) and bottom-panel (Stage 2 complete, Stage 3 ahead). The
bottom-panel block said so in its own text — "this lane is not removed at
2B-3's merge" — so a removal keyed on "the PR merged" would have
discarded live planning. The header now states the rule as ARC-done
rather than PR-merged.
Two method notes are recorded in the ledger because they nearly cost
real work:
- A census by PR number is a proxy, not a measurement. Counting `#NNN`
in the handoff reported that five of these lanes had no record at all.
Counting by content found most already documented, with the real gap
being the implementation PRs while their framings were recorded. The
absorption written from the first count would have duplicated existing
entries.
- #194 and #195 keep their lessons in §3 and §5 rather than §1, which is
why a number search finds them once each. Sufficient under rule 3:
durable knowledge needs a home, not a required section.
Docs only. No code, no protocol change.
Integrate canonical main, restore PTY errno capture through the safe
filedescriptor duplication bridge, narrow the macOS occurrence to what
it actually measured, and make the job-control fixture foreground and
discriminating.
Record the portable framing lane and exact recovery commands in the
active-work ledger.
`docs/active-work.md` was the only conflicting file, in the same shape
as #191's: `main` inserted the generated-buffer Stage 1 lane immediately
above the bottom-panel header this branch had rewritten. The resolution
keeps both.
Each side's newer text wins where that side owns the fact: `main` carries
the corrected #188 status (MERGED/APPROVED, replacing "OPEN, PROPOSED —
do not implement"), and this branch carries the bottom-panel lane's 2B-3
state and the newer snapshot date, replacing main's "2B-2 MERGED; 2B-3 IS
NEXT" and its 2B-2 boundary paragraphs.
Verified: no conflict markers; every line absent from either parent is a
deliberate supersession by the other, enumerated and checked one by one
rather than counted; all three lane headers present exactly once.
`docs/active-work.md` was the only conflicting file. #196 added the dired
Stage 2a lane at the position this branch had used to relabel the #188
framing lane header; the resolution keeps both, changing neither side's
wording.
`src/editor_core.rs` auto-merged. Both lanes touch it, so a clean
textual merge is not evidence of a clean semantic one — the gate suite
is re-run in full on the merged tree rather than inherited from the
pre-merge head.
Resolution verified for line loss in both directions: the resolved file
differs from `main` only by this branch's own authored edits, and
differs from this branch only by additions taken from `main`.
Review round 2, three findings.
setsid is util-linux, not coreutils, and the standard `cargo test --lib`
gate must not hard-fail on a tool the README does not declare -- a
minimal or BusyBox container would fail without ever testing pmacs. The
hard assert becomes skip-unless-armed via PMACS_REQUIRE_SETSID, which is
the pattern the silent-skip lane already established, so the test cannot
quietly report `ok` having never run where the tool is guaranteed. CI
arms it on Linux; README declares it. Both arms verified against a PATH
with setsid genuinely removed: unarmed skips with its message, armed
FAILS with the diagnostic.
The durable causal account was wrong, and this corrects it in the
framing, the handoff and the ledger. basedpyright's console script runs
bundled node through `subprocess.run` and WAITS
(nodejs_wheel/executable.py:50, verified in the installed 1.39.6). It
does not exit at spawn. What orphans node is pmacs: `shutdown()` SIGTERMs
the recorded pid -- the Python wrapper -- which dies without forwarding
the signal, leaving node at PPid 1 holding the pipes. The refutation was
already in hand: the initialize handshake succeeds, which a wrapper that
exited at spawn could not have done, and the PPid 1 observation was taken
after shutdown had killed it.
The fix is unaffected -- the deadlock and its bite are unchanged -- but
the parked follow-up changes target: not "tolerate servers that
self-orphan" but "stop orphaning them", i.e. signal the process group
rather than a wrapper pid that swallows the signal. Framing section 5 P2
restated.
Also corrects a stale CI-ordering claim: the handoff said pyright must
stay unarmed until the timeout lane lands, but #195 is this PR's base and
gave every job a timeout-minutes. The one live reason is that CI does not
install basedpyright at all. The ci.yml comment asserting the job has no
timeout-minutes was stale for the same reason and is rewritten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Integrated main (#195) first so the lane block is written on top of it
rather than conflicting with it — the one conflict this file always has,
paid at the merge that was happening anyway.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
The four findings were one shape — a failure that left state wrong and
told nobody — so the lane records them as one lesson rather than four
bugs: every one was a `pcall` or a discarded return value, and each
looked like defensive coding.
Also records the round-1 pin that passed with its own bug restored
(acceptance 53's attribution assertion was satisfied by the deleted
path's basename appearing elsewhere in the same message), the refreshed
gate numbers, and that `main` was re-measured after the round and had
not moved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Round 2 facts for the lane-4 entry: the dash/bash divergence that
falsified the `<&0` form and how the positive control caught it, the
eleven-suite Bet 2 result, and the evidence that acc28 on macos/lua54 was
a flake -- a rerun of the same job on the identical head, not an
assumption.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Integrated late, immediately before push, per the ledger-contention
rule. Records the measured base, the recovery command, the defect, the
reproduce-first diagnosis method, the full gate table with the
revert-verified bite, and what is deliberately parked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Handoff section 1 gains the 2B-3 bullet and the protocol anchor moves to
v21 with the rule that matters stated once: advertise the baseline,
negotiate up from the frontend's AttachRequest, and reserve moving the
advertised version for a change that cannot be expressed additively at
all. 2B-1's forward-looking constraint is marked discharged rather than
deleted, because its acceptance still passes unchanged and that is the
evidence.
COHERENCE section 14 grades the bottom/side panel primitive as complete on
BOTH frontends rather than 'Stage 2 pending its own framing', section 20
P5 follows it, and the section-19 protocol bullet records that the v21
family is live in production without an incompatible handshake change.
The active-work lane is rewritten to the shipped slice, including the
rejected activation alternatives and why the server-first shape forces
each one out, and the one-way compatibility window it leaves open.
The 2B-2 acceptance suite's header said production keeps panel_capable
false for every semantic session. That is no longer true, and its
assertions did not change — which is the point, so the header now says so
rather than being quietly left stale.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Rides this branch rather than a standalone ledger PR: with several PRs
open, a lane written on `main` for work that lands elsewhere
re-conflicts on every merge.
Records the measured merge-base as pasted output, what 2b and 2c still
owe so the split boundary is auditable, the two re-pinned m4 rows, the
one framing claim found wrong, the two bites that were vacuous as
specified and why, the gate numbers, and the §16 ownership warning
against starting Journey Stage 1b while this is open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
One conflict, `docs/active-work.md`, with three strands rather than the
usual one: main gained #190's lane, this branch carries its own Stage 1
lane and a relabel of the #188 framing lane, and main had removed the
documentation lane while this branch still had it.
Resolved by construction. Main's file taken whole; this branch's Stage
1 lane reinserted at its own position ahead of the bottom-panel lane;
this branch's relabelled framing lane ("MERGED AS PR #188") kept in
place of main's stale "OPEN, PROPOSED" version; main's removal of the
documentation lane preserved.
Verified against both parents rather than by inspection: the Stage 1
block is byte-identical to this branch's, the documentation lane is
gone, no conflict markers survive, and the update-protocol rule 6 seam
check finds no double blanks.
Note for whoever absorbs next: main now carries three lanes describing
merged PRs (#190, #188, #194). This merge keeps this branch's more
accurate labelling of the #188 one but does not remove any of them ---
rule 4 permits removal only once durable facts reach
`docs/agent-handoff.md`, and none of those three PRs touched it.
Pure insertion: main's ledger taken whole with this lane's 3a block
placed before the parked lane. Verified against both parents --- zero
lines removed relative to main, the lane-3a block byte-identical to
this branch's, no conflict markers, and the update-protocol rule 6 seam
check clean.
Deliberately NOT absorbed here, because absorption is not mechanical
and this PR is approved on its current content. `main` now carries
THREE lanes describing merged PRs: the resource-op delete guard
(#190), the generated-buffer immutability framing (#188), and this
arc's own silent-skip arming (#194). Rule 4 forbids relabelling any of
them and permits removal only once their durable facts reach
`docs/agent-handoff.md` --- and none of the three PRs touched that
file, so all three absorptions are genuinely owed rather than
overlooked.
Two belong to other arcs. The third (#194) belongs to this one, and
its durable fact is not yet written down anywhere: that
`PMACS_REQUIRE_*` arms an otherwise-vacuous skip, and that
basedpyright stays unarmed until the reader-join hang and the CI
timeouts both land. That wants a handoff bullet, which is content
rather than a merge resolution.
P3 --- required status checks are name-coupled to job names, and a
required context that no longer exists does NOT fail. It leaves every
PR pinned on "Expected --- waiting for status" forever, which is `main`
becoming unmergeable by policy rather than by a red run. Three of this
lane's own deferrals will do exactly that: the macOS matrix trim
removes two contexts outright, and nextest or the serial/parallel split
rename or add jobs.
The rule is now in the ledger entry --- any job rename, removal, or
matrix change updates the branch-protection required-checks list in the
same motion --- and it is recorded HERE deliberately, because this is
the single entry that both enabled protection and named the lanes that
will invalidate it. Arming the warning anywhere else would separate the
trap from the thing that sets it.
P4 --- the rewritten top comment said "everything else keeps 25 against
a sub-4-minute observed max" and dropped the clause noting that
`m6-perf-gates` keeps its own tighter 15. Restored. Worth the fixup in
a change whose entire subject was comments matching reality.
Beyond the PR, and taken here rather than deferred: `TEST_IMPROVEMENT.md`
on `main` still said "no branch protection on `main` (verified via API:
404, so every job is advisory)" and listed §5.1 as open. Both went
stale during this session, and THIS lane is what made them stale, so it
carries the correction rather than leaving it for whoever touches the
file next. Struck through in both places rather than rewritten: the 404
was a true reading at audit time, and the document is the arc's scoping
record, so what changed is more useful than a clean-looking present
tense. Note also that protection shipped wider than §5.1 proposed ---
all 12 contexts required, not the cheap-jobs-only starter --- which the
correction states.
Verified: YAML parses; the seam check from update-protocol rule 6 finds
no double blanks; `git diff --check` clean.
P1 --- the ceiling was justified against the wrong number. Revision 1
cited "~14.6 min, ample headroom", which was one reading quoted as a
property, and this ledger's own rule applies to it: a census is a
reading, not a constant. Re-measured over two windows --- 17 min max
over 25 runs, 15.8 over 12, both macOS/luajit, every other job under
4 --- so a flat 25 was about 1.5x the observed tail, not "ample".
Two facts shape the fix. `timeout-minutes` counts EXECUTION, not queue,
so the 33-minute wall-clock run in that window executed its longest job
in 17 and no run in observed history would have been killed by either
value. And the real exposure is the case no window contains: a cold
cache. A stable-toolchain bump invalidates Swatinem's key on every leg
at once, and a cold macOS debug build plus suite is the plausible way a
HEALTHY run overruns --- presenting as four legs timing out
simultaneously the day after a Rust release.
So the test job takes 35 (~2x its observed max) and the rest keep 25
(~6x theirs), and the diagnosis is written into the workflow BEFORE the
event: simultaneous four-leg timeouts after a toolchain release are a
cold cache, not a hang; a single leg timing out beside passing siblings
is the hang case these ceilings exist to catch. 35 still beats the
360-minute default by an order of magnitude, so the basedpyright
arming this gate unblocks is unaffected.
P2 --- §5.1 was missing from both lists, and review was right that the
omission matters. But its premise had gone stale, which is worth
recording rather than quietly working around: branch protection is ON.
It was enabled earlier in this session, and I re-verified against the
API rather than trusting either the review or my own memory of doing
it:
{"enforce_admins":false,"force_push":false,
"required_checks":12,"strict":false}
Recorded in the ledger as DONE with the settings and the reasoning for
each --- `strict` off so a PR need not rebase every time `main` moves,
`enforce_admins` off so the user keeps an override. This also settles
the concurrency comment, which justifies exempting `main` pushes by
appeal to "the branch-protection record": that record exists, so the
justification is real rather than aspirational, and no softening is
needed.
P3 --- the double blank line before the parked lane, third PR running.
Fixed, and added to the ledger's own update protocol as step 6, since
fixing the instance three times has not stopped it: a block ending in a
blank line inserted above a heading already preceded by one leaves the
seam, and it survives review by sitting beneath the level anyone reads
at. The rule now names the check.
Verified: YAML parses; ceilings are 25 except test at 35 and
m6-perf-gates at its tighter 15; the seam check finds no double blanks
anywhere in the ledger; `git diff --check` clean. Workflow and ledger
only.
Keep both active-work lanes while taking the silent-skip arming and
generated-buffer framing changes from current main. The resource-op lane
retains its round-2 fixes and updates its recorded merge-base.
Record the path-normalization and partial-first-operation fixes, their
acceptance criteria and bite pre-images, and the green round-2 gate
results in the active-work ledger.
Lane 3a of the testing arc --- the three cheap, deterministic items of
`TEST_IMPROVEMENT.md` §5-6. The larger ones (nextest, the
serial/parallel split, a parallel canary leg, the nightly cron, the
macOS matrix trim) are deliberately NOT here: each changes what CI
certifies or how it runs, and each deserves its own decision rather
than riding in on a timeout patch.
`timeout-minutes` on every job (§5.2). Measured before changing rather
than assumed: SEVEN of eight jobs had none and inherited GitHub's
360-minute default; only `m6-perf-gates` had one, at 15. So a single
hung test burnt six hours --- times four on the test matrix --- and
reported nothing useful at the end of it. Set to 25 against a measured
~14.6 min critical path (macOS/luajit), which leaves ample headroom for
a slow runner while catching a hang in under half an hour.
This is the gate that has to exist before `PMACS_REQUIRE_PYRIGHT` can
ever be set. Lane 2 left basedpyright unarmed *because* this did not
exist; the two decisions are the same decision, half a lane apart.
`concurrency` with `cancel-in-progress` (§6.1), scoped to pull
requests. This project rebases heavily --- the ledger re-conflicts on
nearly every merge --- so branches take several pushes while earlier
runs are still going, and macOS minutes are both the expensive ones and
the critical path. Pushes to `main` are deliberately exempt:
`github.event.pull_request.number` is empty there, so the fallback keys
those runs by SHA and none can cancel another. Cancelling a `main` run
would leave the branch-protection record ambiguous about a commit that
has already landed, which is the one place the saving is not worth
having.
`-p pmacs-protocol` clippy (§5.7). The root-package clippy never
covered it --- the workspace default member is only `pmacs` --- so a
warning introduced through a protocol-only change would reach `main`
unseen. Verified passing locally BEFORE proposing it, so it cannot turn
CI red on arrival.
The timeout rationale is stated once above the job list rather than
copied onto each job: the first draft duplicated a seven-line comment
across seven jobs, which is the same degraded-copy shape this arc keeps
removing elsewhere.
Verified: YAML parses; all eight jobs carry a timeout (seven at 25,
m6-perf-gates keeping its tighter 15); `cargo fmt --all --check`,
`clippy -p pmacs-protocol` and `clippy -p pmacs-gpu` all exit 0;
`git diff --check` clean. The diff touches `ci.yml` and the ledger and
nothing else, so no code gate is affected.
One conflict, in `docs/active-work.md`, with an extra strand: main
gained #188's lane while this branch had removed the documentation
lane, so the two sides disagreed about a region neither had edited
against the other.
Resolved by construction rather than by editing markers --- main's file
taken whole, the documentation lane removed, this lane's block
inserted before the parked lane. Verified against both parents: exactly
26 lines removed relative to main, which is the documentation lane and
nothing else, and the lane-2 block byte-identical to this branch's.
Records, without fixing, that #188's own lane now reads "OPEN,
PROPOSED" on a merged PR. Rule 4 forbids relabelling and allows removal
only once the facts reach `docs/agent-handoff.md`, which #188 did not
touch. That absorption belongs to the immutability arc's next PR, not
to a testing lane reaching across into it.
Bring the approved generated-buffer immutability framing onto the Stage
1 branch and update both active-work lanes to the landed #188 state.
Main @ 27b1185 changes documentation only relative to the prior base.
Record the revision-7 selection and acceptance reconciliation, the
exact code checkpoint, the non-vacuous fan-out bite, and the final gate
results. Keep PR 188's proposed status and merge ordering explicit.
P1 --- the workflow contradicted this lane's own claim. `components:
rust-analyzer` rode the shared `dtolnay/rust-toolchain` step, which
matrixes over ubuntu AND macos, so the binary would have been present
on both. PRESENCE, not PMACS_REQUIRE_LSP, is what decides whether a
gated test body runs --- the unset variables on macOS only meant
absence would be tolerated there, not that the tests stay skipped. Two
rust-analyzer tests would therefore have executed on macOS for the
first time ever, on the legs that are simultaneously the CI critical
path and the documented flake surface, one of them doing real indexing,
none of it covered by the Linux-only local runs behind this lane. Moved
into the Linux-gated step so the text and the workflow agree.
P2 --- the ledger promised a base and pasted nothing. Worse than the
review knew: a script was run to substitute it, reported success,
matched no text, and the result was never re-read. The claim shipped on
the strength of a tool's exit status. The entry now carries the pasted
base and a recovery command, and records the lesson in the terms this
ledger keeps relearning --- asserting a measurement is not making one,
and a tool reporting success is not the measurement either.
P3 --- tool versions pinned (gopls v0.16.2,
vscode-langservers-extracted 4.10.0, yaml-language-server 1.15.0), so
CI no longer drifts with upstream publishes and a break has a commit
here to bisect against. `tests/support/` now states why it exists
beside `tests/common/`: the latter re-exports daemon and PTY
machinery, and pulling that into three binaries that spawn neither to
reach a six-line environment check is the wrong trade. Recorded as a
cost, with the rule that a third such directory means consolidating
rather than continuing.
Also recorded, because §1.2 is NOT fully closed by this lane: the
guards arm the ENTRY skip only, and m4_acceptance's mid-test
"workspace likely still indexing; skipping" survives --- so even armed,
that test's one assertion can still vanish under load, which is exactly
when a regression would show. Mid-test skips want their own pass. And
the follow-up this lane creates: removing the documentation lane
removes the only pointer to githubsucks/handoff-2026-07-20, so that
branch needs deleting after merge or nothing will ever mention it
again.
P4 --- the consolidated lua skip message names PMACS_TEST_LUAJIT again;
the `or_else(locate_shell("luajit"))` path still honours it and the
escape hatch had become undiscoverable. Double blank line before the
parked lane collapsed.
One gate note worth carrying rather than burying. A `PMACS_REQUIRE_GPU=1
cargo test -p pmacs-gpu` run failed once during this round, naming the
`--bin pmacs-gpu` target, and my gate chain still printed a clean
summary because `cmd | tail -2` in an `&&` chain returns TAIL's exit
status, not cargo's. Four subsequent runs pass 202/202 and the failure
has not reproduced, so it is recorded as observed-and-unreproduced
rather than explained. The masking is the durable part: a gate summary
assembled through a pipe can report success over a failure.
Gates re-run after the fix: fmt; clippy -D warnings; --lib 1863; --lib
--features crdt 2048; m4_acceptance 121; m6_5 11; m6_8 8; required GPU
202 (x4); git diff --check clean.
Absorb #193 without rewriting the reviewed revision 6 or revision 7
commits. Resolve docs/active-work.md by retaining PR #188's proposed
lane and accepting main's removal of the landed bite lane.
Record that the intervening base change touches no product source, so
revision 7's source readings remain current.
Lane 2 of the testing arc (`TEST_IMPROVEMENT.md` §1.2, §5.4).
The shape being fixed reports GREEN when the tool is missing:
let Ok(_) = which_binary("gopls") else {
eprintln!("gopls not on PATH; skipping");
return;
};
CI installed none of these tools, so a block of real-language-server
and multi-shell tests had never once executed their bodies while
reporting success on every run. A suite that cannot distinguish
"passed" from "never ran" is worse than a missing suite, because it
reads as coverage in exactly the place someone would go looking for it.
The fix is this project's own pattern rather than a new one:
PMACS_REQUIRE_GPU already turns a missing adapter into a hard failure
for the headless render job. This adds PMACS_REQUIRE_LSP,
PMACS_REQUIRE_SHELLS and PMACS_REQUIRE_LUA, and the CI step that
installs the tools they promise. Per-tool variables rather than one
blanket flag, so a tool that must stay unarmed keeps that decision
visible at the call site instead of buried in a workflow file.
basedpyright is deliberately NOT installed and NOT armed. Its test has
no timeout and hangs forever; the root cause is the non-interruptible
reader-thread join in `RuntimeHandles::drop`, already a named deferral
in `src/process.rs`, and the `test` job has no `timeout-minutes`.
Arming it today would trade a vacuous green for a six-hour hang across
four legs. PMACS_REQUIRE_PYRIGHT exists and is never set, so the flip
is one line after the hang fix and the CI timeouts land.
A trap found while writing the workflow rather than after: the natural
Actions idiom
PMACS_REQUIRE_LSP: ${{ runner.os == 'Linux' && '1' || '' }}
sets the variable to the EMPTY STRING on every other platform, and
`var_os(..).is_some()` is true for `Some("")`. That would have armed
the guard on precisely the runners with none of the tools installed
and failed every one of them. The helper treats empty as unset, which
makes the common spelling safe instead of subtly wrong.
The helper is SHARED via `#[path = "support/mod.rs"]` rather than
copied into three test binaries. `m6_8_multi_repl_acceptance.rs`
carried a comment saying cross-test-binary sharing "would need a
fixture crate"; it does not, and a correct helper in one file beside a
degraded copy in another is this suite's most repeated defect.
Verified by execution in all three states, using a tool genuinely
absent from this machine (vscode-json-language-server): unset skips
green; armed fails hard, naming the CI step that should have installed
it; empty string skips green. On `main` the armed state cannot fail at
all, because no guard exists.
And the question none of this could answer until now --- whether the
tests pass when they actually run --- is answered: armed locally, 11
m6_5 and 8 m6_8 REPL tests are green, and all six real-LSP tests
(clangd x2, gopls x2, rust-analyzer x2) pass individually. The coverage
was real the whole time. It just never ran.
Linux only for now, deliberately: macOS needs the brew equivalents and
roughly doubles install cost on the slowest matrix leg. The variables
stay unset there, so those tests skip cleanly as before.
Also removes the documentation lane from the ledger. Its disposition
was left undecided pending confirmation that its branch carried
nothing unique; measured, `githubsucks/handoff-2026-07-20` is 1 ahead
and 365 behind, and its whole unique diff is four doc files at 42
insertions against 88 deletions --- merging it would REVERT current
documentation. The section asked whoever confirmed that to remove it.
Gates: fmt; clippy -D warnings; --lib 1863; --lib --features crdt
2048; m4_acceptance 121 (unarmed, per CLAUDE.md); m6_5 11; m6_8 8;
PMACS_REQUIRE_GPU=1 -p pmacs-gpu 202; git diff --check clean.
The entry pasted `4 0`, measured immediately before the commit that
wrote it; at the pushed head the same command said `5 0`. An
ahead-count is self-referential in the file that carries it, so the
entry now pastes only the merge-base — which is stable — and says to
run the count on demand. Behind-count stays the thing that matters,
and the merge-base tells you whether it can still be zero.
Close review round 6 by moving CRDT quarantine to common divergence
detection, guarding every snapshot and later owner-write route, and
making the acceptance pre-image classifications honest.
Advance the active-work lane to the actual integration checkpoint and
replace the obsolete delete-to-insert recovery rule.
The count in the previous commit was measured against `64883eb` and
was stale the moment that merge landed. Re-measured and pasted as
command output. `main` moved twice during this round; the entry now
says so, and says why a stale base matters under branch protection.
Resolution is a deletion, not a rewrite: `main` REMOVED the
`scripts/bite` lane entry when #192 landed, so this side drops it too
rather than resurrecting a merged lane. This lane keeps its own entry;
its pasted position measurement is re-taken in the next commit,
because the merge itself moves it.
The previous commit pasted `git rev-parse`/`git log`/`merge-base` output
showing `main` at `64883eb` and "main IS integrated". #193 merged between
writing that and pushing it, so the pasted output was false in the tree
that carried it --- and "main is integrated" is exactly the kind of claim
this review round was about: the tree asserting something the record does
not support.
Re-measured and re-integrated, and the bullet now says the thing the
first attempt got wrong. Pasting command output is necessary and NOT
sufficient: a base measured before the last push is a claim about a tree
nobody has, so it has to be re-measured at push time and treated as
expired on sight. `main` moved three times inside this one lane
(#187 -> #192 -> #193).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Four findings, all reproduced by the reviewer, all accepted. Two of
them are one defect class — a guard whose scope was REASONED ABOUT
rather than enumerated — so both are recorded in the framing's new §9
together with a sweep of every other place this lane decides something
is "affected".
P1 — the delete preflight broke ordered resource operations
(§9.3). Every delete was judged against the filesystem's INITIAL
state, at plan-construction time, so a valid `create X -> delete X`
was refused with a fabricated NotFound about a path the batch was
about to create; likewise `rename A -> B -> delete B`. This was a
regression this lane introduced, not a pre-existing defect.
Decision: DEFER, do not simulate. A delete whose target is related by
component-aware path containment to a path an EARLIER op in the same
plan creates, renames onto, renames away from, or removes is not
judged at plan time; the primitive judges it when it runs. Q#RD3
already calls this check a filter, not a transaction, so declining to
judge is inside its contract and refusing a legal batch is not.
Simulating instead would mean modelling filesystem presence AND the
registry's path bindings across create/rename/edit — the transaction
Q#RD3 declines to build — and a wrong simulation emits false `clear`
verdicts, which is the dangerous direction. `edit` ops are excluded
from the deferral set on purpose: an edit changes no path's existence,
so it can only turn a plan-time `clear` into a primitive-time
refusal, which Q#RD3 already documents and accepts. The
buffer-and-filesystem half therefore still fires early for any target
no prior op touches, which is what criterion 11c pins.
P1 — the production-boundary acceptances are landed (§9.5).
Criteria 11, 11a-11d, 12 (both directions), 13 and 15 now drive a real
`pmacs_fake_lsp` child over a real transport. One parameterized mode,
`applyeditplan`, replaces the eight the framing named: it reads its
whole WorkspaceEdit from a test-written file and publishes the
client's response to a sink, so each of the eight fixtures sits next
to the assertions that depend on it instead of being mirrored across
two files. Fail-closed — an unreadable plan sends no applyEdit and
reports itself through the sink, so a broken fixture cannot read as a
pass — and the sink is written `.part`-then-rename so a polling reader
never sees a partial record. There is no skip-and-return-ok arm
anywhere: `fake_lsp_path` resolves through `env!("CARGO_BIN_EXE_...")`,
a compile-time constant, so a missing binary is a build failure.
P1 — mid-batch failures were misreported as complete aborts (§9.4).
`apply_workspace_edit` now returns `nil, message, applied_op_count`,
and ONE renderer serves both the user-facing status line and the
server's `failureReason`, so the two cannot disagree. All three
callers are updated, not only the rename one.
P2 — non-recursive deletes inspected descendants (§9.2). `recursive`
is now a parameter of the shared query and descendant matching is
reserved for recursive deletes. The old doc comment argued at length
for the wrong behaviour and is replaced by the counterexample that
falsifies it: a modified buffer at `tree/gone.rs` whose file is
already gone blocked a non-recursive delete of the now-EMPTY `tree/`,
an op that would have succeeded and that removes none of that
buffer's contents. This narrows the Q#RD6 query #171 adopts.
Criterion 3's stated bite: fixed by fixing the SETUP, not the doc.
The first commit's test comment carried a correction saying the
framing's wording was wrong. It was wrong only against that setup —
and §9.2's narrowing would then have left the setup with no bite at
all, since a non-recursive delete no longer inspects a descendant.
So the buffer is now bound to the EXACT deleted path: a file is
opened, then replaced on disk by a non-empty directory, and
`remove_dir` fails with ENOTEMPTY deterministically under any uid.
Both of the framing's stated pre-images now bite, so the framing
needed no amendment there. The correction is recorded in §9.1 rather
than only in a test comment, which is where the review asked for it.
WHY THE SHIPPED SUITE PASSED WHILE FINDINGS 1 AND 4 WERE LIVE — two
coverage facts for the next lane. Every delete criterion drove the
PRIMITIVE directly, so nothing in the suite ever built a multi-op plan
and the preflight's plan-time behaviour had no test at all; the only
batch test, `m4_15`, happens to delete a path no earlier op touches.
And every recursive-delete criterion (7, 8, 9) passes `recursive =
true`, while every non-recursive one binds its buffer to the exact
target, so no test in the suite ever combined a non-recursive delete
with a descendant buffer — the exact cell finding 4 lives in.
Sweep, per the review's request. Seven sites decide something is
"affected"; the table is in framing §9.7. Three were the defects
above. Two are unchanged by design and named so they are not mistaken
for oversights: phase-4 reconciliation compares paths RAW via
`BufferRegistry::find_by_path`, which Q#RD10 pins as "exactly today's
behaviour" and which correcting would widen reconciliation — the one
thing Q#RD5 forbids; and `delete_verdict` stats the raw path while
comparing normalized ones, a latent inconsistency whose every branch
fails safe and which matches the primitive's own `remove_file`. Two
are consistent: the `_delete_verdict` binding defaults `recursive` and
`ignore_if_not_exists` the same way the primitive does, and the
deferral set is enumerated (create: 1 path; rename: 2; delete: 1;
edit: excluded, with the argument written down) rather than reasoned
about. Nothing else in the lane decides an affected set.
Bites. Every row was RUN, with the positive control `scripts/bite`
gained in #192 (merged into this lane), and every ref-based row below
reports `OK (assertion)` rather than `OK (COMPILE)`. `1873be6` is this
lane's own first commit: findings 1, 3 and 4 were introduced by it, so
`main` cannot falsify their pins.
rd11a builtin/runtime/lsp.lua @ main OK (assertion)
rd11b builtin/runtime/lsp.lua @ main OK (assertion)
rd11c builtin/runtime/lsp.lua @ main OK (assertion)
rd11d builtin/runtime/lsp.lua @ main OK (assertion)
rd12a builtin/runtime/lsp.lua @ main OK (assertion)
rd12b builtin/runtime/lsp.lua @ main OK (assertion)
rd13 builtin/runtime/lsp.lua @ main OK (assertion)
rd15 builtin/runtime/lsp.lua @ main OK (assertion)
rd18 src/lua_bindings/mod.rs @ 1873be6 OK (assertion)
rd19a builtin/runtime/lsp.lua @ 1873be6 OK (assertion)
rd19b builtin/runtime/lsp.lua @ 1873be6 OK (assertion)
rd19c builtin/runtime/lsp.lua @ 1873be6 OK (assertion)
rd20 builtin/runtime/lsp.lua @ 1873be6 OK (assertion)
Two rows need their weakness stated rather than hidden.
rd11 is VACUOUS against `main`'s `lsp.lua` and the script says so — a
preflight-less applier passes it, which is expected, because rd11 is
the direction that asserts the guard does NOT over-refuse (the same
shape as criteria 2, 7, 9 and 14). It bites two other ways, both run:
`OK (assertion)` against `main`'s `src/lua_bindings/mod.rs`, where the
primitive's absent-plus-ignore branch destroys the buffer; and against
a hand mutation dropping `ignore_if_not_exists` from the preflight
call, which is the pre-image the framing actually names for it.
rd3's two pre-images are designs never committed, so no ref carries
them and `scripts/bite` cannot be used. Hand-mutated instead:
reconciliation moved ahead of the filesystem mutation makes rd3 fail
on exactly its stated assertion (and rd4 with it). On this setup that
mutation and "validation that removes rather than inspects" are the
same mutation, because the buffer is bound to the exact deleted path —
stated because the first shipped setup could see neither.
The eight rows against `main`'s `lsp.lua` all fail by TIMEOUT rather
than by a value assertion, and that is the pre-image behaviour, not a
flaky harness: on `main` the primitive's raise escapes the applier,
escapes `handle_server_requests`, is swallowed by the
`pcall(handle_server_requests)` at the bottom of the file, and the
server is never answered at all. The sink is therefore never written.
That unanswered request is the defect criterion 13 exists to pin.
Gates: fmt; clippy -D warnings; --lib 1863; --lib --features crdt
2048; m4_acceptance 146 (was 132); lsp_dispatch_seams_acceptance 15;
dired_acceptance 25 and autosave_acceptance 29 (the framing's watch
items); PMACS_REQUIRE_GPU=1 -p pmacs-gpu 202; git diff --check clean.
No protocol change.
The lane itself carried two instances of the defect the review found in
the tests, so it is rewritten rather than appended to.
- Its "two framing criteria were wrong and the tests say so" bullet was
a restated contract in the ledger, which is the same shape as the one
in the test comments. Replaced by a statement of the boundary --- #188
owns the acceptance contract, this lane adopts it and files findings
there --- and by what each finding actually was.
- Its bite list said "five criteria are falsified by revert" over six
enumerated items, and counted the disambiguated-panel criterion among
them. That criterion fails on `main` only at its fixture premise, so
it is a mutation bite. Both errors corrected, and the list is now
split by falsification method rather than by a single count.
The base is measured in the lane with the command output pasted, and
labelled a reading rather than a constant: `main` moved twice while this
lane was open, so a SHA quoted forward from an earlier message would
already be wrong.
Adds the sweep result (7 sites, 4 named by the review, 3 found by the
sweep), the re-run bite results under `scripts/bite`'s new positive
control, and the one deliberately-recorded VACUOUS result --- criterion
7's listview half, which passes under the criterion's own mutation
because `window.switch_buffer` rebuilds the `TextView`. That is measured,
not inferred: the same mutation reports VACUOUS against the listview half
alone and BITES against the dired half.
Also records that the dired 200 ms perf test is load-sensitive rather
than regressed, with the pre-image comparison: 0.09 s either way over
five runs each, so the whole-buffer conversion costs nothing measurable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Answers review round 5 on cab3404 -- five P1, two P2. All seven confirmed
against the tree. Withdraws two of revision 5's decisions and corrects one
assertion it made about containment.
Numbering: the review asked for "revision 5", but the reviewed commit
already carried revision 5 (238fd04, closing round 4), so this is revision
6. Recorded in the document rather than silently renumbered.
Base measured at the moment of writing, not carried from a briefing:
githubsucks/main is 64883eb, merged here at 76cfaac. Every count and every
pre-image is stated as a reading at 76cfaac rather than a constant, because
the base has moved four times while this document has been open. Verified
rather than assumed that 300cbc4..64883eb touches only docs/ and
scripts/bite -- no product code -- which is what lets the source citations
survive the move.
P1-1, criterion 5 impossible. begin_edit and apply_edit each call
ensure_writable as their first statement, so a locked rope returns ReadOnly
and no intercept runs. The named-error requirement moves behind a Rust-side
lift, where the intercept genuinely is the guard; criterion 5 splits into
(a) refused-and-unchanged and (b) refused-by-the-intercept-under-a-lift,
and (b) is where the original bite survives. Consequence revisions 1-5 all
missed: after adoption the intercept is unreachable on the ordinary path
including in the shipped precedent -- terminal.lua's has been dead since
#178.
P1-2, Diverged classifier. Revision 5 got three of seven cases wrong, not
one: export_updates_since runs after every successful CRDT mutation for all
three op shapes, and revision 5 mapped all three to Rejected, whose cleanup
restores a fresh buffer to writable while CRDT and rope disagree -- the
exact harm round 4's P1-3 withdrew the four-variant fallback to prevent.
Replaced by a crdt_mutated flag carried out of the routing function, total
by construction rather than an enumeration a future failure point escapes.
P1-3, containment. Revision 5's "strongest available containment" was
wrong; neither export path reads read_only and neither can, since read_only
is about inbound mutation. New crdt_quarantined flag with three consumers
-- both export sites and queue_daemon_origin_crdt_op, the third not named
by the review. Repair stays deferred and quarantine's cost is stated.
P1-4, stale views. The review named one broadcast site; the sweep found
four, and two it did not name are load-bearing here: buffer.rs:1250 is the
arm every NoOp takes, including Q#GB5's ensure_slot lock, and :1033 is how
a locked buffer's write reaches a replica. All four adopt
continue-and-retain-first-error. Criterion 15a gains a RecordingView after
FailingView, without which it cannot see the gap.
P1-5, selection anchor. The only one of the three coordinates that panics.
Clamp-or-clear in both clamp sites, for every affected window -- and the
rule is not invented: src/terminal/view.rs:715-721 already solved the
identical problem for TerminalSelection, collapsed_by_clamp included. Also
found: a stale anchor reaches the presence broadcast, not only the local
slice.
P2-6, pre-images. Four named, six wrong. Relabelled with a table stating
what the base actually does per criterion, re-derived at 76cfaac and
labelled as a reading at that commit.
P2-7, unfold before refusal. A read-only generated_preflight runs exits 1-4
before the unfold; apply_generated_edit still re-checks all four because
the borrow is released between and Buffer stays the only authority.
Sweep F, recovery mechanisms audited for unenumerated failure points: 10
examined, 4 broken (all four are the review's), and within them three exits
the review did not name.
Sweep G, pre-images re-derived from what the base does: 41 criteria
audited, 6 mislabelled. Arithmetic shown, since a count asserted without
one is what the sweep exists to catch.
Citation audit: PR #187 moved four source files, and 15 stale citation
instances across 9 distinct citations were found and corrected. The tell
was internal -- revision 6's new text cited editor_core.rs:1836 and :1865
for the same two functions older text still cited as :1814 and :1843. A
citation is a reading too, and this document had not been re-reading them.
Records the #188/#191 acceptance-contract boundary verbatim, and the two
divergences it has already had to settle.
Status stays PROPOSED. No runtime code, no protocol change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
P2 --- the MIXED arm said "OK" and exited 0 while its likeliest cause
is vacuity. Reaching it means cargo failed, at least one `test result:`
line exists, and none says FAILED --- i.e. the named tests may have run
CLEAN against the old tree, the vacuous outcome, with an unrelated
failure supplying the exit status. The message admitted as much while
the exit code contradicted it, so a scripted caller, or a framing doc
quoting "bite exited 0", would record a certified bite. It now exits 4
and is labelled INCONCLUSIVE rather than OK. The exit ladder in the
header gains the code.
While exercising that arm I found the review's stated trigger --- and
my own comment repeating it --- is WRONG, which is worth more than the
fix. `--test A --test B`, where B's swapped file will not build, does
NOT produce MIXED: cargo builds every named target before running any,
so B's build failure stops A from running too and the swapped run
emits no summary at all. Verified by running exactly that shape
against an ancient `tests/m4_acceptance.rs`; it lands in COMPILE, as
it should. What genuinely reaches MIXED is a run that prints summaries
and *then* fails --- doc-tests failing to compile after the lib tests
pass, or a harness dying after its summary line.
So the arm stays as defence but is documented as defensive, and the
ledger lists it as the one path NOT reproduced rather than implying
the validation was complete. Manufacturing a doc-test failure to reach
it would test the arm at the cost of a contrived fixture; saying which
paths were executed and which were not is the more useful record.
Two wording fixes from the same round. The COMPILE message said the
file "does not build against the current tree", but the no-summary
condition also covers a harness that built and then died before
printing one; it now says "produced no test summary (build failure, or
a harness that died before printing one)" so the label never overstates
the observation. And the colour comment no longer overclaims: exporting
CARGO_TERM_COLOR=never defeats an ambient variable, but an explicit
`--color always` in the passed-through args still beats the
environment. Self-inflicted and fail-closed, and now said so.