Refine GPU initial-target framing after review
Pin launcher-owned tilde expansion, require same-buffer dedup hooks, fail closed when hooks kill the target, and document stderr feedback during the pre-window bootstrap wait. Record the observed protocol-version echo and non-Unicode argv panic.
This commit is contained in:
parent
ec0e40117b
commit
71039d1699
|
|
@ -1,7 +1,13 @@
|
||||||
# GPU initial target — session-scoped file opening framing
|
# GPU initial target — session-scoped file opening framing
|
||||||
|
|
||||||
**Revision 1 — proposed framing for user review. Ground truth: canonical
|
**Revision 2 — user-review findings resolved. Ground truth: canonical `main`
|
||||||
`main` @ `4daa1b8`, protocol v19, 2026-07-23. No implementation yet.**
|
@ `4daa1b8`, protocol v19, 2026-07-23. No implementation yet.**
|
||||||
|
|
||||||
|
Revision 2 pins launcher-owned tilde expansion, requires `after-switch` even
|
||||||
|
when dedup selects the view's existing buffer, fails bootstrap when a hook
|
||||||
|
kills the target, and records the deliberate stderr-only wait during slow
|
||||||
|
pre-window bootstrap. It also sharpens the observed argv panic and negotiated
|
||||||
|
protocol-version echo.
|
||||||
|
|
||||||
One-command GPU startup landed in #141:
|
One-command GPU startup landed in #141:
|
||||||
|
|
||||||
|
|
@ -41,9 +47,9 @@ solve installation, service management, remote GPU attach, or reconnect.
|
||||||
- The root broker invokes the sibling/PATH GPU binary as
|
- The root broker invokes the sibling/PATH GPU binary as
|
||||||
`pmacs-gpu --managed-attach SOCKET DAEMON_EXE`, waits for its status, and
|
`pmacs-gpu --managed-attach SOCKET DAEMON_EXE`, waits for its status, and
|
||||||
reflects success/failure.
|
reflects success/failure.
|
||||||
- Both root and GPU parsers consume `std::env::args()` into `String`. That
|
- Both root and GPU parsers consume `std::env::args()` into `String`; Rust
|
||||||
cannot represent a non-UTF-8 Unix filename and may terminate before the
|
panics when an argument is not valid Unicode. They cannot admit a
|
||||||
parser can issue a useful error.
|
non-UTF-8 Unix filename or issue a useful parser error for one.
|
||||||
- The direct GPU CLI is intentionally strict. Public direct attach remains
|
- The direct GPU CLI is intentionally strict. Public direct attach remains
|
||||||
`pmacs-gpu --attach RAW_SOCKET`; managed and headless forms are private
|
`pmacs-gpu --attach RAW_SOCKET`; managed and headless forms are private
|
||||||
root/acceptance seams.
|
root/acceptance seams.
|
||||||
|
|
@ -55,6 +61,9 @@ solve installation, service management, remote GPU attach, or reconnect.
|
||||||
and becomes the reader for `FrontendEvent`s.
|
and becomes the reader for `FrontendEvent`s.
|
||||||
- `AttachRequest` contains protocol version, capabilities, and initial cell
|
- `AttachRequest` contains protocol version, capabilities, and initial cell
|
||||||
size. It has no target. `FrontendEvent` has no open-path request.
|
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
|
- `handle_session_established` creates the authenticated frontend's own
|
||||||
`FrontendView`, initially sharing the daemon-local active buffer, then sends
|
`FrontendView`, initially sharing the daemon-local active buffer, then sends
|
||||||
CRDT snapshots and installs grid or semantic render state.
|
CRDT snapshots and installs grid or semantic render state.
|
||||||
|
|
@ -98,15 +107,18 @@ invariant as any other mid-session buffer creation.
|
||||||
|
|
||||||
1. **Authenticated source owns the target.** No client-supplied frontend id,
|
1. **Authenticated source owns the target.** No client-supplied frontend id,
|
||||||
daemon-local active view, or ambient “last frontend” selects the window.
|
daemon-local active view, or ambient “last frontend” selects the window.
|
||||||
2. **Launcher cwd owns relative resolution.** The daemon's cwd is irrelevant,
|
2. **Launcher environment owns path resolution.** Root applies the existing
|
||||||
including when the daemon predates the launcher.
|
leading-tilde rule with the launcher's `$HOME`; any still-relative result
|
||||||
3. **Path bytes survive.** On Unix, argv → broker → GPU → wire → daemon file
|
resolves against the launcher cwd. Daemon cwd/HOME are irrelevant.
|
||||||
I/O preserves the exact `OsStr` bytes. Display text may be lossy; backing
|
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.
|
identity and filesystem access may not be.
|
||||||
4. **One target, one buffer identity.** Reuse an already-open normalized path;
|
4. **One target, one buffer identity.** Reuse an already-open normalized path;
|
||||||
never discard unsaved edits by reloading it.
|
never discard unsaved edits by reloading it.
|
||||||
5. **Target before first draw.** The first buffer content eligible to render is
|
5. **Target before first draw.** The first buffer content eligible to render is
|
||||||
the requested target. “Connecting…” is acceptable; scratch content is not.
|
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
|
6. **Ready means usable.** Success is reported only after the target buffer is
|
||||||
loaded/created, selected in the authenticated view, CRDT-backed, and its
|
loaded/created, selected in the authenticated view, CRDT-backed, and its
|
||||||
matching snapshot has been written to the GPU.
|
matching snapshot has been written to the GPU.
|
||||||
|
|
@ -167,13 +179,20 @@ pmacs-gpu --managed-attach SOCKET DAEMON_EXE \
|
||||||
--initial-target LAUNCHER_CWD FILE
|
--initial-target LAUNCHER_CWD FILE
|
||||||
```
|
```
|
||||||
|
|
||||||
`LAUNCHER_CWD` is captured by root before spawn and must be absolute. Both cwd
|
Before transport, root applies the existing identity seam's tilde rule using
|
||||||
and file are passed as `OsString`/`Path` operands, not encoded into UTF-8,
|
the **launcher** environment: a valid-UTF-8 leading whole `~` or `~/…` expands
|
||||||
environment variables, JSON, or a delimiter-separated string. The marker
|
through `std::env::var_os("HOME")`; `~user`, a non-UTF-8 spelling, or an
|
||||||
makes an option-like `FILE` unambiguous. The no-target private argv remains
|
unset `$HOME` remains unchanged. Expansion happens exactly once, before any
|
||||||
unchanged.
|
cwd join. This makes quoted `~/x` dedup with an already-open `$HOME/x` buffer
|
||||||
If `current_dir()` fails, root reports that error and does not spawn the GPU;
|
and prevents a long-lived daemon's different `$HOME` from changing identity.
|
||||||
falling back to the daemon cwd would violate the target's authority.
|
|
||||||
|
`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
|
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
|
to advertise only the root command and advanced direct attach; the private
|
||||||
|
|
@ -229,18 +248,22 @@ transaction without yielding to another event:
|
||||||
|
|
||||||
1. Register the new frontend's view and set `active_frontend` to the
|
1. Register the new frontend's view and set `active_frontend` to the
|
||||||
authenticated `FrontendId`.
|
authenticated `FrontendId`.
|
||||||
2. Resolve a relative `path` against the supplied launcher `cwd`, then
|
2. Accept root's already tilde-expanded `path`; if it is still relative, join
|
||||||
lexically normalize the result without canonicalizing symlinks.
|
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
|
3. Reuse an existing buffer with that normalized backing path; otherwise load
|
||||||
the file; on `NotFound`, create an empty path-backed buffer and set
|
the file; on `NotFound`, create an empty path-backed buffer and set
|
||||||
`[new file]`; on any other error, take the failure path below.
|
`[new file]`; on any other error, take the failure path below.
|
||||||
4. Select that buffer in only the new frontend's active window.
|
4. Select that buffer in only the new frontend's active window.
|
||||||
5. Fire `buffer.after-switch` on dedup or `buffer.after-load` on a fresh disk
|
5. Fire `buffer.after-switch` exactly once on every dedup, including when the
|
||||||
load, with the authenticated frontend active. A newly created missing file
|
fresh view already shares that `BufferId` and selection is a same-buffer
|
||||||
matches local startup and does not fire `after-load`.
|
no-op. Fire `buffer.after-load` on a fresh disk load. Both run with the
|
||||||
6. Reassert the requested target after hooks so startup configuration can
|
authenticated frontend active; a newly created missing file matches local
|
||||||
inspect the right session but cannot accidentally make `pmacs --gpu FILE`
|
startup and fires neither load nor switch hook.
|
||||||
acknowledge a different buffer.
|
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.
|
7. Establish CRDT/snapshot coherence, then acknowledge readiness.
|
||||||
|
|
||||||
The target-opening helper must be Rust/editor-core state, not synthetic keys
|
The target-opening helper must be Rust/editor-core state, not synthetic keys
|
||||||
|
|
@ -318,6 +341,15 @@ scratch snapshot can become drawable.
|
||||||
daemon detail. Managed startup returns nonzero; root reflects that status.
|
daemon detail. Managed startup returns nonzero; root reflects that status.
|
||||||
The daemon itself remains alive, whether reused or newly spawned.
|
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. This is deliberate for the
|
||||||
|
explicit terminal command: before blocking, `pmacs-gpu` writes one bounded,
|
||||||
|
lossy-display-only `opening …` notice to stderr. 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
|
### Q#GT9 — Failure cleanup never creates a ghost session
|
||||||
|
|
||||||
On any target failure before readiness, the dispatcher:
|
On any target failure before readiness, the dispatcher:
|
||||||
|
|
@ -327,6 +359,9 @@ On any target failure before readiness, the dispatcher:
|
||||||
size/baseline entries, and stream entry if any were installed;
|
size/baseline entries, and stream entry if any were installed;
|
||||||
- shuts down the connection so the per-attach reader wakes and emits at most
|
- shuts down the connection so the per-attach reader wakes and emits at most
|
||||||
idempotent detach cleanup;
|
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
|
- leaves every pre-existing buffer/view/session unchanged, except that a file
|
||||||
successfully loaded before a later CRDT/export failure may remain as an
|
successfully loaded before a later CRDT/export failure may remain as an
|
||||||
ordinary daemon buffer. It must not become another frontend's active view.
|
ordinary daemon buffer. It must not become another frontend's active view.
|
||||||
|
|
@ -359,7 +394,7 @@ source of truth for a mandatory v20 semantic handshake step.
|
||||||
## Startup state machine
|
## Startup state machine
|
||||||
|
|
||||||
```text
|
```text
|
||||||
root parses FILE bytes + cwd bytes
|
root parses FILE bytes, expands eligible `~` with launcher HOME, captures cwd
|
||||||
|
|
|
|
||||||
+-- spawn/await pmacs-gpu managed child
|
+-- spawn/await pmacs-gpu managed child
|
||||||
|
|
|
|
||||||
|
|
@ -500,10 +535,11 @@ process behavior.
|
||||||
6. **Old live daemon refusal:** target mode against a real/fake supported v19
|
6. **Old live daemon refusal:** target mode against a real/fake supported v19
|
||||||
daemon fails with the protocol-v20 requirement, invokes no daemon spawner,
|
daemon fails with the protocol-v20 requirement, invokes no daemon spawner,
|
||||||
leaves the socket/process untouched, and creates no GPU window.
|
leaves the socket/process untouched, and creates no GPU window.
|
||||||
7. **Existing-daemon relative path:** start a real CRDT daemon from cwd A;
|
7. **Existing-daemon path authority:** start a real CRDT daemon from cwd/HOME
|
||||||
launch the real headless managed GPU target from cwd B with `sub/file.txt`;
|
A; launch the real headless managed GPU target from cwd/HOME B with
|
||||||
require the snapshot contents from B, not A, and a ready buffer matching the
|
`sub/file.txt`; require contents from B, not A. Repeat with a shell-quoted
|
||||||
result.
|
`~/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
|
8. **Missing-daemon target:** from no socket/lock, the production managed path
|
||||||
starts one daemon, opens the target, reaches ready, and leaves that daemon
|
starts one daemon, opens the target, reaches ready, and leaves that daemon
|
||||||
connectable after the probe/window exits. Repeating reuses it and creates no
|
connectable after the probe/window exits. Repeating reuses it and creates no
|
||||||
|
|
@ -528,13 +564,19 @@ process behavior.
|
||||||
any CRDT op for it; both replicas accept later operations without unknown-
|
any CRDT op for it; both replicas accept later operations without unknown-
|
||||||
buffer fallback or disconnect.
|
buffer fallback or disconnect.
|
||||||
14. **Hook context and count:** fresh disk load fires `buffer.after-load` once;
|
14. **Hook context and count:** fresh disk load fires `buffer.after-load` once;
|
||||||
dedup fires `buffer.after-switch` once; missing-file creation fires neither
|
dedup fires `buffer.after-switch` once even when the fresh view already
|
||||||
load hook. Each hook observes the authenticated frontend and requested
|
shares that exact buffer and the select itself is a no-op; missing-file
|
||||||
buffer, and the target remains selected afterward.
|
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
|
15. **Pre-window barrier:** the headless seam records that the only initial
|
||||||
semantic `BufferSnapshot` is the target and precedes matching `Opened`.
|
semantic `BufferSnapshot` is the target and precedes matching `Opened`.
|
||||||
Injected load/export failure records no ready phase. A window-path unit seam
|
Injected load/export failure records no ready phase. A window-path unit seam
|
||||||
proves bootstrap state is applied before the first redraw request.
|
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
|
16. **Concurrent same-target launch:** two target launchers racing an absent
|
||||||
daemon and the same file converge on one daemon, one buffer identity, two
|
daemon and the same file converge on one daemon, one buffer identity, two
|
||||||
ready sessions, and the existing #141 losing-daemon child is reaped.
|
ready sessions, and the existing #141 losing-daemon child is reaped.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue