Retain panel statusline segments only for the exact side-window and
buffer presentation that published them. Clear that baseline whenever
the daemon publishes authoritative Absent.
Pin both transitions: same-window buffer replacement under NoMessage,
and Absent-to-Present under NoMessage.
All five review findings reproduced with a failing test before any fix,
and every fix falsified by reverting it.
R1-1 — the wire-area clamp lived only in `panel_grid_size`, so the daemon
shipped an authoritative `Absent` while `panel_hidden` stayed false:
keys kept reaching the invisible window and a panel terminal kept its
controller. Q#BP2b calls hiding a DURABLE state transition and the
exhaustion arm had made it a per-frame effect. Fixed structurally rather
than pointwise: `presentable_panel_grid` is now the one derivation behind
both the renderer and `reconcile_panel_layout_core`, so the two cannot
drift apart again.
R1-2 — closing and reopening the same PERSISTENT buffer inside one
dispatcher burst left the shipped declaration intact while the window it
described was already dead, and same-buffer/same-size made the successor
indistinguishable by every other field. A presentation epoch only
identifies a presentation if something checks that the presentation it
names is still on screen, so `panel_declaration_matches` now takes the
live side window and buffer.
R1-3 — the semantic terminal-layout twin consulted only the full-document
declaration, which a panel terminal deliberately lacks, so the child kept
its opening geometry through the drain. `sync_semantic_panel_terminal_
layout` is the missing case; it resolves through `side_window_for` while
its sibling resolves through `primary_document_window`, so the two are
disjoint by construction and nothing is resized twice per tick.
R2-4 — `NoMessage` means publish nothing, not publish empty. Treating it
like `Invalidated` removed the band's provider text on a transient
buffer-follow mismatch. The band repaints its whole mode line every
frame, so "publish nothing" has to be a retained baseline; it is keyed by
window id so a replaced panel inherits nothing.
R2-5 — non-`Move` activation is Q#BP16's TERMINAL clause, because the
shared adapter claims the controller for wheel steps too. A document
panel keeps scroll-without-focus, matching `dispatch_mouse`.
The sweep for R1-1's and R1-3's shape found one more, and it is the same
bug as R1-1: a panel wider than the terminal subsystem's per-axis cap is
legal on the wire (Bet B5') but its content rect was refused by
`snapshot_for_view`, collapsing the projection to `None` — a per-frame
`Absent` with the durable state still saying visible, reachable with one
`FrontendCellGeometry` declaration. The band is legitimately that wide,
so the child is clamped to the columns a PTY can have and the remainder
paints as band background, exactly as a narrower snapshot already does.
One knowingly per-frame `Absent` remains and is recorded in the code
rather than fixed: presentation-epoch exhaustion, which takes 2^64
shipped presentation changes in one session and cannot be reached by any
frontend.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
§14 said references, outline, buffer-list and project-search all use
listview. Measured at ad41cf1, there are exactly three
`pmacs.listview.open` call sites and all three are in
builtin/runtime/lsp.lua: *references* (:2056), *outline* (:2102) and
*lsp-help* (:2513). buffer-list and project-search are not consumers.
The document already knew this and contradicted itself. Twenty lines
below the claim it says "(*workers*, *help* and *buffer-list* are
generated but do not use this idiom.)" The second statement was right.
The other three `listview` mentions under builtin/ are comments in
compile.lua and dired.lua citing "the listview idiom" — a pattern being
copied, not the primitive being used. Counting imitators as adopters is
what produced the wrong number, and it has a concrete cost: the
erroring-intercept pattern those comments copy is exactly the one the
Output-channel bullet directly below records as NOT read-only. The
copies inherited the defect without inheriting the primitive.
Why this matters beyond the count: it changes the remediation. A
primitive with three sibling call sites in one file is a good primitive
with an adoption problem, not a missing one — the work is migrating
buffer-list and search onto it rather than building anything. The grade
moves from ✓ to ◐ for that reason, and the scorecard row now carries the
narrower claim.
Two consistency fixes ride along: the §6 picker/panel parenthetical
listed two of the three listview panels and now lists all three, and
line 503's "listview is Lua-usable" is left alone because it is about
extensibility and is accurate.
No runtime code. No other open PR touches this file — verified across
all four branches — so this does not enter the active-work contention.
Docs-only and one m4 test fix; no overlap with the daemon projection.
Integrating now so CI builds a real merge ref for the head SHA.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
`clippy::format_collect` rejected the generated body; the replacement
was shorter than the hard-coded caret offset, so the caret landed past
the end and the focused half stopped exercising the clamp. Derive the
offset from the body instead of restating it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Q#BP16 step 3 compares an inbound event's geometry epoch against BOTH
the declaration the frontend was looking at and the daemon's latest
accepted one, and mutation testing showed the second half unpinned: no
fixture made the two diverge, so deleting it changed nothing. They
diverge exactly once — between a declaration being accepted and the
next frame answering it — which is the font/scale/resize race the epoch
exists for.
Also pins the attach/resize gate change at the seam it would break: a
semantic frontend's `Resize` must mint no frame geometry even when its
view is panel-capable, while a grid frontend's real frame size still IS
its declaration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Bottom-panel Stage 2B-2, second half: the producer, the presentation
epoch, and the three inbound event gates.
The producer lives beside the terminal pass in `semantic_render.rs` and
follows its shape: compare the complete payload first, validate only a
payload that differs, and store only what was actually shipped. The
presentation epoch is allocated from the side window and its buffer, so
a new side window, a replaced buffer, and every `Absent` -> `Present`
transition each take a fresh identity; `Absent` clears the identity,
which is what makes close/hide/reopen of the SAME persistent buffer
unaddressable by a stale `PanelPointer`. Allocation is checked and
exhaustion fails closed to `Absent` rather than wrapping into a live
identity. The `Absent` baseline is seeded rather than left empty: a
fresh session has no band, so the opening state is a fact the peer
already holds.
The band rides both render paths and does not wait for a declared byte
viewport: it is a separate surface, and gating it on the document
declaration would leave the first panel unpaintable. Its mode line takes
the side window's segments from the SAME provider invocation that serves
the document's wire segments.
Inbound, `peer_may_send_panel_events` checks four facts together — an
installed semantic projection, the negotiated version, the daemon's own
capability bit, and (via the transport source) that the payload's
claimed id is never consulted. `panel_event_epochs_are_current` then
runs Q#BP16 steps 2-4 as one predicate so no caller can check the
geometry epoch and forget the presentation epoch.
Two daemon gates moved from `panel_capable` to `!semantic_render`. Stage
1 could conflate them because panel capability implied grid; now that a
semantic view can be panel-capable, a capability-keyed gate would feed it
the permanent 24x80 attach placeholder that Q#BP15a forbids, and parent
acceptance 40 would fail through the attach line rather than through the
projection. Not a live defect — no production semantic session is
panel-capable yet — but it is the landmine Stage 2B-3 would have stepped
on.
`panel_capable` is unchanged for production negotiation and the
unsolicited `Hello` still advertises v20. Nothing here is reachable by a
user.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Bottom-panel Stage 2B-2, first half: the daemon-side primitives the
panel producer needs.
`GeometryUpdate` is three-valued rather than a boolean because the
caller must act differently on each arm. `declare_frame_geometry` stays
the grid/LOCAL allocator, keeps value dedup, and moves from
`saturating_add` to checked allocation with a fail-closed exhaustion
arm: it clears the declaration back to unknown, which is already
non-presentable, so reconciliation hides the panel rather than painting
one sized to a frame that no longer exists.
`accept_frame_geometry` is the separate semantic path. No value dedup —
a font or scale change can invalidate a panel frame while `CellSize` is
identical, which is exactly what daemon-side dedup cannot see (Q#BP2S1)
— and a lower epoch is rejected even when it carries identical data.
`panel_grid_size` derives Q#BP15a's third geometry: full declared
width, `fixed_rows` clamped by the recursive document minimum and then
by the shared wire area budget, with the stored request left alone.
`prepare_panel_projection` paints the side window through the Stage 2A
extracted painter, gating folds on the OWNING frontend rather than
`fold_map_for_window`'s active-frontend gate (Q#BP17), and takes the
side window's statusline segments as a parameter so one provider
invocation serves both surfaces. `window_cursor_cell` is `paint_frame`'s
caret derivation lifted out so the band does not become a second,
drifting copy of it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
#174 merged while this PR was open, so the lane it had just been given
is already a merged lane. Rule 4 removes those rather than relabelling
them — but only once the durable facts are in the handoff, so do both
here in that order.
docs/agent-handoff.md
- §5 gains the lesson: a wait predicate WEAKER than the assertion it
guards is a race on whichever platform loses it, and "wait until the
file mentions X, then assert Y" races whenever Y is stricter than X.
- Two things that generalize past the fix ride with it: a race you
cannot reproduce can still be bitten at one remove (an unsatisfiable
predicate proves the wait is load-bearing; the old predicate still
passing proves a local run cannot tell them apart), and the obvious
fix is sometimes worse — the sibling m4_26 is deliberately left alone
because closing it the easy way trades a precise assertion diff for a
vague five-second hang.
- Header and §1 anchor advance to 0442d78, noting #174 is test-only and
#184 remains the substantive change at that head.
docs/active-work.md
- Remove the #174 lane; the header paragraph now says where it went.
- Canonical base and recovery floor advance to 0442d78. The floor
explicitly rejects 6bee09d as well as 7fd646d, per its own rule that a
check accepting an older commit than the declared base passes on a
tree the file does not describe.
- Bottom-panel lane: 2B-2 is IN FLIGHT, not merely "next" — branch,
worktree, and the fact it is branched fresh rather than stacked. Its
boundaries are restated because they are easy to overrun: production
Hello stays v20, panel_capable stays false, and 2B-3 may not simply
bump the unsolicited Hello.
- dired lane: the re-scout is under way on the existing branch, so #171
keeps its three-round history and the product is a rev 5.
- Both lanes note that main advancing to 0442d78 changes nothing for
them, since #174 is test-only.
The bottom-panel framing doc keeps its 6bee09d ground-truth line: that
is a scouting statement about what was actually read, and re-anchoring
it to a commit nobody re-scouted against would be a false claim.
Neither #174 nor #171 had any entry in this ledger. An open PR is
exactly the volatile work the file exists to record, so its absence is a
defect rather than a tidy omission — #171 drifted 153 commits while
invisible here, and its still-green old CI run describes a tree nobody
has looked at since.
#174 — M4 config-sink race, revived and regated. Records the predicate
fix, the gate numbers on the integrated head 302c21c, and two things
verified rather than inherited: the "one writeln! is the only writer"
argument re-checked against a fake_lsp that DID change upstream, and the
wait bitten two ways (unsatisfiable predicate fails on the pump deadline;
the old predicate still passes locally, confirming a green local run
cannot tell them apart).
#171 — dired Stage 2 framing, stale, do not merge as-is. Measured 4
ahead / 153 behind with merge base c8ec8f3, which is itself #165's merge
— so dired Stage 1 and find-file are its base, not new arrivals. Names
the three landed changes that move ground under it: #178's
generated-buffer write invariant (dired is a non-adopting writer),
#182's demotion of dired to a replaceable directory-handler slot, and
#179/#181's typed-edit consumer chain. Restates the five-path-owner
rename transaction the framing still has to answer.
Also records that three review rounds in a commit history is not the
same as approval, and that dired 2a's file overlap with Journey 1b needs
assigning before either runs.
PR #184 (bottom-panel Stage 2B-1) merged, but the four documents that
new work is supposed to start from still described it as open. Advance
every canonical anchor and close the one obligation that was blocking a
lane removal.
docs/agent-handoff.md
- §1's head-of-main anchor moves 7fd646d -> 6bee09d.
- State schema support and production advertisement as two separate
facts: SUPPORTED is now v6..=v21, the server-first Hello still says
v20, and #184 landed only the first.
- New §1 bullet for Stage 2B-1 carrying its durable substrate: the
server-first incompatibility argument, the shared wire_grid boundary
and why per-axis caps are a parameter, authoritative Absent, and the
panel_epoch / geometry_epoch split.
- New §1 bullet for the PTY terminate diagnostic (#176), plus two §5
ops lessons: a drain that ticks is not an observation, and how to
prove a child exited without waitid or unsafe.
docs/active-work.md
- Canonical base and the recovery floor both advance to 6bee09d.
- Remove the #176 lane. It was retained only because rule 4 requires
its facts to reach the handoff first; that move is in this commit, so
the lane goes.
- Rewrite the bottom-panel lane as 2B-1 MERGED with 2B-2 next, branching
fresh from landed main rather than stacking.
- Annotate the documentation lane with measured staleness (1 ahead, 320
behind) without deleting it, since nothing has decided its fate.
docs/bottom-panel-stage2-framing.md
- Status header records 2A and 2B-1 as merged and 2B-2 as next. No
revision bump: the header is landed state, and no design decision in
the document changed.
COHERENCE.md
- §16 names #184 as the PR that landed the reserved v21 family.
No runtime code, no protocol change, no gate suite implicated.
Capture the follow-up review finding, the evidence-driven completion
contract, the exact corrected CAT duration, and the proportional green
gate matrix at 9c79ce1.
Let producer probes name the frame text they require while input probes
finish on their latched echo observation. Report and assert whether the
probe reached that evidence so the 20-second safety deadline cannot
masquerade as successful completion.
The CAT acceptance now finishes in 0.32 seconds instead of waiting out
the full deadline, while the VTERMROW producer still waits for its own
PTY breadcrumb.
Capture the exact review-fix and GPU probe checkpoints, the full green
gate evidence, and the classified sandbox-only socket failure in the
cross-machine active-work ledger.
Keep the real GPU/PTY acceptance probe running until the child output
that its report asserts has actually reached the terminal frame. This
closes the blank-last-frame race exposed by the v20-compatible handshake.
Reserve the additive v21 panel schema without advertising it in the
server-first production handshake. Pin a real shipped-v20 client attach,
make the two aggregate-budget ratchets exactly one byte over, and update
the framing, coherence audit, handoff, and volatile lane record.
Record the complete post-#183 gate matrix for bottom-panel Stage 2B-1
at c8895a8 and mark the lane ready to open for review.
Retain the required-GPU first-pass classification: one unrelated math
render assertion failed, passed immediately in isolated single-threaded
execution, and the mandatory complete 202-test rerun passed.
Integrate main through the Journey/GPU directory-target ratchet (#183).
The public managed-GPU probe additions compose with the v21 wire layer.
Resolve the volatile ledgers to the landed state: advance the canonical
anchor to 7fd646d, remove the completed ratchet lane, preserve the
approved Stage 2 revision-5 recovery facts, and mark the integrated
2B-1 tree as awaiting its from-start full gate rerun.
Capture the managed daemon's lifecycle facts from the ready report before
waiting for the post-quiescence directory snapshot. If that regression
assertion times out, the acceptance harness can now terminate the daemon
instead of leaving it orphaned.
Drive pmacs --gpu . through the root broker and real managed GPU
connector, keep the session alive through the asynchronous dired
replacement, and assert its canonical listing before daemon reuse.
Expose snapshot count and materialized text through the private
display-less acceptance probe so the public path is observable.
Consume the directory session's later replacement snapshot and assert
the canonical dired header plus a known listing entry before checking
daemon reuse.
Correct the bottom-panel revision-5 recovery branch and advance the
durable handoff to the Journey Stage 1a main anchor.
Capture the complete green gate matrix and retain the diagnosed setup
and transient full-sweep failures so the lane remains recoverable and
the evidence is not flattened into an unexplained rerun.
Replace the stale directory-negative in the GPU initial-target suite
with an explicit snapshot-first readiness path, while retaining all
genuinely malformed and unloadable failure cases. Record the portable
side-quest and bottom-panel dependency state.
Make the statusline and Vterm Stage 3 acceptance suites track the
bottom-panel v21 bump, including the real daemon and headless GPU probe.
Record the full gate result and the unrelated stale directory-target
assertion reproduced on canonical main.
Integrate the Journey Stage 1a merge without rewriting the already
reviewed protocol branch. Record the approved three-way Stage 2B split,
advance the canonical recovery anchor, remove the landed Journey lane,
and put 2B-1 into its full-gating state.
The ledger preamble already says Lean 4's merged lane was removed, and
the durable Stage 4b facts already live in the handoff. Remove the stale
section that still called Stage 4b in review so PR #182's post-merge
state is internally consistent.
Rev 4 §9 scoped Stage 2B as a single PR: v21 protocol, daemon panel
projection, GPU band, and the negotiated `panel_capable` flip.
Implementation showed that to be roughly four thousand lines across
three crates with three different failure modes. Rev 5 splits it into
2B-1 (the wire layer), 2B-2 (the daemon projection and epoch machine),
and 2B-3 (the GPU band and the flip), on the rule that a slice ends
where the next thing to build has a different authority.
No decision changes. What changes is the allocation:
- §7.2 becomes three subsections, and criteria that span a boundary are
named in every slice they touch with their half stated, rather than
assigned wholesale to one. Parent 39 is the clearest case: its
shared-validation and transport-budget halves are wire properties
provable in 2B-1, while "the previous valid frame is retained" and "a
duplicate does no work" need the epoch machine and are 2B-2. A2B-1
splits the same way — grid exhaustion in 2B-2, the frontend latch in
2B-3.
- §9 lists four serial PRs instead of two, each cut from `main`, and
states that every slice runs the full gate set rather than the subset
its own crate suggests.
- §6 records which slice pays the coherence debt. The journey claim
belongs to 2B-3 alone: with `panel_capable = false`, a GPU user still
gets the Stage 1 non-side fallback after 2A, 2B-1 and 2B-2 have all
landed. Three quarters of this stage is preparation.
Two things recorded because they are easy to inherit silently:
- This revision is retroactive for slice 1. `bottom-panel-stage2b`
already carried the v21 protocol layer, written before the revision
existed, which inverts framing -> approval -> branch -> implement. The
slicing was sound; taking it in code rather than in the document is
how a stage's scope drifts without anyone deciding that it should.
- 2B-1 and 2B-2 ship dark. The bump advertises a capability whose only
distinguishing feature is unreachable until 2B-3, so the arc must not
stall between them. Safe for compatibility — appended variants,
extended ladder, a v20 peer still negotiates 20 — but a stall should
be visible as a decision, not inherited as a default.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LFvC4FQtux4y32KuevZ7B
Two open documentation PRs editing the same three ledger files
re-conflict on every merge to `main` — the treadmill #169 and #176 each
paid three resolution rounds for. #180 was docs-only and already
CONFLICTING against `main`; folding it here retires one of the two
writers instead of resolving the same file twice.
Also integrates `githubsucks/main` @ `42025e4` (Lean 4 Stage 4b #181).
Conflicts resolved, and three claims corrected rather than picked:
* **The head-of-`main` anchor.** Both sides named `74301d1`, which is no
longer true — `#181` has landed. The anchor now reads `42025e4` and
leads with it, in `docs/agent-handoff.md` §1 and the active-work
canonical-base line, and **the recovery floor moved with it**: the
`git log` check requires `42025e4` or newer, per that section's own
rule that a floor accepting an older commit than the declared base
passes on a tree the file does not describe.
* **The Lean 4 bullet contradicted the anchor I had just written.** Its
header still said "stages 1, 2, 3a, 3b LANDED" and its Stage 4b
sub-bullet still said "implemented and in review", while the anchor
above it now records #181 as merged. Both corrected: all six stages
landed, #181 named.
* **The COHERENCE §2 grade.** #180's side still carried "broken at step
3" / "Broken at entry" in both the ground truth and the §0 scorecard;
this branch's corrections win, since they are what the code change in
this PR makes true.
The removal note at the top of `docs/active-work.md` is reattributed:
the terminal config + copy mode lanes were #180's removal, and it now
says so and says why the fold happened.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A count is a reading, not a constant. Recorded as of rev 8 with an
explicit instruction to re-read rather than quote the line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review round 1 of PR #182. One implementation gap and two stale claims.
**The scope pins the frontend; it does not pin the window.** Framing
§4.4 specified `display{ window = dest:window() }`, but dired's commit
still ended in `pmacs.window.switch_buffer`, which targets whatever
window the scoped frontend has selected. A split or panel that took
focus while `read_dir` was pending therefore received the listing, and
`prev` was captured from it too — with every preflight check passing,
because the captured window was still live and still held its captured
buffer. Both sites now read the captured window: `display` routes to it
with `select = true` (the later `seat_cursor` acts on the active
window), and the `prev` read asks it directly.
N4c pins both halves. The suite's existing routing pins all varied
*frontend* identity; none varied the selected window within one
frontend, which is exactly why 23 green pins missed this. Bite: dired's
`display` back to `switch_buffer` fails N4c alone; `prev` read from the
ambient window fails N4c alone.
Two stale documentation claims, both of which this PR was supposed to
have already fixed:
* **The §0 scorecard still graded §2 "Broken at entry"** while §2's own
ground truth had been rewritten. The scorecard is a second copy of the
same claim and §25's protocol covers both. §19's row and ground truth
were stale the same way — this PR creates the first cross-subsystem
suite, which §19 says should exist and grades as missing — and are
corrected too.
* **P4 still read "leaves exactly one buffer"**, the exact claim rev 6
corrected as false everywhere else in the framing. Restated to what it
actually pins: the file is in the *active window*. The test was
already written correctly; only the framing lied.
Framing rev 8.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
P1 — `PanelPointer` was missing the approved `buffer_id`. Q#BP16 gives it
and `panel_epoch` different jobs and neither subsumes the other:
`buffer_id` catches an A->B buffer replacement, `panel_epoch` catches
close/hide/reopen of the SAME persistent buffer, which a buffer id alone
cannot see. Added in the framing's field order, with a pin asserting each
field independently reaches the wire.
P1 — added parent criterion 39's transport-safety ratchet. It builds the
maximum legal panel payload, asserts the fixture actually spends the whole
aggregate glyph budget (otherwise the ratchet measures something smaller
than the worst case), asserts one byte more is rejected, and pins the
encoded `InstanceMessage::PanelFrame` below `MAX_FRAME_BYTES`. Shaped
`1 x MAX_PANEL_VISIBLE_CELLS` deliberately: no per-axis cap makes that a
legal panel geometry a terminal cannot express, so it is the worst case
the terminal's own ratchet never measured. Bitten by tripling the glyph
budget — 30,342,696 bytes against the 16 MiB cap.
P2 — the shared bounds were duplicated literals. `MAX_TERMINAL_GRAPHEME_BYTES`
now aliases `MAX_WIRE_GRID_GRAPHEME_BYTES`, so the terminal screen's
truncation (`src/terminal/screen.rs:697`, `:777`) and the validator cannot
drift. Two more had the same defect and are aliased too:
`MAX_TERMINAL_VISIBLE_CELLS` and `MAX_TERMINAL_FRAME_GLYPH_BYTES`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RuhVYUPHXMHG8r2z4tsDPR
Completes Journey Stage 1a: the `commit_to` acceptance suite (framing
§6 N4, N6, N6b, N6c, P1, P2, P3) plus the documentation updates
COHERENCE §25 requires the PR to carry.
Bite-testing the new pins found a real gap. Deleting the
`ScopedFrontend` arm from `acting_frontend` left N4 green, because
`ScopedFrontend::enter` also swaps `core.active_frontend` and the
ambient fallback then answers correctly on its own. The arm is
load-bearing in exactly one case — a commit reached from inside an
interactive command, where the origin sits between the override and the
ambient value — and nothing pinned it. N4b is added, driven through
`dispatch_key` because that is the only thing that establishes an
interactive origin, and the mutation now bites it.
Two smaller corrections found the same way:
* `commit_to`'s forged-destination message was unreachable. Typed as
`AnyUserData`, mlua rejected a table during argument conversion, so a
caller got "error converting Lua table to userdata" — true, but naming
neither the rule nor the remedy. The parameter is now `mlua::Value`
and the pointed message fires.
* P1 and P2 also fail on full revert, since `commit_to` does not exist
on the pre-image, so §6.0's "legitimately green on the pre-image" does
not describe them. They stay in the P list because their
discriminating falsifier is the named mutation — a revert-only check
cannot distinguish "validates" from "validates in time" — and each pin
now says so at its own site rather than being silently mislabelled.
Bite results, each run against the whole suite:
scope stops swapping `core.active_frontend` -> N6a, P3 fail; nothing else
preflight moved after the callback -> P1, P2 fail; nothing else
drop the `ScopedFrontend` arm -> N4b fails; nothing else
Docs: COHERENCE §2 grade + step-3 verdict row, §20 Priority 1 and the
arc list; the GPU initial-target framing's Q#GT6 and acceptance 10,
whose directory case this stage deliberately supersedes; handoff §1;
the active-work ledger; framing rev 7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 11 put the nesting count in the expander, which is optional. A
consumer at a lower priority can CLAIM and stop the chain before the
expander runs, while that fan-out's deferred-expansion subscriber still
runs — so the nested pass went uncounted, looked like the outermost
one, expanded early, and outer pairing resumed with a record the
replace had invalidated. `\alp(` gave `α(` again.
The count now comes from a no-op consumer registered at the minimum
priority, which runs first in every chain invocation that reaches any
consumer at all. Its guarantee is exactly the ordering contract the
chain already rests on, and it degrades safely: the only thing that can
skip it is a claim ahead of it, which skips the expander too, so
nothing is queued in that fan-out either.
The other plausible home does not work and the comment now says why: a
subscriber registered beside `run_deferred` is too late, because the
whole nested fan-out completes inside the OUTER chain's subscriber,
before either of them runs.
Acceptance 45o pins the short-circuit path — a consumer at 25 that
claims when the record is nil, so the nested pass never reaches the
expander. 45n passes against this bug, which is why both exist.
Counting in the expander fails 45o and nothing else.
Framing rev 12 also names the shape rounds 10–12 share: each fix was
correct about the failure it was shown and wrong about the boundary of
the mechanism it leaned on — the chain's copy semantics, then its
re-entrancy, then its short-circuit. A queue that outlives the thing
that filled it has to name that thing, not approximate it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LFvC4FQtux4y32KuevZ7B
`buffer.after-edit` fan-outs NEST — the typed-edit contract supports a
consumer calling `pmacs.hook.run`, and typed_edit.lua's header says so
in its second paragraph. A nested run re-enters every subscriber,
including the deferred expansion's, while the OUTER chain is still
walking its consumer list and pairing has not yet seen the terminator.
So a consumer registered at priority 75 — between the expander at 50
and pairing at 100 — that runs one nested fan-out made `\alp(` yield
`α(` again: the nested pass consumed the queued expansion and edited,
and outer pairing then resumed holding a record the replace had
invalidated. That is round 10's failure reached through the chain's
documented re-entrancy seam rather than through claiming, which is why
deferring alone did not close it.
Deferring work past a fan-out means owning WHICH fan-out it belongs to.
The chain's subscriber and this module's each run exactly once per
fan-out, in that order, so counting invocations of the first and
matching them off in the second identifies the nesting level. Only the
outermost pass expands; a nested one leaves the expansion queued. No
new seam in typed_edit.lua, which is merged Stage 4a substrate.
Both halves bite: removing the level check and never counting
invocations each fail the new acceptance 45n.
Also fixes a test comment that still described the span design round 10
discarded — it claimed the expansion replaces the span "INCLUDING the
terminator". The behaviour asserted was right; the explanation was
stale. Framing rev 11.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LFvC4FQtux4y32KuevZ7B