Two blocking, two cleanups. All four accepted, and both blockers
verified by running Lua rather than by reading it.
Recovery was inconsistent across buffers sharing an affinity. Revision
2 cleared `failures[K]` on a successful spawn but cleared only the
succeeding buffer's projection, so: buffer A fails, buffer B succeeds
for the same key, `M-x lsp.status` reports nothing wrong, and A's
modeline still reads `LSP:!`. Revision 2's claim that the two tables
are "written and cleared at the same moment" was false for exactly the
cross-buffer case, which is the normal case for a project with more
than one file. Each projection now carries its affinity key and a
success sweeps every projection holding it. The new pin asserts on A,
not B — a version checking B passes on the broken implementation.
The markerless key had no Lua representation. `key_uri` is deliberately
nil and `t[nil] = v` raises "table index is nil" — confirmed under both
LuaJIT and Lua 5.4 — so the central markerless criterion was literally
unimplementable as written, and leaving it to implementation would have
produced two ad-hoc encodings for the two tables. The framing now
prescribes one key function used by both, with a `u`/`n` discriminator
that cannot collide with any URI and a NUL separator (Lua strings are
8-bit clean, checked).
Acceptance 10 could not have observed what it claimed: making the
command resolvable changes no state by itself, since `failures` is
cleared by a successful spawn. The pin now reattaches before pressing
`g`, and says why that step is load-bearing.
Also corrects the ledger heading, which still said revision 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
Two blocking, three major, one minor. All six accepted and verified in
the code before the doc changed.
The affinity key was misstated. `ensure_server` sets `key_uri` only
when the root came from config or a marker walk, so a markerless file
keys on nil and loose files across unrelated directories deliberately
share one server per language. Revision 1's "(root, language)" would
have split what the runtime shares and re-reported one failure once per
directory. The memo now uses the real key and the stage changes no
reuse behaviour.
Dedupe and current-failure state were conflated into one record, and
revision 1 said nothing about recovery. One record cannot do both jobs:
keep it and `*lsp*` shows a failure the user has already fixed; clear
it and the message returns on the next file open. They are now two
records with different lifetimes, the reported identity includes the
command so repointing config at a different missing executable reports
again, and recovery is pinned in both surfaces.
The modeline provider is a pure per-buffer projection by design — it
runs for every window on every paint. Revision 1's "read the failure
table" would have made it derive an affinity key, invoking user root
resolvers and project detection inside painting. The failure is now
projected per buffer at attach time and the segment stays one map
lookup, with a preservation pin that counts resolver invocations.
"Adopt listview's idiom" is replaced by requiring `pmacs.listview.open`
and naming what it guarantees, including `on_refresh` — without which
`listview.refresh` early-returns and `g` is a bound key that silently
does nothing.
The acceptance had no journey ratchet row despite the stage making step
6 real; `tests/journey_acceptance.rs` states that steps 6-12 join as
later stages make them real, and 1b-1 added step 9 the same way. The
end-to-end row is now acceptance 1 and the M4 pins stay beneath it.
Also refreshes the ledger's canonical-base anchor and recovery floor
from `7586905` to `fbcf235`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T
`COHERENCE.md` §1.2's canonical silence, and journey step 6: a
preconfigured-but-missing language server fails with no status message,
no record, and no modeline marker, while tree-sitter highlighting keeps
working and masks it.
Three findings shape the design, all verified in the tree:
- The reporting pattern is already adopted twice inside `lsp.lua`
itself — root-resolver failures and subscriber failures both report
through `pmacs.editor.set_status` with the `pmacs.error` arm riding
along. The canonical spawn failure at `:658-674` was simply never
converted, so this stage finishes an adoption rather than starting
one.
- Half the stage is already built and unwired.
`LspManager::status_buffer_text()` renders "the `*lsp*` status
buffer", `last_error(sid)` exists, and both are exposed to Lua and
tested — with no production caller, no `*lsp*` buffer and no command.
Several doc comments already refer to that buffer as though it
exists.
- COHERENCE's frequency note is wrong, and the correction decides the
hardest question. It records the failure as firing once per project
root; `LspManager::spawn` returns early *before* both
`status_tracker.ensure` and `clients.insert`, so a failed spawn
leaves no record, the affinity loop cannot see it, and the real rate
is once per file open. Hence the rule the stage adopts: memoize the
report, not the failure — the spawn is still retried, so installing
the binary mid-session recovers with nothing to invalidate.
Records one limitation rather than hiding it: `status_buffer_text`
renders from `self.clients`, which a failed spawn never enters, so the
durable surface cannot yet show the failure natively. The record stays
in Lua for this stage.
Also logs four stale `COHERENCE.md` §1.2 citations for correction when
the stage lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lv428Fth9LRtffwJSsqH7T