Commit Graph

3 Commits

Author SHA1 Message Date
Levi Neuwirth 8e032d75d8 docs(framing): generated-buffer immutability, revision 3
Answers review round 2 on #188 @ 516bd35 -- three P1, two P2 -- integrated
to main @ 7586905 (#189, COHERENCE.md only; clean merge).

P1-1, the route cannot write to a locked buffer. Confirmed, and the
review's first repair option cannot work: reordering begin_edit's two
checks leaves ensure_writable running one line later, so a locked buffer
is still refused and only the reported error changes. Any admitting path
must not reach ensure_writable at all. Q#GB3 is reversed -- generated
writes get their own run_buffer_edit arm and never touch begin_edit -- and
new section 3.4 specifies the transaction: one &mut Buffer method, eight
named exits with the post-state of each, relock and editing_in_progress
clear unconditional, bounds pre-validated so an invalid range costs no
history, and history cleared iff the revision advanced (exact, because
revision bumps after the undo push and before the on_edit broadcast).
Proved along the way that no Lua runs inside a generated write:
LuaInterceptView implements only intercept_edit and inherits View::on_edit's
no-op default, so the only re-entrancy direction that needs a gate is
outward.

P1-2, disambiguation. New section 2.10 is the census: 19 units across 14
grep lines, five classes, arithmetic shown, each classified by reading its
enclosing function. Two sites break; between them six downstream consumers,
not three. The fourth listview consumer is listview.open's own
never-capture-a-panel guard, which fails inverted and silently -- it
captures the panel as its own q target, the exact loop its comment says it
prevents. compile's slots table is NOT affected (keyed by a module
constant, slot_for_buffer is id-based). New Q#GB18 routes both by owned
BufferId, and notes listview registers no on_removed so its new list must
compact on scan like dired's live_handles.

P1-3, unlock_generated. Withdrawn as written; both halves of the finding
hold. Sweep C found the mirror-image hole the review did not: the SHIPPED
set_generated_contents lifts a lock it did not install, so it can already
overwrite a live terminal identity buffer. Provenance (new Q#GB15,
generated_lock beside read_only) therefore earns its cost in both
directions rather than existing to make the unlock safe. Q#GB7 moves to
Stage 2, its claim narrows from "brick escape" to "the closure of the
capability { generated = true } adds", and the asymmetry with the standing
no-Lua-set_read_only policy is addressed directly: a provenance-bounded
unlock adds no reachable state that { generated = true } did not.

P2-4, criterion 6. Confirmed, and the framing cited the wrong precedent:
terminal_copy_mode_acceptance has zero dispatch_idle_for references. The
model is dired_acceptance.rs:969. dispatch_idle_for has six ways to return
false and the review named one, so the criterion now carries three halves
-- document-window premise, the gate, and acc16's positive control, which
rules out the other five clauses in one assertion.

P2-5, the path-backed refusal. Now exercised on all four surfaces with
guard misplacement as the explicit bite.

Sweep C (obligation 3) also found that read_only is one boolean serving
three policies: the generated lock, terminal identity, and -- as a reader
-- fold.rs:313's "is this a document buffer" test, pinned by
folding_acceptance.rs:570. Locking five families silently disables
pmacs.fold.fold on all five, which contradicts Q#GB3's own stated rationale
for preserving the unfold seam. New Q#GB16 names it and recommends
accepting it with a criterion rather than accepting it silently.

Two further corrections the review did not name. COHERENCE.md section 14's
listview list was corrected by #189 while revision 2 was open, so section
1.5 goes from owed to landed -- a merged correction is removed, not
restated. And *help* has two independent writers, src/help.rs:354 (Rust,
apply_edit, mark_clean) and default.lua:1239 (Lua, no mark_clean), each
with its own find-by-name and its own copy of the name constant, so the
inventory's headline is five mechanisms, not four.

Section 2.9's is_modified sweep is corrected a second time: seven
production consumers, two load-bearing. Revision 1 said two, revision 2
said four. The conclusion is unchanged but was reached twice from an
incomplete count.

New section 9b records the cross-lane boundaries verbatim and confirms
Q#DR25 against #171 revision 7 at fd7ae37. Revisions 1 and 2 never
mentioned Q#DR25 at all despite owning it; revision 3 claims it, and
carries #171's trap that dired_acceptance.rs:969's read-only status
assertion passes both before and after adoption.

Twelve acceptance criteria added or rewritten, each stating what it asserts
about content produced and the mutation that falsifies it. Stage 2 gains
criteria for the transaction's error paths specifically -- a leaked
editing_in_progress asserted through the NEXT edit, not the failing one,
because the failing call reports the same error either way.

Why revision 2 shipped these: it audited criteria against pre-images
(sweep A) and capabilities against who may use them (sweep B), but never
read the guards its own mechanisms had to pass through. begin_edit,
document_bytes and terminal/session.rs:305 were all cited elsewhere in the
tree and none was opened. The process fact for revision 4: for every seam
the design routes through, read the callee, not the caller's description of
it.

Status stays PROPOSED. No runtime code, no protocol change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
2026-07-28 20:26:18 -04:00
Levi Neuwirth 516bd3579a docs(framing): generated-buffer immutability, revision 2
PROPOSED. Framing only; still no runtime code.

Answers five review findings on PR #188 at 9101bf5 (three P1, two P2),
plus the two sweeps the review asked for by class.

P1-1: Stage 2 criteria 3, 4 and 5 all passed on the pre-image. Ordinary
edits are already refused by the existing intercept, and Buffer::undo
checks read_only before it inspects history, so "undo returns false"
passes against an implementation that locks and never clears. Rewritten
to use a bypass write or Rust-side is_read_only for the lock, and to lift
the lock inside a Rust test before asserting NothingToUndo.

P1-2: ownership by handle is now a prerequisite of the stage that locks
each writer. Measured: a foreign *references* is clobbered and left
permanently un-editable, a pmacs.compile.run that raises on validation
still leaves a foreign *compilation* un-editable, and M-x buffer.undo is
currently the only recovery -- which this arc removes.

P1-3: revision 1 was wrong that is_modified drives only the mode line.
autosave.rs:363 and desktop.rs:302 read it. The rule chosen: a generated
write refuses a buffer that has a file_path.

P2-4: view_top is a line index, cursor is a byte position. The clamp is
now per-coordinate and ungated.

P2-5: re-measured at ad41cf1, 276 dark, with the command shown.

Sweep A found Stage 1 criterion 7's stated bite was wrong, and that eight
criteria pass on main by design without saying so; every criterion now
carries a pre-image class. Sweep B found the two halves of the protection
are asymmetric -- the intercept is removable, the rope lock is not --
which falsifies revision 1's reason for deferring the unlock, so Q#GB7 is
upgraded to Stage 1 work.

Adds the PR #188 lane to docs/active-work.md, which the ledger now
requires for every open PR.
2026-07-28 18:32:05 -04:00
Levi Neuwirth 9101bf5b78 docs(framing): generated-buffer immutability, revision 1
PROPOSED. Framing only; no runtime code.

Buffer::undo gates on ensure_writable() and never consults the intercept
chain, so the add_intercept "read-only" idiom leaves the rope writable
and the owner's own bypass_intercept paint on the undo stack. Four writer
mechanisms across five buffer families are affected; every one was
reproduced by execution at ad41cf1, not inferred.

The document carries the measured reproductions, the full classified
census with its arithmetic shown, the primitive decision with numbered
alternatives, per-criterion bite obligations, and the staging proposal.

Recommended primitive: Buffer::apply_generated_edit(op), exposed as a
{ generated = true } option on the existing Lua mutators, with
set_generated_contents reimplemented as its whole-buffer wrapper. It is
the only candidate in which the buffer is never observably unlocked.
2026-07-28 17:57:57 -04:00