Merged 2026-08-29 at approved head b8c51b7, merge commit 6c9bae6, via
--match-head-commit so the merge is provably of the reviewed head. 14/14
CI green on that head and the 16-stage local gate green on it too, with
HEAD and a clean worktree captured before and after.
Acceptance 48 is implemented: panel selection, terminal mouse reporting
and the vertical wheel replay, and the gesture ends correctly, which was
the larger half. The horizontal document-panel wheel stays a named
deferral to GUI Stage 1b B1-B3.
GUI arc 1b is unblocked and rebases onto this merge.
U12 arrived with the merge rather than needing separate absorption.
Records two owed follow-ups, both their own lanes. ci.yml has no
workflow_dispatch, so a merge-base control could only be had by
re-running a week-old job --- that shaped the entire U11 recurrence
handling. And dispatch_parse_round_trips_a_rust_source_file has now
redded twice on macOS/lua54 and passed on rerun both times, with
async_runtime.rs byte-identical to main throughout, while BOTH margins
are unrecoverable because the assertion omits the measured value.
Intermittence is established; cause and magnitude are not, and a third
occurrence will be no better until that one-line fix lands.
Per the standing rule, this absorption does not advance any canonical
base to its own commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Head-exact gate at 6142acc: fifteen of sixteen stages green, including
sweep, m4, gpu, diff-check and all eight touched acceptance suites. The
red was 04-lib-crdt, where composition_overhead_under_ten_percent
(1.247x against a 1.10x budget) and
setsid_escapee_is_not_reaped_and_teardown_reclaims_readers failed
together. Both green on isolated rerun.
Filed as U12 rather than folded into U6 or U9, because both of those
instruct it: U6 says one of its selectors redding without the other is a
separate incident, and this is composition_overhead alone for the second
time; U9 is the same budget-plus-PTY shape but in 11-sweep with a
different PTY selector.
src/process.rs is not touched by this branch at all. src/editor.rs is,
but only in the panel-replay paths, not in composition.
The row does NOT claim load caused it. It records that the run was
knowingly taken on a machine that was quieter but not quiet --- load
11.04 at the start, 27.79 five-minute at the end, two foreign python
processes throughout, an apt install shortly before --- which are
conditions, not a mechanism.
Four incidents in this family now, and the discriminating control U9
named remains unrun: pin test-binary concurrency to 1, and separately
load a lone --lib binary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Closes the gap recorded through tasks 18 and 19. The third drain sits
inside the daemon's per-frontend frame loop, which no unit row could
enter, so it was written down as owed rather than assumed covered by its
neighbours.
The seam is now `project_semantic_frame`, extracted from that loop. It
returns its messages UNWRITTEN, and that is what makes the ordering
assertable: a caller holding them has by construction not sent the
successor frame, so a release already delivered at that moment provably
precedes it rather than merely arriving alongside it.
The row arms a gesture on a reporting terminal, takes the panel away so
publish_absent_panel cancels from inside projection, calls the seam, and
asserts the child already has the release while the successor frame is
still in the caller's hands.
It bites its own drain and no other: removing the drain from the seam
fails Q5 while Q1-Q4 stay green on the effect and detach drains.
Grid sessions no longer reach the drain at all --- they hold no panel and
no gesture --- which is tighter than the previous per-fid call.
Also folds in the reported prose typo, and records the gate's
precondition: a foreign C++/java build has been at load 114+ through
this work, and the three wall-clock rows that redded under it were green
in isolation every time. Running the gate into that would manufacture
another U6/U9/U10 rotating-red incident.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of c37c066.
ABSENT WAS MISSING. The approved G5b table has five transitions, not
four, so the matrix was 16 quadrants where it should be 20. Added, and
it earns its place twice: as the fifth row, and as a CONTROL on SS5b's
own cancellation --- removing that cancel now fails the Absent quadrants
while the four this lane added still pass.
The quadrant count is asserted in the row. A loop that quietly stops
covering a combination passes exactly as loudly as one that covers them
all, which is the same read-success-from-absence shape as a test filter
that matches nothing.
G5m ASSUMED its composites. Both scenarios asserted one release without
ever checking the mapping moved, so either would pass as a
single-cause transition wearing a composite's name. It now peeks the
generation before and asserts it advanced after --- peeked, because
reading through the authoritative accessor would advance the key and
manufacture the very second cause the row is meant to observe. The
assertion bites: a same-size geometry change does NOT move the mapping,
and substituting one fails the fixture.
The doubled P12 rustdoc line is back and removed again. I wrote the note
about this exact seam after the first occurrence and then spliced
through it a second time. The crate-wide sweep is now a real check
rather than a grep I improvised: every `///` line containing a second
`///` that is not a URL.
Machine note: a foreign C++ build was running at load 114 during the
final verification, and three wall-clock rows redded under it ---
m6_2_pty_streaming_respects_byte_ceiling,
composition_overhead_under_ten_percent and
full_buffer_summary_flatten_scales_on_large_grammar_file --- each green
in isolation. The gate still wants a quiet machine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of e2b080f. The transition detection was sound; the rows
proving it were not.
G5b(a)-(c) and G5m stopped at has_pending_release() or a cancellation
count. Both pass while delivery or recorded-domain routing is broken,
and the mutation that parks a release and never delivers it proves it:
it now fails all three rows and previously failed none. Every quadrant
drains explicitly and asserts the effect --- exact release bytes for a
reporting terminal, the cleared empty selection for a document, and an
empty slot afterwards.
The matrix is now the table the framing asks for: four transitions x two
families x two targets, sixteen quadrants. All four earlier rows used
the legacy terminal fixture, so the mapped legs and every document leg
were simply absent.
G5m takes both composites the framing names --- changed-size geometry,
which moves the epoch AND the mapping generation, and a buffer
replacement that also moves the mapping --- rather than the one
wrong-shaped combination it had.
One quadrant asserts less and says so: for window replacement on a
document the window the gesture belonged to is gone, so the completion
has nothing left to clear and the ending is the whole effect. Written
into the row, because a silently absent assertion is how a quadrant
stops testing anything.
Two fixture facts the failures taught: the document legs press at row 0
because foreign_edit replaces buffer contents without refreshing the
window's cached line index, so only the first display row resolves to a
byte and a press that anchors nothing does not arm; and cargo check
--lib does not compile #[cfg(test)] code, which is why a missing test
helper passed a check and failed the build.
Corrects two records. The ledger had the mutation labels reversed ---
dropping the BUFFER comparison misses the buffer transition, not the
window one. And semantic_render still told readers these transitions
were left armed on this branch, which was true of SS5b and false here
since the matrix landed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Task 19. SS5b wired Absent and left the other four transitions armed,
inert while nothing consumed the latch and defects the moment
cancellation gained an effect.
Three are visible in the producer, where the declaration is built: the
side WINDOW replaced, its BUFFER replaced, and the GEOMETRY epoch moved.
The last needed a retained geometry_epoch on PanelPresentation, because
nothing else the producer holds moves with a same-size geometry change
--- not the panel epoch, not the identity, and on a legacy peer not a
mapping key either --- so the transition was invisible and a live gesture
survived a grid it no longer belonged to. DETACH cancels in the
dispatcher, before any teardown, because it is the transition with no
later opportunity of any kind.
The release follows the RECORDED domain, so a buffer replacement pays
the child the gesture was pressed on rather than whatever occupies the
panel now.
G5b(a)-(d), G5m and G5j, each reading the child's byte stream or the
document's selection rather than the latch. The mutations discriminate
exactly: dropping the window half of the identity check fails only the
buffer leg, dropping the buffer half fails only the window leg, dropping
the geometry check fails only that leg, and dropping detach's cancel
fails only detach.
G5m survives all four, and correctly --- two coincident causes take the
same latch, so one release, and removing either cause still leaves one.
G5j's two legs differ and the row proves it: an empty selection is
cleared without moving point, while a real dragged region survives
anchor-and-cursor exact. Clearing every selection fails the second leg,
which is the mutation that matters --- ending a gesture is not a reason
to discard what the user selected.
Machine note: a foreign java build was running at 213% CPU during this
work, and the wall-clock budget row composition_overhead_under_ten_percent
redded twice under it, green in isolation both times. Functional rows
are unaffected; the gate should wait for a quiet machine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of ab8ddae. The entry drain could not close this: it
looks for an OWED release, and a gesture that is still LIVE owes nothing
yet. Arming was what cancelled it, and arming runs after the replacement
press has already reached the target --- so a second press with the first
never released put `old press, new press, old release` on the wire. Two
presses outstanding, then a release arriving for the wrong one.
The Down arm now ends the live gesture and drains it before applying the
replacement, so the child sees `old press, old release, new press`.
The invariant moved to where it is relied on. arm_accepted_gesture now
asserts that neither a live gesture nor an owed release remains, at the
point of ARMING rather than inside cancellation --- arming is what the
ordering protects, and checking during cancellation cannot see the case
where nothing has been cancelled yet. The defensive cancel stays for
release builds, because parking late is recoverable and overwriting is
not.
Q6 was rewritten, because the old one never sent a second press while
the first was live and so could not observe any of this; its final
assertion also ran after a further cancellation. It now expects the
exact bytes `release(1,2), press(2,4)` in that order.
Both layers are witnessed separately. Reverting the ordering trips the
new debug assertion at the point of arming; reverting it AND compiling
that assertion out --- which is what a release build does --- fails the
byte-order assertion instead, with the child receiving only the new
press.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Task 18. Cancellation now PARKS the record instead of returning it into
a context that drops it: two of the three cancellation sites --- the
mapping-generation advance and publish_absent_panel --- run inside frame
production, where no target effect can happen, so the gesture ended with
the child still holding its button.
A SLOT, not a queue. The latch holds at most one gesture per frontend,
so at most one release can be owed, and the bound is structural rather
than a cap someone chose. Overwriting a still-parked release is a
debug_assert rather than a silent loss --- a backstop, since the ordering
is what actually prevents it.
Three drains, each against a specific way the release would otherwise
arrive too late or not at all: before any subsequent panel-pointer
effect, before detach teardown (the next statement in that arm drops the
state holding the record), and at the projection seam between
render_frame returning and its messages being written.
Q1, Q2, Q3, Q4 and Q6, each biting its named mutation with the run
counts checked. The mutations discriminate: dropping the park fails all
three delivery rows; removing the effect-drain spares Q4, which has its
own; removing the detach drain hits Q4 alone. Q3 asserts ORDER rather
than arrival, and the mutation that keeps the drain but moves it after
the press effect fails exactly that assertion, with the child's stream
coming back press-then-release.
Q5 IS OWED, AND RECORDED AS SUCH. The projection-seam drain needs a row
that drives the real per-frontend frame loop; these rows call
render_frame directly and never enter it. The seam still matters --- a
cancellation with no following panel event and no detach would let the
successor frame overtake the release its own new mapping required --- so
it is written down as unwitnessed rather than treated as covered by its
neighbours.
Two fixture facts worth keeping: the Q rows cancel through Absence
rather than a mapping advance, because a terminal panel's key tracks its
screen and anchor, not its buffer, so a foreign edit does not move it;
and the helper re-shows AND re-declares the panel, because a panel left
Absent fails the inbound ladder and the row would then be observing the
ladder rather than the slot.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of 557ea6d. Capturing the outcome and asserting it LAST
makes the focus assertion reachable: removing the buffer check accepts
the press, an accepted press activates the panel before it replays, and
the row now fails on focus --- WindowId(3) against WindowId(2).
I had recorded this as a limit of the type boundary, claiming no
mutation could reach the effect assertions because the daemon applies
only on Accepted and the disposition gives Refused no target. That was
wrong. The obstacle was that the row asserted the refusal BEFORE
dispatch and aborted there. Ordering, not architecture.
The classification is still checked, at the end, so the row cannot go
vacuous if it ever stops testing a refusal.
Controller and byte assertions stay documented as defence in depth, and
now for an accurate reason: the mutation that reaches them routes
through a document buffer, which touches neither.
Also replaces failure text that still described an out-of-range anchor,
which this fixture stopped using when its refusal lever became a foreign
buffer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of 9028e1b.
P2 recorded `active_frontend`, which is which FRONTEND is current, not
which window has focus. Focus is `views[&fid].active` and `focus_window`
moves it, so the row would have watched a panel steal focus without
noticing. It now records the focused window, and asserts up front that
the panel does not already hold it --- otherwise "focus did not move to
the panel" asserts nothing.
P2's refusal lever also changed, because the old one could not exercise
what the row claims. An out-of-grid cell with the row bound removed
becomes `on_chrome`, so the press classifies Consumed and still reaches
no target: the row bit on its own precondition while focus was never
touched. A foreign buffer at an in-content cell is the refusal whose
mis-gating actually yields Accepted.
And the row now says what is falsifiable about it. Removing the buffer
check makes the press Accepted and P2 fails --- but on the precondition,
which fires first, so the focus, controller and byte assertions cannot
fail under that mutation and no other mutation reaches them: the daemon
applies only on Accepted, and the disposition enum gives Refused no
target to apply. They are defence in depth against a future refactor,
labelled as such rather than presented as coverage.
The ledger claimed every row reads a target effect and never the latch
alone. That was false. P9 and P10 read the LATCH, and correctly so ---
the defect they fence is a record existing for a gesture that never
began, so the record is the artifact, and an effect assertion would not
distinguish their mutations. The line now separates effect rows from
arming-gate rows and names P2's third case.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of 2ea39aa. Two of my claims were wrong and the code
disproved them.
WIDE PANELS HAD DEAD POINTER INPUT. A panel deliberately does not
inherit the terminal's per-axis PTY caps (Bet B5'), and the renderer
clamps through terminal_projection_size so a band wider than
MAX_TERMINAL_COLS paints correctly. Pointer routing passed the RAW panel
width, and view_status_for_size refuses anything over the cap --- so on
exactly those panels every click inside the visible terminal resolved to
None while the band looked perfectly normal. Routing and the recorded
viewport now go through the same clamp, with P12 as a POSITIVE control
at MAX_TERMINAL_COLS + 128.
P10 IS REACHABLE, and my note saying otherwise was wrong. I claimed
anchor_at resolves every in-grid cell of a live view, "measured, not
assumed". I had measured ROWS and generalised to cells. anchor_at
refuses coord.col >= row.cells.len(), and the fixture's band is 80
columns over a 20-column child, so columns 20..79 are painted padding
inside accepted content. The row exists now and the begin_selection gate
is witnessed rather than excused.
THE DISPOSITION IS AN ENUM. As {outcome, Option<target>} the invalid
pair --- refused, yet carrying a target --- stayed representable inside
editor.rs, so my "the type makes it impossible" was also wrong. Refused
now holds no target at all. ResolvedPanelTarget is public as a type and
opaque as a value: every field stays private, so the daemon hands the
disposition back rather than reading a derivation out of it.
P2 also now asserts what it observes: the classification is Refused, and
focus and terminal-controller ownership are preserved. A misclassified
press focuses the panel and claims the controller BEFORE its
out-of-range anchor fails, so byte and latch assertions alone stayed
green through exactly that bug.
MY MUTATION HARNESS WAS READING SUCCESS FROM SILENCE. `cargo test --lib
"r4_p\|g5k_"` is a literal substring filter, not a regex: it matched
nothing and ran ZERO tests, and I read the absence of failures as
"the mutation did not bite". One whole round proved nothing. The harness
now prints the run count and says so loudly when it is zero. Re-run
properly, all three fixes bite their named rows.
Also updates the ledger's witness list, which omitted P2, P9, P11, the
recorded viewport and the exact-byte strengthening.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of 48057d7. The G5k routing fix held; the record still
leaned on ambient state in three places.
TerminalLocal now records the accepted content VIEWPORT. Replay fetched
the current panel_grid_size and returned when it was None --- which is
exactly what a hidden or absent panel produces, so a cancellation could
not finish the drag it was cancelling, and a size-changing cancellation
would have finished against the successor's geometry.
A press that anchors NOTHING no longer arms. The document path returned
Some(Document) unconditionally even when panel_cell_byte found no byte,
and the terminal path returned Local while discarding begin_selection's
answer. Both now report what actually began.
The child rows assert EXACT BYTES rather than a count: a wrong event or
encoding passed the old length checks. The literals are written out
rather than built with the encoder's own formula, which would only
assert that the encoder agrees with itself. G5k(b) pins the ruling that
the SGR framing comes from the record while the modifier bits still
report live state, so its release carries code 4 rather than 0.
New rows: P2's effect half (a refused press reaches no target), P9 (a
document press that anchors nothing does not arm), P11 (a recorded local
completion still runs with the panel HIDDEN, which is what the recorded
viewport is for).
THREE ROWS IN THIS ROUND WERE VACUOUS BEFORE THE MUTATIONS CAUGHT THEM,
and the fixtures now assert their way past each cause. The panel grid in
this fixture is FOUR rows, so content is rows 0..=2: my first P9 and P10
cells were out of grid and refused before reaching the path they claimed
to test, and P9's earlier cell clamped to byte 0 instead of failing to
anchor. Both rows now assert the disposition is Accepted before
asserting anything about the effect.
P10 IS DELIBERATELY ABSENT AND RECORDED AS UNWITNESSED. The
begin_selection gate has no reachable false branch through the daemon:
classify has already established the buffer is the side window's live
terminal, and anchor_at resolves every in-grid cell of a live view ---
measured on the fixture, not assumed. The gate is kept as insurance and
the gap is written where the row would have been, rather than covered by
something that would pass whether or not the gate existed.
P2's effect half is likewise not falsifiable by any mutation I could
construct, because a Refused disposition carries no resolution, so no
path can apply it. That is a stronger guarantee than a test, and it is
stated rather than dressed up as coverage.
Also corrects the last false ledger tense.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of 39b6fa7. The three-state disposition was sound; the
record and effect half downstream of it were not.
apply_panel_pointer returned a bare bool, so the record carried neither
the resolved target nor the reporting contract, and the daemon drove
accepted Drag/Up back through the mode-sensitive adapter. That adapter
re-reads Shift, the scrollback position and the child's mouse modes on
every event --- which is G5k's named mutation verbatim. A press reported
to the child followed by a release re-evaluated after the child turned
reporting off leaves that child holding a button down; the reverse
transition sends a child an Up for a Down it never saw. The recorded
completion had the same defect and additionally re-derived the current
side window, returning when it had changed --- precisely the transitions
task 19 must terminate, so the completion they need was the one thing
that refused to run.
The press now resolves a PanelGestureDomain --- Document{window},
TerminalChild{window, buffer, modes} or TerminalLocal{window, buffer}
--- and the record carries it. Tails and completions route through
replay_panel_gesture_in_domain, which gates on nothing: not Shift, not
the scroll position, not the child's current modes, not the panel's
current identity. apply_terminal_gesture reports which way it routed so
the domain is measured where the branch is taken. Arming now requires an
effect: a press the target refused records nothing.
G5k(a)-(d) plus P3's reporting leg, P4 and P5. Every row reads a TARGET
EFFECT --- the child's byte stream in order, the terminal drag state, or
the document selection --- never the latch. Each bites its own mutation,
and G5k's four legs all fail under the framing's own named mutation
applied verbatim.
Two seams exist because nothing else exposes what the child received: an
opt-in child-input tap, off by default, and a drag-state read.
Also corrects the recovery ledger, which still said implementation was
paused and the bool collision unfixed.
Records for the ci-red registry rather than hiding it: during this work
composition_overhead_under_ten_percent and pty_mode_child_sees_a_tty
redded together in one --lib run at load 21 and each passed in isolation
immediately after --- U9's signature, and neither path is touched here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Make the three-state panel-pointer result a pre-effect disposition and
replace the competing latch matrices with one complete lifecycle table.
Accepted tails now require a live gesture, accepted release replay and
record-driven termination are mutually exclusive, and the witnesses
cover stale tails plus immediate and later duplicate completion.
Place projection-raised release delivery at the enforceable daemon
boundary after semantic rendering returns and before its messages are
written. Keep the arm invariant as a backstop behind pointer, detach,
and frame-order witnesses.
Split horizontal-wheel ground truth by terminal reporting/local and
document targets. Record the missing document-panel effect explicitly
under GUI Stage 1b B1--B3, update that framing's surface scope, and
synchronize the active-work checkpoint.
Four blocking gaps, all of them cases where revision 14 asserted a
guarantee its own mechanism did not provide.
1. Q#BP-R4's release half was bookkeeping in an effect's clothes. It
ruled that a chrome Up "consumes" the latch and claimed this stops a
child being left button-down. It stops nothing: update_accepted_gesture
does `let _ = state.consume_accepted_gesture()` at daemon.rs:1067, and
terminal chrome returns at editor.rs:2862-2870, BEFORE
apply_terminal_gesture at :2899. P3/P4 asserted only that the latch
empties, so they would have passed while the child heard nothing ---
the failure looking fixed because the bookkeeping looked right. Now
ruled as TERMINATE, with a per-target completion effect (child release,
or local terminal-selection completion, or the document completion SS5b
already assigned here), a no-duplicate leg, and P3 written explicitly
against the latch-only assertion.
2. The pending-release slot had an invariant and no ordering. The seam
replays before it arms (daemon.rs:2608-2616), so a check inside
arm_accepted_gesture could only fire after the new press had already
reached the child. Drain order is now part of the ruling: before any
subsequent panel-pointer effect, before detach teardown, before the
next frame --- with an old-release-before-new-press row that reads the
child's byte order, and the invariant demoted to a backstop.
3. The ground truth was knowingly false, and the workflow requires
scouting before framing. RE-MEASURED at 2c0d3ff instead of deferred.
Two clauses the old table called MISSING are DONE on this branch ---
panel selection and terminal mouse reporting --- the vertical wheel
works, and the horizontal wheel turns out to be an UNRULED gap sitting
in a catch-all arm with no comment. Revision 15 names it rather than
ruling it, because inventing a ruling inside a re-measurement is how an
unexamined default becomes a decision. The 72da24a table is kept as
history.
4. "This lane MOVES the grade" was wrong against the authoritative
scorecard. Step 5/GPU is floored Partial by 5(a) IME, so completing
5(c) cannot lift it, and step 8 is already Works on all three columns.
The draft had read this section's MISSING column as if it were the
scorecard; COHERENCE.md is the only thing that decides grades.
Also drops a false TUI claim --- dispatch_semantic_panel_pointer has
exactly two callers, both FrontendEvent arms, so only semantic legacy and
mapped peers reach it --- and clears two merged-state contradictions: SS5b
no longer says "Nothing implemented", and the lane no longer says the v26
correction is owed by 1b while the block below records SS5b making it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The ledger listed the merge's duplicate-SS5b defect but not the worse
one: the merge commit does not compile and its message asserts that it
does. Recorded with the bisect consequence spelled out, so a reader
crossing that range knows why the build fails there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Revision 13 blocked this lane on a protocol-bearing mapping generation.
That slice merged as #242 (47b5463), so the block is DISCHARGED and
revision 13 stands as history rather than as a gate.
Also fixes a defect I introduced in the merge b758c2e: it kept BOTH
copies of SS5b, this branch's stale pre-split one and main's
authoritative one. I discarded the uncommitted stub edit as obsolete
and missed that its deletion half was still owed. Exactly one SS5a and
one SS5b now remain.
Q#BP-R4 is new, and the merge created it. SS5b and this lane agreed on
`bool` while disagreeing on its meaning --- accepted-as-a-gesture versus
consumed-here --- so a press on the band's mode line currently ARMS the
accepted-gesture latch for a gesture that never began in content. That
is the defect class SS5b's review round four fixed, re-entering by merge
rather than by edit.
RULED: a three-state PanelPointerOutcome (Refused/Consumed/Accepted),
with an asymmetric latch rule --- arm only on Accepted + Down(Left),
consume on any Up(Left) that was not Refused, because a button-up ends
the gesture wherever it lands while a refused release cannot be known
to concern it at all. The asymmetry is what earns the third state: it
makes all three outcomes behaviourally distinct at the latch, so P1-P4
each have something to bite. A two-state bool with the corrected
meaning would be right today and would let the next author restore the
collision without touching a test.
Revision 14 also takes the rows SS5b's split table assigned here, rules
a bounded per-frontend pending-release SLOT for the cancellation record
SS5b leaves nowhere to wait --- with the no-arming-over-pending
invariant asserted rather than assumed --- and records that the four
transitions SS5b left inert become defects the moment this lane
attaches effects.
Records a re-measurement obligation with evidence rather than
suspicion: every production anchor in SS5a has moved, four of them
measured here, and the DONE/MISSING verdicts are not assumed to have
survived a dispatcher SS5b rewrote.
Coherence: journey steps 5(c) and 8(b), and unlike SS5b this lane MOVES
the grade --- it supplies what SS5a's own table lists as missing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Merged rather than rebased, by decision: the lane's 12 commits include
10 framing revisions that all touch the same 800-1000 line doc regions,
so a rebase meant twelve rounds of large-block conflict resolution ---
the operation that produced a committed diff3 marker on the last lane.
One pass instead, with all 12 commits preserved.
Resolutions:
- src/daemon.rs --- took main's structure whole, both inbound arms with
the latch gated on the dispatcher's answer, and threaded replay's
`mods` through both call sites. `mods` is newly BOUND in the mapped
arm, which SS5b left in `..`; the mapped family carries the same
modifiers, so leaving it would have given a v25 session the inverted
Shift behaviour that parent 48 R-a fixed for v24.
- pmacs-gpu/src/main.rs --- additive throughout: both new struct fields
(`gesture_last_content_cell`, `last_pointer_generation`), both resets
at each site, and both test blocks.
- src/editor.rs --- auto-merged; the merged dispatcher keeps SS5b's
`#[must_use]`, its four rejection paths and its `-> bool`, plus
replay's `&mut self`, `mods`, chrome/mode-line handling and terminal
gesture application.
- docs/active-work.md --- the active replay lane above main's corrected
#239/#240/#242 headers.
- docs/bottom-panel-framing.md --- 5a then 5b. The paragraph arguing
the v25->v26 bump should be "recorded as required rather than made"
is marked superseded: SS5b made it and merged as #242.
Workspace compiles clean, all targets, no warnings.
THE MERGE SURFACES A SEMANTIC COLLISION THE FRAMING MUST RULE ON, and
it is not resolved here. The two branches give the dispatcher's bool
different meanings: for SS5b `true` means the gesture was ACCEPTED, and
it drives the accepted-gesture latch; for replay `true` means the event
was CONSUMED HERE, including chrome swallows. So a press on the band's
mode line now returns true and ARMS the latch --- a gesture that never
began in content, which is the defect class SS5b's review round four
found and fixed. Recorded, not patched, because which rows own the
answer is a framing question and the next revision owes it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
SS5b merged as #242 (47b5463) at approved head 61f0faf, via
--match-head-commit so the merge is provably of the reviewed head.
PROTOCOL_VERSION is now 25. panel-pointer-replay is unblocked and is the
next step in the arc.
#239 (ca92796) and #240 (72da24a) both merged on 2026-08-13 and the
ledger has been calling them OPEN for a week. Their blocks are kept for
their reasoning, relabelled for their status.
#240's block gains the postscript it earned: its TMPDIR isolation was
the thing I defeated during SS5b review round 4 by running the CRDT
sweep by hand, outside scripts/gate. Two m4_24 base-resolution rows
failed, I reported them as pre-existing and proposed CI as the arbiter,
and the actual cause was /tmp/.git being inherited as an ancestor
project root. Through the gate, both pass.
Adds U11 to the red registry, the row deferred during #242's review so
that no docs commit would invalidate that PR's head-exact gate evidence.
It carries the exact selector and panic fragment, both attempt IDs, the
1960/1 counts, and the fact that the margin is unrecoverable because
duration_ms is omitted from the assertion message --- which is why a
recurrence owes a merge-base control rather than a comparison. The four
exact-head local passes, the two macOS/luajit greens and the identical
async_runtime.rs blob are recorded as narrowing evidence and explicitly
not as causality.
Per the standing rule, this absorption does not advance any canonical
base to its own commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two consecutive scripts/gate runs at 70b334d, worktree verified clean
before and after each run, were 15/16 green apiece. Run A redded
13-sweep on dired_open_renders_10k_entries_under_200ms at 263.961465ms
against a 200ms budget; run B redded 15-sweep-crdt on
criterion_1_end_of_line_typing_completes_sub_frame_per_keystroke at
1.044609ms against a 1ms budget. Each red is green in the other run,
and both are green isolated at load 9.34.
That excludes the tree more strongly than U7 could: not "the diff
touches no render path" but the SAME COMMIT passing and failing each
row. Neither failing path is touched by the branch under test.
It does NOT establish load as the cause --- load was not sampled during
either failing step, and the row says so rather than borrowing a
reading taken elsewhere in the run.
Honours both escalation rules it trips. U7 says a repeat of one of its
selectors is a separate incident, and run A repeated one; U6 says one of
its pair redding alone is a separate incident, and run B did that. Both
are filed here rather than appended. The row also declines to pick
between the repeat and the rotation, because both are true of these
observations.
Closes one rival U7 left open: per-worktree gate target directories mean
no sibling shared this one.
Names the standing discriminating control U9 already specified and which
remains unrun --- pin test-binary concurrency to 1, and separately load a
lone --lib binary --- and asks that this family stop consuming review
rounds until it runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The branch was two behind, not one --- 24e4039 and 5f2015c both landed
on main after the first rebase. Rebased onto 5f2015c cleanly, no
conflicts; both commits picked up are docs-only.
The previously recorded 16-stage green ran about a minute BEFORE
3d08255 was committed, which makes it worktree evidence rather than
head-exact evidence. This commit is the last content change before the
gate is re-run, so the run that follows measures the head that is
proposed for merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Both inbound panel-pointer arms discarded the bool from
dispatch_semantic_panel_pointer and called update_accepted_gesture
unconditionally. The ladder authenticates the SENDER; only the
dispatcher re-derives the TARGET, so an event can clear every rung and
still be refused --- for an out-of-grid coordinate, an absent side
window, or a buffer that is no longer the one in the side window.
A rejected Down therefore ARMED the latch, so a later authority loss
counted a cancellation for a gesture that never began, and once replay
attaches effects it would deliver a release to a child that was never
pressed. A rejected Up CONSUMED a real armed gesture, so the authority
loss that should have ended it found nothing armed and that child holds
the button down for good. A rejected Down on top of a live gesture was
worse again: arm_accepted_gesture ends what it overwrites, so it also
counted a spurious cancellation.
Both arms now gate on the return. The dispatcher is #[must_use], so the
class cannot recur silently --- clippy runs with -D warnings, making a
future discarded answer a build failure rather than a review finding.
Four rows, g5_substrate_a_refused_{press_never_arms,
release_never_consumes}_on_the_{legacy,mapped}_arm. Each drives the
refusal from a coordinate one past the last grid row and ends in a
positive control differing only in that coordinate; without the control
a row would pass just as well if an unrelated rung had dropped the
event. Mapped rows read the generation through the validator's own
accessor so a mapping-rung refusal cannot masquerade as a dispatcher
refusal. Three mutations, each biting its named rows: ungating the
legacy arm fails exactly the two legacy rows, ungating the mapped arm
exactly the two mapped rows, and relaxing the dispatcher's >= to > fails
all four.
Also removes a committed diff3 ancestor marker this file carried since
8c9afde --- the only one in the branch's 32 commits, and invisible to a
clean-worktree `git diff --check`.
Also withdraws the claim that the local CRDT sweep could not go green.
The full 16-stage gate is green, sweep-crdt included. The two m4_24
failures came from running that sweep outside scripts/gate, where it
inherited TMPDIR=/tmp; /tmp/.git exists on this machine and project
detection walks upward, so both base-resolution rows resolved against
the wrong root. That is the exact hazard #240's isolation exists for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Rebased onto f13506c at 3e68b76 --- 31 ahead, 0 behind, clean, signed.
One conflict, in this file, where main's merged-#241 block and this
lane's own first-commit block landed at the same position; both kept.
No code file overlapped: #241 touched scripts/, tests/common/ and two
suites, while this lane touches the protocol, daemon and GPU sources.
The blocker is gone --- #241 fixed the sweep-crdt defect this lane was
held behind.
Gate run in two labelled pieces, because 16 stages exceed the session's
10-minute command cap and the guard rightly forbids the backgrounding
that would evade it: 14 stages green (plain gate plus all six
--acceptance suites, at load 5.28), build-crdt green, and sweep-crdt red
on exactly two rows --- m4_24_bare_string_glob_stays_relative and
m4_24_d3_fallback_base_is_the_smallest_attachment_dir.
Those two are PRE-EXISTING and local-only: they fail identically at
current main f13506c, and they passed in CI on #241's Test (crdt) job.
Also records that an earlier attempt is not evidence. I wrapped it in
`timeout 580` to fit the command cap, which killed the sweep mid-run and
reported it as a stage failure --- the same self-inflicted shape as the
SIGTERM truncation in the probe lane, and it should not be read as a
result.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The held lane still identified the teardown framing as revision 8, so
the approved state was not portable from this branch. Revision 9 adds
the total run classifier, the void budget, the endpoint verdict table,
the bisect-step policy and counterbalanced ordering --- none of which a
reader arriving here would have found.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two state pointers were wrong. It named ledger tip fb40d88 while the
branch was four commits past it, and identified the teardown framing as
revision 5 on one line and revision 8 on the next.
The tip pointer is removed rather than corrected. A literal SHA naming
this branch's own head is stale the moment the next commit lands ---
which is precisely how it came to say fb40d88 --- so the ledger now
points at the branch head itself and says why.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two corrections.
- This ledger still said 72da24a being an ancestor of the passing
7599661 while failing today is something "no source-monotonic cause
explains". Withdrawn: the two observations differ in commit AND
environment AND time, so they are non-comparable and support no
causal conclusion of any kind. Different commits can
deterministically produce different outcomes, so the pair says
nothing about determinism either.
- It identified the teardown lane's framing as revision 5; the
portable lane is revision 7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two statements this ledger still carried after 3313d8a said withdrew
them.
- The reduction entry still said the artifacts "are byte-different".
Historical byte identity is UNKNOWN and is not claimed: target dirs
have been overwritten, so a hash computed today is the current
occupant's. What is established is differing Cargo suffixes, hence
differing metadata hashes, hence different compilations --- enough
to void the comparison, and all that is asserted.
- The onset entry still called the window the sharpest lead and
called for bisecting it. Cleanliness was captured at neither
endpoint, so the first move is reproducing 7599661 and 724b785
clean in isolated target dirs, and deciding from that.
Also corrects the onset counts here to match the manifest: per test copy
the 17 sweep-crdt logs are 13 both-copies-ok, 1 where neither copy
executed (the stage died compiling pmacs, error[E0308]), and 3
both-failed --- not "three earlier reds on unrelated rows".
And narrows the ancestry note. 72da24a being an ancestor of the passing
7599661 while failing today shows only that outcome is not determined by
commit alone. It does not discriminate an environmental change, a
source/environment interaction, or a fix before 7599661 with a
regression before 724b785; an ancestor outside the interval says nothing
about whether the interval regressed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Three corrections to what this ledger still asserted.
- It called the reductions a "bisect". They were not: no run isolated
a variable, because the subsets and the sweeps executed different
Cargo compilations.
- It said those compilations were "byte-different". Withdrawn --- the
bytes a historical run executed are not knowable now, since target
dirs have been overwritten and a hash computed today is the current
occupant's. What IS known is the differing Cargo suffixes, i.e.
differing metadata hashes, which is enough to void the comparison
and is all that is claimed.
- The onset was described as a bisectable window. It is not. Reflog
and commit times put HEAD at 7599661 during the last green ---
3c06176 landed 40s after that run finished --- and at 724b785
during the first red, since 5174f73 landed 08:45:41 after that run
ended 08:42:01. Cleanliness was captured at neither endpoint.
Decisively, 72da24a is an ANCESTOR of the passing 7599661 yet fails
today, and no source-monotonic cause produces that. Reproduce clean
endpoints before considering a bisect.
Also corrects the onset counts: per test copy the 17 sweep-crdt logs are
13 both-copies-ok, 1 where neither copy executed (the stage died
compiling pmacs with error[E0308]), and 3 both-failed. "14 passes" and
"the earlier reds failed on unrelated tests" were both wrong --- one of
those runs never reached the test at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
779a6bd corrected four claims but left the ones that mattered most. This
ledger still said --workspace unification was refuted, that R9 ran the
"same binaries, same order, same tests", that later packages cannot be
implicated, and that the cause is cumulative across the preceding 37
binaries. All four are withdrawn:
- R9 executed gpu_initial_target_acceptance-91f51d0b and
gpu_invocation_acceptance-6b4b8223; the sweeps executed -5d9105cb
and -d4dae4f0, byte-different. Command shape changes Cargo's
fingerprint, so the comparison was never made. --workspace
selection and the preceding tests are both OPEN, not refuted.
- "Later packages cannot be implicated because their targets run
after the failure" ignores that they affect the build graph and
fingerprints before their tests execute.
- No cumulative cause follows from reductions that ran different
binaries. What is established is narrower: reproducible in the full
sweep, not reproduced in any subset attempted.
And a finding that reframes the defect. sweep-crdt appears SEVENTEEN
times in this target dir's gate logs. The ctrl_c failure appears in
exactly the last three, and the test passed --- both copies, "... ok" ---
inside the stage before them. Last green 20260815T185708Z, first red
20260816T063330Z, no reboot between; the three earlier red sweeps failed
on unrelated rows. "Pre-existing on main" still holds, since 72da24a
reproduces it, but "always broken" is contradicted, and bisecting that
window is now the sharpest lead. The red full-sweep count is SEVEN, not
five, each enumerated with its own log digest in the teardown lane's
docs/probe-sigint-evidence.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Pushing 16cf3a2 made the retraction portable but not the correction.
This ledger still carried four falsified statements, and a held branch
that transports them is worse than one that never recorded them.
- "119 binaries green, one red" -> 119 green result summaries and TWO
red binaries. `gpu_initial_target_acceptance` includes the suite as
a module, so a reproducing sweep reds twice (:3097 and :3131).
- The ">=8s lifetime" arithmetic behind the retraction is FALSE. Both
reproducing binaries finish in ~5.19s INCLUDING the 5s timeout, so
the failing launcher lives about 5.1s --- inside what the sampler
saw. The ">6s selector" proposed as the remedy would have captured
nothing. The "mechanism located" claim is therefore not refuted by
that argument; it stays unproven because the suite spawns root
launchers from six call sites under --features crdt, so command
line alone cannot attribute one to this test.
- "the probe should die on SIGINT's default action" -> withdrawn.
Absence of handler code does not establish default disposition;
SIG_IGN is inherited across fork and survives exec, which is why
inherited ignore is the leading hypothesis.
- "never blocked indefinitely" -> only the event loop is bounded, at
50ms. The process is not: the stdin reader blocks in read_to_end
and the loop leaves only when stdin closes.
Also records that the defect now has its own lane,
`gpu-probe-sigint-teardown`, whose framing supersedes every diagnostic
claim here, and that §5b is held behind it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
a92ef7f said the mechanism was located: the launcher blocked in
`do_wait` on a GPU probe child stuck in `futex_do_wait`. Checking the
instrument against the claim shows it does not support it.
The sampler caught 394 distinct launchers across a reproducing sweep and
the longest-lived was 5s TOTAL. For this test to fail, a launcher must
outlive its SIGINT by 5s, so its lifetime would be 8s or more. The
failing instance was never captured. What I described is a healthy
launcher from one of the suite's other tests --- the normal teardown
shape, reported as the defect.
This is the same error as the `available`-memory reading earlier in this
lane: a measurement that looked conclusive, reported before checking
that it discriminated. Retracted here rather than left to be found.
Two facts do survive and constrain the next attempt: `pmacs-gpu`
installs no signal handling at all, so the probe should die on SIGINT's
default action; and its main loop is a 50ms `recv_timeout`, so it never
blocks indefinitely. The next instrument must key on the failing
instance --- launchers outliving ~6s, or a PID recorded by the test
itself --- rather than sampling every launcher and hoping.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Sampled the process table twice a second through a reproducing sweep.
After the test SIGINTs the launcher's process group:
- `pmacs --gpu --socket ...` sits in `do_wait` for the full 5s. It is
waiting on a child, not ignoring the signal.
- `pmacs-gpu --headless-managed-probe ...`, its child and in the same
process group so it received the SIGINT, sits in `futex_do_wait`
and never exits.
The deadline is missed because the GPU probe does not tear down under
SIGINT. The fix belongs in the probe's shutdown path; raising the 5s
would only hide it. Why the child hangs ONLY in a complete sweep is
still open --- every prior GPU suite has exercised the adapter by then,
which is where to look first.
Five explanations are recorded as refuted so nobody re-runs them: load,
leaked daemons, inotify, `--workspace` feature unification, and any
specific preceding test --- all 37 preceding targets plus the suite run
green, which is the genuinely strange part.
The tmpfs hypothesis got a real experiment rather than an argument:
/tmp went 21G -> 1.2G, available memory 27G -> 45G, and the sweep stayed
red. Recorded with the note that my earlier `available`-based dismissal
was itself unsound, since tmpfs pages are not reclaimable yet still
appear in buff/cache --- right conclusion, wrong reasoning, and it took
the experiment to know which.
Also notes the test exists in two binaries: gpu_initial_target_
acceptance includes it as a module, so a reproducing sweep fails it
twice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Attribution is settled. The identical `build-crdt && sweep-crdt` pair,
run at the merge base 72da24a in the primary worktree with its own
target dir, fails the SAME test: 119 binaries green, one red,
`ctrl_c_on_launcher_group_does_not_reach_spawned_daemon`. This branch is
not implicated, and no branch can pass this gate stage on this machine
until the underlying defect is fixed --- main included.
The mechanism is unknown, and this records two explanations I offered
and then refuted, so nobody re-runs them:
- Load: refuted. Red on a quiet machine, load 2.77 at launch.
- Memory pressure: refuted by correcting my own instrument. The
sampler showed free memory at 543MB, which looked damning, but it
recorded `free` --- not the meaningful figure on Linux. `available`
was 27G. The 543MB was reclaimable cache. I had already reported
the memory story before checking that, which was wrong.
- Leaked daemons: refuted. Peak 58, up only 8 during the sweep, and
the green standalone runs already ran at 46-50.
What the bisect did establish: green in every smaller context tried ---
the test alone three times at 0.15s against its own 5s deadline, its
whole suite, a workspace run filtered to just it, the lib binary then
the suite, and the three GPU suites in sweep order --- and red 4/4 in
the full workspace sweep across two trees. Cumulative across the 37
binaries preceding it, and not flaky.
Also notes that /tmp is a 30G tmpfs holding 21G of an unrelated
project's stale target directories. Recorded as an observation about
this machine, not as the cause, and not touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Review corrections:
- The header said "ACTIVE, framing only" and named 3c06176 as the
checkpoint. Now code-complete at 5174f73, ledger tip fb40d88, gate
red.
- The gate-status entry claimed isolation established an
ENVIRONMENTAL cause. It does not. Isolation establishes
INTERMITTENCE. Narrowed to: intermittent; foreign load is a
measured confound; cause unresolved. No experiment here separated
sweep contention, foreign load, and a genuine defect in the row.
And one found while checking the user's "eleven-stage" phrasing against
what actually ran. Yesterday's runs printed ELEVEN stages, today's
printed TEN, and the difference is `05-acceptance-bottom_panel_stage2b_
daemon_acceptance`. `--acceptance` is an explicit repeated flag; the
gate derives nothing from the diff. Both of today's runs used bare
`--protocol`, so **no acceptance stage ran at all** --- the suites were
verified by hand instead, which is not the gate.
The `Gates:` line named suites but never the flag form, which is how
that happened. It now carries the exact invocation and the expected
stage count, with the note that a ten-stage run is missing every
acceptance stage. It also said "the four `bottom_panel_*` suites" when
there are FIVE on disk; `bottom_panel_stage2b_protocol_acceptance` is
the fifth and belongs in a protocol-bearing lane above all others. All
five are listed rather than guessing which four an earlier writer meant.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Nine of ten stages green at 5174f73. `09-sweep-crdt` red twice on
`ctrl_c_on_launcher_group_does_not_reach_spawned_daemon` --- "child did
not exit within 5s" --- which is a NEW signature, absent from
`ci-red-signatures.md` under any id.
The row holds a fixed 5s wall-clock deadline for a spawned child to
exit after a signal, and it runs inside the heaviest stage the gate has.
It is green alone and green with all fourteen of its suite siblings, the
latter in 0.15s against that same 5s deadline. Nothing about it touches
panels or the wire.
Sweep contention could not be separated from foreign load, and that is
recorded as inconclusive rather than dressed up: running the sweep-crdt
command alone red the same row, but `uptime` hit 59.51 during that run.
An unrelated turso workload is running in a LOOP on this machine,
holding a 16-core box at load 20-60, so clean gate evidence is not
obtainable here. The remaining work on this lane is one clean gate run
and nothing else.
Recorded in the lane ledger rather than the registry, per this branch's
standing reason: the registry here ends at U9 while the unmerged replay
branch already holds a U10, and duplicate ids have survived a clean
merge in this file once before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Found by reading the arming path back after the G5 checkpoint, not by a
failing row. `arm_accepted_gesture` overwrote an already-armed latch, so
a dropped `Up` --- one lost to an outbox that closed under a stall ---
was followed by the next press silently discarding the first gesture's
record, without counting it as a cancellation.
Inert on this base, where records are only counted. Once
`panel-pointer-replay` attaches a child release to each record, the
discarded one leaves a button held down with nothing left to release
it, and the arming code that decides this is this slice's.
Mutation: restore the plain overwrite -> the new row alone.
Also records three CI-red observations from the slice-completion gate,
which is the first entry on this lane with a MEASURED confound instead
of the standing uncontrolled one. Three wall-clock-deadline rows red in
one run --- criterion_1 by 0.12%, a PTY lifecycle race, and a 5s child
-exit deadline --- all green in isolation, the last in 0.15s against
its 5s deadline. `uptime` during the run went 14.02 -> 28.35, from an
unrelated turso test suite on the same machine with one binary at 693%
CPU. Not a controlled experiment, but the same evidence U9's synthetic
-load control was meant to produce, and it points at load.
Two process traps are recorded with them, because both were made here.
The Bash tool caps a command at ten minutes and SIGTERMs it, which the
gate reports as `FAILED (exit 143)` on whatever stage was running and
which reads exactly like a real failure. And `pkill -f <pattern>` kills
the invoking shell when the pattern appears in its own command line, so
the intended target survives while the operator believes it died --- and
here `pkill -f "cargo test"` would have destroyed an unrelated
project's build. Identify by PID.
Verified: `cargo fmt --check`; `cargo clippy --workspace --all-targets
-- -D warnings`; the four §5b G5 rows. The full protocol gate follows on
a quieter machine; the run described above is not evidence for this tree
and is recorded as an observation only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Three rows that share a shape: each is a gate this slice owns whose
downstream EFFECT belongs to the rebased replay lane.
G10b --- ordering, and a carve-out. `panel_mapping_is_current` now takes
the event kind. Zero is refused FIRST, then coordinate-free wheels skip
the freshness comparison. The order is the row: run the carve-out first
and a sender emitting zeroed wheels faces no check at all, which is an
inbound opt-out through the exempt path. The exemption exists because a
tick changes `view_top` and so advances the key --- the next tick already
queued behind it echoes the previous generation, and without the
carve-out the panel scrolls once per frame and appears dead. It returns
before the read, so a wheel does not advance the key either; advancing
would make a wheel invalidate the press after it.
The framing's carve-out-to-the-carve-out, re-imposing the check for
CHILD-REPORTED terminal wheels where SGR carries row and column, is
replay's. Whether a wheel is forwarded is decided by the reporting mode,
and no panel pointer coordinate is consumed on this base at all.
G11a --- exhaustion fails CLOSED. `saturating_add` froze the key at the
ceiling while the mapping kept moving underneath it: the stale-gesture
hole the key exists to close, with the check still appearing to pass.
Now `checked_add`, and overflow publishes `Absent`, clears input
authority, and latches for the session.
G13a/G13b --- `PanelPointerMapped` fell through `coalesce_kind` to
`None`, so pixel-rate mapped motion was lossless and filled the bounded
outbox. Two tags of its own; tail-replacement takes the whole event, so
coordinate and generation advance together and a collapsed run can never
pair a new coordinate with a stale one. Press, release and every wheel
kind stay lossless.
Mutation results, including two that changed the design:
- exemption before the nonzero check -> G10b(zero) alone
- no wheel exemption -> G10b(exemption) alone
- saturating instead of checked add -> G11a alone
- no exhaustion latch -> G11a, but only AFTER the row was extended.
The first version of G11a did not bite: the latch had no proven
job, because the overflow path already returns before storing the
ceiling snapshot, so the next read re-takes the changed arm anyway.
Measured, the two are ALTERNATIVES --- either alone keeps the band
down; only removing both resurrects it. The latch is kept as the
primary because it has a job the ordering does not: `peek` now
honours it, so the peek and the authoritative read agree that an
exhausted session has no key rather than reporting the ceiling.
The source comment says this, rather than the "second half" claim
it made before the measurement.
- mapped variants untagged / one tag for all kinds / sharing the
legacy tags -> the mapped coalescing row alone, three times
Witness-shape note: the two G10b rows call the predicate directly, and
say why. A wheel has no dispatcher-visible effect on this base --- a
document panel focuses on `Down` only --- so asserting focus for a wheel
would prove nothing. Each row carries a press leg, which does have an
effect, to show the predicate is wired into the production arm.
Verified: `cargo fmt --check`; `cargo clippy --workspace --all-targets
-- -D warnings`; `cargo test --lib` (1959); `cargo test -p pmacs-gpu
--bins` (275); both `bottom_panel_stage2b_*` suites (39); `git diff
--check`. `composition_overhead_under_ten_percent` red once during this
work and green in isolation --- a second occurrence of a signature the
lane ledger already carries, now recorded there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
G5a is the one G5 row this slice owns: the mapping key advancing must
raise cancellation at the advance, not reactively when a later event is
refused. Reactive cancellation loses a race --- if the successor frame
reaches the frontend before the physical `Up`, the producer clears its
latch and the cancelling event never arrives.
Cancelling requires something to cancel, so the accepted-gesture latch
lands with it: `AcceptedPanelGesture` (button, coord, buffer, and
whether the press reached a child) in a per-frontend slot on
`SemanticRenderState`, armed from the daemon's accepted inbound arms
after every gate has passed.
Writing that code decides three things the framing's deferred rows later
assert about, so they are pinned here under SUBSTRATE names rather than
under G5c/G5d/G5g/G5p. Those IDs stay on `panel-pointer-replay` per
SS5b's split table --- each asserts something about a synthetic release
or a real drag continuation that does not exist on this base, and
claiming an ID in two branches is the merge hazard `active-work.md`
already records surviving a clean merge once.
Two consequences are recorded rather than fixed:
- Cancellations are COUNTED, not queued. The record queue is what
replay drains to deliver each release; landing it here would grow
one entry per cancelled drag with nothing ever draining it. A
saturating count is bounded and still separates a consume from a
cancellation.
- The other G5b transitions --- panel epoch, buffer replacement,
same-size geometry, detach --- leave the latch armed on this base.
Each strands a live gesture whose release can never be accepted.
That is inert while nothing consumes the latch, and becomes a defect
exactly when replay supplies effects, in the branch that owns the
row. `Absent` is wired anyway, because `publish_absent_panel`
clears input authority two lines later; leaving it out would be an
inconsistency inside one function rather than a clean deferral.
Five mutations, each biting only its named row:
- drop the advance trigger (reactive cancellation) -> G5a alone
- arm on every accepted pointer event -> the arming substrate alone
- an ordinary `Up` no longer consumes -> the arming substrate alone
- a consume counts as a cancellation -> the arming substrate alone
- one global latch via a shared slot -> the ownership substrate alone
Also repairs a fourth rustdoc split on this branch. Inserting
`AcceptedPanelGesture` at what read as a blank gap adopted
`SemanticRenderState`'s doc comment AND its
`#[allow(clippy::struct_excessive_bools)]`, silently un-suppressing a
lint on the struct that needed it. Same mechanism all four times; the
ledger now records the check as "look UP from the insertion point".
Verified: `cargo fmt --check`; `cargo clippy --workspace --all-targets
-- -D warnings`; `cargo test --lib` (1956 passed); the two
`bottom_panel_stage2b_*` acceptance suites (39 passed); `git diff
--check`. The full eleven-stage `--protocol` gate is reserved for slice
completion per the standing procedure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The G6/G7/G8 matrix, plus the probe correction and the corrections
review found in my first attempt at these rows. This is the coherent
checkpoint: full eleven-stage `--protocol` gate, green.
**THREE OF THESE ROWS PASSED WITHOUT PROVING THEIR CLAIM**, and each
failed differently:
G8b/G8d had no atomicity. G8b installed a LEGACY frame and then
switched the session to Mapped, so `mapping_generation` was `None`
throughout --- asserting it stayed `None` after the refusal asserted
nothing. Each direction now uses an independent state, accepts a
CORRECT-FAMILY baseline so there is real authority to preserve, and
primes both pointer latches. Two new mutations pin it: clearing
authority before refusing fails G8b, discarding the retained frame
fails G8d. The family-gate mutation touched neither.
G8e covered one direction. An authority check that only holds one way
is one a peer walks around by choosing which identity to forge, so a
mapped session now also fails to borrow a legacy identity --- and
BOTH claimed identities have real registered sessions, or a
payload-keyed lookup fails for want of a session rather than for want
of authority. That was why G8e's own named mutation did not bite on
the first attempt.
G6b measured ambient state. It pre-focused the panel and then asserted
against `active_window_id()`, which tracks `active_frontend` too ---
satisfiable by a frontend switch that never routed anything. Every
routing and refusal row asserts `views[fid].active` now, with the
document precondition stated rather than assumed.
**And the probe measured the payload rather than the band, twice over.**
Its identity tuple was `(panel_epoch, geometry_epoch, size)`, which
ordinary content, focus, cursor and generation updates all leave
unchanged --- so accepted frames went uncounted, including the
identical-frame/higher-generation case this slice requires, and a
fixture waiting for two frames would wait forever. It snapshots the
complete accepted authority now, `(presented frame, mapping_generation)`,
and keeps the raw payload kind ONLY to tell a real `Absent` from a
refusal: inferring absence from `presented() == None` turned a rejection
into "the daemon says there is no band", a different fact entirely.
Nine rows, ten mutations, each biting its own:
G6a legacy outbound G7a mapped outbound, live generation
G6b legacy inbound routing G7b mapped inbound routing
G8a bare from v25 refused G8c mapped from v24 refused
G8b legacy at v25 refused, atomically
G8d mapped at v24 refused, atomically
G8e both forgery directions
plus: an Unsupported session accepts NEITHER family
G6c/G7c remain replay-lane effects.
**The gate earned its keep**: it caught a real regression I would have
shipped. `one_daemon_serves_a_v21_panel_session_and_a_shipped_v20_client`
counter-offers `PROTOCOL_VERSION`, now 25, so it is a MAPPED session
whose helper drained for legacy `Present` and timed out. Third suite
whose helpers assumed one family --- daemon acceptance, the GPU probe,
now GPU acceptance --- each written when only one family existed and
each quietly deciding what "a panel arrived" means.
Four `--protocol` runs were needed. Three failed on unrelated
signatures: the composition budget twice, in different steps, and
`setsid_escapee_is_not_reaped_and_teardown_reclaims_readers` once, a
new signature. All are recorded in the lane ledger rather than
`ci-red-signatures.md`, which ends at U9 here while the unmerged replay
branch already holds a U10.
Gates: all eleven green under `env -u TMPDIR` with `--protocol`,
log 20260815T185708Z, verified by exit status.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Closes the fourth terminal gap. The row I could not drive was
reproducible; **my delta had the wrong sign** --- `scroll_view(key,
viewport, 3)` moves, `-1` does not --- and I concluded the fixture was
at fault after three attempts rather than trying the other direction.
The sequence: `frame()` registers the panel terminal view, the key is
built from the side window, the viewport is `panel_grid_size` less its
mode-line row, forty published line feeds build the history, and the
baseline is taken AFTER that history exists. Taking it after is what
makes the leg discriminating --- a constant anchor leaves the
post-history baseline unchanged, so the row fails even while
`mapping_revision` is perfectly live.
Two mutations, each failing this row alone:
constant ANCHOR, live revision -> the anchor is not in the key
constant REVISION, live anchor -> the screen is not in the key
Neither passes on the other's evidence, which is the separation the
terminal half needed: `screen.rs` proves the counter classifies events,
the domain row proves the branch is taken, and these two prove the
daemon's key actually reads both halves of what
`view_mapping_identity` returns.
The owed-witness note is removed from the ledger.
Verified: focused suite 37/37, clippy clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Three of the four terminal gaps. The fourth is recorded as owed rather
than faked; see below.
**THE STABLE CLASSIFICATION WAS INCOMPLETE.** Cursor motion still
advanced the mapping revision: `restore_cursor`, `horizontal_tab`,
`move_vertical`, `move_horizontal`, `set_col` and `set_row` all called
`changed()`. Moving the caret denotes nothing new, and a child that
merely repositions its cursor would have cancelled a drag. All six take
the display-only path now.
Worse, **rewriting the same glyph under another style advanced it**,
which is precisely the control SS5b requires to hold. `write_character`
now compares the glyph before writing --- sampled BEFORE
`clear_wide_at`, which blanks a cell that is part of a wide pair and
would otherwise make every rewrite look like a change. That ordering
was found by instrumenting the failing row, not by reading the code.
**THE SNAPSHOT CARRIED DOCUMENT-ONLY STATE FOR TERMINALS.**
`view_top`, `view_left`, wrap, content columns, fold policy and folds
describe a document projection and take no part in a terminal's, where
the child's screen decides the mapping. They live inside the `Document`
arm now; only common geometry --- buffer identity, rows, columns ---
stays outside.
**AND THE REVISION WAS NOT PUBLICATION-CONSISTENT.**
`view_mapping_identity` read the LIVE screen revision while
`projection_ref` returns the last PUBLISHED cells, so buffered output
under synchronized-output would stamp displayed cells with authority
they were never painted under --- a frontend echoing a generation
matching nothing it can see. `ScreenProjection` carries
`mapping_revision` now and the published value is what is read.
**The witnesses were separated across the seam**, which review named
exactly: `screen.rs` proved the counter, the daemon proved enum
selection, and a `view_mapping_identity` returning a constant would
have left both green. A daemon-level row now drives real events through
a panel terminal and asserts the daemon's generation moves on a new
glyph and holds across a style-only rewrite and across cursor motion.
**OWED, NOT DONE: the scroll-anchor row.** The anchor is in the key,
but three attempts failed to drive a scroll from this fixture ---
`scroll_lines` wants a viewport the projection registers on its own
schedule, and `scroll_view` with an explicit size reports no movement
after forty line feeds. Recorded in the ledger rather than faked or
quietly dropped: without it, a constant ANCHOR alongside a live
revision still passes every terminal row that exists.
The two test hooks are `#[doc(hidden)] pub`, not `#[cfg(test)]`,
because the rows needing them are integration tests and those link the
library without `cfg(test)`.
Verified: focused suite 37/37, `cargo test --lib` 1945 green, clippy
clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
SS5b's first implementation commit: the two appended variants, the
version constants, and the pins that hold them in place. No gating, no
key, no replay --- those are the next commits, and the variants are
REFUSED everywhere until their gate lands.
**APPENDED AT THE TRUE END, confirmed by the discriminants.**
`PanelPointer` is 15, `TextInput` 16, `PanelPointerMapped` **17**;
`Present` 0, `Absent` 1, `PresentMapped` **2**. "Beside `Present`" would
have been adjacent insertion, which shifts every discriminant below and
silently re-interprets an older peer's bytes. `mapping_generation` is a
`u64`, last within each variant, documented invalid at zero --- the
value a default-constructed sender produces, so accepting it would let
a peer opt out of the check by sending nothing.
**THE COMPILER NAMED EVERY SEAM.** Four non-exhaustive matches:
`semantic_render`'s declaration accessor now sees through both
families, and the three routing sites REFUSE the mapped variant rather
than unwrapping it to legacy meaning. Refusal is the correct default at
an intermediate commit, not a placeholder --- until the frontend can
prove it negotiated v25 it IS a `<= v24` peer for gating purposes, and
painting first would ship a window in which the band is hit-tested with
no mapping identity at all.
**Five mutations, each biting its own rows:**
insert `PanelPointerMapped` before `TextInput`
-> the TextInput pin and the mapped pin. `PanelPointer`'s v23 pin
correctly SURVIVES: its discriminant did not move, which is the
"only the pin whose discriminant moved fails" behaviour G0a
specifies
insert `PresentMapped` before `Absent`
-> the Absent pin and the mapped-frame pin
swap `geometry_epoch` / `panel_epoch`
-> the exact-bytes assertion, while the round-trip stays green.
That is the blind spot G0b exists for, and it is why every
adjacent same-typed field carries a distinct value
bump the wire version without extending the supported set
-> both new tripwires and 1a's v6 ladder
move `ADVERTISED_PROTOCOL_VERSION` to 25
-> the baseline pin
**Version fallout, enumerated rather than discovered one gate at a
time.** Four acceptance-suite tripwires (`bottom_panel_stage2b_gpu`,
`discovery_stage2` x2, `vterm_stage3`, `statusline_segments`) each say
"a wire bump must be a conscious edit here" and each worked. Rather
than fix them one run at a time I grepped the tree for version
assertions and updated all four in one pass.
Review folded five further corrections, two of which fix reasoning of
mine that was wrong:
- I claimed reversing `frame` and `mapping_generation` "fails to
compile" because they are different types. **False for NAMED
variant fields** --- the initializer uses names, so reordering the
declarations compiles and shifts postcard's positional bytes
silently. The pin is the only thing catching that.
- Ladder loops now track `PROTOCOL_VERSION` while TRIPWIRES stay
literal. I had flattened both to `25`. A tripwire is literal so a
bump is a conscious edit; a ladder must move, or the next bump
silently stops testing the top rung. G14b is unaffected ---
`PANEL_MAPPING_MIN_VERSION` stays literal, because there the
arithmetic is exactly the hazard.
- `assert!(24 < MIN)` was a compile-time tautology holding for every
value above 24. Replaced with the literal equality plus
`assert_ne!` against `TEXT_INPUT_MIN_VERSION`: the mapped family
must not share v24's gate, or it is admitted on sessions that
negotiated only `TextInput`.
- Statusline support loop reaches `PROTOCOL_VERSION`; public protocol
history records v25.
**CI-red observations are in the LANE LEDGER, not the registry**, and
that is deliberate: `ci-red-signatures.md` here ends at U9 while the
unmerged replay branch already added a U10, so a row from this branch
would duplicate an id or invent one blind --- which this file's own
history records going wrong, two branches' entries merging "without a
conflict, producing duplicate ids across four sites". R7 twice and the
composition budget once, fragments verified, owed to the registry by
whichever branch merges second.
Gates: all eleven green under `env -u TMPDIR` with `--protocol`,
log 20260815T103555Z. Four runs were needed; three were lost to those
two signatures, not to this diff.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Close the producer, receiver, protocol-family, and gesture-lifecycle
cross-products in one pass. Separate route witnesses from replay effects,
freeze both old boundaries and new variant fields, and record the bounded
review method for the remaining chain.
Answers review of 15. Framing only. Three items reverse a rule 15
introduced, and one retracts a mutation that was not a defect.
**THE PRODUCER RULE CONTRADICTED PROACTIVE CANCELLATION.** The daemon
cancels BEFORE emitting the replacement frame, so the frontend needs
only to clear its local latch when that frame arrives, and send
nothing. Revision 15 asked it to emit a cancellation tail or retain the
latch: the tail is redundant --- the daemon would receive a release for
a gesture it has already settled, which is the duplicate release the
latch exists to prevent --- and RETAINING IS ACTIVELY HARMFUL, because
it manufactures a `Drag` under the NEW generation with no accepted
`Down`. That is the exact orphan the section exists to prevent,
produced by the rule meant to prevent it.
Ordering is what makes the simple rule safe: cancel, then emit. The
frame's arrival IS the cancellation signal; no second channel is
needed. Witnessed as `Down` -> key advances -> replacement frame ->
motion and physical `Up` produce no new drag and no duplicate release.
**THE LATCH HAD ONE TRIGGER AND NEEDED FIVE.** Cancellation runs on
every loss of gesture authority: generation advance, `Absent`, panel or
buffer identity change, geometry-epoch change EVEN AT AN UNCHANGED CELL
TOTAL, and detach. And an ordinary accepted `Up` must clear the latch,
or a later invalidation finds a gesture it believes live and
synthesises a duplicate release for a button already up --- the replay
lane's D1/D2 orphan race, arriving from the daemon's side.
**G9b's MUTATION WAS A VALID IMPLEMENTATION, NOT A DEFECT.** Keying the
dedupe by `(mapping_generation, coord)` preserves same-generation
suppression and naturally admits the first motion under a new
generation. Requiring it to fail would have forbidden a correct design.
Replaced with two real defects: compare only the cell and never key or
reset by generation (the first post-change motion is eaten), and reset
on every same-generation repaint (pixel-rate traffic returns).
**"PROJECTED CELL IDENTITY" CONTRADICTED THE STYLING CONTROL** in the
same section. The wire `Cell` derives `PartialEq` over `glyph`, STYLE
and `attachment` (`pmacs-protocol/src/cell.rs:153`), so an identity
keyed on cell equality moves on a pure recolour --- while the stable
controls rule style out. Terminal identity is now glyph and row
TOPOLOGY plus the view anchor, excluding face, style and cursor, with a
same-glyph/different-style control: the row that catches an
implementation reaching for `Cell` equality because it is right there.
P2s: zero-generation rows added in BOTH directions as independent legs
(a valid `PresentMapped` with generation zero must be rejected
atomically; a zero-generation `PanelPointerMapped` must be refused);
G7 split into outbound mapped-frame and inbound mapped-pointer legs,
since its old mutation only withheld the frame; G2's grid rows/columns
and fold-map-content/`fold_projection`-policy composites split; and
SS20 now names journey steps 5 and 8 while stating neither grade
changes --- an auditor scanning for grade movement alone would
otherwise conclude this slice touches no journey.
**AND R7 RECURRED, ON A DIFF THAT IS ENTIRELY DOCUMENTATION.** The
first `--protocol` run of this tree failed the `gpu` step on
`managed_retry_survives_transients_and_uses_the_successful_stream`,
with all three required fragments verified from the durable log
(`20260815T072601Z`). Recorded as R7's FIFTH occurrence.
It carries the strongest tree exclusion the row has had: occurrences 1
and 4 argued "unrelated lane", while this branch cannot be related at
all --- no Rust, no wire surface, no `pmacs-gpu` file. The line moved
to `attach.rs:1728` from `:1680`, which the row already treats as
occurrence-specific rather than a fragment. Isolated rerun green, and
the full gate green on the re-run (271/271 in the `gpu` step) --- which
per this file's rerun rule establishes INTERMITTENCE ONLY, though here
there is no tree change to exonerate.
What five occurrences across three flavors and five unrelated lanes now
support is that the failure is NOT LANE-CORRELATED. That is evidence
about where the cause is not. The retirement condition is unchanged.
Gates: all eleven green under `env -u TMPDIR` with `--protocol`,
log 20260815T073556Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of 14. Framing only. Four of the six reverse something
14 asserted, and a green protocol gate would not have caught any of
them.
**"BESIDE `Present`/`PanelPointer`" WAS POSITIONALLY DANGEROUS.**
"Beside" reads as adjacent, and adjacent insertion shifts every
discriminant below it --- the exact hazard the appended-only rule
exists for. Appended means LAST: `PresentMapped` after `Absent`,
`PanelPointerMapped` after `TextInput`, with a diagram so the next
reader cannot re-derive it wrongly. Field order is stated exactly,
`mapping_generation` is a `u64`, ZERO IS INVALID --- it is what a
default-constructed or half-initialised sender produces, so accepting
it would let a peer opt out of the check by sending nothing --- and the
gate reads `PANEL_MAPPING_MIN_VERSION = 25` rather than a literal.
**BLANKET REFUSAL STOPPED THE WHEEL AFTER ONE TICK.** The first
effective document wheel changes `view_top`, which advances the key, so
the next already-queued tick carries the old generation and is refused:
the panel scrolls once and goes dead until the frontend observes the
new frame. Local terminal scrollback has the same shape.
The discriminator is whether the gesture USES its coordinate.
Coordinate-free gestures --- the document wheel, non-reporting terminal
scrollback --- cannot be mis-aimed by a stale mapping and are EXEMPT.
A child-reported wheel is the opposite case: SGR carries row and
column, so a stale one aims an application action at a cell the user
never pointed at, and it keeps the check. Two-tick witnesses added,
because without them a blanket-refusal implementation passes every
single-event row in the matrix.
**CANCELLATION WAS REACTIVE AND LOSES A RACE.** If the replacement
mapped frame reaches the frontend before the physical `Up`, the
producer resets `pointer_held` and SUPPRESSES THE VERY EVENT that would
have cancelled --- so the daemon is never told, the selection stays
armed, and the child keeps holding its button. It is now PROACTIVE,
triggered by the authoritative key advancing while a gesture is
accepted, and the producer must emit a cancellation tail or retain the
latch rather than clearing first.
That needs state 14 assumed and never specified: an ACCEPTED-GESTURE
LATCH recording whether the `Down` was accepted, whether it reached the
child, and the coordinate, button and encoding a release must match.
Two rules fall out and are ruled here --- a stale `Up` with no accepted
`Down` is INERT, and cancellation NEVER reclaims a controller another
frontend has since taken, because a stale gesture must not steal a live
one's terminal.
**THE EXISTING SCREEN GENERATION CANNOT BE THE TERMINAL KEY.**
`Screen::changed()` bumps from 39 call sites including `SetStyle`,
`Bell`, the tab-stop operations, cursor-only motion and `SetTitle`.
None of those change what a coordinate denotes, so keying on it would
cancel a drag every time the child recoloured a character. A dedicated
terminal mapping revision is defined over projected cell identity,
retained-row identity and the per-view scroll anchor --- with those
five events as explicit STABLE CONTROLS, so a reader who later reaches
for the convenient counter fails a test instead of shipping a cancelled
drag.
**G5'S EFFECTS ARE NOT PROVABLE ON THIS BRANCH**, and 14 claimed them.
`gesture_last_content_cell` and the document/terminal replay exist only
on `panel-pointer-replay` (`pmacs-gpu/src/main.rs:2143` there); the
same struct here is at `:2124` with no such field. The obligations are
split in a table. G5a --- that the key advancing RAISES cancellation
--- stays here on purpose: the trigger is this slice's rule, and moving
the whole row out would leave the proactive ruling with no witness in
the slice that introduces it.
P2s: mutation legs split (wrap vs gutter, terminal content vs
scrollback, G5a-c, G8a/b, G9a/b); G7 given a positive-path mutation;
G11 expanded --- exhaustion must publish `Absent`, clear input
authority, cancel any accepted gesture and LATCH, or a stale panel
stays painted and permanently inert; the v26 correction finished at the
gate and old-peer cells (`:573`); and the SS20 impact statement added
--- hardens an existing panel island, no journey grade changes, no
config, no background work.
**And the pin correction is mine to make: it EXISTS**, at
`src/protocol.rs:1975`, in the ROOT crate's test module rather than
under `pmacs-protocol/` or `tests/` --- which is exactly where I
searched. `message.rs:524` was right and the doubt was wrong; the
contrary claim is removed from both records.
Gates: all eleven green under `env -u TMPDIR` with `--protocol`,
log 20260814T180105Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Own branch, own slice, protocol-bearing, runs alone. Framing only; no
implementation. Blocks `panel-pointer-replay`, which blocks GUI arc 1b.
Answers review of revision 13. Every item below reverses or completes
something 13 got wrong.
**GATING IS REFUSAL, NOT FALLBACK.** Revision 13 said a bare
`PanelPointer` from a new peer would be "handled under the old
semantics". That is a BYPASS: it leaves the exact hole this slice
exists to close, reachable by omitting a field. A >= v25 session
sending the legacy event is REFUSED before mutation, and a >= v25
frontend REJECTS a legacy `Present` rather than painting a band it
cannot safely hit-test. Only negotiated <= v24 keeps legacy semantics;
`Absent` stays common to both families.
**ONE AUTHORITATIVE PER-FRONTEND KEY**, used by projection AND inbound
validation, advanced after any mapping mutation and BEFORE the next
inbound pointer is handled --- whether or not anything has rendered.
Comparing against the last EMITTED frame recreates the hole, because a
mutation not yet painted has still changed the inverse mapping.
**STALE TAILS TERMINATE; THEY DO NOT VANISH.** A blanket drop breaks
liveness: a refused `Up` leaves an empty document selection armed with
a stale anchor, and leaves a reporting terminal child HOLDING A BUTTON
FOREVER. Cancellation is now a ruled outcome --- producer latch reset,
daemon selection and click-chain cleanup, and the child's release
delivered at the last coordinate known good. A cancelled gesture is
explicitly not a replayed one: the release is for liveness, and no
selection or scroll effect is applied from the stale event. Stale
BEGINNINGS may still simply drop.
**THE DOMAIN WAS INCOMPLETE.** `view_left` is added, because 1b makes
horizontal scrolling real. "Cursor movement is stable" is now
CONDITIONAL: a cursor move that triggers vertical or horizontal follow
changes `view_top` or `view_left` and therefore does change the
mapping. Terminal panels are ruled explicitly --- their coordinates are
decided by the SCREEN, so output and scrollback movement change the
generation while their buffer revision does not.
**SS5b HAD NO ACCEPTANCE MATRIX.** G1-G11 now cover the foreign edit
before render, every changing and stable domain entry ROW BY ROW, a
selection repaint that must preserve the generation and let a drag
continue, mid-gesture cancellation, v24 and v25 positive controls with
both wrong-family refusals, identical cells across a generation change
still emitting, atomic retention of frame and generation on an invalid
frame, and fail-closed exhaustion. The per-entry enumeration is
deliberate: one aggregate row cannot show WHICH input moved the key,
and a key ignoring `view_left` passes every vertical-only row.
**MAPPED MOTION KEEPS ITS COALESCING TAGS.** A new variant falling
through to the lossless default would put pixel-rate `Move`/`Drag` on a
bounded queue.
**PINS ACCUMULATE.** Revision 13 said the pin "moves", which would
delete coverage of the shape it protects. `PanelPointer` is retained;
exact `TextInput` bytes are added as the previous-final
`FrontendEvent`; the complete nested `PanelFrame(Absent)` bytes are
added as the previous-final `PanelFramePayload`. Recorded honestly: I
could find NO exact-bytes pin for `PanelPointer` anywhere in the tree,
though `pmacs-protocol/src/message.rs:524` says one is in the tests.
Either my search missed it or the doc overclaims; this slice resolves
it either way, since it must add exact pins regardless.
**AND THIS SLICE OWNS THE VERSION CORRECTION.**
`docs/gui-stage1-input-framing.md` now says 1e's `OpenTarget` is
**v26**, with the reason stated at the top. An expected rebase conflict
on `gui-stage1b-pointer-scroll` is not grounds for leaving the
canonical document false --- which is what I argued last round, and it
was wrong. `ADVERTISED_PROTOCOL_VERSION` stays pinned at 20.
Gates: all ELEVEN green under `env -u TMPDIR`, with `--protocol`
(`build-crdt`, `sweep-crdt`), log 20260814T162843Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Section 3 already forbids starting the gate from a shell that ignores
SIGINT, and already records that an ordinary tool-level background
launch is measured deliverable. It did not cover the other way a
harness convenience turns into false evidence.
The 16-stage suite outruns a ten-minute agent command cap. Wrapping it
in `timeout 580` kills sweep mid-run, and the runner records that stage
as a failure --- indistinguishable in the log from a real red. That
produced one false SS5b gate result.
Records the supported alternative, which is the tool-level background
launch the section already vouches for, and the fallback of labelled
pieces with the record saying which piece produced which result.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The row rd_precondition_validates_the_whole_conformance_set failed once
in a sweep-crdt run on 2026-08-20 and passed on the two sweeps after it.
The message was not captured, so nothing here explains it --- the
occurrence is recorded and the diagnosis is not.
Also withdraws a mechanism I offered for it. I described the test as
spawning 46 concurrent stubs under load; it runs 45 stubs SEQUENTIALLY
plus one intentional nonexistent-path spawn probe, so there is no
concurrency to be pressured and 46 was a miscount. Thirty consecutive
user-run repetitions at load ~10.5 --- 1,350 stub executions --- did not
reproduce it.
Records the standing instruction that a recurrence must capture the
exact case and error before anyone theorises again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Records what the lane closed, measured rather than argued.
A6a is closed by measurement: status 1 with no token classifies as a
boundary error, never `ignored`, green on macOS --- the platform whose
shell exits 1 for an exec failure, which is what produced the original
defect and what a status-only ABI could not distinguish.
A7 stops being "satisfied by disclosure". Both macOS flavours exercised
the helper and gate consumers across the full 45-case shared set. The
R-d consumer stays Linux-only, because its test is crdt-gated while the
macOS jobs build without crdt and Test (crdt) is ubuntu-only --- recorded
as an open gap rather than quietly closed.
Also records that the two m4_24_* rows failing locally under crdt do not
reproduce in CI, at this branch or at 72da24a: local-environment
-specific, not a code defect and not this lane's.
panel-mapping-generation is unblocked, and its sixteen-stage gate must
run in the foreground --- the condition its stage 15 always needed.
Per the standing convention this absorption does not advance any
canonical base to its own commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two closure gaps.
1. Both suites asserted only `cases.len() == 45`, so the exact
45-entries-over-43-distinct-inputs regression could recur unnoticed
--- the one where X3 collapsed into 1/E/empty and X4 into
0/V/safe/bare, leaving two framing-specified cases silently
unexercised. shared_cases() now asserts uniqueness over
(status, stdout, stderr), inside the generator so no consumer can
forget it. Verified by reverting both payloads to the sentinel: it
fails naming X3.
2. Comments and ledger still said every stub emits the sentinel, which
the explicit X3/X4 payloads had made false. They now say the
BRANCH-DISCRIMINATING cases carry it while X3 and X4 deliberately
carry their own --- X3 the canonical ignored wording with no token,
X4 noise --- and that this is what makes them distinct inputs. The
duplicated `self::`/`super::` explanation left over from the nesting
fix is reduced to the correct one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Five findings. The first was red CI that my local gate could not have
caught.
1. `crate::common` cannot resolve when gpu_invocation_acceptance.rs is
compiled as a nested module of gpu_initial_target_acceptance.rs,
where `crate::` is the outer test crate. Now `super::common`, which
resolves in both modes --- verified by compiling each target
explicitly. Clippy's `(Some(1 | 2), true)` folding applied too.
The reason this shipped: plain `./scripts/gate` omits sweep-crdt,
the only stage that compiles the nested target under crdt, while
04-lib-crdt builds the lib alone. This lane gates with `--protocol`,
and the ledger now says so.
2. X3 and X4 had stopped being the cases the framing specifies:
stub_script() gave every case the same sentinel stderr, so X3 lacked
the canonical ignored text and X4 was byte-identical to
0/V/safe/bare --- 45 entries, 43 distinct inputs. Case now carries an
explicit stderr payload; X3 emits the canonical wording with no
token, and both consumers assert they never repeat it.
3. The capture-creation-failure row asserted exit, wording and stage
output but not residue. It now inspects the temporary root before
its RAII drop and requires it empty.
4. The exact-token test covered safe and error but not ignored, despite
the ledger claiming all three. The ignored arm now asserts its exact
stdout, driven through a SIGINT-ignoring shell.
5. The ledger's claim that the status-2 mutation is caught only by the
dedicated row is superseded --- the sentinel matrix catches it --- and
the self-referential "this commit" is replaced by bc7d776.
Also records two PRE-EXISTING crdt-only failures found while gating
properly (m4_24_bare_string_glob_stays_relative and
m4_24_d3_fallback_base_is_the_smallest_attachment_dir): they reproduce
in isolation and fail identically at 72da24a, so they are not this
lane's, and no cause is claimed for them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Full gate GREEN on the committed head 8802d6a, all 8 stages, log
20260820T072102Z-3009434.
Two provenance corrections recorded rather than smoothed over:
- The first attempt on that same head failed 07-sweep on
composition_overhead_under_ten_percent, a perf budget unrelated to
this lane's surface, green in isolation and already recorded as a
recurring signature on the panel-mapping-generation ledger. Both
runs are kept. No cause is claimed for the first --- only that the
second is the head-exact evidence.
- The earlier 20260819T190930Z-2647615 run finished about thirty
seconds BEFORE bc7d776 was committed, so it described the
implementation tree, not a committed head. It is relabelled
accordingly rather than left standing as gate evidence for a commit
that did not yet exist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Four acceptance gaps, all upheld.
1. Neither suite distinguished a validated refusal from a boundary
error. Both exit 2 (and both produce Err in Rust), so comparing exit
codes or is_ok() let a validator that accepts EVERY status-2 pair
pass the whole matrix --- the precise defect A6c exists to catch.
Every stub now emits a sentinel on stderr, and an Outcome enum
(Safe / ValidatedIgnored / ValidatedError / Boundary) is asserted
branch-exact: a validated verdict must surface the sentinel, a
boundary failure must withhold it. Verified: mutating the gate to
accept any status 2 now fails the MATRIX, where before it only
failed a dedicated row. Each helper arm's exact stdout token is
asserted as well.
2. The 45-case set was duplicated in both suites and could drift while
both still reported length 45. It now lives in
tests/common/sigint_conformance.rs and both validators consume the
same vectors.
3. A8 was incomplete --- nothing forced capture-directory creation to
fail. A bounded row points TMPDIR at a missing directory so
`mktemp -d` fails, asserting boundary error 2, no stage execution and
no residue; mutating the failure branch to fall through makes it
fail. Temporary directories are RAII throughout, replacing the
keep()-plus-manual-cleanup shape.
4. The R-d comment still claimed a shared helper means the consumers
"can never disagree" and described status-only behaviour. Both were
withdrawn by revision 13; the comment now points at the shared matrix
as what actually keeps them in step.
36 gate rows, 16 GPU rows, clippy clean, full gate green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
The helper now emits its verdict token on stdout with diagnostics on
stderr, and both consumers validate the PAIR rather than the status
alone. This closes the macOS defect CI found: a shell that cannot
execute the helper exits 1, which the status-only ABI read as
`ignored`, so a broken guard told the operator their environment
ignores SIGINT.
Gate (shell consumer):
- guard-local capture directory, created before the gate's own
temporary roots exist, with cleanup armed BEFORE the helper runs and
disarmed on the safe path so the gate's later trap is undisturbed;
- `|| sigint_status=$?` retained --- a bare invocation dies under
`set -eu` before the status is read, which was the original bug;
- `expected_token` selected by an explicit status case before any
`set -u`-sensitive use, since an out-of-range status has none;
- byte comparison via `cmp` against both permitted encodings, because
a shell variable neither preserves NUL nor carries the child status;
- the helper's stderr is surfaced ONLY for validated verdicts; a
boundary failure prints the gate's own wording and withholds the
untrusted child output;
- every refusing branch prints status= and token=.
R-d (Rust consumer) validates the same pair from Command::output()
bytes. It needs no capture files, and its spawn-error path has no status
at all --- the boundary the shell cannot represent.
Conformance: 45 shared cases generated as a cross-product over token
class, encoding and status, run by BOTH validators so they cannot
diverge, plus Rust's X2 for 46 overall. 34 gate rows, 16 GPU rows, full
gate green.
Mutations, each biting its row: accepting any status 2 regardless of
token; surfacing child stderr on a boundary failure; emitting the token
to stderr. The first is caught by the dedicated error row rather than
the conformance set --- most of the set's boundary cases have empty
stderr, so they cannot tell which branch produced the exit 2 --- and
that limitation is recorded rather than left implicit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Revision 13 is approved at 5dece3e after closing the status-preserving
capture, guard-local cleanup, exact-byte grammar, stderr trust, complete
pair matrix and consumer-specific boundary blockers.
The replacement may now be implemented under the A1-A8 contract. PR
#241 remains unmergeable until that implementation is complete, gated,
and green on macOS.
Close the last three approval blockers in revision 13.
The shell algorithm now removes and disarms its guard-local capture on
the safe path, selects the expected token through an explicit status
case before any set-u-sensitive use, and sends every out-of-range status
straight to boundary error. The load-bearing `|| status=$?` remains in
place. A mechanical set-eu exercise covers safe, ignored, validated
error, status 126 and capture-creation failure; every path returns the
specified public status and leaves no capture residue.
The conformance accounting now distinguishes the 45 cases shared by the
shell and Rust validators from Rust's additional no-status spawn-error
case. A shell exec failure necessarily becomes a shell status, so it
cannot exercise that Rust-only input. The text also stops claiming that
Rust uses file-backed capture: only the shell needs files, while Rust
compares Command::output byte vectors directly.
No remedy implementation. PR #241 remains blocked until revision 13 is
recorded approved.
Three blockers, all upheld.
1. The file-backed snippet dropped `|| status=$?` and invoked the helper
bare. Under scripts/gate's `set -eu` that terminates the gate at the
helper's non-zero exit, before the status is ever read --- which is
the ORIGINAL shipped bug, reintroduced in the very section written to
replace it. The load-bearing shape is restored and commented as such.
2. $tmp does not exist where the guard runs. The guard sits immediately
after the worktree resolves and deliberately precedes the log
directory, ambient root and GATE_TMPDIR, so it must create and own
its capture directory --- with the cleanup trap armed BEFORE the
helper is invoked, and a disarm on the safe path so the gate's own
later trap setup is undisturbed. New A8 witnesses that no capture
directory survives any path, including failure to create one:
the guard was placed early to leave nothing behind, and a capture
directory must not weaken that.
3. The case count was fiction. T0 + LF is a valid third encoding per
status --- and is what the shipped helper actually emits, since it
prints with echo --- and "a different valid token" has two
possibilities per status, so sampling one left half the mismatches
untested. Now enumerated: two valid encodings, six mismatched
valid-token pairs each in both encodings, eight malformed classes,
giving 14 per status x 3 = 42, plus four out-of-band cases = 46.
Earlier drafts claimed twelve, then twenty-three, then thirty-four,
each a count of a set that had not been enumerated; the document now
says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Three blockers, all upheld, and the first two say the same thing: the
capture mechanism I specified cannot implement the contract above it.
1. The sentinel idiom destroys the helper status. In
out=$("$helper"; printf x) the last command is printf, so the
assignment returns 0 whatever the helper did --- measured: a helper
exiting 1 gives assignment status 0.
2. A shell variable cannot carry the byte grammar. Command substitution
drops NUL in POSIX sh and bash --- and, measured here, zsh KEEPS it.
So TOKEN NUL validates in one shell and not another, which is worse
than lossy for a contract two consumers must implement identically.
Both defects live in variable capture, so the spec now uses
file-backed capture: redirect stdout and stderr to files, read the
helper's own status directly, and compare bytes with `cmp` against
generated want/want_lf files. Files preserve every byte including
NUL; Rust compares out.stdout against TOKEN and TOKEN+LF. If a
future consumer must use a variable, the status has to be carried
out explicitly and the NUL divergence still bars a byte-equality
claim --- both recorded.
3. The matrix was not the claimed cross-product: it omitted
(1, unknown-version) and applied malformed and whitespace cases only
at status 0, so a validator that checked tokens strictly for 0 and
accepted arbitrary status-1 output passed all 23 rows. Replaced by a
generated ten-token-class x three-status cross-product --- only the
diagonal validates, the other 27 combinations are boundary errors ---
plus four out-of-band cases: out-of-range status, spawn failure, the
untrusted-stderr case, and stderr noise on an otherwise valid pair.
34 cases. The stale "same twelve cases" sentence is gone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Four blocking issues, all upheld. The first defeats the whole design if
left standing.
1. Boundary errors trusted unvalidated stderr. A helper exiting 1 with
NO token but the canonical "SIGINT is ignored" text would classify
as boundary error --- correctly --- and then tell the operator their
environment ignores SIGINT. A6 satisfied in the classification,
violated in the message actually read. Now: a validated pair's
stderr IS the diagnosis and is surfaced unchanged; a boundary
failure's stderr is untrusted, and the consumer emits its own
wording, omitting the child's or labelling it untrusted. New A6b
witnesses exactly that case (conformance row 23), with a mutation
for a consumer that surfaces it anyway.
2. The matrix did not prove exact-pair validation: no invalid status-2
pair existed, and the expected column collapsed validated
(2, :error) with boundary errors, so a validator accepting every
status 2 passed all twelve rows. The matrix is now a 23-case
cross-product distinguishing `error (validated)` from
`error (boundary)`, with (2, missing), (2, :safe), (2, :ignored) and
(2, unknown-version) all boundary. New A6c pins it.
3. Normalisation was internally inconsistent and not implementable
identically. "Strip one newline then trim ASCII whitespace" removes
further newlines, so TOKEN\n\n would have validated while the same
clause demanded single-line output --- and POSIX $() strips ALL
trailing newlines while Rust returns raw bytes, so the consumers
could not have agreed even on a correct rule. Replaced by one byte
grammar, stdout := TOKEN | TOKEN LF, with NO trimming, plus the
shell sentinel idiom `out=$(helper; printf x); out=${out%x}` so the
shell preserves what it must compare. Vectors added for extra
newline, leading newline, surrounding spaces, CRLF and doubled
token.
4. The ledger's old A7 assertion --- satisfied by disclosure, Linux-only,
no non-Linux unix reachable --- contradicted its own macOS record
twenty lines above. Marked explicitly as revision-12 history with
the live record named.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Five blocking inconsistencies, all upheld. The first was the worst: the
document specified a validated pair and then printed an algorithm that
emits no tokens and a consumer flow that proceeds on exit 0 alone ---
accepting 0 with a missing token, the exact defect revision 13 forbids.
1. The algorithm now emits exactly one token per arm on stdout with
diagnostics on stderr; the consumer flow is pair-validation with
explicit normalisation (strip one trailing newline, trim ASCII
whitespace, require exactly one line); and the outcome table is
keyed on pairs, with a fourth row for boundary error including
macOS's status 1 with no token. `safe` is validated like the
others --- a status arriving without its token did not come from
this helper.
2. A6a is SCOPED TO THE GATE. R-d never sees a shell status: the gate
goes through /bin/sh, which turns an exec failure into an exit
status, while Rust's Command returns a spawn error with no status
at all --- conformance row 12, not row 5. And macOS CI does not
compile R-d's test, which is crdt-gated while the macOS jobs build
without crdt. R-d on macOS is unexercised, and the framing says so
rather than implying coverage.
3. A7 is restated against measurement. It cannot still say no
non-Linux unix was tried when macOS ran and went red: five of six
helper/gate rows pass there, one defect is named, R-d is recorded
Linux-only, and the remaining portability claim is labelled a
contract argument.
4. "Both consumers use the same helper so they can never disagree" is
withdrawn --- true when the status WAS the verdict, false once each
consumer validates a pair independently in a different language.
Replaced by a twelve-case conformance matrix both validators must
agree on, including the macOS case and a normalisation case.
5. The token-to-stderr mutation is remapped from A2 to A1/A3, with
the reasoning recorded: with stdout empty every outcome becomes
boundary error, which still satisfies A2 as written since A2 only
requires "not the deadline message". A2 stays broad and A6 pins
which diagnosis appears.
The ledger is aligned: the mechanism is established rather than
hypothesised, the "stderr prints the raw status" claim is corrected ---
the number appears only in the catch-all, and this failure took the
other branch --- and revision 12 is marked superseded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
CI found revision 12's status-only ABI unsound on macOS. An unexecutable
helper makes macOS /bin/sh exit 1, which the ABI already reads as
`ignored`, so the gate told the operator their environment ignores
SIGINT when in fact the guard never ran. Linux returns 126 and mapped it
correctly, which is why local gating never saw it. Five of six SIGINT
rows pass on macOS; this is the sixth.
My proposed repair --- move `ignored` from 1 to 3 --- was rejected in
review, correctly: it relocates the collision rather than closing it,
since an execution failure can return any nonzero status. The
generalisation is what matters: NO EXIT STATUS CAN PROVE THE HELPER RAN.
Revision 13 therefore replaces the status-only ABI with a validated
(status, token) pair --- 0/1/2 paired with pmacs-sigint-v1:safe /
:ignored / :error, token on stdout, diagnostics on stderr. Any other
pair, including macOS's status 1 with no token, is a boundary error
mapped to 2. The public status meanings are preserved; what changes is
that a status must now be corroborated by something only the helper
could have printed.
Every refusing branch must also print the observed status and the token
state --- valid, missing or unexpected --- as diagnostic context, never
as the classifier. Revision 12 printed the number only in its catch-all,
so the macOS path had to be identified indirectly by which message text
appeared.
A4 gains four token mutations, each named against the row it must bite,
including accepting a missing token --- the shipped defect itself. A6 is
extended to cover missing, mismatched and unknown tokens in both
consumers, and a new A6a makes the macOS case a concrete obligation:
status 1 with no token must classify as boundary error, never ignored,
and the row is satisfied only when that platform is green.
Also records that A7 earned its keep: satisfied by disclosure because
the portability claim was argued rather than measured, and wrong the
first time it was measured.
No implementation. PR #241 stays blocked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
CI on 916007b: 12 green, 2 red, both macOS Test jobs, and exactly one
row --- gate_maps_an_unexecutable_helper_to_error_not_ignored, left
Some(1) right Some(2). The other five SIGINT rows pass on macOS.
This is the A7 portability finding the review pre-declared, and it is a
real one: the gate returned 1, meaning `ignored`, for a helper it could
not execute --- the exact conflation §7c forbids.
The cause is not established. The leading hypothesis is that the ABI's
1 is ambiguous by construction: 1 means "ignored", and 1 is also a
status shells hand back for assorted failures. On Linux an unexecutable
file yields 126 and the catch-all maps it to 2; if macOS /bin/sh
returns 1 instead, the two cases are the same number at the boundary
and no catch-all can separate them. That would call for verdicts
outside the range shells produce, which is a design change needing its
own revision --- not something to patch here.
This commit only makes the failure self-diagnosing: the assertion now
includes the gate's stderr, which prints the raw probe status it saw.
The first failure could not say which status produced it, because the
message discarded stderr.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two ledger findings, both mine.
The lane block still said "No PR" while its own header and a new entry
recorded PR #241.
And the self-referential checkpoint wording had gone false, which is the
same trap as naming a branch's own tip: "this entry's own commit adds
the A6 rows" was true when written at 167d830 and false by d64d300, and
"the entry's own commit adds only the gate record" was 7cef9ca. Every
event now carries its IMMUTABLE sha --- implementation 3206433, A6 rows
and bounded negative path 167d830, factual corrections c9cc8dd, gate
record 7cef9ca, PR record d64d300 --- and only the branch tip stays
symbolic, which is the one pointer that has to.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Opened from gpu-probe-sigint-teardown into main after the quiet 8/8 gate
on c9cc8dd. Not merged; awaiting review rounds.
Docs-only, per the recording exemption that keeps gate evidence from
recursing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Full gate GREEN on the committed head c9cc8dd, all 8 stages, log
20260819T160220Z-2339958, started at load 3.90.
The preceding attempt on the SAME head is kept rather than dropped. It
failed 04-lib-crdt and 07-sweep on four wall-clock rows --- the
composition budget, the summary-flatten scaling row, dired's 200ms
budget and a lean4 progress notification --- none of which touches this
lane's change. Load average was 49.6 and an unrelated
./verify_task_state.sh run was compiling under a separate toolchain at
/usr/local/rustup, having started about three minutes in and
overlapping precisely the two failing stages.
That overlap is recorded as evidence of WHEN, not proof of WHY. This
lane already retracted one confident environmental attribution, so the
red run was treated as "not valid evidence" rather than explained away,
and the green run on the same commit is what settles it. Had any of the
four failed again on a quiet machine it would have been a real finding
on this branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Both mine, both checkable against evidence already in the repo.
The ledger said 35 gate-acceptance rows. The suite has 33. The 35 was
git_status_stage1_acceptance's result line, which sits immediately
below gate_script_acceptance's in the sweep log; I read the wrong one.
The correction names the misread so the next reader can see how a
transcription from a sweep log goes wrong.
The framing header newly attributed revision 12's approval to 7752bcb.
It was 1fc0df6 --- as the ledger says and as 7752bcb's own commit
message says in its first line. Restored.
The full gate is re-run on THIS commit rather than on the tree that
preceded it; the previous run finished twenty seconds before 167d830
was committed, so it described an uncommitted tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Three findings, all upheld.
1. A6 was witnessed only for the helper. Both consumers now have real
-path rows.
Gate side, driven through a stub worktree --- a temp git repo holding
a copy of scripts/gate and a controlled helper --- so the gate's own
code path runs against each verdict without touching the checked-in
helper: a stub exiting 2 refuses with the ERROR wording and never
"SIGINT is ignored"; a NON-EXECUTABLE stub maps 126 to boundary
error 2 with its own wording. That second case is what the original
guard got wrong twice.
R-d side: the precondition is split into sigint_diagnosis() ->
Result, so the message is testable rather than reachable only
through a panic in a test that cannot run under the condition it
describes. The new row asserts safe proceeds, ignored says so and
says "NOT a teardown defect", error says "could not determine" and
never "ignored", and an unrunnable helper is undecidable at the
boundary.
2. The refusal row violated this suite's no-recursion constraint: it
invoked the ordinary gate, so a regression of the exact `if !` bug
would have launched eight real gate stages inside the gate suite.
It now uses --self-test, which drives the same runner over a
hardcoded synthetic plan, so the negative path stays bounded
whatever the guard does. under_ignored_sigint() also takes the
program and arguments POSITIONALLY --- `exec "$@"` --- instead of
interpolating them into script text, which broke for any path
containing a space or shell metacharacter, and every path here comes
from a tempdir or CARGO_MANIFEST_DIR.
3. The portable checkpoint is recorded: implementation at 3206433,
pushed, signed, clean, full default gate green 8/8 foreground. The
framing header no longer says implementation "may proceed" --- it
reports IMPLEMENTED. And docs/agent-handoff.md §3 gains the durable
rule: never start the gate or cargo test from a shell that ignores
SIGINT, `setsid nohup ... &` is forbidden, SIG_IGN is inherited
across fork and survives exec, the gate refuses with no override,
and scripts/check-sigint-deliverable answers the question directly.
35 gate-acceptance rows, 16 gpu_invocation_acceptance rows, full gate
green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Four findings, all upheld, and the first was a live bug I shipped.
1. R-b's non-zero handling was unreachable. scripts/gate runs under
`set -eu`, so the bare helper invocation killed the shell at exit 1
or 2 and neither `sigint_status=$?` nor the refusal messages ever
ran; an unexecutable helper would have escaped as raw 126/127 rather
than boundary error 2. Reproduced before fixing.
The first repair was ALSO wrong, and worse: `if ! helper; then
sigint_status=$?; fi` captures the status of the NEGATED condition,
which is always 0, so the gate printed the ignored diagnosis and
then ran the entire suite. The working shape is `helper ||
sigint_status=$?` --- failure handled, so `set -e` does not fire and
`$?` is the helper's own --- which is the idiom the helper already
uses internally. Statuses 1 and 2 pass through unchanged; everything
else, including 126/127, maps to 2 at the boundary and is never
reported as "SIGINT is ignored".
The guard also moved to immediately after the worktree resolves,
before any log directory, ambient root or tmpdir exists, so a
refused run leaves nothing behind.
2. The behaviour had no durable coverage, which is exactly why 27
passing gate tests missed both bugs. Four rows added: helper safe,
helper ignored, helper error (and never ignored), and gate refusal
before stage 1. Ignored-SIGINT is simulated with `trap "" INT`,
which is the real mechanism --- SIG_IGN inherited across fork and
surviving exec --- not a stand-in. Verified to bite: mutating the
gate back to either shipped bug fails
gate_refuses_to_start_when_sigint_is_ignored and nothing else.
3. The ledger now records the implementation, both bugs, the four rows
and their mutation check.
4. A7 is recorded SATISFIED BY DISCLOSURE, which is the fallback
revision 12 allows when no non-Linux unix is reachable. The earlier
"stays open" contradicted the approved contract and is withdrawn.
Tried: Linux x86_64, all three outcomes, all consumers. Not tried:
every non-Linux unix. Claimed: POSIX shell only, no /proc, no
sigaction --- labelled a contract argument, not a measurement.
The full default gate passes all eight stages foreground; it caught a
rustfmt violation in the new test code on the first attempt, which is
the guard-and-gate arrangement working as intended.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
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
Revision 12 is approved at 1fc0df6 after closing the controlled-arm
provenance, total-helper-ABI, and standing-ledger blockers. Record that
R-b plus R-d implementation may proceed under the replacement A1-A7
contract.
Make the second controlled-arm record portable without changing what it
claims: identify head 77b623c, transcribe the actual foreground and
background harness invocations, include the exact evidence-recording
harness, label the captured exit as cargo's, and carry both full binary
digests in both arm columns.
Turn the signal probe into an implementable shared ABI. The checked-in
helper owns classification and diagnostics: 0 is safe, 1 is inherited
ignore, and 2 is probe error. Preserve kill failure in the inner shell,
surface the helper's stderr unchanged in both consumers, and witness the
error outcome in both paths. Correct the mutation mapping so removing
the trap bites foreground success rather than the ignored-signal rows.
Synchronize the active-work ledger with the rerun head, total helper
contract, A1-A7 witnesses, and qualified portability claim.
Three findings, all upheld.
1. The arm provenance was malformed and over-claimed. The "fully
expanded" background command still contained <the fg command above>
and <log> placeholders; both table rows were one cell short of the
header, putting log prefixes under "binary hashes" and leaving the
digest column empty; and the full binary hashes had been read later
from reused paths, which cannot retroactively prove what each arm
executed --- the same provenance rule this document states in §7,
applied against my own record.
Rather than weaken the claim, the arms were re-run at head 77b623c
with FULL SHA-256 captured per run, immediately after each run,
before anything could rebuild them. Both arms: identical
0890b78c...4124c and ef6ff1c1...c696, dirty=0, fg exit=0 ok=2, bg
exit=101 failed=2 SigIgn=0x1007. Byte identity is now carried by the
capture rather than by inference. Commands are written out with no
placeholders, and the table cells line up.
2. The ledger still transported superseded operative instructions: a
"remedy not selected" heading, D0b still owed under A3, journey step
12(a) still assigned, and the old three-consecutive-run A2 contract.
All four now match revision 12's §8/§9 --- remedy selected, D0b
satisfied and not owed, journey steps NONE with gate trustworthiness
named instead, and A1-A7 replacing the three-run contract, which was
written for a flakiness that is now explained.
3. The helper contract was not total. The raw probe reaches exit 0 both
when the kill was a no-op AND when the kill itself failed, so a
broken probe would report "inherited SIG_IGN" and fail the gate for
the wrong reason. The helper now owns the classification and returns
one of safe / ignored / error; consumers consume the verdict and
never re-derive it. `error` is not folded into `ignored` --- it fails
the gate with a different diagnosis, because "your environment
ignores SIGINT" and "the guard could not run" are different
problems. A6 witnesses the distinct error outcome, A7 requires a
non-Linux unix exercise or an explicit statement of what was tried,
and A4 gains a mutation for collapsing error into ignored. R-b's
stale "needs an explicit override" is reconciled with §7c's no
-override decision.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Third occurrence of the same process failure, and the one I had already
written the lesson for twice. ab43132's message said the ledger no
longer claims implementation-absent or mechanism-unknown. The ledger
script died on a stale anchor, and because I separated the steps with a
newline instead of chaining them, `git commit` ran regardless. Gating
one step is not enough when the next step is not gated too.
The ledger now records what the framing does: mechanism KNOWN, remedy
SELECTED as R-b + R-d via the portable probe, A3/D0b satisfied by the
controlled explanation so D0b is not owed, revision 12 awaiting
approval, D1/D2 done rather than "the next step", and the diagnostic
instrument named as the only implementation so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two record defects plus the remedy decision.
1. Withdrawn claims were still asserted elsewhere. The header and §4c's
consequences still said bet 1 FALSIFIED, A5 STRUCK, and that a real
pmacs --gpu "behaves correctly" --- none of which D4 established,
since D4 never ran. Both now say withdrawn/retired BY SCOPE, with
the explicit note that nothing here shows a real session is correct,
only that no observed evidence of a user-facing defect survives.
§4c's pre_exec-implies-assertion conclusion is replaced by a pointer
to §7b/§7c. A3/D0b are marked SATISFIED by the controlled
explanation --- D0b is not owed and will not run. §9's "Beyond step
12(a)" is gone, since no journey step is touched. The ledger no
longer says implementation-absent, mechanism-unknown, or D1/D2-next.
2. Provenance made portable. Both arm commands are fully expanded
rather than delegating to a machine-local arms.sh. Full SHA-256 of
the two executed binaries are recorded; the 16-character log values
are relabelled PREFIXES and carry no claim. The standalone
foreground/background SigIgn table is labelled UNRECORDED
CORROBORATION --- read ad hoc, no head, no log, no digest --- and the
portable probe supersedes it as the recorded check.
Remedy selected, §7c: R-b + R-d through one checked-in helper wrapping
a behavioural probe --- sh -c 'trap "exit 23" 2; kill -INT $$; exit 0' ---
which exits 23 when SIGINT is deliverable and 0 when inherited as
ignored. Verified here in both contexts. POSIX shell only, so it answers
§7b's portability criterion: no /proc, so not Linux-only, and no
sigaction, so no unsafe. scripts/gate fails immediately with the
explicit diagnosis; the target test reports the same precondition
failure if run directly; no override, because a gate under ignored
SIGINT cannot produce valid evidence. R-c rejected. The Linux-only
D1/D2 instrumentation is removed once its evidence is portable.
A1-A5 are replaced for the new work --- guard bite, direct-test
diagnosis, foreground success unaffected, mutation, and an otherwise
unchanged gate --- with the old teardown criteria kept in §8b, marked
non-binding, so the change of target is visible rather than silent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
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 BEFORE
afe3631 committed 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 head 38f2af4,
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
afe3631's message described revision 11 in detail. The commit contains
only the test file: the script that was to write the framing died on a
stale anchor --- the approval commit had reworded the header --- and the
shell chain ran `git commit` regardless of its exit status.
This is the SECOND time in this lane, and I recorded the lesson for it
in ea0f3bf: "asserting the edit is not enough if the commit does not
depend on it". I then repeated it. This commit gates `git commit` behind
the editing script's exit status, which is what the earlier note should
have changed and did not.
The framing is now actually at revision 11, AWAITING APPROVAL, carrying
§4c: SIGINT ignored group-wide (SigIgn=0x1007, signal 2), zero SigPnd
and zero per-thread SigBlk so ignored rather than blocked delivery,
shared pgid so nothing escaped the group; the foreground/background
SigIgn comparison; the controlled two-arm experiment; the invalidation
of the subset-vs-full matrix as confounded with my own invocation
method; and the consequences --- bet 1 falsified, A5 struck, the §7/§8
remedy withdrawn in favour of a runner practice and a precondition
assertion.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Revision 10 is approved at 4fba9f6 after aligning A3 with the D0b
contingency. The demonstrated D1/D2 mechanism may account directly for
the subset/full difference; otherwise D0b remains mandatory before the
lane closes.
Record that diagnostic-only D1/D2 are authorised but have not started.
No mechanism or fix is claimed yet.
Revision 10 retires D0b only as a precondition: a demonstrated D1/D2
mechanism may account directly for the subset/full difference, while a
mechanism that does not account for it triggers D0b before closure.
A3 still stated the old unconditional rule that D0 must recreate the
comparison in every case. Make the acceptance criterion match the
diagnostic decision: record the direct explanation when it exists;
otherwise run D0b under captured provenance and explain or explicitly
leave its result unexplained. Either path remains mandatory before the
lane can close.
Three statements survived the narrowing and contradicted it, plus one
ellipsed path in the supposedly exact command block.
- §4b's heading still read "the source hypothesis is eliminated" ---
the exact claim the section body withdraws. It now reads "the
commits do not discriminate today".
- §4a said the endpoints settle whether 7599661..724b785 contains a
regression. They do not: they settle only whether a BISECT IS
CURRENTLY JUSTIFIED. Those are different questions, and D0a's
both-uniform-red answers the first while leaving the second open.
- §4b claimed execution "under the approved contract" while the same
revision acknowledges uptime was never captured. The departure is
now stated up front, before the results rather than after them:
uptime is UNKNOWN for all ten runs, everything else held, no
classification depends on the missing field, and D1/D2's harness
must capture the full list.
- The manifest's <TD> definition still abbreviated the second target
directory as .../d0a-B inside a block labelled exact. Both paths
are written out; no ellipsis remains in it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two findings, both upheld.
1. The portable provenance was corrupted and incomplete --- worse than
the machine-local pointer it replaced, because it looked verifiable
and was not. Every log digest had lost its leading hex character
(A#1 recorded as 1c0fe47d55d8f5e... where the value is
e1c0fe47d55d8f5e): the extraction started one byte late in
`logsha=<value>`. The captured /tmp and MemAvailable columns were
dropped, and the command block used ellipsed paths. All ten digests
are corrected, both columns restored, and the command is written out
in full with only two named placeholders.
Separately: `uptime` was NEVER CAPTURED. §7's condition list names
it; the harness kept the load averages from it and discarded the
elapsed time. It is now recorded as UNKNOWN for all ten runs, with
the condition list marked as only partially satisfied rather than
implied met. The classifications stand --- none depends on uptime ---
and D1/D2's harness must capture the whole list.
2. Retiring D0b materially changes the approved diagnostic sequence,
which made D0b mandatory before every other diagnostic. The document
still claimed revision 9, approved at 15c25ec, for a decision that
approval does not contain. Promoted to revision 10 and marked
AWAITING APPROVAL; D0a's execution and result are reported under
revision 9, and D1/D2 do not begin until revision 10 is approved.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
18b74d7's message said the framing was corrected on all three findings.
It was not. That script asserted its anchors and died on the second one
--- the endpoint-table rows carry a two-space indent my anchor omitted ---
and since it writes only at the end, NONE of the framing edits landed.
The manifest and ledger edits in that commit are real; the framing ones
were not, and I pushed the claim anyway.
The assertions worked exactly as intended and I ignored their verdict:
the shell chain ran `git commit` regardless of the script's exit status.
Asserting the edit is not enough if the commit does not depend on it.
Now actually applied to the framing:
- §4b: "source hypothesis is eliminated", "the interval cannot contain
the transition" and "not reachable by source" are withdrawn. What
survives is that the two commits DO NOT DISCRIMINATE UNDER CURRENT
CONDITIONS, so no bisect is justified now. A historical regression
could be masked by a later environmental effect or a source/
environment interaction; failing to discriminate is not the same as
not differing. The onset window is deprioritised, not excluded.
- §7 endpoint table: both uniform-same rows now say the commits do
not discriminate under current conditions, rather than that the
interval does not contain the transition.
- §7 D0b: retired as a precondition, with the reason recorded and the
obligation preserved under A3 --- if D1/D2 do not account for the
subset-vs-full difference, D0b runs before this lane closes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Three findings, all upheld.
1. The causal conclusion overreached, in the same way this lane has
overreached before. Uniform-red at both endpoints today proves only
that the two commits DO NOT DISCRIMINATE UNDER CURRENT CONDITIONS.
"Source hypothesis eliminated", "the interval cannot contain the
transition" and "unreachable by source" are withdrawn from the
framing, the manifest and the ledger: a historical regression could
be masked by a later environmental effect, or by a source/environment
interaction under which both commits now fail. Failing to
discriminate is not the same as not differing. "No bisect is
justified under current conditions" is what survives, and the
approved endpoint table's two uniform-same rows are corrected to say
the same thing.
2. D0b was still mandatory, and going to D1/D2 would have skipped an
approved step. It is now RETIRED AS A PRECONDITION with the reason
recorded: it existed to make the reduction matrix trustworthy so the
subset-vs-full comparison could locate the mechanism indirectly,
and D0a has since produced a reliable direct reproduction that D1/D2
measure against. Re-running ten reduction rows to sharpen an
indirect instrument while a direct one is in hand is the wrong order
of work. The obligation is NOT discharged: A3 still binds, so if
D1/D2 fail to account for why every subset passed, D0b runs before
this lane closes.
3. Provenance is now portable. The exact per-run command and a
transcribed ten-row table --- start time, class, red bins, load,
freeMB, daemon count, log digest --- are committed, rather than
delegated to a machine-local results.tsv. Raw logs stay local by
design. The transcription also surfaces something the delegation hid:
the leaked-daemon count climbs 72 -> 108, four per run, monotonically
while every run classifies identically. Recorded, not implicated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Ten runs under the approved contract: counterbalanced A B B A A B B A A
B, N = 5 per endpoint, clean detached worktrees at 7599661 and 724b785,
isolated target directories, the gate's build-crdt precondition then its
sweep-crdt command, dirty=0 verified per run. Zero voids, zero splits.
A (7599661) uniform-red. B (724b785) uniform-red. By the approved
endpoint table that is the both-endpoints-uniform-same row: the
difference is NOT captured by those two commits.
What it settles:
- No bisect of 7599661..724b785 is justified, and none will run.
7599661 passed inside sweep-crdt on 08-15 and fails 5/5 clean today,
so the interval cannot contain the transition.
- The onset window is demoted --- still a true observation, but not
reachable by source.
- A RELIABLE REPRODUCTION now exists: 10/10 today across two commits
at ~4 minutes per run. This is D0a's most useful product, because
D1/D2 no longer depend on catching a rare event.
What it does not settle: anything about the mechanism. One cheap
negative on "what else changed" --- no package activity in the window per
pacman.log, nearest on 08-18 --- and it is not pursued further, because
with a reproduction in hand direct measurement dominates archaeology.
A's three extra failing binaries are recorded rather than swept up:
a54_real_daemon_real_pty_and_headless_gpu_render..., a v21/v20 row
expected to differ at that older commit, and m6_1_pty_mode_lifecycle.
Two of the three are process/PTY-spawn rows, the same family as the
target. None affect classification, which reads only the two target
copies.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Revision 9 is approved at 15c25ec after the portable manifest and compact
ledger summary preserve the endpoint direction required by D0a.
Record that approval durably before diagnostic implementation begins. The
mechanism remains unknown, no fix is proposed, and panel-mapping-generation
remains held until this teardown lane closes.
The portable manifest collapsed the two clean-split directions even though
the governing endpoint table permits a bisect only when 7599661 is uniform
green and 724b785 is uniform red. Preserve that direction explicitly, and
carry the same distinction in the compact active-work summary.
The inverted split remains a real difference, but it contradicts the onset
reading and therefore requires that reading to be re-examined before any
bisect.
74dbd34 corrected the framing so that "the difference is not captured"
attaches to the uniform-same endpoint verdicts rather than to mixed ---
mixed means intermittency --- but the manifest kept the wrong pairing.
The edit's anchor did not match and the script reported success anyway,
so the commit touched only active-work.md and the framing.
The manifest now defers to §7 D0a for the governing contract and states
the three verdicts with the conclusions that actually belong to them:
clean split permits the bisect, mixed means intermittency under fixed
source, both-uniform-same means the difference is not captured by those
two commits.
Process note, since this is the third silent no-op of its kind in this
lane: an unasserted string replace that misses its anchor is
indistinguishable from one that succeeded. Every edit here now asserts
the anchor before writing and re-asserts the result afterwards.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two D0a findings on revision 8, both upheld.
1. The classifier was not total. "Clean split" and "mixed" left five
outcomes unprescribed, and two of them are in the historical logs
already: 20260815T182846Z-708693 died compiling pmacs so neither
copy executed, and ...-2839374 / ...-830195 were red on unrelated
rows while both ctrl_c copies passed.
A run is now classified from THE TWO COPIES OF THE TARGET TEST and
nothing else --- green (both ok), red (both FAILED), split (copies
disagree), void (either did not execute). A sweep red only on
unrelated tests is therefore a green run, with the unrelated
failures recorded as evidence about environment stability. A split
STOPS the procedure, since two copies of one source disagreeing
within a run is its own defect. Voids are discarded and re-run on a
budget of 3, after which the environment is too unstable to classify
anything and D0a stops.
Endpoint verdicts are uniform green, uniform red, or mixed, and a
six-row table prescribes every combination: clean split permits the
bisect; an inverted split is a real difference that falsifies which
endpoint was believed good; both-uniform-green and both-uniform-red
each mean the difference is not captured by those commits; mixed at
either endpoint means intermittency under fixed source and forbids a
bisect. The manifest had attached "difference is not captured" to
the mixed case --- that conclusion belongs to the uniform-same rows,
and is moved.
2. Strict A/B/A/B does not make drift "hit both arms equally": B always
follows A and owns the final time point. Runs are now counterbalanced
AB BA AB BA AB, which removes systematic order confounding; the
residual last-slot asymmetry is accepted and stated rather than
claimed away.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Four findings on revision 7, all upheld.
1. The old one-run D0 rule survived in three durable places --- the
manifest, this branch's ledger, and the framing's own §4a --- each
still permitting a bisect when the endpoints merely "differ". That
contradicts the N = 5 clean-split contract added in revision 7. All
three now defer to that contract, and §4a's "needs only that the two
clean endpoints differ now" is marked as the superseded rule it is.
2. D0a still overstated its evidence, in three ways now fixed:
- "context-sensitive by construction, appearing only in the full
sweep" is downgraded to what has been OBSERVED so far;
- the historical 7/7 and 13/13 are stated as NOT endpoint-specific
rates --- of seven reds only F6 ran at 724b785, of the greens only
the last at 7599661, both with unknown cleanliness;
- five runs are named a PREDEFINED EVIDENTIARY THRESHOLD chosen so
the outcome cannot be argued after the fact, not something that
mathematically separates intermittency.
And the bisect now specifies its own classifier: every intermediate
commit uses the identical N = 5 protocol, and a mixed classification
ABORTS the bisect rather than being guessed, skipped, or rerun until
it agrees. A bisect with cheaper steps than its endpoints would
inherit the weakness the contract exists to remove.
3. The artifacts column is now exact per run, read from each log:
R1/R2 UNKNOWN (no log preserved), R3 -5d9105cb/-d4dae4f0, R4 and R5
-6b4b8223 only, R6 -91f51d0b/-6b4b8223. R8's citation was half2.log:1;
the executable lines are 438 and 459. The framing's last "not same
binaries" is now "not the same compilations".
4. (Held ledger, 5274d6b.) It named a stale ledger tip and two different
framing revisions on consecutive lines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Five findings on revision 6, all upheld.
1. The ancestry pair supports nothing causal. Revision 6 had already
retreated to "outcome is not determined by commit alone"; that is
withdrawn too, because different commits CAN deterministically
produce different outcomes --- this document's own fix-then-regression
scenario is an example. The two observations differ in commit AND
environment AND time, so they are simply NON-COMPARABLE. The held
ledger's "no source-monotonic cause does that" goes with it.
2. D0a was not a valid decision procedure: one unspecified run per
endpoint cannot establish a regression for a failure that only
appears in the full sweep. Now specified --- N = 5 full sweep-crdt
runs per endpoint, INTERLEAVED A/B/A/B so session drift hits both
arms, identical captured conditions including uptime/free//tmp/
leaked-daemon count, and a bisect permitted ONLY on a clean split.
A mixed result means intermittency under fixed source, and no bisect
is justified at all.
3. "Neither binary contains signal-handling code" is FALSE. The pmacs
binary does: install_signal_handlers (src/daemon.rs:628) registers
SIGINT and SIGTERM; it is simply not on run_gpu's path. A grep of
project sources also cannot exclude a runtime or dependency
installing a disposition. The established fact is narrow --- no
explicit installation on run_gpu's path --- and "whatever disposition
they hold was inherited" is restored to a HYPOTHESIS that D2 must
measure.
4. Artifact wording finished: no "artifact family", "reduction/
workspace artifacts" or "different binaries" remain. Every manifest
row now carries its exact Cargo suffixes read from its log, with a
stated caveat that those logs are machine-local and this manifest is
the portable transcription of them.
5. Held ledger pointed at revision 5; it now points at revision 7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Three findings on revision 5, all upheld.
1. The ancestry argument overreached. 72da24a failing today while its
descendant 7599661 passed on 08-15 shows exactly one thing: outcome
is not determined by commit alone, since the observations come from
different environments at different times. Revision 5 said a source
cause was "positively discouraged", that the ancestry "says to
expect" equal endpoints, and that the change was environmental.
None follows. It cannot discriminate an environmental change, a
source/environment interaction, or a fix before 7599661 with a
regression before 724b785 --- and an ancestor OUTSIDE the interval
is irrelevant to whether the interval regressed, since a bisect over
7599661..724b785 needs only that the clean endpoints differ now.
D0a is unchanged as an action but is now stated as a decision
procedure with NO predicted outcome: endpoints differ -> bisect that
interval; endpoints agree -> ask what else changed across the window.
2. The byte-identity withdrawal was incomplete in both ledgers. This
branch's said the artifacts "are byte-different" and then withdrew
it two lines later, still said R9 ran "different binaries", and
still promised an "artifact family". The held ledger still said
"byte-different" and still called the window a bisect target with
revision 4's onset conclusion. Both now say "different Cargo
suffixes/compilations" throughout; historical byte identity is
UNKNOWN and is never claimed.
3. Provenance slips: R9's observation-table row listed only -6b4b8223
although it executed both -91f51d0b and -6b4b8223; R10's suffixes
are at log lines 3 and 24, not 3 and 4; R9's are at 3066 and 3087,
not 3066 alone. All corrected against the logs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Four findings on revision 4, all upheld. The third changes what the
lane should do next.
1. Section summaries still carried revision-3 language while the
manifest carried revision 4's. Framing and ledger now agree: seven
red runs (F1-F7), not five; the observation table is keyed on
compilation set rather than an invented "workspace artifact family";
and it is labelled an observation, not an isolated interaction.
2. The onset count was wrong. Per test copy across the 17 sweep-crdt
logs: 13 with both copies ok, 1 where NEITHER executed because the
stage died compiling pmacs (error[E0308]), and 3 with both failed.
Revision 4's "14 runs, 11 green, 3 red on other tests" mis-stated
both the count and the kind --- one of those runs never reached the
test. The two genuinely red-on-other-tests sweeps did execute
ctrl_c, and it passed.
3. D0a cannot be a source bisect, and the evidence argues against one.
Reflog and commit times put HEAD at 7599661 during the last green
(3c06176 landed 40s after it finished) and at 724b785 during the
first red (5174f73 landed 08:45:41, after that run ended 08:42:01;
the manifest had recorded F6 at 5174f73, which was wrong).
Cleanliness was captured at neither endpoint. And 72da24a is an
ANCESTOR of the passing 7599661 yet fails today --- no
source-monotonic cause produces that. D0a now reproduces the two
endpoints CLEAN, in isolated target directories, and a bisect is
justified only if they differ.
4. Manifest completed: R9 carries full argv rather than a recipe; R7
lists only gpu_invocation-6b4b8223, since R7 does not select
gpu_initial_target; R10 lists both -5d9105cb and -d4dae4f0.
Also withdraws "byte-different" everywhere. The bytes a historical run
executed are not knowable --- target dirs have been overwritten, and a
hash computed today is the current occupant's. Three levels are now kept
apart in the manifest: suffix (known), today's bytes at a path (known),
and the bytes a past run executed (UNKNOWN). Differing suffixes mean
differing Cargo metadata hashes, which is enough to void the comparison
and is all that is claimed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Four findings on revision 3, all upheld. Answering finding 1 turned up
something that reframes the lane.
THE ONSET. sweep-crdt appears SEVENTEEN times in this target directory's
gate logs. The ctrl_c failure appears in exactly the LAST THREE, and the
test passed --- both copies, "... ok" --- inside the stage before them.
Last green 20260815T185708Z, first red 20260816T063330Z, no reboot
between. The three earlier red sweeps failed on unrelated rows. So
"pre-existing on main" holds (F1 at 72da24a reproduces it) but "always
broken" was never established and is now contradicted. D0 gains a first
part: bisect that window. A test that passed fourteen times in this
stage and then failed three times running has a change behind it, and
that is worth more than further reduction --- which has isolated
nothing.
1. Both ledgers still carried the falsified R9 conclusions. This branch
listed --workspace unification and preceding tests as ruled out
while the section above described an interaction; said "five call
sites" immediately before correcting to six; and labelled the
framing revision 2. The held branch was worse: --workspace refuted,
R9 "same binaries", later packages not implicable, cause cumulative
across 37 binaries. All corrected and pushed (5b9abd8). §11 no
longer asserts the held lane is clean; it records a re-verified
checklist, since asserting that prematurely is what went wrong.
2. Manifest now carries complete argv for R7-R9 and F5 --- abbreviations
are not reconstructable invocations. F5 is disambiguated: the
framing cited gate ...-2144707 while the manifest cited ...-2375685,
two distinct real runs. Enumerating them gives F1-F7: the red count
is SEVEN, not five, each with its own log digest. F5 also carries an
extra failing binary the others do not.
3. "Workspace artifact family" conflated Cargo suffix with byte
identity and is withdrawn as a grouping. Demonstrated: F1 in the
main worktree executed the same suffixes -5d9105cb and -d4dae4f0,
but the bytes there are e0578039/00f06aeb versus the panel
worktree's 1b3cc86c/ede0c07d. Each run now records the suffix its
log shows and byte identity as UNKNOWN, since target dirs have been
overwritten and a hash computed today is not the hash that ran.
4. The interaction table is demoted to a description of what was
observed. Revision 3 disclaimed its inputs and then asserted a
finding from them, which cannot both hold. A3 no longer speaks of an
established "R9 paradox" --- there is none to explain, because the
comparison was never made; it requires D0 to recreate it first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Five findings on revision 2, all upheld. The first invalidates its
strongest claim.
1. R9 executed gpu_initial_target_acceptance-91f51d0b and
gpu_invocation_acceptance-6b4b8223; the failing sweeps executed
-5d9105cb and -d4dae4f0. Verified byte-different by sha256. Cargo's
target selection changes the fingerprint, so command shape changes
the executable. "Same binaries" is now "same target names and
order". What the evidence supports is an INTERACTION --- prior
targets alone green (R9), workspace artifacts alone green (R10),
both together red (F1-F5) --- so --workspace selection is not
sufficient by itself and NOT ruled out. The claim that other
packages "cannot be implicated" because their targets run after the
failure is withdrawn: later-selected packages can affect the build
graph and fingerprints before their tests ever run.
2. Both ledgers made internally consistent and portable. This branch's
asserted default-disposition death and then withdrew it further
down; the assertion is gone. panel-mapping-generation still carried
"119 binaries green one red", the >=8s arithmetic, the default-action
claim and the >6s selector --- corrected on its own branch and pushed
at 779a6bd.
3. Provenance is now a pushed document, docs/probe-sigint-evidence.md:
exact command, worktree, HEAD, cleanliness, artifact family, result
and log digest per physical run. R1 and R2 have no preserved log,
and revision 2 double-counted one log as both R2 and R6. Cleanliness
is UNKNOWN for every pre-manifest run and is not inferred. R1-R10
ran in the panel-mapping-generation worktree, not at main. D0 now
precedes every other diagnostic: re-run the matrix at main under a
harness capturing provenance AND the artifact hashes executed.
4. "The probe never blocks indefinitely" narrowed to "the event loop
wakes at least every 50ms". The stdin reader blocks in read_to_end
(:1109) and, once ready, the loop leaves only when stdin closes
(:1212), so the process is not bounded.
5. Launcher call sites: six under --features crdt (:509 :534 :544 :574
:725 :1097, inside #[cfg(feature = "crdt")] mod crdt). The other two
--gpu arguments are under #[cfg(not(...))] and compiled out.
Revision 2 said five while citing eight.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Revision 1 rejected on five findings, all upheld.
1. The >6s selector could not have captured the failure. Both
reproducing binaries finish in ~5.19s INCLUDING the 5s timeout
(:3097, :3131), so the failing launcher lives about 5.1s. This also
falsifies my earlier retraction, which had argued the instance "must
live >=8s" --- so the "mechanism located" claim is NOT refuted by
that argument. It stays unproven for a different reason: the suite
spawns launchers from five call sites, so command line alone cannot
attribute one to this test. Key on the PID the test records.
2. Diagnostics rewritten to DISCRIMINATE blocked delivery, inherited
ignore, and an escaped process group: before-and-after snapshots for
test parent / launcher / probe, per-thread SigBlk from
/proc/<pid>/task/*/status, SigPnd/ShdPnd, and PID/PPID/PGID/SID.
Relatedly, "two processes with default disposition" is withdrawn ---
SIG_IGN is inherited across fork and survives exec, so absence of
handler code says nothing about runtime disposition, and inherited
ignore is the leading hypothesis precisely because the source is
silent. Revision 1 contradicted its own hypothesis.
3. Counts corrected: 119 green result summaries and TWO red binaries,
not "119 binaries green, one red". Reductions are now enumerated
R1-R10 and F1-F5 with command, run count and log each, preserved off
the tmpfs --- /tmp is a tmpfs and these were nearly lost mid-lane.
4. Acceptance contract corrected: A2 now requires three consecutive
green runs on the reviewed fixed head of this branch, not on main,
which is unobtainable before approval and merge; journey step 12(a)
"closing is clean" is named, since revision 1 reasoned from grade
movement which §20 warns against; and A5 is explicitly conditional
on D4, with bet 1 restated as a bet --- the witness uses a wrapper
and headless probe, not the real GUI path.
5. Portability closed: this branch now tracks
githubsucks/gpu-probe-sigint-teardown, and panel-mapping-generation
was pushed to 16cf3a2 so its retraction travels.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
`ctrl_c_on_launcher_group_does_not_reach_spawned_daemon` fails in gate
stage `sweep-crdt` with "child did not exit within 5s". It is
PRE-EXISTING on main --- 72da24a fails it in a clean worktree with its
own target dir --- so while it reds, no branch can present a green
sixteen-stage gate, main included. §5b is held behind this lane.
Framing revision 1, and it proposes NO FIX, because the mechanism is not
known. What it does instead is fix the shape of the problem so the next
attempt is not another guess:
- Ground truth, cited: neither binary handles signals. `run_gpu`
(src/main.rs:324) blocks in `command.status()` with no handler, and
grepping all of pmacs-gpu/src for signal machinery returns nothing.
The probe polls at 50ms. Two processes with default SIGINT
disposition should both die at once --- this deepens the puzzle
rather than explaining it, and the framing says so.
- Ruled out by measurement, with the method for each: load, tmpfs
(tested by experiment, not argument), leaked daemons, inotify,
--workspace feature unification, and any specific preceding test.
- The reduction paradox stated as the problem's real shape: 5/5 in
the full sweep, 0/N in every reduction, including all 37 preceding
targets plus the suite.
- One retracted claim kept as a warning, because it was mine: the
"mechanism located" report described a healthy teardown. The
sampler behind it caught 394 launchers with a 5s maximum lifetime
while the failing instance must live 8s or more.
The first step is diagnostic only: an instrument keyed on the FAILING
instance --- launchers outliving ~6s --- capturing /proc/<pid>/status
signal masks, since SigIgn survives fork and exec while handlers do not.
Acceptance criteria are written now so the fix cannot quietly become
"make the test pass": a demonstrated mechanism with a mutation-tested
witness, sweep-crdt green three consecutive times, the reduction paradox
explained or recorded as unexplained, and no deadline raised or test
skipped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Records the overrule and frames the slice (SS5b). No implementation;
the lane is PAUSED behind it.
**ALL THREE OF MY BOUNDS WERE WRONG**, and each is recorded because
each is why the trade looked cheap:
"self-inflicted" another frontend, or a background process, can
edit the same buffer --- the mapping moves with
`view_top` untouched and the clicking frontend
did nothing
"bounded by 3 rows" unbounded: ticks, paging, folds, edits and
reloads accumulate before the new frame is seen
"one round trip" until the frontend PRESENTS the replacement
frame; a slow or backed-up frontend widens the
window arbitrarily
With the narrowness gone there is nothing left to trade on.
**AND THE FIX IS A GENERATION, NOT A TOKEN.** A per-frame token would
invalidate a gesture on every repaint --- the exact mistake
`panel_epoch` exists to avoid, and it would break drags outright.
`mapping_generation` identifies the INVERSE MAPPING: it moves on
viewport, folds, wrap/gutter geometry and buffer content, and holds
across focus, styling, cursor motion and selection-only changes. The
stability half is load-bearing rather than an optimisation --- a drag
provokes selection repaints on every motion, and a generation that
moved with them would cancel the drag after one step.
SS5b frames the slice: appended variants (`PresentMapped`,
`PanelPointerMapped`) rather than widened structs, because postcard
encodes positionally and a shipped variant's field list is frozen;
bilateral gating, with neither side assuming the other's support and a
bare `PanelPointer` from a new-enough peer NOT silently upgraded; and a
drop-before-mutation check at the same rung of the ladder as the epoch
gates, one level finer. Dropping mid-drag is correct: if the mapping
changed, the cells being dragged across no longer mean what they meant.
This is where the hole always was --- `buffer_id` catches replacement,
`panel_epoch` catches close/reopen, `geometry_epoch` catches a
declaration race, and nothing catches "the text under that cell
changed".
Consequence: SS5b takes the next protocol version and GUI arc 1e's
`OpenTarget` moves to the one after. That edit is OWED BY THE 1b
BRANCH, which already revises `docs/gui-stage1-input-framing.md`
heavily; making it here would collide at the rebase 1b is scheduled
for, so it is recorded as required rather than made.
Gates: all nine green under `env -u TMPDIR`, log 20260814T155745Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Two of review's findings: the framing blocker, and a live defect in the
commit before this one. The remaining six are queued.
**I REPORTED M-D2 AS BITING AND IT DID NOT.** `presented()` filters on
`frame.geometry_epoch == self.panel.geometry_epoch`, and a geometry
change advances that field FIRST --- so by the time the matching frame
arrives, `presented()` already answers `None` and my
`is_some_and` predicate skipped the reset entirely. The shipped D2 did
nothing on the production sequence.
The witness could not see it because it invented a higher-epoch frame
without driving `next_geometry_declaration`, leaving
`self.panel.geometry_epoch` untouched so `presented()` still matched.
A test that skips the step which breaks the code cannot fail on it.
The predicate now compares against the RETAINED frame
(`self.panel.frame`), which survives the epoch advance, and the witness
drives `GeometryTrigger::Metrics` for real --- asserting along the way
that `presented()` IS `None` in that window, so the trap is pinned
rather than merely avoided. Restoring the `presented()` predicate now
fails the row.
**M-D3 WAS ALSO UNCONSTRAINED**, for a smaller reason: arming clears
`last_pointer_cell`, and the leg only armed, so the field was already
`None` before the replacement and deleting its reset changed nothing.
The arm helper now seeds the baseline with one accepted motion --- what
a real gesture would have produced --- and after replacement the row
requires `panel_motion_is_new` at that same cell to return true.
Deleting only that line now fails.
**Q#BP-R3 IS RULED: current-state hit semantics, narrowly, with the
token named as follow-up.** `PanelPointer` carries epochs and a cell
but nothing identifying the frame CONTENT the user saw, and
`panel_epoch` is stable across ordinary frames by design. So a document
wheel moves `view_top` daemon-side, and a click sent before the new
frame lands is inverted through the NEW `view_top` --- selecting a row
the user never saw, with every validation passing.
Closing it properly needs a per-frame token on `PanelFrame` echoed by
`PanelPointer`: a WIRE CHANGE, and this lane is non-protocol-bearing
with 1b blocked behind it. A daemon-only mitigation was considered and
does not work --- inverting against the last EMITTED frame still cannot
tell which frame the user SAW, and the failing window is identical.
So the lane accepts current-state semantics and says so: the window is
narrow and self-inflicted (the same frontend must move the view and
then click within one round trip), the magnitude is bounded by
`SCROLL_LINES`, and the TUI is structurally unaffected. The token is
recorded as a named follow-up for the next protocol-bearing slice, so
it is inherited rather than rediscovered. Overrule stated explicitly:
the trade is a narrow same-frontend mis-hit now, against serializing
this lane and 1b behind a v25 wire change.
Gates: all nine green under `env -u TMPDIR`, log 20260814T154611Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
SS5a is the acceptance-48 ground truth and its rulings are settled:
Q#BP-R1 (a single click selects a listview row only), Q#BP-R2 (a
terminal-chrome wheel is consumed daemon-side, before activation), the
R-a..R-d replay edges, and the witness matrices A1-A5, B1-B6, D1-D4.
Also records what the eight rounds cost, because the pattern repeated:
every round found a witness or a rule that would have PASSED against a
broken implementation. A `#[cfg(test)]` fixture cited as a production
handler. Activation ordering mistaken for a safety proof. A mode-line
rule that was per-row when the tree's is per-kind, and document-only at
that. A producer rule the frontend had no information to obey. A
consume check that would have left focus stolen. And twice, an
assertion added without a state in which it could fail.
No round found a design disagreement. The rulings from 8 and 9 survived
intact; what changed each time was WHERE a rule lived or WHAT A WITNESS
COULD SEE.
Implementation begins.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of revision 11. Still framing only; no implementation.
**"FOCUS UNCHANGED" IS VACUOUS IF THE PANEL IS ALREADY FOCUSED.** With
the terminal side window active, the below-activation mutation calls
`focus_window` on the window that is ALREADY active --- nothing
changes, and the row stays green against the exact bug it exists to
catch. I added the assertion in revision 11 and did not give it a state
in which it could fail.
Leg 2 now starts passive, and says so as assertions rather than setup:
the primary document window is active, the terminal side window is
distinct from it and passive, and the terminal controller identity is
captured as a baseline. Asserted rather than arranged, because each is
the kind of detail a later edit changes silently, and each failure mode
is a witness that passes while proving nothing.
**AND THE TWO ASSERTIONS ARE NOT INTERCHANGEABLE**, which revision 11's
prose blurred by naming them together as though either would do:
consume check below activation
-> `focus_window` runs, the panel takes focus, and NOTHING
scrolls, so every movement assertion still passes
-> caught by FOCUS, and focus alone
terminal branch calls `apply_terminal_gesture`
-> `claims_control` is `!matches!(kind, Move)` (`:3555`), so a
wheel CLAIMS THE PANEL'S CONTROLLER at `:3571`, before any
local handling at `:3575`; the chrome coordinate then fails
the reporting bounds check and drops into the local branch,
which may also move scrollback
-> caught by CONTROLLER IDENTITY, plus scrollback when it moves
Activation alone claims nothing --- `focus_window` and
`claim_terminal_controller` are separate acts --- so a row asserting
only controller identity misses the ordering bug entirely, and one
asserting only focus misses a replay that quietly claims the terminal.
Gates: all nine green under `env -u TMPDIR`, log 20260814T144513Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
Answers review of revision 10. Still framing only; no implementation.
**"CONSUME BEFORE `apply_terminal_gesture`" WAS NOT EARLY ENOUGH.**
`activates` is `!matches!(kind, Move)` for a terminal panel
(`src/editor.rs:2695`), which INCLUDES the wheel, and focus plus
`active_frontend` are written at `:2699` --- ahead of any replay
decision. A consume check below that block leaves the wheel CHANGING
FOCUS while scrolling nothing and claiming no controller: exactly the
half-state AC48's activate-then-claim rule exists to prevent. The panel
steals focus and does not move.
A terminal-chrome wheel is therefore not a terminal gesture at all, and
the dispatcher must treat it that way. The order is now stated:
1. authenticate and validate (the existing ladder)
2. resolve the side window and its buffer kind
3. terminal + chrome wheel -> CONSUME IMMEDIATELY, before
`focus_window`, `active_frontend`, any controller claim, any
command-chain mutation, and the shared terminal path
4. otherwise, the existing activation and replay rules
Step 3 is implementable where it belongs: `is_terminal` is resolved
from `buffer_id` at `:2687`, before the `core` borrow and before the
activation block, so the kind is already in hand.
**The witness gains focus and controller assertions, and they are
load-bearing.** The document->terminal replacement row now asserts the
focused window and terminal controller identity are unchanged, on top
of no child bytes, no scrollback and no document movement. Two
mutations bite different halves:
- terminal branch calls `apply_terminal_gesture` -> the chrome
coordinate fails the reporting bounds check, falls into the local
branch, and the row catches the accidental local scrollback
- the consume check sits BELOW activation -> nothing scrolls, so
every movement assertion still passes and ONLY the focus and
controller assertions catch it
That second mutation is the reason the new assertions exist. Without
them the row stays green against an implementation that steals focus on
every chrome wheel over a terminal, because the half-state is invisible
to any assertion about movement --- nothing moves either way.
Gates: all nine green under `env -u TMPDIR`, log 20260814T143558Z.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai