pmacs/docs/gpu-invocation-framing.md

31 KiB
Raw Blame History

GPU invocation — one-command broker framing

Revision 3 — pre-implementation. Ground truth: canonical main @ 96d0bae, protocol v19, 2026-07-23.

The GPU editor works, but reaching it is still a development-session ritual: build two packages with different feature requirements, keep a foreground daemon alive in one terminal, reconstruct its resolved Unix-socket path, and pass that raw path to a second binary in another terminal. This framing makes the normal local GPU path one explicit command:

pmacs --gpu
pmacs --gpu --socket research

This is an additive first stage. It does not yet change bare pmacs from TUI to GUI, and it does not pretend that pmacs --gpu FILE works before the daemon has a real per-frontend initial-file contract. It preserves the separate pmacs-gpu binary and its independent dependency graph.

Revision 2 closes the first review round: the spawned daemon is isolated from the launcher's foreground process group; the required Vterm headless probe is retained; retry/error/reaping behavior is complete; a real display-less managed-attach seam is named; bare --socket stops being silently ignored; and the non-CRDT gate is explicitly justified as default-socket protection.

Revision 3 makes the managed probe deterministic for signal/reaper tests, keeps Interrupted / WouldBlock transient inside the post-spawn retry window, states the process-group signal simulation in CI-executable terms, and distinguishes the socket type check from liveness inference.

Ground truth

Current user path

The daemon is a mode of the root pmacs binary; there is no pmacs-daemon executable (Cargo.toml, src/main.rs). pmacs-gpu is a separate unpublished workspace package and binary (pmacs-gpu/Cargo.toml). The current source-checkout path is:

cargo build --release --workspace --features pmacs/crdt

# terminal 1
target/release/pmacs --daemon

# terminal 2
runtime="${XDG_RUNTIME_DIR:-/tmp/pmacs-$(id -u)}"
target/release/pmacs-gpu --attach "$runtime/pmacs/default.sock"

The unified workspace build above succeeds. The README currently documents two separate build commands instead. Its cargo run --release -- <file> example is not runnable as written: the root package has no default-run, and Cargo reports that it cannot choose among pmacs, pmacs-audit, pmacs_fake_lsp, and pmacs_fake_mcp.

A live smoke on this base exposed the cost of independent builds: the first target/release/pmacs-gpu was protocol v15 while the daemon was v19. The GPU opened but rejected the attach. Rebuilding pmacs-gpu produced a successful v19 attach. The handshake caught the mismatch correctly; the invocation path made it easy to create.

Existing CLI and process boundaries

  • pmacs [FILE] runs the in-process TUI. -nw / --no-window is already parsed as an explicit TUI choice, though it is currently equivalent to the default (src/main.rs:94-131,288-318).
  • The source comment at src/main.rs:28-49 reserves the future shape: explicit -nw wins, a future --gui can select GUI, then an environment / display-based default may choose GUI. None of that GUI selection is implemented today.
  • pmacs --daemon [--socket NAME|PATH] runs in the foreground. Bare names resolve to <runtime>/pmacs/NAME.sock; omission means default.sock; a value containing / is used as a path (src/socket_path.rs).
  • <runtime> is nonempty $XDG_RUNTIME_DIR, otherwise /tmp/pmacs-<uid>. The daemon creates a private parent, locks a sibling lockfile, removes a stale socket only after acquiring the lock, and binds the socket owner-only (src/socket_path.rs, src/lockfile.rs, src/daemon.rs:437-530).
  • pmacs-gpu --attach <socket> takes a raw pathname. It has no default/name resolver and no file positional (pmacs-gpu/src/main.rs:548-565,802-848).
  • pmacs-gpu has three current modes: bare hello-world, direct --attach, and the test/acceptance seam --headless-probe <socket> <report>. The required Vterm Stage 3 acceptance invokes that third mode as a real subprocess (tests/vterm_stage3_acceptance.rs:679-691).
  • Bare pmacs-gpu still opens the inert Session-2 hello, pmacs window. It is scaffolding, not an editor session.
  • The GPU parser consumes the attach/probe operands but does not reject later argv, so trailing values are silently ignored.
  • The GPU package intentionally depends on pmacs-protocol, not the root editor crate. The distribution decision in docs/pmacs-gpu-design.md:195-202 keeps wgpu, winit, font, and window-system dependencies out of TUI-only installs. That boundary still holds.

Capability and attach constraints

A usable GPU daemon must advertise all of multi_frontend, crdt_replica, and semantic_render. The root crdt feature enables those capabilities; pmacs-gpu itself has no Cargo features. The GPU validates the daemon's Hello before sending AttachRequest and produces an actionable capability mismatch instead of waiting forever (pmacs-gpu/src/attach.rs).

pmacs-gpu currently attempts one UnixStream::connect from ApplicationHandler::resumed. A connect/handshake failure stays visible in the window but is not retried. Later disconnect also requires a manual relaunch. Automatic reconnect is an existing named deferral, separate from startup (docs/gpu-attach-robustness-framing.md:175-186).

The root already has daemon auto-start precedent in src/daemon_attach.rs: try an existing socket, otherwise spawn current_exe --daemon --socket PATH, wait up to five seconds, then enter its byte bridge. That helper must retain the successful connection because a disposable connect probe makes the daemon send Hello into a stream the probe drops, producing a broken pipe. The GPU cannot directly reuse the helper: it lives in the root crate and returns the stream to the stdio bridge, while pmacs-gpu owns its own direct Unix transport.

The SSH-side daemon auto-start precedent does not isolate the daemon into a new process group: daemon_attach.rs relies on the SSH spawn having no controlling terminal (src/daemon_attach.rs:53-60). A local pmacs --gpu launcher does have one. Without an explicit process-group split, root, GPU, and the auto-started daemon inherit the foreground group; terminal Ctrl-C reaches all three, and the daemon deliberately treats SIGINT as graceful shutdown (src/daemon.rs:617-633). SIGHUP is already ignored, so foreground SIGINT is the specific lifecycle gap.

Initial-file constraint

Every daemon attachment currently receives a fresh scratch view in handle_session_established (src/daemon.rs:1533-1552). The code explicitly names cloning or taking an initial-buffer argument as future work. Opening a file in the daemon before attach would not put that file in the new GPU frontend's view. There is no frontend OpenPath event and no initial target in AttachRequest (pmacs-protocol/src/message.rs:2023-2040).

Therefore a launcher that accepts FILE without new daemon/session work would either ignore it, drive the minibuffer by synthetic keys, or open it in the wrong view. All three are rejected.

Distribution gaps

There is no editor installation recipe, desktop entry, user service, Make/Just target, Cargo alias, or launcher wrapper. The only repository script is the test-development helper scripts/bite. Cargo does not install repository shell scripts, and pmacs-gpu has publish = false.

Decisions

Q#GI1 — Add the explicit root command pmacs --gpu [--socket NAME|PATH]

The first-stage public surface is:

pmacs --gpu [--socket NAME|PATH]

It is additive. Bare pmacs and pmacs FILE continue to run the local TUI; pmacs -nw remains the explicit TUI spelling. --gpu is mutually exclusive with -nw / --no-window, --daemon, --attach, and --daemon-attach. It rejects every positional argument with:

pmacs: --gpu does not yet accept FILE; open it from the GPU with C-x C-f

The parser also closes the adjacent existing hole: --socket without one of --daemon, --attach, --daemon-attach, or --gpu exits 2 instead of being silently discarded by Mode::Local. A flag is used rather than a pmacs gpu subcommand because gpu is a valid existing positional filename. No PMACS_FRONTEND environment selection and no display auto-detection land in this stage.

Rejected alternatives:

  • Bare pmacs becomes GUI immediately — this mixes launcher correctness, daemon lifecycle, initial-file semantics, and a default-behavior change in one cut. -nw reserves that eventual migration; it does not make an incomplete migration safe.
  • A shell wrapper — not installed by Cargo, duplicates readiness and path policy, and makes version-skewed binaries easier to combine.
  • Link GPU into the root binary — violates the deliberate independent dependency graphs and adds wgpu/window dependencies to TUI-only builds.

Q#GI2 — Root owns policy and paths; pmacs-gpu owns the successful connection

The root launcher owns:

  1. CLI validation;
  2. the canonical resolve_socket_path call;
  3. the CRDT-build gate;
  4. discovery of the separate pmacs-gpu executable;
  5. waiting for that executable and reflecting its outcome.

The GPU child owns the actual connection that becomes the session. For the managed launch, root passes two hidden/internal arguments: the resolved raw socket path and current_exe() as the daemon executable. The child first tries the socket itself; the stream that completes Hello / AttachRequest is retained as its real AttachClient. There is no disposable readiness connection and therefore no deliberate broken-pipe noise.

The hidden argument shape is not a second user-facing launcher. Direct users keep pmacs-gpu --attach PATH; the documented managed surface is pmacs --gpu.

Q#GI3 — Managed GPU attach may start the supplied daemon, then retries boundedly

Managed attach follows this state machine before creating the window:

  1. Try the resolved Unix socket once.
  2. If connection reaches Hello, perform the normal version and capability validation. Either failure is final and surfaced; never start a replacement daemon over a live incompatible instance.
  3. NotFound authorizes daemon startup. ConnectionRefused authorizes it only when metadata(socket) says the existing entry is a Unix socket, or the entry disappeared in the race between connect and metadata. An existing non-socket file is a final error and is never handed to pmacs --daemon, whose established stale-path transaction would otherwise unlink it after acquiring the sibling lock.
  4. Every other initial connect error (PermissionDenied, invalid path shape, resource exhaustion, Interrupted, WouldBlock, and other errno classes) is final, surfaced with the socket path, and invokes no daemon spawner. During the post-spawn retry window, NotFound, authorized ConnectionRefused, Interrupted, and WouldBlock / EAGAIN continue to the deadline; all other errors remain final. This tolerates a signal- interrupted connect(2) and the just-bound daemon's temporarily full AF_UNIX accept backlog without broadening what may trigger daemon startup.
  5. Spawn the supplied executable as pmacs --daemon --socket <resolved-path>, with the process-group isolation in Q#GI13.
  6. Retry the real GPU connect every 50 ms for up to five seconds, matching AUTO_START_POLL_INTERVAL / AUTO_START_TIMEOUT in daemon_attach.rs:149-160. The first successful stream becomes the real attach; no probe is dropped.
  7. If the spawned child exits during startup, reap it, retain its status, and keep retrying until the deadline: another concurrent launcher may have won the socket lock and be about to listen.
  8. At the deadline, exit nonzero with the socket path, timeout, and spawned child status when available.

Connection/retry occurs before run_app, so the winit event thread never freezes for five seconds behind startup polling. On success, events sent to the already-created EventLoopProxy may queue until run_app starts; the window then assembles with the connected frontend id. Existing direct --attach behavior may retain its in-window failure banner.

During startup the managed connector owns Option<Child> so every early exit is reaped. After a successful attach, a still-running spawned daemon moves to a named reaper thread whose only job is blocking Child::wait; this prevents a daemon that later crashes or quits from remaining a zombie throughout a long GPU session. Closing the GPU process still leaves a live daemon orphaned and long-lived per Q#GI5.

Do not extract a generic launcher crate for two constants, a short loop, and one child reaper.

Q#GI4 — Auto-start is race-safe through the existing daemon lock

Two simultaneous pmacs --gpu commands may both observe a missing socket and spawn a daemon. The sibling lockfile is the arbiter: one daemon binds, the other exits. Both GPU children continue their bounded connect loop and attach to the winner. The loser child is reaped; its lock error is not treated as a launch failure if the socket becomes usable.

A stale socket follows the existing daemon transaction: acquire the lock, unlink the stale socket, bind the replacement. The launcher may read filesystem metadata solely to distinguish a socket entry from non-socket data (Q#GI3); it neither deletes entries nor treats path existence as proof of liveness. The daemon lock and successful protocol connection remain the arbiters.

Q#GI5 — An auto-started daemon remains long-lived

Closing the GPU window detaches that frontend but does not kill a daemon the launcher started. This matches pmacs's long-lived-instance architecture and the existing --daemon-attach auto-start behavior. A later pmacs --gpu reuses the same default/named daemon and retains buffers, processes, and language services.

The startup child has null stdin/stdout/stderr, matching the existing background auto-start precedent. Startup failure is reported through child status + timeout; detailed daemon diagnostics remain available by running pmacs --daemon --socket ... directly. Q#GI13, not the SSH precedent, defines the local terminal's signal isolation. The launcher does not daemonize, install a service, or invent idle shutdown in this stage.

Q#GI6 — Require a CRDT-capable root build before launching

In a root binary compiled without feature = "crdt", pmacs --gpu exits before executable discovery or any socket connection with an actionable message:

pmacs: --gpu requires pmacs built with --features crdt

This prevents default-socket poisoning: without the gate, a non-CRDT root could auto-start an incapable daemon that successfully owns default.sock; the GPU would then reject its capabilities, and Q#GI3's correct never-replace-a-live-instance rule would make later managed launches keep failing until the user manually stopped that daemon.

The deliberate trade-off is that a non-CRDT root also refuses the managed fast path when a separate capable daemon is already listening. The broker's root executable is its daemon-start authority and must be capable for deterministic behavior. Advanced users may still attach the independent GPU directly to a known capable daemon with pmacs-gpu --attach PATH; that path retains its own Hello capability validation.

Q#GI7 — Prefer the sibling GPU binary, then fall back to PATH

Discovery order:

  1. test-only PMACS_TEST_GPU_BIN override;
  2. current_exe().parent()/pmacs-gpu when that path exists;
  3. pmacs-gpu through PATH.

Sibling-first keeps ordinary source builds and side-by-side installations on the same release/protocol build. The handshake remains authoritative: path co-location is not proof of protocol compatibility. Failure names the sibling candidate and PATH fallback rather than reporting a generic spawn error.

There is no production PMACS_GPU_BIN configuration knob in v1. A permanent override would become distribution policy; tests need substitution, users need an installation that places the two shipped binaries coherently.

Q#GI8 — Root waits for the GPU child and reflects failure

pmacs --gpu remains the terminal-visible parent while the window is open. A successful GPU exit returns success; a nonzero exit returns failure and prints which GPU executable failed. Spawn failure is immediate and actionable. No shell, nohup, or detached launcher process sits between the user and the frontend.

The daemon is independent after startup (Q#GI5); root waits only for the GPU child.

Q#GI9 — Retire bare pmacs-gpu hello-world and make argv strict

The hello-world window was Session-2 dependency scaffolding and no longer serves a user workflow. Bare pmacs-gpu exits with usage that points to pmacs --gpu for managed startup and pmacs-gpu --attach PATH for direct debugging.

The existing test-only pmacs-gpu --headless-probe <socket> <report> mode is retained byte-for-byte as the Vterm Stage 3 real daemon + PTY + wgpu seam. Its parser becomes strict about exactly those two operands; tests/vterm_stage3_acceptance.rs keeps its current subprocess command and semantics. Q#GI14 adds a separate managed headless probe rather than overloading this vterm contract.

The GPU parser rejects:

  • trailing argv after direct-attach or probe operands;
  • a missing attach/probe operand;
  • user attempts to invoke the hidden managed modes without every broker operand;
  • unknown flags.

Add pmacs-gpu --version so reports can name both package version and protocol version without opening a window. --help labels direct attach as an advanced/manual path and does not advertise internal broker/probe arguments.

Q#GI10 — No protocol change and no initial file in this stage

The broker changes process orchestration only. It sends the existing AttachRequest, negotiates protocol v19, and uses the existing semantic/CRDT session. SUPPORTED and every wire discriminant remain unchanged.

pmacs --gpu FILE is rejected rather than accepted partially. The future file feature must target the authenticated source frontend's view, preserve non-UTF-8 local paths, define relative-path resolution, surface open failure, and avoid a scratch-buffer flash. It receives its own framing and protocol review.

Q#GI11 — Fix checkout build/run instructions with the broker

Set root package default-run = "pmacs", making the existing README cargo run --release -- ... family unambiguous. Document one coherent build:

cargo build --release --workspace --features pmacs/crdt

Then document:

target/release/pmacs --gpu
# explicit TUI remains:
target/release/pmacs -nw [FILE]

The build line deliberately compiles both binaries together. Keep the advanced two-process commands in a troubleshooting/manual-attach subsection, including the canonical XDG fallback rather than Linux-only $UID prose. Do not claim cargo install support until installation is exercised and the unpublished GPU package has a deliberate distribution story.

Q#GI12 — Scope stays on invocation, not reconnect or packaging

The managed startup retry ends when the first attach succeeds. A later daemon disconnect retains today's (daemon disconnected) state and manual relaunch. Desktop files, system services, release bundles, package managers, and remote GPU transports are not smuggled into this feature.

Q#GI13 — Put the auto-started daemon in its own process group

Before spawning the managed daemon, call the safe Unix std::os::unix::process::CommandExt::process_group(0). The daemon becomes the leader of a new process group while the root broker and GPU child remain in the terminal's foreground group. Terminal Ctrl-C therefore terminates the foreground launcher/frontend without delivering SIGINT to the daemon or any other attached frontend.

This is process-group isolation, not a new session or full daemonization. Direct pmacs --daemon remains foreground and keeps its established SIGINT/SIGTERM graceful-shutdown contract. Terminal close remains safe through the daemon's existing SIGHUP no-op. No unsafe, setsid helper process, or platform-specific FFI enters the codebase.

Q#GI14 — Add a real display-less managed-attach acceptance seam

Extract the pre-run_app production path as connect_managed_with_sink(socket, daemon_exe, sink). The normal managed window calls it with the existing EventLoopProxy sink. A new hidden strict subprocess mode:

pmacs-gpu --headless-managed-probe <socket> <report> <daemon-exe>

calls the same function with a channel sink and keeps processing that channel after the real BufferSnapshot. At the snapshot checkpoint it atomically writes a complete report with phase=ready and initial named facts (protocol, whether this invocation spawned, child status so far), then holds the live session until stdin reaches EOF. A dedicated stdin-reader thread reports EOF to the probe loop; the loop itself remains free to receive disconnects and reaper observations. Each such lifecycle observation atomically refreshes the phase=ready report, so a harness can wait for a named fact such as daemon_reaped=true without sleeping. On EOF the probe atomically replaces the report with phase=complete plus final child/reaping facts and exits without winit or wgpu.

Tests that need a hold spawn the probe with piped stdin, wait for phase=ready, perform the signal/daemon action, wait for any required named fact, then close stdin when they want normal probe completion. An ordinary invocation with null stdin advances immediately from ready to complete. There is no timing-based linger duration.

This is the acceptance seam for Q#GI3GI6 and Q#GI13: real binary, real Unix socket, real Hello/capability negotiation, and real daemon subprocess. A decoded-message fixture or a second test-only connect implementation is not accepted evidence.

The existing --headless-probe <socket> <report> remains separate and still drives real offscreen wgpu for Vterm Stage 3 (Q#GI9). Narrow injected-spawner unit tests pin rare errno/timeout branches, including post-spawn Interrupted / WouldBlock followed by success, but they do not replace the managed subprocess acceptance.

Categorical bets

  1. An explicit broker is enough to validate lifecycle policy before changing the default frontend. Users gain a one-command GPU path without forcing GUI startup into scripts, $EDITOR, SSH sessions, or terminals that rely on today's bare pmacs TUI.
  2. Sibling-first discovery covers source and coherent installed layouts. A fallback to PATH handles split prefixes; the protocol handshake catches stale or foreign binaries.
  3. The existing lock is the correct concurrency arbiter. Launcher-side PID files, path-existence checks, or socket deletion would duplicate and weaken the daemon's established ownership transaction.
  4. The successful connect must be the session connect. Disposable probes are observably wrong because the daemon speaks first; retrying the actual GPU connect avoids false BrokenPipe logs and frontend-id churn.
  5. A five-second pre-window startup bound is acceptable. Existing remote daemon auto-start uses the same bound. A missing/broken daemon fails before creating a misleading inert window.
  6. Persistent auto-start matches user expectation. The instance owns buffers and services across frontend lifetime; killing it on window close would turn the daemon split into implementation overhead with no persistence benefit.
  7. Foreground job control must not own the daemon. A local launcher's terminal process group is not the SSH no-controlling-terminal precedent; a safe process-group split preserves the daemon across Ctrl-C without full daemonization.
  8. Rejecting FILE is better than synthetic input. Driving C-x C-f or the minibuffer from a launcher is timing-dependent, configuration-dependent, and cannot provide an atomic initial view.
  9. Non-socket paths are data, not stale sockets. Managed auto-start never feeds a regular file to the daemon's existing unlink-and-bind transaction.
  10. No new shared crate is warranted. Root already resolves paths and hands the result to the GPU; the GPU adds only bounded connect-or-spawn behavior around its existing transport.

Deferred (named)

  • GUI as the automatic default. Complete the reserved FrontendChoice::Auto plan after the broker is proven: display detection, PMACS_FRONTEND, and pmacs -nw precedence. This is a user-default change, not part of additive startup.
  • Initial file(s) for an attached frontend. A real per-session target that opens/switches in the authenticated source's view, reports errors, handles path bytes and relative cwd, and avoids scratch flash. This is prerequisite to honest pmacs --gpu FILE and eventual bare pmacs FILE GUI startup.
  • Automatic reconnect/resync. Startup retry does not reconcile an optimistic replica after a live connection drops; retain the attach- robustness deferral.
  • Direct pmacs-gpu --socket NAME. Managed users go through root, which already owns canonical resolution. Revisit only if direct frontend use is a supported standalone workflow.
  • Remote GPU attach. The TUI owns SSH/daemon-attach transport today; generic GPU stream transports need separate latency, clipboard, and frontend-resource semantics.
  • Daemon service management. systemd/launchd units, socket activation, idle shutdown, logs, restart policy, and a pmacs --stop-daemon command.
  • Distribution/install bundles. Cargo install, release archives, desktop entries, icons, and ensuring both binaries land together.
  • Multiple files and client/server open commands. Follow the initial-file contract rather than widening this stage's rejected positional grammar.
  • GPU executable override for users. Keep only the test override until a real packaging use case establishes precedence and diagnostics.

Acceptance

CLI and launcher cases run against the real built binaries where process behavior is the contract. Pure parsing/discovery helpers receive unit tests; no source-text assertions substitute for subprocess behavior. Managed connection cases use Q#GI14's real display-less binary seam; the existing Vterm probe continues to cover offscreen wgpu.

  1. Root CLI grammar: pmacs --gpu and pmacs --gpu --socket research select managed GPU mode. Combinations with -nw, --daemon, --attach, --daemon-attach, or a positional file exit 2 with the conflicting argument named. Bare pmacs --socket research (with or without a local file / -nw) exits 2 and says which owning mode is required.
  2. Non-CRDT build fails before socket or spawn: a default-feature pmacs --gpu names --features crdt; a capable daemon already listening does not weaken the gate, and fake GPU/daemon executables record zero invocations. The default socket remains unowned.
  3. Sibling discovery wins: with executable fixtures at the current-exe sibling and on PATH, the sibling receives the managed arguments. Removing it uses PATH. With neither, the error names both lookup attempts.
  4. Existing daemon fast path: start a real CRDT daemon on a private socket, run --headless-managed-probe, and assert a v19 session establishes and a real BufferSnapshot arrives without invoking the supplied daemon spawner.
  5. Missing daemon auto-start: from no socket/lock, the managed probe starts the supplied real CRDT daemon, completes the real Hello/capability handshake, receives the first BufferSnapshot, and leaves the daemon connectable after the probe exits.
  6. Ctrl-C does not kill the daemon: spawn the managed probe/broker in its own process group, wait for the probe's phase=ready, attach a second real frontend, then simulate terminal Ctrl-C with kill(-pgid, SIGINT). Assert the launcher/frontend exit while the separately grouped daemon and second session remain usable. This does not require a controlling terminal or a foreground-process-group claim in CI. Direct foreground pmacs --daemon still exits cleanly on SIGINT.
  7. Concurrent launchers converge: start two managed probes together on an absent named socket. Exactly one daemon owns the lock; both clients establish sessions; the losing daemon child is reaped rather than becoming a zombie or aborting its frontend.
  8. Stale socket recovery stays daemon-owned: leave a stale Unix socket with no lock owner, launch managed GPU, and assert the daemon replaces it and the frontend attaches. The launcher itself performs no unlink.
  9. Other connect errors fail closed; retry transients survive: a permission-denied initial socket path invokes no daemon spawner and reports the path/error immediately. A regular file at the socket path survives unchanged, invokes no daemon, and reports that managed startup refuses to replace a non-socket entry. An injected post-spawn sequence of Interrupted, WouldBlock, then a successful real connection stays within the deadline and establishes the session.
  10. Live capability mismatch is not replaced: against a non-CRDT daemon, managed launch reports the existing capability mismatch, invokes no second daemon, and leaves the live daemon untouched.
  11. Live protocol mismatch is not replaced: a real/fake-Hello listener advertising an unsupported protocol produces the version-mismatch error, invokes no daemon spawner, and leaves the listener/path untouched.
  12. Bounded startup failure: substitute a daemon executable that exits nonzero without binding. Managed GPU exits after five seconds (50 ms polls) and reports socket + child status. A concurrent-winner fixture proves an early losing-child exit does not abort while another process binds before the deadline.
  13. Post-attach daemon exit is reaped: let the spawned daemon establish a managed session, wait for the probe's phase=ready, then terminate the daemon while keeping the probe's stdin open. Poll the atomically replaced ready report until it records both disconnect and named reaper completion, close stdin, and assert the phase=complete report retains the wait outcome. The daemon never remains a zombie until frontend exit.
  14. Root reflects the GPU outcome: fake GPU success makes pmacs --gpu succeed; fake nonzero and spawn failure make it fail with the executable named.
  15. GPU argv is strict without breaking probes: bare invocation points to pmacs --gpu; direct --attach PATH, existing --headless-probe SOCKET REPORT, and hidden --headless-managed-probe SOCKET REPORT DAEMON_EXE accept exactly their operands. Missing/trailing/unknown/incomplete args exit 2. --version prints package and protocol versions without initializing winit/wgpu.
  16. Existing direct and Vterm paths remain intact: rebuilt pmacs-gpu --attach RAW_PATH still renders an existing CRDT daemon, and tests/vterm_stage3_acceptance.rs still invokes its unchanged --headless-probe command and passes the real daemon + PTY + wgpu criterion.
  17. One-command visible smoke: on a Vulkan/display-capable machine, build the workspace once, run only target/release/pmacs --gpu, observe the GPU scratch buffer attach at protocol v19, close the window, then invoke the same command again and observe reuse of the still-running daemon.
  18. Documentation commands are executable: the README's unified release build succeeds; cargo run --release -- --version selects pmacs through default-run; no documented command requires users to spell the resolved socket pathname for managed GPU startup.