Commit Graph

539 Commits

Author SHA1 Message Date
Levi Neuwirth 48057d7667
fix(panel): G5k --- the gesture records its domain, and every tail obeys it
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
2026-08-20 21:40:02 +02:00
Levi Neuwirth 1fc3af63de
docs(framing): revise panel replay contract after review
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.
2026-08-20 20:50:21 +02:00
Levi Neuwirth cda078fa91
docs(framing): SS5a revision 15 --- answers review of 14, mostly by correction
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
2026-08-20 20:25:22 +02:00
Levi Neuwirth 2c0d3ff907
docs(lane): record that b758c2e does not build, and that its claim is withdrawn
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
2026-08-20 20:06:11 +02:00
Levi Neuwirth 3cd7b8aa22
docs(framing): SS5a revision 14 --- the lane is unblocked, and it inherits a changed substrate
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
2026-08-20 20:04:38 +02:00
Levi Neuwirth b758c2e76f
Merge githubsucks/main into panel-pointer-replay
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
2026-08-20 19:51:50 +02:00
Levi Neuwirth 879f309c8e
docs: absorb #242, and the #239/#240 blocks that were still marked OPEN
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
2026-08-20 19:26:41 +02:00
Levi Neuwirth 61f0faf12c
docs(ci-reds): U10 --- the budget red rotates across one commit
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
2026-08-20 18:20:23 +02:00
Levi Neuwirth 70b334d951
docs(lane): rebase onto 5f2015c, and note the gate that follows is head-exact
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
2026-08-20 17:56:06 +02:00
Levi Neuwirth 026bb4955f
fix(daemon): the accepted-gesture latch must follow the dispatch
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth e06fb83850
docs(lane): §5b rebased onto post-#241 main, and gated
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 08588361a5
docs(lane): point at teardown framing revision 9
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 6b0ad8e5dd
docs(lane): stop naming a stale ledger tip, and point at revision 8
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth c8ae975ed9
docs(lane): the ancestry pair is non-comparable; point at revision 7
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 4751aa99e6
docs(lane): finish the byte-identity withdrawal and drop the bisect claim
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 8b2bcb53df
docs(lane): reductions were never a bisect, and the onset is not a source boundary
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 1180888291
docs(lane): the R9 conclusions were still here, and the onset is datable
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 6adcf17c25
docs(lane): correct the claims this held branch was still transporting
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth c1cc98ad5a
docs(lane): retract the "mechanism located" claim --- it was unsupported
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth a4117adef8
docs(lane): locate the sweep-crdt mechanism --- the GPU probe will not die
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 9562fa7930
docs(lane): the sweep-crdt red is pre-existing on main
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth cb830df7a7
docs(lane): three record corrections, one of which cost two gate runs
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 2c60509e75
docs(lane): the slice-completion gate is RED, and why no PR followed
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth bf4ffe0995
fix(panel): a second press ends the gesture it replaces
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth a8a996c446
feat(panel): the wheel exemption, exhaustion, and mapped coalescing
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 8c5ca10698
feat(panel): G5a's cancellation trigger, and the latch it needs
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth a242038e8a
test(panel): the nine family-gate rows, and three that proved nothing
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 921989a77c
test(panel): the terminal scroll-anchor row, which I had given up on
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 0bdfc9b6dd
fix(panel): the terminal mapping half --- classification, domain, publication
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 758b985c35
feat(protocol): the mapped panel family --- v25 wire shapes and their pins
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 775a046ce4
docs(framing): fold the mapping-generation closure audit into revision 16
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.
2026-08-20 17:55:21 +02:00
Levi Neuwirth 137c7fc736
docs(framing): SS5b revision 16 --- the producer rule was self-defeating, and R7 recurs on a docs-only diff
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth fa980ef1ba
docs(framing): SS5b revision 15 --- appended means LAST, and cancellation had a race
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth ca816a1917
docs(framing): the panel cell-mapping generation (v25) --- SS5b revision 14
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
2026-08-20 17:55:21 +02:00
Levi Neuwirth 24e4039eb6
docs(lane): record the one rd_precondition failure, without a cause
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
2026-08-20 17:31:13 +02:00
Levi Neuwirth f13506caf5
docs: absorb the SIGINT-guard lane --- #241 merged at f8033bc
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
2026-08-20 15:43:46 +02:00
Levi Neuwirth b492426c69
test(sigint): assert the 45 inputs are DISTINCT, and stop claiming every stub carries the sentinel
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
2026-08-20 14:22:12 +02:00
Levi Neuwirth fb8a904923
test(sigint): distinct X3/X4 vectors, nesting-safe paths, and the gate I should have run
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
2026-08-20 11:50:34 +02:00
Levi Neuwirth 9321975197
docs(lane): head-exact gate evidence, and the run that was not
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
2026-08-20 09:25:48 +02:00
Levi Neuwirth 8802d6a1a2
test(sigint): share the conformance vectors and assert the exact branch
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
2026-08-20 09:15:47 +02:00
Levi Neuwirth bc7d776569
feat(gate,test): implement revision 13 --- the validated (status, token) pair
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
2026-08-19 21:14:30 +02:00
Levi Neuwirth 2a6625ddb9
docs(framing): record revision 13 approval
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.
2026-08-19 20:52:25 +02:00
Levi Neuwirth c3ad66f578
docs(framing): revision 13 round 3 --- untrusted stderr, exact pairs, one grammar
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
2026-08-19 20:32:54 +02:00
Levi Neuwirth 8b8a692528
docs(framing): revision 13 round 2 --- the algorithm still implemented r12
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
2026-08-19 20:27:43 +02:00
Levi Neuwirth 70f0bc960e
test(gate): carry the gate's stderr into the boundary assertion
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
2026-08-19 20:01:38 +02:00
Levi Neuwirth 916007b391
docs(lane): immutable checkpoint SHAs, and drop the stale "No PR"
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
2026-08-19 19:46:32 +02:00
Levi Neuwirth d64d3009d8
docs(lane): record PR #241
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
2026-08-19 19:42:09 +02:00
Levi Neuwirth 7cef9ca375
docs(lane): record both gate runs on c9cc8dd --- the red one included
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
2026-08-19 18:07:28 +02:00
Levi Neuwirth c9cc8dd969
docs: two wrong facts --- 33 rows, and approval at 1fc0df6
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
2026-08-19 17:22:01 +02:00
Levi Neuwirth 167d830932
test(gate): witness A6 in both consumers; bound the negative path
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
2026-08-19 16:43:37 +02:00