Merge pull request #183 from levineuwirth/journey-gpu-directory-ratchet
test(journey): ratchet directory GPU bootstrap success
This commit is contained in:
commit
7fd646dcac
|
|
@ -1,6 +1,6 @@
|
||||||
# Active work — cross-machine resume ledger
|
# Active work — cross-machine resume ledger
|
||||||
|
|
||||||
**Snapshot: 2026-07-26.** This file records volatile work that has not
|
**Snapshot: 2026-07-28.** This file records volatile work that has not
|
||||||
landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed
|
landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed
|
||||||
entries when their PR merges; do not let this become a second permanent
|
entries when their PR merges; do not let this become a second permanent
|
||||||
backlog.
|
backlog.
|
||||||
|
|
@ -27,19 +27,12 @@ landed regardless of what a lane says.
|
||||||
machine-local: `origin` may name this canonical URL, a release mirror,
|
machine-local: `origin` may name this canonical URL, a release mirror,
|
||||||
or something else, and therefore has no authority by name alone.
|
or something else, and therefore has no authority by name alone.
|
||||||
- Canonical base at this snapshot:
|
- Canonical base at this snapshot:
|
||||||
`githubsucks/main` @ `42025e4` (Lean 4 Stage 4b #181, atop the dired
|
`githubsucks/main` @ `c2d56ff` (Journey Stage 1a #182, which
|
||||||
Stage 1 landed docs #169 and the PTY-terminate diagnostic #176,
|
incorporated terminal configuration + copy mode #180, atop Lean 4
|
||||||
terminal copy mode #178, the GPU-terminal-input landed docs #168, Lean
|
Stage 4b #181 and the previously recorded landed work; protocol v20).
|
||||||
4 Stage 4a #179, bottom-panel Stage 2A #177, the bottom-panel Stage 2
|
The previous snapshot named `42025e4`, and **the recovery floor
|
||||||
framing #175, terminal configuration Stage 1 #173, Lean 4 Stage 3b
|
advances with it**: the check below now requires `c2d56ff` or newer,
|
||||||
#170, Stage 3a #167, the CRDT undo repro #157, the inline-math
|
so a tree at `42025e4` no longer passes. That is
|
||||||
landed-doc refresh #172, the bottom-panel landed-doc refresh #156, the
|
|
||||||
inline-math slice #158, dired Stage 1 #165, the GPU terminal input fix
|
|
||||||
#166, Lean 4 Stage 2 #161, the dired framing #164, COHERENCE.md #163,
|
|
||||||
find-file #162, Lean 4 Stage 1 #160, and the minimap blank-slab fix
|
|
||||||
#159; protocol v20). The previous snapshot named `74301d1`, and **the
|
|
||||||
recovery floor advances with it**: the check below now requires
|
|
||||||
`42025e4` or newer, so a tree at `74301d1` no longer passes. That is
|
|
||||||
deliberate — the floor moves with the base, because a check that
|
deliberate — the floor moves with the base, because a check that
|
||||||
accepts an older commit than the declared base passes on a tree the
|
accepts an older commit than the declared base passes on a tree the
|
||||||
rest of this file does not describe.
|
rest of this file does not describe.
|
||||||
|
|
@ -78,7 +71,7 @@ git worktree list
|
||||||
git status --short --branch
|
git status --short --branch
|
||||||
```
|
```
|
||||||
|
|
||||||
The `git log` command must expose `42025e4` — the base named above — or a
|
The `git log` command must expose `c2d56ff` — the base named above — or a
|
||||||
newer intentional main. Keep this threshold and the canonical-base line in
|
newer intentional main. Keep this threshold and the canonical-base line in
|
||||||
step: a recovery check that accepts an older commit than the base it
|
step: a recovery check that accepts an older commit than the base it
|
||||||
declares canonical will pass on a tree the rest of this file does not
|
declares canonical will pass on a tree the rest of this file does not
|
||||||
|
|
@ -178,50 +171,91 @@ If it does not, stop and repair the remote/fetch configuration.
|
||||||
to recur; the next occurrence carries its own evidence under whoever's
|
to recur; the next occurrence carries its own evidence under whoever's
|
||||||
PR, and a Stage B framing follows then.
|
PR, and a Stage B framing follows then.
|
||||||
|
|
||||||
## Journey Stage 1a — PR #182 OPEN, review round 1 closed
|
## Journey/GPU directory-target ratchet — PR #183 GATED ON PUBLIC PATH
|
||||||
|
|
||||||
- Framing `docs/journey-stage1a-framing.md` **rev 8** (four review
|
- **Approved correction, not new product behavior.** GPU initial-target
|
||||||
rounds, two correction revisions found during implementation, one from
|
framing Q#GT6 / acceptance 10 and Journey Stage 1a N2/N5 already make
|
||||||
review round 1 of PR #182).
|
a directory target a success; `COHERENCE.md` §2 treats that path as a
|
||||||
Branch `journey-stage1a-directory-open`, based on `githubsucks/main`
|
protected journey. The stale GPU integration test still listed `"."`
|
||||||
@ `42025e4` (rebased onto `74301d1`, then integrated `42025e4` and the
|
among malformed/unloadable targets after #182 landed.
|
||||||
landed-docs work below by merge — the branch is under review, so its
|
- Branch `journey-gpu-directory-ratchet`, based directly on canonical
|
||||||
history is no longer rewritten).
|
`main` @ `c2d56ff`. Recovery: `git fetch githubsucks && git checkout
|
||||||
- Recovery: `git fetch githubsucks && git checkout
|
journey-gpu-directory-ratchet`. Everything described here is
|
||||||
journey-stage1a-directory-open`. Everything below is committed and
|
committed and pushed; nothing depends on the `/tmp` worktree.
|
||||||
pushed; nothing depends on a worktree or `/tmp`.
|
- PR #183:
|
||||||
- **Ships:** the directory arm on `resolve_target_buffer`,
|
<https://github.com/levineuwirth/pmacs/pull/183>. It is intentionally
|
||||||
`EditorState::open` rewritten as a caller of it (the unification), the
|
open and unmerged pending user review.
|
||||||
`path.open-directory` chain + `pmacs.path.directory_handler` fallback
|
- **Scope is one acceptance ratchet:** remove `"."` from the four
|
||||||
slot, `pmacs.window.commit_to` with its scoped frontend and preflight,
|
genuinely invalid cases and drive the public `pmacs --gpu .` root
|
||||||
the nonconstructible destination userdata, the daemon bootstrap arm,
|
broker through the real managed GPU connector. The positive requires
|
||||||
and `tests/journey_acceptance.rs` (**24 pins** as of rev 8 — a count,
|
snapshot-first + `InitialTargetResult::Opened`, then consumes the
|
||||||
not a constant; re-read it rather than quoting this line). No protocol
|
post-quiescence replacement snapshot and requires dired's canonical
|
||||||
change — still v20.
|
header plus a known directory entry before proving the same daemon can
|
||||||
- **Doc updates ride the PR** per COHERENCE §25: §2 grade + step-3
|
open a following file target. The private display-less acceptance
|
||||||
verdict row, §20 Priority 1 + the arc list, the GPU initial-target
|
probe now reports its snapshot count and final materialized text so
|
||||||
framing's Q#GT6 / acceptance 10 supersession, handoff §1.
|
that public path is observable. No normal frontend/daemon behavior,
|
||||||
- **Bite results** (each mutation run against the full suite): scope
|
protocol, framing decision, or coherence grade changes.
|
||||||
stops swapping `core.active_frontend` → N6a + P3 fail, nothing else;
|
- **Review round 1: three findings, all real and corrected.** The
|
||||||
preflight moved after the callback → P1 + P2 fail, nothing else; drop
|
first test stopped at the deliberately pre-existing bootstrap
|
||||||
the `ScopedFrontend` arm from `acting_frontend` → N4b fails, nothing
|
document, so it did not pin the resolver's later dired commit. The
|
||||||
else. That last mutation is why N4b exists — it left N4 green.
|
Stage 2 rev-5 recovery bullet named rev 4's framing branch. And the
|
||||||
Round 1 of PR #182 added two more: dired's `display` back to
|
durable handoff still named pre-Journey `main` even though this ledger
|
||||||
`switch_buffer`, and `prev` read from the ambient window → each fails
|
had advanced. The first is now a post-quiescence transport assertion;
|
||||||
**N4c** alone. **The scope pins the frontend, not the window** — every
|
the latter two are corrected in this revision. The complete matrix
|
||||||
routing pin before N4c varied frontend identity and none varied the
|
below is green on the corrected tree.
|
||||||
selected window within one frontend, so 23 green pins missed it.
|
- **Live public-path check tightened that correction further.** A user
|
||||||
- Ordering: PR #177 MERGED (2026-07-26), so 1a was unblocked. 1a lands
|
report that `pmacs --gpu .` differed from `pmacs .` did not reproduce:
|
||||||
before dired Stage 2. When 1a lands, Stage 2 must re-scout and revise
|
the live default daemon delivered both snapshots, and a traced
|
||||||
its framing around the scoped `pmacs.window.commit_to` boundary before
|
windowed invocation applied both and displayed dired. It nevertheless
|
||||||
its implementation branch is cut. That revision is a prerequisite, not
|
exposed that the corrected acceptance still attached a raw protocol
|
||||||
a review-time discovery.
|
client rather than invoking the public root broker and real GPU
|
||||||
- **Named deferrals carried out of this stage:** dired's *interactive*
|
connector. The test now covers those surfaces and passes **15/15**;
|
||||||
paths (`C-x d`, tree descent, refresh) still rely on the ambient
|
its full matrix is green at `34b8f28`.
|
||||||
frontend a tick later and are not migrated onto captured destinations;
|
- **Review round 2 found and fixed one failure-path cleanup gap at
|
||||||
the stale startup scratch buffer is still not removed (only the false
|
`dc2dc42`.** The public-path test waited directly for dired's second
|
||||||
doc comment is corrected); `resolve_target_buffer`'s directory arm has
|
snapshot, but `ManagedProbe` only retained a spawned daemon's PID after
|
||||||
no picker, only the chain that leaves room for one.
|
a successful wait. If the exact missing-snapshot regression recurred,
|
||||||
|
the timeout would therefore be unable to terminate that daemon. The
|
||||||
|
test now consumes the initial ready report first, retaining lifecycle
|
||||||
|
facts before it starts the post-quiescence assertion.
|
||||||
|
- **Review-round-2 gates are green:** `cargo fmt --check`; strict
|
||||||
|
workspace clippy; library **1,849 passed / 3 ignored**; CRDT library
|
||||||
|
**2,034 passed / 4 ignored**; focused public-path test **1/1** and full
|
||||||
|
GPU invocation suite **15/15**; M4 **121 passed / 3 ignored / 1
|
||||||
|
filtered**; required GPU package **202/202**; `git diff --check`.
|
||||||
|
The first in-sandbox GPU-package attempt reproduced the repository's
|
||||||
|
documented Unix-socket restriction as three attach failures plus a
|
||||||
|
blocked peer read; the authoritative out-of-sandbox rerun passed all
|
||||||
|
**202** tests in under one second.
|
||||||
|
- **Full gate matrix is green on the public-path revision at `34b8f28`:**
|
||||||
|
- `cargo fmt --check`;
|
||||||
|
- strict workspace clippy;
|
||||||
|
- library **1,849 passed / 3 ignored**;
|
||||||
|
- CRDT library **2,034 passed / 4 ignored**;
|
||||||
|
- touched GPU initial-target suite **15/15**;
|
||||||
|
- connected Journey/Dired/find-file/theme/bottom-panel acceptance
|
||||||
|
suites **125/125**;
|
||||||
|
- M4 **121 passed / 3 ignored / 1 filtered**;
|
||||||
|
- required GPU package **202/202**;
|
||||||
|
- `git diff --check`;
|
||||||
|
- isolated-config, one-invocation full workspace sweep green on its
|
||||||
|
final run.
|
||||||
|
- **Gate diagnostics retained:** initial fresh-worktree attempts without
|
||||||
|
the documented `pmacs-gpu` prerequisite and without out-of-sandbox
|
||||||
|
Unix-socket permission were setup failures, not product evidence.
|
||||||
|
During the review rerun, an in-sandbox library attempt reproduced the
|
||||||
|
latter setup failure as `EPERM` in three attach socket tests; the
|
||||||
|
authoritative out-of-sandbox rerun passed all **1,849** non-ignored
|
||||||
|
tests.
|
||||||
|
The first full-workspace attempt then exhausted the `/tmp` filesystem
|
||||||
|
quota while linking. Moving only the disposable Cargo target to disk
|
||||||
|
let the sweep run; its first completed pass exposed one transient
|
||||||
|
`pmacs-gpu` font-facts unit-test red after the standalone GPU gate had
|
||||||
|
passed. The exact test passed immediately against the identical build,
|
||||||
|
and the required complete one-invocation rerun was green, including
|
||||||
|
GPU **202/202**.
|
||||||
|
- This side quest lands before bottom-panel 2B-1 opens its PR. After it
|
||||||
|
merges, 2B-1 integrates the new `main` and reruns its full matrix.
|
||||||
|
|
||||||
## The CRDT half of the test corpus is dark in CI — NEEDS A LANE
|
## The CRDT half of the test corpus is dark in CI — NEEDS A LANE
|
||||||
|
|
||||||
|
|
@ -358,10 +392,43 @@ If it does not, stop and repair the remote/fetch configuration.
|
||||||
never been enforced. Any CI job that compiles the `crdt` targets has to
|
never been enforced. Any CI job that compiles the `crdt` targets has to
|
||||||
fix them first or it will be red on arrival.
|
fix them first or it will be red on arrival.
|
||||||
|
|
||||||
## Bottom-panel lane (Arc 7) — Stages 1, 2A + framing MERGED; 2B is next
|
## Bottom-panel lane (Arc 7) — 2B-1 GATED; waiting on ratchet side quest
|
||||||
|
|
||||||
Stage 1, the Stage 2 framing, and Stage 2A are all on `main`. **Stage 2B
|
Stage 1, the Stage 2 framing, and Stage 2A are on `main`. Framing
|
||||||
has not started.**
|
revision 5's three-way split of 2B was explicitly approved on
|
||||||
|
2026-07-27. **Stage 2B-1 is implemented, integrated with canonical
|
||||||
|
`main`, and pushed at `b9123c2`; no PR is open.** Its own omissions found
|
||||||
|
by gating are corrected. The Journey/GPU ratchet lane above is its sole
|
||||||
|
remaining dependency.
|
||||||
|
|
||||||
|
- **Stage 2B-1 branch:** `bottom-panel-stage2b`, based on
|
||||||
|
`githubsucks/main` @ `c2d56ff` by merge because review had begun.
|
||||||
|
Recovery: `git fetch githubsucks && git checkout
|
||||||
|
bottom-panel-stage2b`. Everything is committed and pushed; nothing
|
||||||
|
depends on a worktree or `/tmp`.
|
||||||
|
- **Ships only the v21 wire layer:** the four wire shapes, version bump,
|
||||||
|
shared cell-grid validator, and version-ladder move. It has no
|
||||||
|
producer, consumer, or capability change; `panel_capable` stays
|
||||||
|
`false`, so this slice changes no user-visible journey grade.
|
||||||
|
- **Review round 1 closed:** two P1s and one P2, all corrected at
|
||||||
|
`9b364ad`: `PanelFrame` now identifies its buffer, the transport
|
||||||
|
ratchet covers the actual attach path rather than a detached codec
|
||||||
|
assertion, and shared grid bounds have one validator.
|
||||||
|
- **The full gate found and corrected two further 2B-1 omissions at
|
||||||
|
`b9123c2`:** the statusline version ladder still pinned v20/rejected
|
||||||
|
v21, and Vterm Stage 3 pinned v20 both structurally and in its real
|
||||||
|
headless probe. Those ratchets now expect v21 and, where applicable,
|
||||||
|
reject v22.
|
||||||
|
- **Green evidence except for the main-side stale ratchet this side
|
||||||
|
quest owns:** formatting and strict Clippy; default/CRDT libraries;
|
||||||
|
every bottom-panel, folding, GPU-font, statusline, semantic, Vterm,
|
||||||
|
M4, and required-GPU gate; and the isolated-config full-workspace
|
||||||
|
sweep. The exact counts and the classified M8 timing rerun live at
|
||||||
|
`b9123c2`.
|
||||||
|
- **Next ordering is fixed:** 2B-2 branches from `main` only after 2B-1
|
||||||
|
lands; 2B-3 branches only after 2B-2 lands. The daemon epoch machine
|
||||||
|
belongs to 2B-2; the GPU band and negotiated capability flip belong
|
||||||
|
to 2B-3.
|
||||||
|
|
||||||
- **Stage 2A MERGED as #177** (`main` @ `0a3fcd1`, 2026-07-26, all twelve
|
- **Stage 2A MERGED as #177** (`main` @ `0a3fcd1`, 2026-07-26, all twelve
|
||||||
checks green at `8424172`, three review rounds). Branch
|
checks green at `8424172`, three review rounds). Branch
|
||||||
|
|
@ -374,19 +441,7 @@ has not started.**
|
||||||
`primary_document_window` returns `view.active` in every existing
|
`primary_document_window` returns `view.active` in every existing
|
||||||
configuration, so this is seam adoption that becomes load-bearing in
|
configuration, so this is seam adoption that becomes load-bearing in
|
||||||
2B.
|
2B.
|
||||||
- **Stage 2B is approved and unstarted.** It branches from `main`, **not
|
- **Stage 2A verification on its merge result:** `cargo fmt --check` clean; strict
|
||||||
stacked on 2A**, per the framing §9. Scope: protocol v21, the daemon
|
|
||||||
panel projection, the GPU band, and the negotiated `panel_capable`
|
|
||||||
flip. `docs/bottom-panel-stage2-framing.md` §7.2 carries its five
|
|
||||||
acceptance criteria (A2B-1..5) plus the reassertion of parent
|
|
||||||
criterion 52, and §8 records **no open items**, so 2B needs no further
|
|
||||||
framing round. Its sharpest trap is §5.3's three-boundary split: the
|
|
||||||
GPU `text_area_bottom` is `status_band_top`,
|
|
||||||
`geometry_capacity_bottom` and `document_text_bottom` at once, and a
|
|
||||||
blanket rewrite moves the status chrome along with the document while
|
|
||||||
still satisfying an "everything moved" assertion — hence A2B-4's
|
|
||||||
contrast form.
|
|
||||||
- Verification on the merge result: `cargo fmt --check` clean; strict
|
|
||||||
workspace Clippy clean; **1,832 default + 2,015 CRDT** library tests;
|
workspace Clippy clean; **1,832 default + 2,015 CRDT** library tests;
|
||||||
`bottom_panel_stage2a_acceptance` **17**; bottom-panel Stage 1 46;
|
`bottom_panel_stage2a_acceptance` **17**; bottom-panel Stage 1 46;
|
||||||
statusline segments 8 CRDT; m11_5 semantic 2 CRDT; GPU initial target
|
statusline segments 8 CRDT; m11_5 semantic 2 CRDT; GPU initial target
|
||||||
|
|
@ -442,26 +497,31 @@ has not started.**
|
||||||
`docs/agent-handoff.md` §1; the two round lessons are in §5.
|
`docs/agent-handoff.md` §1; the two round lessons are in §5.
|
||||||
- Landed-docs follow-up merged as **#156** (`main` @ `d152120`,
|
- Landed-docs follow-up merged as **#156** (`main` @ `d152120`,
|
||||||
2026-07-25).
|
2026-07-25).
|
||||||
- **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 4**,
|
- **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 5**
|
||||||
on branch `githubsucks/bottom-panel-stage2-framing` (three commits,
|
is commit `56301ed` on branch `githubsucks/bottom-panel-stage2b`,
|
||||||
one per revision), worktree `../pmacs-bp-stage2`, based on
|
worktree `../pmacs-bp-stage2b`. Revisions 1–4 remain on
|
||||||
`githubsucks/main` @ `ccf29e3`. Round 1 closed 2 blocking + 3 high;
|
`githubsucks/bottom-panel-stage2-framing` (head `4fbd47f`, four
|
||||||
|
framing commits, revision 4 at `49757e5`). Round 1 closed 2 blocking +
|
||||||
|
3 high;
|
||||||
round 2 closed 1 blocking + 2 high + 1 medium and decided both open
|
round 2 closed 1 blocking + 2 high + 1 medium and decided both open
|
||||||
items; round 3 closed 1 blocking + 1 high + 1 medium. No open items
|
items; round 3 closed 1 blocking + 1 high + 1 medium. No open items
|
||||||
remain. The approved
|
remain. Revision 5 adds no decision; it records the approved
|
||||||
|
2B-1/2B-2/2B-3 implementation split. The
|
||||||
parent framing `docs/bottom-panel-framing.md` (rev 4) remains
|
parent framing `docs/bottom-panel-framing.md` (rev 4) remains
|
||||||
authoritative, **including its acceptance criteria 37–55**.
|
authoritative, **including its acceptance criteria 37–55**.
|
||||||
- Retained, carrying nothing unmerged: branch `bottom-panel` and worktree
|
- Retained, carrying nothing unmerged: branch `bottom-panel` and worktree
|
||||||
`../pmacs-bottom-panel`.
|
`../pmacs-bottom-panel`.
|
||||||
- **Stage 2 ships as two serial slices**, 2A landing before 2B branches:
|
- **Stage 2 ships as four serial implementation slices**, each landing
|
||||||
|
before the next branches:
|
||||||
**2A** = classified §1.3 census routing + `paint_frame` per-window
|
**2A** = classified §1.3 census routing + `paint_frame` per-window
|
||||||
painter extraction (with the active-window auto-scroll preparation), no
|
painter extraction (with the active-window auto-scroll preparation), no
|
||||||
protocol change; **2B** = protocol **v21**
|
protocol change; **2B-1** = protocol **v21**
|
||||||
(`InstanceMessage::PanelFrame` plus
|
(`InstanceMessage::PanelFrame` plus
|
||||||
`FrontendEvent::{FrontendCellGeometry, PanelResizeRows, PanelPointer}`,
|
`FrontendEvent::{FrontendCellGeometry, PanelResizeRows, PanelPointer}`,
|
||||||
gated both directions, each extended enum byte-pinned on its own
|
gated both directions, each extended enum byte-pinned on its own
|
||||||
previous final variant), daemon panel projection, the GPU band, and the
|
previous final variant); **2B-2** = daemon panel projection and epoch
|
||||||
negotiated `panel_capable` flip. Stage 3 is the adopter default flip.
|
machine; **2B-3** = the GPU band and negotiated `panel_capable` flip.
|
||||||
|
Stage 3 is the adopter default flip.
|
||||||
- **Correction — this entry previously mis-stated the census contract.**
|
- **Correction — this entry previously mis-stated the census contract.**
|
||||||
It is **not** "route every consumer through `primary_document_window`".
|
It is **not** "route every consumer through `primary_document_window`".
|
||||||
Q#BP14 classifies the 23 reads into four classes and routes only the
|
Q#BP14 classifies the 23 reads into four classes and routes only the
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
# Agent handoff — cross-machine continuity
|
# Agent handoff — cross-machine continuity
|
||||||
|
|
||||||
**Last updated: 2026-07-26, after terminal copy mode (#178) — `C-c C-t`
|
**Last updated: 2026-07-28, after Journey Stage 1a (#182), which made
|
||||||
|
directory startup one coherent local/daemon/GPU path and incorporated
|
||||||
|
the terminal configuration + copy mode landed-doc work (#180); following
|
||||||
|
terminal copy mode (#178) — `C-c C-t`
|
||||||
materializes a terminal's whole retained range into an ordinary buffer,
|
materializes a terminal's whole retained range into an ordinary buffer,
|
||||||
plus `Buffer::set_generated_contents`, the first genuinely immutable
|
plus `Buffer::set_generated_contents`, the first genuinely immutable
|
||||||
generated-buffer write path — and its landed-doc pair (#168); following
|
generated-buffer write path — and its landed-doc pair (#168); following
|
||||||
|
|
@ -39,10 +42,12 @@ reads it the way you just did.
|
||||||
For volatile branches, checkpoints, verification, and recovery
|
For volatile branches, checkpoints, verification, and recovery
|
||||||
commands, read `docs/active-work.md` immediately after this file.
|
commands, read `docs/active-work.md` immediately after this file.
|
||||||
|
|
||||||
## 1. Where the project stands (2026-07-26)
|
## 1. Where the project stands (2026-07-28)
|
||||||
|
|
||||||
- `main` @ `42025e4` (Lean 4 Stage 4b #181, atop the dired Stage 1
|
- `main` @ `c2d56ff` (Journey Stage 1a #182, incorporating terminal
|
||||||
landed docs #169 and the PTY-terminate diagnostic #176, terminal copy
|
configuration + copy mode landed docs #180, atop Lean 4 Stage 4b #181,
|
||||||
|
the dired Stage 1 landed docs #169 and the PTY-terminate diagnostic
|
||||||
|
#176, terminal copy
|
||||||
mode #178, the GPU-terminal-input landed docs #168, Lean 4 Stage 4a
|
mode #178, the GPU-terminal-input landed docs #168, Lean 4 Stage 4a
|
||||||
#179, bottom-panel Stage 2A
|
#179, bottom-panel Stage 2A
|
||||||
#177, the bottom-panel Stage 2 framing #175, terminal configuration
|
#177, the bottom-panel Stage 2 framing #175, terminal configuration
|
||||||
|
|
|
||||||
|
|
@ -936,10 +936,20 @@ fn run_headless_managed_probe(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut client = managed.client;
|
let mut client = managed.client;
|
||||||
|
let initial_message = client.take_initial_message();
|
||||||
let initial_target_ready = matches!(
|
let initial_target_ready = matches!(
|
||||||
client.take_initial_message(),
|
initial_message.as_ref(),
|
||||||
Some(InstanceMessage::BufferSnapshot { .. })
|
Some(InstanceMessage::BufferSnapshot { .. })
|
||||||
);
|
);
|
||||||
|
let mut buffer_facts = ManagedProbeBufferFacts::default();
|
||||||
|
if let Some(message) = initial_message.as_ref()
|
||||||
|
&& let Err(error) = buffer_facts.observe(message)
|
||||||
|
{
|
||||||
|
let contents = format!("phase=error\nerror={error}\n");
|
||||||
|
let _ = write_probe_report(report, &contents);
|
||||||
|
eprintln!("pmacs-gpu managed probe: {error}");
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
let daemon = managed.daemon;
|
let daemon = managed.daemon;
|
||||||
let protocol = client.server_protocol_version();
|
let protocol = client.server_protocol_version();
|
||||||
|
|
||||||
|
|
@ -961,8 +971,14 @@ fn run_headless_managed_probe(
|
||||||
let mut last_wait_result = None;
|
let mut last_wait_result = None;
|
||||||
let mut last_disconnect = String::new();
|
let mut last_disconnect = String::new();
|
||||||
if ready
|
if ready
|
||||||
&& let Err(error) =
|
&& let Err(error) = write_managed_probe_report(
|
||||||
write_managed_probe_report(report, "ready", protocol, &daemon, &disconnect)
|
report,
|
||||||
|
"ready",
|
||||||
|
protocol,
|
||||||
|
&daemon,
|
||||||
|
&buffer_facts,
|
||||||
|
&disconnect,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"pmacs-gpu managed probe: writing {} failed: {error}",
|
"pmacs-gpu managed probe: writing {} failed: {error}",
|
||||||
|
|
@ -976,11 +992,23 @@ fn run_headless_managed_probe(
|
||||||
}
|
}
|
||||||
match event_rx.recv_timeout(Duration::from_millis(50)) {
|
match event_rx.recv_timeout(Duration::from_millis(50)) {
|
||||||
Ok(AttachEvent::Message(message)) => {
|
Ok(AttachEvent::Message(message)) => {
|
||||||
if matches!(*message, InstanceMessage::BufferSnapshot { .. }) && !ready {
|
let is_snapshot = matches!(*message, InstanceMessage::BufferSnapshot { .. });
|
||||||
|
if let Err(error) = buffer_facts.observe(&message) {
|
||||||
|
let contents = format!("phase=error\nerror={error}\n");
|
||||||
|
let _ = write_probe_report(report, &contents);
|
||||||
|
eprintln!("pmacs-gpu managed probe: {error}");
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
if is_snapshot {
|
||||||
ready = true;
|
ready = true;
|
||||||
if let Err(error) =
|
if let Err(error) = write_managed_probe_report(
|
||||||
write_managed_probe_report(report, "ready", protocol, &daemon, &disconnect)
|
report,
|
||||||
{
|
"ready",
|
||||||
|
protocol,
|
||||||
|
&daemon,
|
||||||
|
&buffer_facts,
|
||||||
|
&disconnect,
|
||||||
|
) {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"pmacs-gpu managed probe: writing {} failed: {error}",
|
"pmacs-gpu managed probe: writing {} failed: {error}",
|
||||||
report.display()
|
report.display()
|
||||||
|
|
@ -1006,9 +1034,14 @@ fn run_headless_managed_probe(
|
||||||
|| wait_result != last_wait_result
|
|| wait_result != last_wait_result
|
||||||
|| disconnect != last_disconnect)
|
|| disconnect != last_disconnect)
|
||||||
{
|
{
|
||||||
if let Err(error) =
|
if let Err(error) = write_managed_probe_report(
|
||||||
write_managed_probe_report(report, "ready", protocol, &daemon, &disconnect)
|
report,
|
||||||
{
|
"ready",
|
||||||
|
protocol,
|
||||||
|
&daemon,
|
||||||
|
&buffer_facts,
|
||||||
|
&disconnect,
|
||||||
|
) {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"pmacs-gpu managed probe: writing {} failed: {error}",
|
"pmacs-gpu managed probe: writing {} failed: {error}",
|
||||||
report.display()
|
report.display()
|
||||||
|
|
@ -1021,9 +1054,14 @@ fn run_headless_managed_probe(
|
||||||
}
|
}
|
||||||
|
|
||||||
if ready && stdin_closed {
|
if ready && stdin_closed {
|
||||||
if let Err(error) =
|
if let Err(error) = write_managed_probe_report(
|
||||||
write_managed_probe_report(report, "complete", protocol, &daemon, &disconnect)
|
report,
|
||||||
{
|
"complete",
|
||||||
|
protocol,
|
||||||
|
&daemon,
|
||||||
|
&buffer_facts,
|
||||||
|
&disconnect,
|
||||||
|
) {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"pmacs-gpu managed probe: writing {} failed: {error}",
|
"pmacs-gpu managed probe: writing {} failed: {error}",
|
||||||
report.display()
|
report.display()
|
||||||
|
|
@ -1043,11 +1081,42 @@ fn run_headless_managed_probe(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ManagedProbeBufferFacts {
|
||||||
|
snapshots: u32,
|
||||||
|
last_snapshot_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManagedProbeBufferFacts {
|
||||||
|
fn observe(&mut self, message: &InstanceMessage) -> Result<(), String> {
|
||||||
|
let InstanceMessage::BufferSnapshot { crdt_snapshot, .. } = message else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let doc = loro::LoroDoc::new();
|
||||||
|
doc.import(crdt_snapshot)
|
||||||
|
.map_err(|error| format!("BufferSnapshot import failed: {error:?}"))?;
|
||||||
|
self.snapshots += 1;
|
||||||
|
self.last_snapshot_text = doc.get_text(LORO_TEXT_CONTAINER).to_string();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hex_bytes(bytes: &[u8]) -> String {
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
|
let mut encoded = String::with_capacity(bytes.len() * 2);
|
||||||
|
for byte in bytes {
|
||||||
|
let _ = write!(encoded, "{byte:02x}");
|
||||||
|
}
|
||||||
|
encoded
|
||||||
|
}
|
||||||
|
|
||||||
fn write_managed_probe_report(
|
fn write_managed_probe_report(
|
||||||
report: &Path,
|
report: &Path,
|
||||||
phase: &str,
|
phase: &str,
|
||||||
protocol: u32,
|
protocol: u32,
|
||||||
daemon: &attach::ManagedDaemonFacts,
|
daemon: &attach::ManagedDaemonFacts,
|
||||||
|
buffer_facts: &ManagedProbeBufferFacts,
|
||||||
disconnect: &str,
|
disconnect: &str,
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
|
|
@ -1056,6 +1125,12 @@ fn write_managed_probe_report(
|
||||||
let _ = writeln!(out, "phase={phase}");
|
let _ = writeln!(out, "phase={phase}");
|
||||||
let _ = writeln!(out, "server_protocol_version={protocol}");
|
let _ = writeln!(out, "server_protocol_version={protocol}");
|
||||||
let _ = writeln!(out, "buffer_snapshot=true");
|
let _ = writeln!(out, "buffer_snapshot=true");
|
||||||
|
let _ = writeln!(out, "buffer_snapshots={}", buffer_facts.snapshots);
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
"last_snapshot_hex={}",
|
||||||
|
hex_bytes(buffer_facts.last_snapshot_text.as_bytes())
|
||||||
|
);
|
||||||
let _ = writeln!(out, "spawned_daemon={}", daemon.spawned_daemon());
|
let _ = writeln!(out, "spawned_daemon={}", daemon.spawned_daemon());
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,19 @@ mod crdt {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_hex(encoded: &str) -> String {
|
||||||
|
assert_eq!(encoded.len() % 2, 0, "hex payload must have even length");
|
||||||
|
let bytes = encoded
|
||||||
|
.as_bytes()
|
||||||
|
.chunks_exact(2)
|
||||||
|
.map(|pair| {
|
||||||
|
let pair = std::str::from_utf8(pair).expect("hex pair is UTF-8");
|
||||||
|
u8::from_str_radix(pair, 16).expect("decode hex pair")
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
String::from_utf8(bytes).expect("snapshot text is UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
fn wait_for_fact(
|
fn wait_for_fact(
|
||||||
report: &Path,
|
report: &Path,
|
||||||
key: &str,
|
key: &str,
|
||||||
|
|
@ -356,6 +369,16 @@ mod crdt {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManagedProbe {
|
impl ManagedProbe {
|
||||||
|
fn from_child(mut child: Child, report: &Path) -> Self {
|
||||||
|
let stdin = child.stdin.take().expect("probe stdin");
|
||||||
|
Self {
|
||||||
|
child,
|
||||||
|
stdin: Some(stdin),
|
||||||
|
report: report.to_owned(),
|
||||||
|
daemon_pid: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn(socket: &Path, report: &Path, daemon_executable: &Path, home: &Path) -> Self {
|
fn spawn(socket: &Path, report: &Path, daemon_executable: &Path, home: &Path) -> Self {
|
||||||
Self::spawn_with_env(socket, report, daemon_executable, home, &[])
|
Self::spawn_with_env(socket, report, daemon_executable, home, &[])
|
||||||
}
|
}
|
||||||
|
|
@ -418,18 +441,11 @@ mod crdt {
|
||||||
for (key, value) in envs {
|
for (key, value) in envs {
|
||||||
command.env(key, value);
|
command.env(key, value);
|
||||||
}
|
}
|
||||||
let mut child = command.spawn().expect("spawn managed probe");
|
Self::from_child(command.spawn().expect("spawn managed probe"), report)
|
||||||
let stdin = child.stdin.take().expect("probe stdin");
|
|
||||||
Self {
|
|
||||||
child,
|
|
||||||
stdin: Some(stdin),
|
|
||||||
report: report.to_owned(),
|
|
||||||
daemon_pid: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_ready(&mut self) -> HashMap<String, String> {
|
fn wait_for(&mut self, key: &str, expected: &str) -> HashMap<String, String> {
|
||||||
let facts = wait_for_fact(&self.report, "phase", "ready", Duration::from_secs(10));
|
let facts = wait_for_fact(&self.report, key, expected, Duration::from_secs(10));
|
||||||
if facts
|
if facts
|
||||||
.get("spawned_daemon")
|
.get("spawned_daemon")
|
||||||
.is_some_and(|value| value == "true")
|
.is_some_and(|value| value == "true")
|
||||||
|
|
@ -439,6 +455,10 @@ mod crdt {
|
||||||
facts
|
facts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wait_ready(&mut self) -> HashMap<String, String> {
|
||||||
|
self.wait_for("phase", "ready")
|
||||||
|
}
|
||||||
|
|
||||||
fn close(mut self) -> std::process::ExitStatus {
|
fn close(mut self) -> std::process::ExitStatus {
|
||||||
self.stdin.take();
|
self.stdin.take();
|
||||||
wait_for_fact(&self.report, "phase", "complete", Duration::from_secs(5));
|
wait_for_fact(&self.report, "phase", "complete", Duration::from_secs(5));
|
||||||
|
|
@ -662,6 +682,75 @@ mod crdt {
|
||||||
assert!(probe.close().success());
|
assert!(probe.close().success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_gpu_directory_target_reaches_dired_and_leaves_the_daemon_usable() {
|
||||||
|
let temp = secure_tempdir();
|
||||||
|
let socket = temp.path().join("directory-target.sock");
|
||||||
|
let report = temp.path().join("directory-target-report");
|
||||||
|
let wrapper = temp.path().join("headless-gpu");
|
||||||
|
let listed_name = "listed-before-bootstrap.txt";
|
||||||
|
fs::write(temp.path().join(listed_name), "listed\n").expect("write listed file");
|
||||||
|
write_script(
|
||||||
|
&wrapper,
|
||||||
|
"test \"$1\" = \"--managed-attach\"\n\
|
||||||
|
socket=$2\n\
|
||||||
|
daemon=$3\n\
|
||||||
|
shift 3\n\
|
||||||
|
exec \"$PMACS_REAL_GPU\" --headless-managed-probe \
|
||||||
|
\"$socket\" \"$PMACS_TEST_REPORT\" \"$daemon\" \"$@\"",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut command = Command::new(pmacs_binary());
|
||||||
|
command
|
||||||
|
.args(["--gpu", "--socket"])
|
||||||
|
.arg(&socket)
|
||||||
|
.arg(".")
|
||||||
|
.current_dir(temp.path())
|
||||||
|
.env(TEST_GPU_OVERRIDE, &wrapper)
|
||||||
|
.env("PMACS_REAL_GPU", gpu_binary())
|
||||||
|
.env("PMACS_TEST_REPORT", &report)
|
||||||
|
.env("HOME", temp.path())
|
||||||
|
.env("XDG_CONFIG_HOME", temp.path())
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null());
|
||||||
|
let mut directory =
|
||||||
|
ManagedProbe::from_child(command.spawn().expect("spawn public GPU command"), &report);
|
||||||
|
|
||||||
|
// The public root broker and real managed GPU connector must stay
|
||||||
|
// alive through Journey N2's asynchronous dired commit. Snapshot
|
||||||
|
// one is the deliberately pre-existing bootstrap document; snapshot
|
||||||
|
// two is the post-quiescence directory surface.
|
||||||
|
// Capture the spawned daemon's PID from the ready report first so
|
||||||
|
// ManagedProbe::drop can terminate it if snapshot two never arrives.
|
||||||
|
let ready = directory.wait_ready();
|
||||||
|
assert_eq!(
|
||||||
|
ready.get("spawned_daemon").map(String::as_str),
|
||||||
|
Some("true")
|
||||||
|
);
|
||||||
|
let facts = directory.wait_for("buffer_snapshots", "2");
|
||||||
|
let listing = decode_hex(&facts["last_snapshot_hex"]);
|
||||||
|
let canonical = fs::canonicalize(temp.path()).expect("canonical directory");
|
||||||
|
let mut lines = listing.lines();
|
||||||
|
let expected_header = format!("{}:", canonical.display());
|
||||||
|
assert_eq!(
|
||||||
|
lines.next(),
|
||||||
|
Some(expected_header.as_str()),
|
||||||
|
"the replacement snapshot must be dired's directory surface:\n{listing}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
lines.any(|line| line.trim_end().ends_with(listed_name)),
|
||||||
|
"dired must list the file that existed before bootstrap:\n{listing}"
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::write(temp.path().join("still-alive.txt"), "alive\n").expect("write survivor");
|
||||||
|
let survivor = attach_target(&socket, temp.path(), Path::new("still-alive.txt"));
|
||||||
|
assert_eq!(survivor.replica.materialize_string(), "alive\n");
|
||||||
|
|
||||||
|
drop(survivor);
|
||||||
|
assert!(directory.close().success());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn malformed_or_unloadable_targets_fail_closed_without_poisoning_the_daemon() {
|
fn malformed_or_unloadable_targets_fail_closed_without_poisoning_the_daemon() {
|
||||||
let temp = secure_tempdir();
|
let temp = secure_tempdir();
|
||||||
|
|
@ -673,7 +762,6 @@ mod crdt {
|
||||||
(cwd.clone(), Vec::new()),
|
(cwd.clone(), Vec::new()),
|
||||||
(cwd.clone(), b"bad\0name".to_vec()),
|
(cwd.clone(), b"bad\0name".to_vec()),
|
||||||
(cwd.clone(), vec![b'x'; 32 * 1024 + 1]),
|
(cwd.clone(), vec![b'x'; 32 * 1024 + 1]),
|
||||||
(cwd.clone(), b".".to_vec()),
|
|
||||||
];
|
];
|
||||||
for (index, (bad_cwd, bad_path)) in invalid.into_iter().enumerate() {
|
for (index, (bad_cwd, bad_path)) in invalid.into_iter().enumerate() {
|
||||||
let (frontend_id, mut stream, messages) = open_raw_target(&socket, bad_cwd, bad_path);
|
let (frontend_id, mut stream, messages) = open_raw_target(&socket, bad_cwd, bad_path);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue