All seven citations re-verified against the tree; all seven hold, one is
understated. The split with #186 is untouched.
Revision 7's process read every mechanism it CITED but never read the
mechanisms it RELIED ON: it verified that tick exists and returns
settled ids without reading that the drain is a bare try_recv with no
execution token, and it verified that kill_buffer exists without reading
the order in which it mutates before it can refuse. Reading a symbol to
confirm it is there is not the same as reading it to confirm it does
what the surrounding argument needs.
F1 — reply order is not execution order. AsyncRuntime::tick
(src/async_runtime.rs:1003) drains the reply bus with no execution
token, so a worker can finish, be descheduled before sending, and have a
later reply arrive first. Asked the cheaper question first as directed:
reconciliation does NOT need to be order-dependent. Independent
mutations commute; interdependent ones cannot arise from any production
path (dired serializes, apply_resource_op is synchronous, the fs
primitives have zero production callers); and fs.lua:155-165 already
instructs callers needing ordering to serialize. So the guarantee is
withdrawn rather than engineered. A token under a mutation lock was
rejected: it serializes every fs mutation to close a hazard with no
production reachability and a documented caller-side remedy. No static
ordering rule is offered because none works — worked through in §6.
F2 — preflight, and the review understates it. kill_buffer clears
round_trip_buffers, closes side windows and redirects ordinary windows
before BufferRegistry::remove can refuse. Rev 7 said to treat the
refusal as "keep the buffer", which reads as though skipping phase 2
restored something; it does not. reconcile_delete now preflights
editing_in_progress — sound because phase 1 is pure EditorCore with no
Lua handle, so nothing can re-enter between check and removal.
Acceptance 53b is three separately-asserted properties, each with its
own named bite.
F3 — path-equivalence, not string equality. Names come from
path.display() as given while only file_path is normalized, so a
relative open leaves a short name a string rule mistakes for
user-chosen. Tested both directions; the custom-name arm is what stops
the fix becoming a name-clobberer.
F4 — bounded tombstone, and the census corrected this revision's own
first answer. A first pass checked only handle_notification and
concluded publishDiagnostics was the only uncorrelated writer. Wrong
lens boundary: mark_document_stale takes no LspServerId and creates URI
keys across three stores for every server. The full census also found
that diag_store has zero correlated writers and that
DiagnosticStore.by_uri is keyed by URI alone with no server component —
so the store most needing protection is the one a route purge cannot
help, and a (sid, uri) tombstone would not match it. Recorded as the
round's own defect class occurring inside the round.
F5 — three outcomes, because pmacs.minibuffer.read already has three.
Cancel does not fall through to on_no: for C, declining means copy the
non-colliding entries while cancelling means abandon, and conflating
them would make C-g perform a partial copy.
Sweep for the same defect class: three candidates, two cleared by
reading (run_all_must_succeed really does collect and not abort;
commit_to_refuses_an_await_and_restores really exists), one real and not
raised by review — the framing never said which HookKind the new hooks
register with, and short-circuit would let one subscriber silently stop
every later one reconciling. Both are now all-must-succeed.
P2: pmacs.lsp.forget_uri named with an error contract and pinned; the
ledger note rewritten — it claimed single-file scope while that same
revision edited the ledger — with both line counts measured at this tree
rather than quoted.
Status unchanged: PROPOSED, needs explicit user approval. No runtime
code. Do not merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
PR #186 frames a guard for apply_resource_op's delete arm that refuses
before touching disk when any affected buffer is modified. Rev 6 said the
opposite for the same call site: the file is deleted, the modified buffer
survives orphaned, and §11 named that orphaning as accepted residue. Two
lanes, two answers.
Rev 7 adopts the settled split, recorded verbatim in the framing and in
the ledger lane so the two cannot diverge again: #186 owns the urgent
pre-filesystem refusal for synchronous apply_resource_op; #171 later owns
full post-delete lifecycle reconciliation, including the async race where
a buffer becomes modified after dired dispatch. Not re-litigated.
Census of orphaning claims: 20 matched lines resolving to 13 distinct
passages, classified by reading each enclosing passage rather than the
matched line. 4 ownership claims (this lane closes the LSP data-loss
defect) reassigned to #186; 4 policy claims (LSP intentionally deletes
and orphans) deleted; 3 ground-truth statements kept, because main really
does destroy unsaved work on that arm, but attributed to #186 as fixer;
2 async-race claims kept and narrowed.
Q#DR18 is rewritten, not withdrawn. The reconcile_delete seam survives
and is explicitly not cancelled. Three changes: the synchronous path
refuses before disk (#186 Q#RD1, adopted not re-decided); the policy
becomes symmetric rather than asymmetric; and the walk rule is adopted
from #186's Q#RD6 rather than reinvented — scan every path-bound buffer,
normalize once, component-aware Path::starts_with — which is
character-for-character what rev 6 wrote independently. Whichever lane
lands first owns the query, per #186's own boundary wording.
DeleteReconcile.kept_modified stays and is not dead. The synchronous
caller can no longer produce it; the asynchronous one still can.
Residue kept, and narrowed to one path: dired never goes through
apply_resource_op — it calls pmacs.fs.remove, which dispatches a worker
that an in-applier guard cannot see. So the LSP path has no residue and
dired's is its own. Rev 6 called it "one deferral, two paths"; that was
backwards. Also newly named: pmacs.fs.remove itself is guarded by
neither lane, since both guards sit one layer above it. Latent — zero
production callers.
The full post-delete lifecycle stays here, and #186 hands it over
explicitly rather than by omission: its Q#RD8 parks the window and
last-buffer defects and independently reaches rev 6's R4 finding that
kill_buffer and remove_buffer_and_fire clean disjoint sets; its Q#RD5
keeps reconciliation exact-path precisely so as not to promote those
defects tree-wide. That parked work is this lane's Q#DR27.
LSP failure handling: this document never made the claim — grep returns
10 lines, all about hook fan-out or dired's batch semantics, none about
WorkspaceEdit recovery. Verified the spec anyway and recorded it, since
the two framings will sit side by side: only textOnlyTransactional
degrades to abort for resource ops, transactional covers them, and LSP
3.18 states no default for a client advertising no failureHandling.
Rev 6's one adjacent claim — "refusing mid-edit leaves a half-applied
refactor" — was the load-bearing support for the deleted policy and goes
with it.
Noted without re-litigating: #186's Q#RD5 cites the ledger's "OPEN,
STALE, 153 commits behind, under re-scout" assessment of #171 in support
of taking the delete side now. That citation is stale — the re-scout is
done and this lane is integrated to ad41cf1 — but the conclusion stands
on urgency alone, so nothing about the split changes.
Status unchanged: PROPOSED, needs explicit user approval. Do not
implement, do not merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Revision 5 was reviewed and not approved. Round 5 has one theme, not
six: rev 5 changed the slice split and the ownership of a decision, and
the prose did not follow. Four of the six findings are that same defect
in different places. All six verified against the tree before acting;
two are understated by the review.
R1 (P1) — rev 5 was superseded by its own ledger entry and by a PR body
older still. active-work.md:507 records Q#DR25 as moved out of this lane
(dired's paint is one instance of a CLASS bug — the same
erroring-intercept-over-writable-rope idiom is in listview, compile and
search/grep, and no Lua caller anywhere sets read_only because there is
no Lua set_read_only), while the framing still called it mandatory and
assigned it to 2b, and the PR body still described revision 1's
two-slice plan. All three reconciled: Q#DR25 is withdrawn, §3.1 becomes
a handoff to the generated-buffer-immutability lane, and the PR body is
rewritten. Also corrected in the other direction: rev 5's claim that
Q#DR25 "closes dired's quarter" of the §14 gap was itself wrong — dired
was never a quarter of anything.
R2 (P1) — acceptance allocation contradicted the code split, and the
review undercounts by one. Items 23-24 test apply_resource_op and the
drain harvest (2a substrate) but sat under a 2b header; item 33 needs a
dired.lua subscriber while 2a is defined as containing no dired code.
23-24 moved to 2a; 33 moved to 2b, because 2a's whole review rationale
is that its diff contains no dired code. The cost is stated: between 2a
and 2b a directory rename leaves dired handles stale, which is the
status quo and not a regression. The undercount: that leaves 2a shipping
resource.renamed with no acceptance, so item 50 pins the hook contract.
R3 (P1) — TickOutcome could not carry deletions. Replaced the two ad-hoc
vectors with one ordered Vec<ResourceOp>, and PendingJob carries a
single Option<ResourceOp>. Ordered because a directory rename and a
delete beneath it can settle in one tick; one enum rather than two
Options because two admit a both-Some state that cannot occur, the
argument ResolvedTarget's own doc makes at editor_core.rs:100-102.
R4 (P1) — reconcile_delete stopped short, and the substrate is worse
than the review says. Right that removal is two phases; what it does not
say is that NO existing Rust path composes them. pmacs.buffer.kill
(mod.rs:5476-5491) does, and its doc says it is late-bound precisely to
redirect windows — but apply_resource_op uses remove_buffer_and_fire,
which is phase 2 without phase 1, and BufferRegistry::remove touches
only buffers and order. So an LSP-authored delete leaves a displaying
window pointing at a removed id: a third defect on that arm. §6 now
specifies both phases, the last-buffer and ConcurrentEdit refusals, and
items 51-53.
R5 (P2) — the "every consumer" claim was false for Lean. The deferral
was right and the summary was wrong; fixed the summary. Stage 2 supplies
the hook, Lean's state stays stale until its owner adopts it.
R6 (P2) — pre-three-slice file inventory. minibuffer.lua joins
src/editor.rs's include_str! sequence, so 2b touches a Rust file; "2b is
dired.lua plus one killring binding" was false. + and C retagged 2c.
The sweep the review asked for caught four more stale labels it had not
cited: §4's "C (copy, 2b)", §7's "(in 2b) a recursive delete", §8's op
sections, and §8's d/x line — which was wrong in BOTH halves, saying 2a
where it meant 2b and 2b where it meant 2c, a pure two-slice-era
leftover. Historical round sections keep their original labels, flagged
rather than retconned.
The ledger lane rides this PR and is updated to match.
Status unchanged: PROPOSED, needs explicit user approval. Do not merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
PR #185 merged during the re-scout, so `docs/active-work.md`,
`docs/agent-handoff.md` and `COHERENCE.md` are no longer held by an open
PR. #185 changed docs only — no source — so nothing in rev 5's census
moves, and the two facts rev 5 cites from the durable records survive
intact: the handoff §4 inventory still names `builtin/runtime/dired.lua:371`
as a non-adopter of `set_generated_contents`, and COHERENCE §14 still
names dired and still classifies it as the cheap half.
This branch continues to touch only `docs/dired-stage2-framing.md`. A
standalone docs-refresh PR is exactly the ledger contention treadmill
the ops lessons warn about, and the ledger's own protocol puts a lane
refresh with the work rather than ahead of it.
What §16 now records instead: the landed `docs/active-work.md` lane
still has this branch at head `ab42a79`, calls the document 1,570 lines,
says the re-scout is under way, and states the rename census as FIVE
path owners. Rev 5 makes all four stale — and the census is SIX (§5,
W5). Same lesson in the other direction: a census is a reading, not a
constant. The refresh should lift §5's table rather than re-derive it,
and should note that Q#DR25 added scope the lane does not mention.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
`main` moved mid-re-scout: #174 (fix-m4-sink-races) merged. It touches
`tests/m4_acceptance.rs` only and changes no source, so nothing in the
rev 5 census moves — but it intersects this document at one point, and
in the direction that strengthens it.
#174 fixed the CONFIG sink by waiting for a complete JSONL record
(`ends_with('\n')` instead of `contains("probe")`) and deliberately left
the ROOTURI predicate alone, adding a comment at
`m4_acceptance.rs:5486-5496` giving the same three reasons §11's
deferral gives: it is the same weak-predicate class, it has never been
observed failing, and waiting for the expected value would trade a
precise regression diff for a vague five-second timeout. Closing it
needs a record terminator in the fake server first.
So that deferral is no longer a claim of this framing; it is a claim of
the tree. Item updated with the current line (`:5499`, shifted by
#174's own comment) and the in-tree backing.
The header now records both anchors: the census was read at `6bee09d`,
and it holds unchanged at `0442d78`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Rev 4 was scouted at c8ec8f3, which is dired Stage 1's own merge commit.
Canonical main has moved 153 commits since. This is that re-scout. Every
claim below was read on the tree at 6bee09d, not inferred.
Four dependencies arrived that rev 4 could not have known about:
- #178 landed `Buffer::set_generated_contents`, and dired has NOT
adopted it — the handoff §4 and COHERENCE §14 both name
`dired.lua:371` in the non-adopter inventory. Dired pairs an erroring
intercept with a bypass_intercept write over a still-writable rope, so
`M-x buffer.undo` empties a listing today, with no keybinding needed.
Stage 2 writes that buffer on every mark. New decision Q#DR25 adopts
the primitive at the head of 2b, with acceptance items 48-49.
- #182 (Journey Stage 1a) demoted dired to a replaceable slot
(`pmacs.path.directory_handler`, deliberately not a hook subscriber)
and rewrote `open_directory` around `pmacs.window.commit_to`, whose
scope REFUSES an await. That constrains §9's serialize-and-await batch.
- `tests/journey_acceptance.rs` is a declared ratchet (24 tests, "stages
add rows, none removes them"), seven rows assert on dired, and #183
put the GPU row in `gpu_invocation_acceptance.rs` instead — so the
ratchet is split across two files and both are gates now.
- #179/#181's typed-edit chain: dired participates in neither it nor
`buffer.after-edit`, and `set_generated_contents` fires no hook, so
Q#DR25 does not drag dired onto the chain. The chain's lessons still
bind §5's two NEW hooks.
Seven of rev 4's own claims about pmacs were wrong:
- W1 (load-bearing): §5 named `drain_external_cancelled` and cited
`lsp.rs:1596`. Those are two different functions.
`drain_external_cancelled` (:1561) is the unconditional server-scoped
drain and is the right precedent; `drain_cancelled_externals` (:1596)
is a per-tick token/timeout sweep. A rename flips no token, so
following the line number yields a `forget_uri` whose drain half is a
silent no-op and the awaiting coroutine hangs forever — the exact
failure step 2 exists to prevent.
- W2: there is no `fn restart`. The teardown is `start_generation`
(:1307-1345), and there is a second precedent rev 4 never named,
`LspManager::forget` (:3015-3042). Neither clears the 14 result
stores, so rev 4's "surprising" note now holds twice.
- W3: `ResponseRoute` has 15 variants, 14 URI-bearing; there are 16
insert sites, 15 URI-bearing. Rev 4's fifteen was a correct count of
URI-bearing inserts and an incorrect count of sites. The purge
predicate must retain `WorkspaceSymbol { query }` explicitly.
- W4: `rec.uri` is read at 57 lines in lsp.lua, not "~20".
- W5: the path-owner census has grown to SIX. `lean.lua`'s
`M.file_progress` is a URI-keyed Lua module table in no Rust store, so
`forget_uri` cannot reach it — independent evidence that the hook, not
the Rust method, is the mechanism that scales.
- W6: the ledger note named PR #169, which merged. #185 holds the
durable records now.
- W7: the C1 seam is at mod.rs:7104-7115, and mod.rs defines three
`_tick` bindings in different classes.
The 2a/2b/2c/Stage-3 cut was re-examined and holds unchanged; §16 adds
the ownership warning that 2a overlaps editor_core.rs, lsp.lua and the
URI-keyed LSP state with Journey Stage 1b and must not run concurrently
with it. §0.5 now cites COHERENCE §20 Priority 1 and §14 by number, and
records that #182 put dired on journey step 3 — a claim rev 4 could not
make.
Status is stated honestly: PROPOSED, never formally approved, and
needing explicit framing approval before any implementation branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Three design blockers, four cleanups, and the staging call taken. Round
3's theme: rev 3 named the right seams but sized two of them from a
partial inventory, and one promise was still stronger than its mechanism.
All three verified against c93f9ee.
H1 — the modified-buffer delete check races the syscall. Rev 3's
"immediately before each syscall" was wrong about where the boundary is:
pmacs.fs.remove DISPATCHES A WORKER, so the interval to remove_blocking's
remove_file is wide open, and acceptance 20 (edit before y) could never
have detected it. NARROWED to a TOCTOU-bounded pre-dispatch check, the
same honest framing G6 forced on R, rather than inventing a reservation
primitive inside a dired stage. The residue is stated precisely: the
buffer survives with its contents (that half IS robust — it runs at drain
time), the file does not. So the orphan deferral rev 3 scoped to the LSP
path now covers dired too, as one deferral rather than two. Acceptance 20
says outright that the interval has no test because it is not closed.
H2 — the LSP teardown inventory was a third of the real one. LspManager
holds FOURTEEN URI-bearing store families (lsp.rs:741-819), not five, plus
the `documents` text map didChange diffs against — a stale entry there is
a correctness problem, not a leak — plus pending_routes, whose
ResponseRoute variants CARRY THE URI at fifteen insert sites, so an
in-flight response repopulates the old key AFTER any clear. Rev 4 gives
the full table and one manager-level forget_uri(sid, uri) that purges
routes, drain-cancels the matching awaiters (the existing contract at
:799-803 already requires that wherever routes are purged), and clears all
fourteen plus documents — handling locations_store's kind key and
symbol_store's scope key specially. Modelled on the server-scoped
teardown at :1316-1331. Also records the surprise found on the way: that
teardown clears routes and documents but NOT the fourteen stores.
H3 — the diagnostic-view seam is now chosen, not either/or. Verified the
constraints: DiagnosticView.uri is private and immutable, View has no
downcast, and _attach_view takes active_window_mut() and ERRORS otherwise,
so it reaches one window and cannot drive a per-window loop from Lua; and
a remove-and-re-push loses composition order in an ordered
Vec<Box<dyn View>>. The seam: a View::rename_resource default-no-op hook,
joining overlay_identity and clone_for_split — the family #113 round 6
added for exactly this class — swept over core.windows.values_mut() the
way overlay disposal already is (mod.rs:2016-2019). In-place mutation, so
order is preserved by construction, the field stays private, and future
URI-bearing overlays opt in by overriding rather than growing a special
case. Acceptance 30 now needs TWO windows and an order assertion; a new
item 31 pins the store inventory and the in-flight repopulation.
Staging: TOOK THE FURTHER CUT as directed. Three PRs — 2a the
reconciliation transaction with no dired surface, 2b marks and operations,
2c the new fs primitives. 2a leads with the two defects it closes on main
today (an LSP-authored delete that destroys unsaved work; a workspace-edit
phantom buffer), neither of which needs dired to be worth fixing. Named
for the substrate per #161's precedent. §10 states the cost: three review
cycles, and 2a ships nothing visible.
Cleanups: item 35→40 (now 41), acceptance 27→30 and 28→32 (now 33), and
the §10 table's obsolete rename-only-Rust description, replaced by a
per-PR breakdown of what each actually carries.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126d2sikA6jZpFin3rtLCSK
Four blocking, two high, four cleanups. Round 2's real finding: rev 2
widened the rename fix into a resource transaction, and four of the
consumers it named were not actually reachable by it. All six
substantive claims verified against c8ec8f3.
G1 — acceptance 29 was unimplementable. apply_workspace_edit captures
origin as a STRING (active_buffer_path is pmacs.editor.file_path,
lsp.lua:471-473), so no transaction reaches it and the phantom survives.
The applier itself changes: capture the buffer handle, restore with
switch_buffer, and no path fallback — restoring nothing beats inventing
a file that does not exist.
G2 — the dired subscriber could not rename its own buffer. dired.lua's
module doc says there is no pmacs.buffer.set_name, which is exactly why
Stage 1 chose buffer-per-directory. Rev 3 adds the setter (Q#DR21):
Buffer::set_name already exists and already documents itself as for
"rename operations", §5 needs it anyway for the Buffer.name half, and the
alternative — kill/recreate plus window replacement — loses placement,
cursor, intercept, round-trip input, and mode.
G3 — rec.uri was not the last LSP owner. DiagnosticView captures its URI
at construction and its own field doc anticipates this ("M5 may add
re-rooting if a buffer is renamed", diag.rs:455-457); five more stores
are URI-keyed. §5 now carries the ordered contract: flush pending
didChange, didClose, drop all five stores, re-run ensure_server, didOpen,
re-root the view per window.
G4 — Q#DR18 had no seam and was racy across the prompt. apply_resource_op
kills via find_by_path: raw path, first match, no descendants, no
modified check — it destroys unsaved work today. Rev 3 defines one shared
reconcile_delete called by both paths, harvests remove in the drain like
rename (so fire-and-forget reconciles too), and rechecks modified state
immediately before each syscall, since another frontend can edit while
the prompt is open. The policy stays asymmetric on purpose: dired refuses
the entry, an LSP-authored delete still removes the file but no longer
destroys the buffer.
G5 — w had no surface and the wrong semantics. push_entry is local and
copy() requires a region. Adds pmacs.killring.push (Q#DR22) with copy()'s
own semantics including breaking the kill chain, and makes w SET-BASED:
the parent approved the binding and Emacs copies marked filenames, so
rev 2's point-only narrowing was an unapproved change of its own. R is
now the only point-based operation.
G6 — R's no-clobber was only a preflight. rename_blocking calls plain
std::fs::rename, which silently replaces. The claim is narrowed to a
TOCTOU-bounded preflight refusal, acceptance 12 reworded to promise only
that, and a no-replace primitive named as deferred.
G7 — lsp_multi_root added to the gates, the §13/§7 slips fixed, and the
"2a's only Rust is the rename rebind" line corrected: it is now a rename
and delete reconciliation, two hooks, two new public surfaces, an LSP
teardown contract, and an applier change. §10 says so, and names the
further cut if that is now too large for one PR.
Acceptance renumbered flat (46 items) and the bite obligations are now a
table of eleven item/mutation pairs, three of them round-2 additions
where rev 2's design would have passed a weaker test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126d2sikA6jZpFin3rtLCSK
Seven findings, two blocking. Every checkable claim was verified against
c8ec8f3 before being acted on; all seven held.
F1 (blocking) — the rename contract reached one path owner. Verified the
other four: Buffer::set_name documents itself as for "rename operations"
and set_buffer_path never calls it; rec.uri is cached per LSP attachment
and read at ~20 sites; dired's buffers are PATHLESS so no buffer-keyed
rebind can reach them; and the workspace-edit origin restore does not
fail gracefully — find_or_open on a renamed-away path hits
resolve_target_buffer's NotFound arm, which creates an empty path-backed
buffer, so it materializes a phantom at the obsolete path and selects it.
Rev 2 replaces the rebind with EditorCore::reconcile_rename — whole
registry, equality-or-path-component prefix, updates file_path AND name,
called by BOTH the async drain and apply_resource_op so the two cannot
drift — plus a new resource.renamed(old, new) hook so path-keyed Lua
consumers reconcile. lsp.lua recomputes rec.uri, issues didClose/didOpen,
and re-runs ensure_server because #161 keys affinity on project root, so
a cross-root move needs a different server. dired.lua follows its
handles. Verified the ordering the design needs already holds:
_async.tick calls _tick() before resuming any coroutine.
F2 (blocking) — deletion of visited paths had no policy. New §6 decides
all four cases. An unmodified visited buffer is killed; a MODIFIED one
refuses that entry, deliberately diverging from Emacs, because an
orphaned buffer is indistinguishable from a normal one and the next
C-x C-s silently resurrects the file. The check runs before the confirm
so the prompt states the skip. Adds a symmetric resource.deleted hook.
F3 (high) — the key table silently changed approved scope. The parent
lists `w` and contains no `M`. Restored `w` (Q#DR20); `M` is now an
explicit new-scope decision (Q#DR19) that REFUSES symlinks, since the
parent already ruled that the fixture's symlink-perms rejection "carries
over unchanged" and rev 1's warn-after-the-fact contradicted it.
F4 (high) — Q#DR13 contradicted the R contract. Narrowed to three
classes: set-based (D, M, C), flag-based (x), point-based (R, w).
F5 (high) — five falsifying acceptance items added, and the bite matrix
now names six mutations including "dispatch-all-then-await" and "add a
completion source to confirm".
F6 (medium) — take_settled_renames was underspecified. Took the
reviewer's preferred shape: tick returns a structured TickOutcome so
settle identity and rename metadata stay in one transaction.
F7 (2b) — defined the full C command flow with an up-front collision
scan and one confirm (declining copies the non-colliding entries), pinned
remove_dir_all's lstat safety at the primitive, and stated
dired.recursive-deletes as boolean/default false.
Not done, and said so: R is not widened to the marked set. Multi-file
rename needs a target-directory concept that does not exist, so R stays
point-based and is NAMED as a class rather than left an unstated
exception.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126d2sikA6jZpFin3rtLCSK
Continues docs/dired-framing.md, whose §§6-7 carry the approved shape of
marks and operations. This re-verifies every claim in them against
main @ c8ec8f3 — Stage 1 changed three of the files Stage 2 leans on
most — and adds what the parent did not decide: the batch-execution
contract, the confirmation surface, the staging cut, and acceptance.
Decisions continue the Q#DR scheme from Q#DR12.
Five corrections to the parent, one load-bearing:
- The rename rebind belongs in the drain (the parent's decision, kept)
but CANNOT be implemented in `AsyncRuntime::tick`: AsyncRuntime has no
buffer registry and no core. The seam is `pmacs._async._tick`, one
layer up, which already has `lua` in scope. AsyncRuntime harvests, the
binding rebinds.
- Line references drifted (tick 991 -> 1003, the FsUnit arm 1022 ->
1046, apply_resource_op's raw lookup 3248 -> 3249).
- The frozen fixture has NO mark-and-operate layer — eight commands, two
keys, and its "marks" are wdired text-position marks. So Stage 2 has
no in-repo reference implementation, which the parent's "45 tests pin
dired/wdired behavior" reads as implying it does.
- No y_or_n exists anywhere, and there is no runtime minibuffer.lua at
all — pmacs.minibuffer is Rust-only.
- `remove_blocking` already deletes files AND empty directories, so
`remove_dir_all` is needed only for non-empty ones. This is what makes
the staging cut possible.
Also carries a verified pre-existing defect, confirmed by probe rather
than inferred: a fire-and-forget non-stream job leaks its pending entry
forever (only stream eviction and take_result remove entries, and the
Lua handle has no __gc). Named as a deferral. The same probe establishes
that a settled job IS still readable at drain time, which is what makes
the rebind design sound.
Recommends splitting Stage 2 at the "needs a new Rust primitive" line:
2a is the mark layer plus d/x/D/R/M on the five ops that already exist,
plus the rename correctness fix; 2b adds mkdir/copy/remove_dir_all and
+/C and recursive delete. The reasoning is that 2a's only Rust is the
rename rebind, whose design is subtle enough to deserve a reviewer's
whole attention.
Coherence impact per COHERENCE.md §20 is stated in §0.5, including the
honest part: Stage 2 must add a `rename_paths` field to `PendingJob`,
which is a one-off where §9 wants a general owner/purpose — though a
side map, the alternative, is worse by §9's own diagnosis of the
parse-job link. 2b grows the closed JobKind enum 12 -> 15.
Touches only this file: docs/active-work.md and docs/agent-handoff.md
are held by the open docs PR #169.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126d2sikA6jZpFin3rtLCSK