# GPU initial target — session-scoped file opening framing

**Revision 3 — implemented as built on branch `gpu-initial-target`, protocol
v20, 2026-07-23. Revision 2 was approved against canonical `main` @ `4daa1b8`;
the implementation base includes folding Stage 1 through `c49a8c7`.**

Revision 3 records the completed implementation and verification. Revision 2
pinned launcher-owned tilde expansion, required `after-switch` even when dedup
selects the view's existing buffer, failed bootstrap when a hook kills the
target, and kept slow pre-window bootstrap terminal-only (no graphical
progress surface). It also sharpened the observed argv panic and negotiated
protocol-version echo.

One-command GPU startup landed in #141:

```sh
pmacs --gpu
pmacs --gpu --socket research
```

The remaining daily-use gap is that the same path cannot open a file. This
stage makes one positional target honest:

```sh
pmacs --gpu README.md
pmacs --gpu --socket research ../notes/today.md
pmacs --gpu -- --name-beginning-with-dash
```

“Honest” is load-bearing. The target belongs to the newly authenticated GPU
session, not to the daemon's ambient/local view or whichever frontend most
recently sent input. Relative paths resolve against the launcher's working
directory even when an existing daemon has a different cwd. A new or reused
file becomes the first buffer content the GPU can draw; scratch never flashes.
Open failures return before a window is shown and make the root command fail.

This is one-file startup only. It does not make GUI the automatic default, add
a general client/server `open` command, widen direct `pmacs-gpu --attach`, or
solve installation, service management, remote GPU attach, or reconnect.

## Ground truth

### Root and GPU CLI

- `src/main.rs` already parses one positional `FILE` for local TUI mode and
  rejects multiple files. It accepts `--` before an option-like filename.
- `Mode::Gpu` currently carries only `socket`; `parse_args` rejects every file
  paired with `--gpu` and points users to `C-x C-f`.
- The root broker invokes the sibling/PATH GPU binary as
  `pmacs-gpu --managed-attach SOCKET DAEMON_EXE`, waits for its status, and
  reflects success/failure.
- Both root and GPU parsers consume `std::env::args()` into `String`; Rust
  panics when an argument is not valid Unicode. They cannot admit a
  non-UTF-8 Unix filename or issue a useful parser error for one.
- The direct GPU CLI is intentionally strict. Public direct attach remains
  `pmacs-gpu --attach RAW_SOCKET`; managed and headless forms are private
  root/acceptance seams.

### Handshake and session bootstrap

- The instance sends `Hello`; the frontend sends `AttachRequest`; then the
  per-attach thread immediately sends `DispatcherEvent::SessionEstablished`
  and becomes the reader for `FrontendEvent`s.
- `AttachRequest` contains protocol version, capabilities, and initial cell
  size. It has no target. `FrontendEvent` has no open-path request.
- The GPU copies `Hello.protocol_version` into `AttachRequest.protocol_version`
  rather than sending its own maximum. Every old-daemon gate therefore keys on
  the negotiated/echoed server version; changing that echo is not cleanup.
- `handle_session_established` creates the authenticated frontend's own
  `FrontendView`, initially sharing the daemon-local active buffer, then sends
  CRDT snapshots and installs grid or semantic render state.
- A semantic frontend follows whichever `BufferSnapshot` it most recently
  applied. The attach-time snapshot sweep currently sends every CRDT-backed
  buffer; a later tick repairs the “last snapshot was not my active buffer”
  ambiguity by re-sending the session's active buffer.
- Managed GPU connect completes before winit creates the window, but normal
  instance messages are read asynchronously after that connect call. Merely
  adding an event after attach would race the first snapshot and first draw.

### File and view semantics already present

- Local `pmacs FILE` loads an existing file, or creates an empty path-backed
  buffer with `[new file]` status on `NotFound`; other I/O errors fail startup
  (`EditorState::open`).
- `EditorCore::get_or_load_buffer` and Lua `pmacs.buffer.find_or_open` already
  establish the important dedup rule: an existing path-backed buffer wins, so
  unsaved edits are not replaced by a disk reload.
- Buffer identity is an absolute, lexically normalized path. It deliberately
  does not canonicalize symlinks, so a not-yet-created file has a stable
  identity and the editor does not silently rewrite the user's spelling to a
  filesystem target.
- `FrontendView`s are per-session. Switching one frontend's active window does
  not switch any sibling frontend.
- Load/switch hooks require `EditorCore::active_frontend` to name the source
  before Lua runs; otherwise `pmacs.window.*` resolves against the wrong view.

### Replica composition constraint

A target loaded during attach may be a brand-new, non-CRDT buffer. The new GPU
needs its snapshot, but every already-attached replica must also learn that
buffer before later CRDT operations can reference it. Calling the current
one-stream `send_buffer_snapshots` helper can upgrade the new buffer for the
new GPU while leaving existing replicas unaware: the later lazy-upgrade sweep
then sees an already-backed buffer and has nothing to broadcast. The initial-
target transaction must therefore use the same all-replica publication
invariant as any other mid-session buffer creation.

## Invariants

1. **Authenticated source owns the target.** No client-supplied frontend id,
   daemon-local active view, or ambient “last frontend” selects the window.
2. **Launcher environment owns path resolution.** Root applies the existing
   leading-tilde rule with the launcher's `$HOME`; any still-relative result
   resolves against the launcher cwd. Daemon cwd/HOME are irrelevant.
3. **Path bytes survive defined resolution.** Apart from that deliberate
   root-side tilde substitution, Unix argv → broker → GPU → wire → daemon file
   I/O preserves exact `OsStr` bytes. Display text may be lossy; backing
   identity and filesystem access may not be.
4. **One target, one buffer identity.** Reuse an already-open normalized path;
   never discard unsaved edits by reloading it.
5. **Target before first draw.** The first buffer content eligible to render is
   the requested target. The explicit terminal launcher may report the wait on
   stderr; no editor window exists yet, and scratch content is never drawable.
6. **Ready means usable.** Success is reported only after the target buffer is
   loaded/created, selected in the authenticated view, CRDT-backed, and its
   matching snapshot has been written to the GPU.
7. **Failure is pre-window and fail-closed.** Non-`NotFound` I/O, malformed
   bootstrap data, CRDT upgrade/export, or target snapshot failure produces no
   successful session and no window.
8. **Existing sessions remain coherent.** A fresh target is published to all
   negotiated replica sessions; another frontend's active window never moves.
9. **No legacy wire drift.** Protocols v6–v19 retain their exact
   `AttachRequest`, `FrontendEvent`, and `InstanceMessage` encodings.
10. **No lifecycle regression.** Existing-daemon reuse, bounded daemon startup,
    process-group isolation, named child reaping, and root exit propagation
    stay owned by #141's managed path.

## Decisions

### Q#GT1 — Public grammar accepts exactly one GPU target

The public shape becomes:

```text
pmacs --gpu [--socket NAME|PATH] [--] [FILE]
```

- `FILE` is optional and may appear before or after `--gpu` / `--socket`, as the
  existing single-pass parser already permits for local mode.
- `--` makes the next and only remaining operand a literal filename, including
  one beginning with `-`.
- A second positional remains `multiple files not yet supported`.
- `--gpu FILE` remains mutually exclusive with `-nw` / `--no-window`,
  `--daemon`, `--attach`, and `--daemon-attach` under the existing mode rules.
- `Mode::Gpu` gains `file: Option<PathBuf>`; no-file behavior is byte-for-byte
  the #141 path.
- README/help examples add `pmacs --gpu FILE`; bare `pmacs FILE` remains TUI.

### Q#GT2 — Root parsing moves to `OsString` without weakening option grammar

The root entry point uses `std::env::args_os`. Parsing distinguishes:

- ASCII option names (`-nw`, `--gpu`, `--socket`, `--`, …), which must match
  exactly;
- socket names/paths, whose existing resolver contract remains UTF-8 and
  receives a targeted error when the operand is not UTF-8;
- local/managed `FILE`, stored as `PathBuf` with exact platform bytes;
- positional attach targets, which remain UTF-8 because their syntax is a
  transport URI/hostname rather than a local filesystem path.

Unit helpers may continue to construct UTF-8 `OsString`s for ordinary cases,
but one Unix-only test must pass an invalid-UTF-8 positional through the real
parser. No lossy conversion is permitted on the file path.

### Q#GT3 — The private broker handoff carries target plus launcher cwd

When `FILE` is present, root invokes the GPU child as:

```text
pmacs-gpu --managed-attach SOCKET DAEMON_EXE \
  --initial-target LAUNCHER_CWD FILE
```

Before transport, root applies the existing identity seam's tilde rule using
the **launcher** environment: a valid-UTF-8 leading whole `~` or `~/…` expands
through `std::env::var_os("HOME")`; `~user`, a non-UTF-8 spelling, or an
unset `$HOME` remains unchanged. Expansion happens exactly once, before any
cwd join. This makes quoted `~/x` dedup with an already-open `$HOME/x` buffer
and prevents a long-lived daemon's different `$HOME` from changing identity.

`LAUNCHER_CWD` is captured by root before spawn and must be absolute. The
post-expansion file and cwd are passed as `OsString`/`Path` operands, not
encoded into UTF-8, environment variables, JSON, or a delimiter-separated
string. The marker makes an option-like `FILE` unambiguous. The no-target
private argv remains unchanged. If `current_dir()` fails, root reports that
error and does not spawn the GPU; falling back to daemon cwd would violate the
target's authority.

The GPU parser also moves to `args_os` for path operands. Public help continues
to advertise only the root command and advanced direct attach; the private
marker is not promoted as a supported standalone workflow. Headless managed
acceptance gets the same optional marker so it exercises production target
transport rather than a test-only side channel.

### Q#GT4 — Protocol v20 adds a semantic-session bootstrap envelope

Protocol increments **v19 → v20** and appends v20 to
`SUPPORTED_PROTOCOL_VERSIONS`; v6 remains the compatibility floor.

`AttachRequest` stays byte-identical. After a v20 semantic frontend sends its
normal `AttachRequest`, it sends one additional framed handshake value:

```rust
pub struct SessionBootstrapRequest {
    pub initial_target: Option<InitialTarget>,
}

pub struct InitialTarget {
    pub cwd: Vec<u8>,
    pub path: Vec<u8>,
}
```

- The extra handshake message is required for `protocol_version >= 20` **and**
  negotiated `semantic_render`; `None` preserves ordinary `pmacs --gpu`.
- v6–v19 peers neither send nor read it. Non-semantic v20 grid/TUI sessions
  keep the existing two-message handshake and do not wait on an irrelevant
  semantic startup envelope.
- The target carries no `FrontendId`; the accepted stream and assigned session
  are the authority.
- `cwd` and `path` are Unix path bytes, not text. This stage is the local Unix-
  socket GPU path; it does not claim a cross-platform/remote path protocol.
- Postcard decodes each field under the transport's 16 MiB frame cap; daemon
  validation then bounds each to 32 KiB before filesystem use. `path` must be
  nonempty, `cwd` must be nonempty and absolute, and embedded NUL is rejected
  with a bootstrap failure.

A second message rather than an appended `AttachRequest` field keeps every
legacy postcard shape mechanically unchanged. A post-handshake
`FrontendEvent::OpenPath` is deliberately not used: it cannot precede session
bootstrap and therefore cannot guarantee first-draw ordering.

### Q#GT5 — The daemon resolves and opens inside one dispatcher transaction

The per-attach thread validates the v20 bootstrap envelope structurally, moves
it into `DispatcherEvent::SessionEstablished`, and starts its reader exactly
where it does today. It performs no filesystem or editor work.

The dispatcher, which exclusively owns `EditorState`, performs this target
transaction without yielding to another event:

1. Register the new frontend's view and set `active_frontend` to the
   authenticated `FrontendId`.
2. Accept root's already tilde-expanded `path`; if it is still relative, join
   it to the supplied launcher `cwd`, then lexically normalize without
   canonicalizing symlinks. The daemon never consults or re-applies `$HOME`.
3. Reuse an existing buffer with that normalized backing path; otherwise load
   the file; on `NotFound`, create an empty path-backed buffer and set
   `[new file]`; on any other error, take the failure path below.
4. Select that buffer in only the new frontend's active window.
5. Fire `buffer.after-switch` exactly once on every dedup, including when the
   fresh view already shares that `BufferId` and selection is a same-buffer
   no-op. Fire `buffer.after-load` on a fresh disk load. Both run with the
   authenticated frontend active; a newly created missing file matches local
   startup and fires neither load nor switch hook.
6. After hooks, verify the target `BufferId` is still live. A listener that
   killed it causes the fail-closed bootstrap path in Q#GT9. Otherwise reassert
   the requested target so configuration can inspect the right session but
   cannot make `pmacs --gpu FILE` acknowledge a different buffer.
7. Establish CRDT/snapshot coherence, then acknowledge readiness.

The target-opening helper must be Rust/editor-core state, not synthetic keys
or a call through the user-facing Lua binding. Lua hooks remain policy
observers; they are not the transport implementation.

### Q#GT6 — Dedup preserves edits; new-file behavior matches local startup

- An already-open normalized path reuses its `BufferId`, current contents,
  modified flag, and metadata. Disk is not read again.
- A disk file not already open is loaded once and receives its normalized
  backing path and `FileMeta`.
- Any `NotFound` from the initial load creates an empty path-backed buffer,
  including when a parent is currently absent; save-time errors remain
  save-time errors, matching local `pmacs FILE`.
- `PermissionDenied`, invalid path bytes at the OS boundary, and other
  non-`NotFound` errors fail startup.
- **`IsADirectory` is superseded by Journey Stage 1a**
  (`docs/journey-stage1a-framing.md`). A directory no longer reaches the
  load at all: `resolve_target_buffer` answers `ResolvedTarget::Directory`
  ahead of it, so a directory target now *succeeds*, dispatching the
  `path.open-directory` chain and replying `Opened`. Deliberate
  supersession, not drift — the whole point of that stage is that
  `pmacs .` must not exit 1, and a daemon/GPU bootstrap that still failed
  would leave the two entry points disagreeing about the same argument.
  Non-directory failures are unchanged.
- The buffer display name may use `Path::display()` and therefore replacement
  characters; this must never replace the raw backing path used for dedup,
  load, or save.

### Q#GT7 — Initial-target semantic bootstrap is active-buffer-only

A v20 semantic session with `initial_target = Some` does **not** receive the
legacy “every buffer, then repair active on the next tick” sweep. It receives
exactly the requested active buffer's `BufferSnapshot` before readiness.
Semantic GPU state holds one active rope and already receives a fresh snapshot
when its daemon-side view switches; shipping unrelated buffers only creates a
last-snapshot ambiguity and avoidable work.

If the target was newly created or newly loaded and not yet CRDT-backed:

- upgrade it once using the daemon-owned CRDT peer;
- export one authoritative snapshot;
- publish that buffer/snapshot to every already-attached session that
  negotiated `crdt_replica` before later operations can name it;
- write the same logical snapshot to the new GPU without reloading the file or
  creating a second buffer.

The implementation should avoid cloning snapshot bytes per peer beyond what
framed serialization/write ownership requires. Existing no-target attach and
non-semantic replica bootstrap remain unchanged.

### Q#GT8 — A v20 result is the pre-window readiness barrier

Append a v20-only `InstanceMessage::InitialTargetResult` variant with an
explicit result shape:

```rust
pub enum InitialTargetResult {
    Opened { buffer_id: BufferId },
    Failed { message: String },
}
```

Only a v20 semantic session that requested `Some(initial_target)` may receive
it.

Success ordering on the daemon write stream is:

1. target `BufferSnapshot` written successfully;
2. new session state/render state/stream installed;
3. `InitialTargetResult::Opened` written;
4. normal per-tick messages/events begin.

The GPU connector synchronously reads this bootstrap prefix before returning.
It retains the matching target snapshot as structured bootstrap state,
validates that `Opened.buffer_id` matches it, and only then starts the ordinary
reader thread. The winit path applies that snapshot before its first redraw.
It does not forward unrelated pre-ready buffer state through the event proxy.
Thus no target success can mean “window exists, load may still fail,” and no
scratch snapshot can become drawable.

`Failed` becomes an `AttachClientError` containing the local display path and
daemon detail. Managed startup returns nonzero; root reflects that status.
The daemon itself remains alive, whether reused or newly spawned.

Because the connector waits before winit creates a window, slow dispatcher
work has no graphical “Connecting…” surface. The managed GPU child waits
silently and reports a failure on stderr if bootstrap fails. There is no
second target timeout beyond #141's bounded daemon-start retry; file I/O and
user hooks may legitimately exceed five seconds, and timing out the client
would not cancel dispatcher work. Ctrl-C remains the escape hatch and still
cannot reach the isolated daemon process group.

### Q#GT9 — Failure cleanup never creates a ghost session

On any target failure before readiness, the dispatcher:

- writes `InitialTargetResult::Failed` when the stream is usable;
- removes the provisional frontend view, render state, session-registry entry,
  size/baseline entries, and stream entry if any were installed;
- shuts down the connection so the per-attach reader wakes and emits at most
  idempotent detach cleanup;
- treats a target `BufferId` killed by `buffer.after-load` or
  `buffer.after-switch` as a bootstrap failure, never reasserts a stale id,
  and performs the same provisional-session cleanup;
- leaves every pre-existing buffer/view/session unchanged, except that a file
  successfully loaded before a later CRDT/export failure may remain as an
  ordinary daemon buffer. It must not become another frontend's active view.

The result error string is bounded (4 KiB) and user-facing. It includes the
operation and OS error but never lossy-converts and then reuses the displayed
path for filesystem access.

### Q#GT10 — Compatibility is directional and tested

- New daemon + v6–v19 client: exact legacy handshake; no bootstrap read, no
  v20 result, no new enum discriminant.
- New GPU without target + supported v6–v19 daemon: exact #141 behavior.
- New GPU **with** target + v6–v19 daemon: fail immediately after `Hello` with
  “initial targets require protocol v20”; do not authorize/spawn a replacement
  for the live daemon.
- New daemon + v20 non-semantic client: legacy handshake; the initial-target
  envelope is a semantic-session contract.
- New daemon + v20 semantic client: bootstrap envelope required, including
  `None`; missing/malformed bootstrap closes only that connection.
- `InitialTargetResult` is appended after all v19 `InstanceMessage` variants
  and independently filtered from negotiated `< 20` streams. Existing
  encoding pins for v6–v19 messages remain unchanged; new pins lock the v20
  discriminant and bootstrap round trip.

No new capability bit is needed. Protocol version and negotiated
`semantic_render` jointly identify the state machine; a bit would add a second
source of truth for a mandatory v20 semantic handshake step.

## Startup state machine

```text
root parses FILE bytes, expands eligible `~` with launcher HOME, captures cwd
  |
  +-- spawn/await pmacs-gpu managed child
        |
        +-- connect existing socket or start/retry daemon (#141 unchanged)
        |
        +-- Hello < 20 and target? ----> fail; never replace live daemon
        |
        +-- Hello >= 20
              send AttachRequest
              send SessionBootstrapRequest { initial_target }
                    |
                    +-- daemon dispatcher resolves/opens in source view
                    |     |
                    |     +-- error --> Failed + connection shutdown
                    |     |
                    |     +-- success
                    |           publish fresh buffer to replicas
                    |           write target BufferSnapshot
                    |           install session
                    |           write Opened { buffer_id }
                    |
              connector validates snapshot/result pair
              create GPU state/window
              apply target snapshot before first redraw
```

## Rejected alternatives

### Send `FrontendEvent::OpenPath` after attach

Rejected for initial startup. The daemon has already established the scratch
view and may have sent snapshots before the event can arrive. Correlation ids,
async results, and live session commands will be appropriate for a future
general client/server open API, but they do not provide a pre-window barrier
without duplicating the bootstrap state machine.

### Put the file only on the spawned daemon command line

Rejected. It cannot affect an already-running daemon and would target the
daemon-local view rather than the authenticated GPU view.

### Open globally, then declare a viewport for that buffer

Rejected. `Viewport` is a rendering declaration whose buffer must already be
known to the replica. Treating it as an open command confuses state alignment
with filesystem authority and can switch the wrong view under races.

### Drive `C-x C-f` / minibuffer with synthetic keys

Rejected. It depends on user keymaps and prompts, loses raw path bytes, cannot
report a structured startup result, and visibly renders intermediate state.

### Resolve relative paths in the daemon cwd

Rejected. A long-lived daemon's cwd describes when it was started, not where a
later launcher invoked `pmacs --gpu FILE`.

### UTF-8 path strings

Rejected for the local Unix contract. They silently exclude valid filenames
and would make GPU startup weaker than `PathBuf`-based local editing.

### General multi-file/open-command protocol now

Rejected. Multiple targets need ordering, active-target choice, per-target
results, and behavior for an already-running client. This stage deliberately
pins the one initial target needed by `pmacs --gpu FILE`.

## Scope and touch map

As-built implementation surface:

- `src/main.rs`
  - `args_os` parser, `Mode::Gpu { file, socket }`, help/grammar, exact private
    child argv, cwd capture, exit propagation.
- `pmacs-gpu/src/main.rs`
  - strict `OsString` path parser, optional private initial-target operands,
    managed/headless plumbing, pre-first-redraw bootstrap application.
- `pmacs-gpu/src/attach.rs`
  - v20 semantic bootstrap send, target-version gate, synchronous
    snapshot/result barrier, structured errors; #141 connector/start/reaper
    policy otherwise unchanged.
- `pmacs-protocol/src/message.rs`, `pmacs-protocol/src/lib.rs`
  - protocol v20, `SessionBootstrapRequest`, `InitialTarget`, result wire type,
    appended `InstanceMessage` variant, bounds and documentation.
- `src/daemon.rs`
  - conditional v20 bootstrap read, dispatcher payload, source-scoped target
    transaction, active-only semantic bootstrap, all-replica publication,
    result ordering, failure cleanup, `< 20` send filter.
- `src/editor_core.rs` and/or a small existing file-open helper
  - cwd-aware lexical normalization, dedup/load/create without ambient-view
    selection. Do not add a parallel path-identity convention.
- `src/protocol.rs`, `pmacs-protocol/src/transport.rs`
  - version ladder, legacy placement pins, v20 round-trip/limit tests.
- `tests/gpu_initial_target_acceptance.rs` (new) and focused existing GPU
  invocation/protocol tests
  - real subprocess/session behavior below.
- `README.md`, this framing's as-built tail, `docs/agent-handoff.md`, and
  `docs/active-work.md` per their update protocols after implementation works.

Explicit non-touch unless evidence forces it:

- no renderer/layout/font/shader changes;
- no `builtin/runtime` keymap or command changes;
- no socket resolver, daemon lock, process-group, retry, or reaper policy
  changes;
- no package/install/service files;
- no remote attach transport changes.

## Acceptance

Behavioral acceptance uses real built subprocesses where CLI, cwd, socket,
handshake, process lifetime, or raw argv is the contract. Pure parser/path/wire
helpers receive unit tests; source-text assertions do not substitute for
process behavior.

1. **Root grammar:** `pmacs --gpu FILE`, `pmacs FILE --gpu`,
   `pmacs --gpu --socket research FILE`, and `pmacs --gpu -- -leading` select
   one managed GPU target. Multiple files and every existing conflicting mode
   exit 2 with the conflicting argument/mode named. No-file `pmacs --gpu`
   remains accepted.
2. **Raw argv forwarding:** a Unix invalid-UTF-8 filename survives the real
   root parser and reaches a fake GPU child byte-for-byte alongside the exact
   launcher cwd. Invalid-UTF-8 option/socket/attach-target text fails with a
   pointed usage error rather than panic or replacement characters.
3. **Private GPU grammar:** managed and headless modes accept either their
   unchanged no-target arity or exactly `--initial-target CWD FILE`; missing,
   trailing, or duplicated forms exit 2. An option-like `FILE` after the marker
   remains literal. A relative `CWD` is syntactically accepted and then fails
   the daemon's bootstrap validation before readiness.
4. **v20 wire and legacy pins:** bootstrap request/result round trips preserve
   arbitrary Unix bytes and enforce bounds. Every pinned v6–v19 encoding stays
   unchanged; the new result discriminant is appended. The supported ladder is
   exactly `[6, …, 20]`.
5. **No-target compatibility:** rebuilt `pmacs --gpu` attaches to a v19
   fixture/daemon without sending the v20 bootstrap frame and retains #141's
   existing/missing-daemon behavior. A v20 semantic attach sends `None` and
   reaches its normal first snapshot.
6. **Old live daemon refusal:** target mode against a real/fake supported v19
   daemon fails with the protocol-v20 requirement, invokes no daemon spawner,
   leaves the socket/process untouched, and creates no GPU window.
7. **Existing-daemon path authority:** start a real CRDT daemon from cwd/HOME
   A; launch the real headless managed GPU target from cwd/HOME B with
   `sub/file.txt`; require contents from B, not A. Repeat with a shell-quoted
   `~/file.txt` after that `$HOME_B/file.txt` buffer is already open, and
   require the same `BufferId`, proving root-side expansion and dedup.
8. **Missing-daemon target:** from no socket/lock, the production managed path
   starts one daemon, opens the target, reaches ready, and leaves that daemon
   connectable after the probe/window exits. Repeating reuses it and creates no
   replacement.
9. **New file:** a nonexistent target produces an empty snapshot, `[new file]`
   status/path identity, accepts an edit/save through the real session, and
   creates the requested file under the launcher cwd—not the daemon cwd.
10. **Open error:** a permission-denied target returns a specific
    failure before ready/window creation and makes root fail. The daemon shuts
    down that failed session's socket; a client that lingers or sends another
    event cannot reach uninstalled session state. An existing daemon remains
    connectable; a pre-existing frontend's active buffer and contents remain
    unchanged.
    **Amended by Journey Stage 1a:** the *directory* case is deliberately
    superseded and moved to the success path — see Q#GT6. A directory
    target now reaches ready and the document window shows dired, pinned
    by `initial_target_directory_reaches_ready` and its two siblings in
    `src/daemon.rs`. Permission-denied is unchanged and still fails.
11. **Dedup preserves unsaved edits:** frontend A opens and modifies a file
    without saving; target-launch frontend B opens the same normalized path and
    receives A's authoritative unsaved text with the same `BufferId`, not disk
    contents or a duplicate buffer.
12. **Per-session isolation:** frontend A starts on buffer A while concurrent
    target launches B and C open different files. Each result/snapshot pair
    names its own view; a subsequent input/resize proof shows A, B, and C remain
    independently usable on their original buffers.
13. **Replica publication after target upgrade:** keep grid replica A attached,
    then target-launch B onto a previously unknown file. Repeat for a file that
    Lua loaded into a hidden, not-yet-CRDT-backed buffer before the target dedup.
    A receives each buffer snapshot before any CRDT op for it; both replicas
    accept later operations without unknown-buffer fallback or disconnect.
14. **Hook context and count:** fresh disk load fires `buffer.after-load` once;
    dedup fires `buffer.after-switch` once even when the fresh view already
    shares that exact buffer and the select itself is a no-op; missing-file
    creation fires neither hook. Each hook observes the authenticated frontend
    and requested buffer, and the target remains selected afterward. A fixture
    that kills the target inside either hook yields `Failed`, no ready phase,
    and no stale-id reassertion.
15. **Pre-window barrier:** the headless seam records that the only initial
    semantic `BufferSnapshot` is the target and precedes matching `Opened`.
    Injected load/export failure records no ready phase. A window-path unit seam
    proves bootstrap state is applied before the first redraw request. A held
    dispatcher fixture proves the bounded stderr notice appears while no
    window/ready result exists, then succeeds after release without a separate
    target timeout.
16. **Concurrent same-target launch:** two target launchers racing an absent
    daemon and the same file converge on one daemon, one buffer identity, two
    ready sessions, and the existing #141 losing-daemon child is reaped.
17. **Existing direct/probe paths:** `pmacs-gpu --attach RAW_PATH`, the no-target
    managed probe, Vterm Stage 3's headless probe, TUI attach, protocol/capability
    mismatch preservation, Ctrl-C daemon isolation, and post-attach reaping
    remain green.
18. **Visible smoke:** on the real Wayland/Vulkan workstation, run
    `target/release/pmacs --gpu README.md`; the first editor content shown is
    README (never scratch), editing works, closing the window leaves the daemon
    alive, and a second invocation from another cwd opens that cwd's target in
    a new session without moving an already-attached frontend.
19. **Executable documentation:** the coherent workspace build from README
    still produces sibling binaries; all documented target commands parse;
    no command requires spelling the resolved socket pathname.

## Required implementation gates

After the visible/behavioral path works, run the repository gates independently
as required by `AGENTS.md`:

```sh
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --lib
cargo test --lib --features crdt
cargo test --test gpu_initial_target_acceptance
cargo test --features crdt --test gpu_initial_target_acceptance
cargo test --test m4_acceptance -- --skip basedpyright
PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu
git diff --check
```

Also rerun the touched GPU invocation suite, protocol/transport tests, and
Vterm Stage 3 acceptance in default and CRDT configurations where the suite
supports both. The final full workspace sweep remains required before PR.

The named gate intentionally reuses the managed-lifecycle acceptance module,
so a workspace sweep executes those 14 CRDT cases under both test-binary
names. The duplicate runtime is retained to keep the approved named command
and the complete #141 lifecycle fixture coverage together.

Post-second-review verification on 2026-07-24:

- `cargo fmt --check` and strict workspace Clippy passed.
- Library gates passed 1,815 default and 1,992 CRDT tests.
- The named initial-target gate passed 1 default and 14 CRDT tests; the
  underlying GPU invocation suite passed 14 CRDT tests.
- Folding Stage 2 passed 48 CRDT tests after integrating current `main`.
- M4 passed 121 tests with the documented basedpyright skip; required real-GPU
  tests passed 152.
- Vterm Stage 3 passed 5 default and 7 CRDT tests.
- The isolated-config workspace CRDT sweep passed 3,334 tests across 88 suites,
  with 29 ignored and the documented basedpyright case filtered.
- A coherent release build launched two concurrent real Wayland/Vulkan GPU
  windows on one daemon, targeting distinct `alpha` and `beta` files. Both
  remained visible on their own buffer after the second target attached.

## Deferred (named)

- **Multiple initial files.** Needs result ordering, active choice, and partial
  failure policy; do not turn `Option<InitialTarget>` into a vector casually.
- **General client/server open command.** A live frontend request needs request
  ids, asynchronous per-target results, and clear behavior when no frontend is
  waiting. It may reuse the file transaction, not the bootstrap wire phase.
- **GUI as automatic default.** After this stage and distribution are proven:
  display detection, `PMACS_FRONTEND`, and `-nw` precedence can make bare
  `pmacs FILE` select GPU.
- **Distribution/install bundles.** Ensure root and GPU binaries land together
  before making GPU the default.
- **Automatic reconnect/resync.** Startup target success does not reconcile an
  optimistic replica after a later disconnect.
- **Remote GPU paths.** Raw Unix path bytes and launcher cwd are intentionally
  local. Remote transports need an explicit remote-cwd/path authority model.
- **Daemon services and idle shutdown.** Unchanged from #141's deferral.
- **Direct `pmacs-gpu` target syntax.** Normal users go through root; advanced
  raw-socket attach stays attach-only.

## Approval boundary

Approval of this framing authorizes one implementation branch/PR for
`pmacs --gpu [--socket …] FILE`, protocol v20 semantic bootstrap, exact Unix
path/cwd transport, target-before-first-draw readiness, and the acceptance
matrix above. It does not authorize automatic GUI selection, multiple files,
or a general open-command protocol.
