Files
browser/openspec/changes/follow-current-supervised-log-session/design.md
T

76 lines
7.4 KiB
Markdown

## Context
Run currently persists a supervised process identity and forwards plugin-declared `process.stdout` and `process.stderr` through its durable spool. Autonomous lifecycle stream IDs are stable per Run endpoint, server, and stream key, so Platform cannot separate a later process start from the previous generation. The terminal then lists and replays all server streams, including old jobs, before it listens for SSE appends.
Platform remains the authorization and persistence boundary. Run remains the sole authority for the supervised process. The design must expose only a logical session identifier and timestamps, never output paths, PIDs, command lines, credentials, or direct process handles.
## Goals / Non-Goals
**Goals:**
- Follow only the current plugin-declared supervised process output in the live terminal.
- Preserve the same session when Run restarts and resumes an already-running supervised process.
- Switch an open terminal atomically when the supervised process starts a new generation.
- Keep bounded recent replay for the selected session and keep all older streams readable as explicit history.
- Keep RCON and any future plugin command transport independent of the log data path.
**Non-Goals:**
- Expose browser shell access, host paths, raw sockets, or process stdin by default.
- Infer game logs from arbitrary files or special-case SCUM, Minecraft, or another game.
- Delete, migrate, or reinterpret legacy log bodies.
## Decisions
### Process-generation session is generated and persisted by Run
`ProcessIdentity` receives a random/logical session ID and its start timestamp when a new managed process is started. The identity is journaled, so a Run restart resumes tailing and continues using the identical session for the still-running process. Starting a replacement process generates a new session.
The session ID is added to process log batches and autonomous process stream IDs. This avoids sequence collisions and gives Platform a durable grouping key. A stable stream ID with an inferred time was rejected because delayed spool upload could make an old process appear newer.
### Platform selects the current session only from supervised process streams
Platform persists the logical session ID and process-start timestamp with each stream. Run identifies an observed generation as `log-session:<session-id>`, and a persisted session is eligible as current only while the bound Run endpoint is online, Platform's latest Run-reported supervised-process fact is `running`, and that fact names the same session. This generation binding prevents a delayed batch from an older or not-yet-observed process from becoming current merely because it has a newer timestamp. Among the matching session's process streams, timestamp and deterministic ID ordering remain useful only for stable replay ordering. Only `source=process` streams with a non-empty session ID participate. Legacy, job, file-tail, and management-program logs remain historical and cannot displace a current terminal session.
Run reports observations for every process it supervises, including servers handled by a general worker rather than only a generated single-server Run. Platform publishes process-state changes into the existing server log subscription. A stopped, exited, or failed fact emits an empty session boundary; a later running fact re-evaluates the persisted streams. Run endpoint disconnect alone does not end a session because the game process may survive a Run restart, but a newly opened terminal treats an offline endpoint as having no proven current session until Run reconnects and reports the process fact.
Dispatching a new start clears Platform's previous managed-process binding before the job is queued. Historical output from the prior generation therefore cannot reappear during the interval between desired start intent and Run's first observation for the replacement generation.
Using the newest arbitrary stream was rejected because a file backfill or command result is not evidence of the process that the terminal must follow.
### SSE carries an explicit session boundary
The server log event endpoint begins with a `session` event, then exposes only the selected session's streams and a bounded replay. On each incoming process event Platform re-evaluates the active session. When it changed, Platform emits a new `session` event, its stream metadata, and a new selected-session replay before normal appends. The client clears the live buffer on the session boundary and remains connected; it never has to guess based on timestamps.
Re-opening a new EventSource on each restart was rejected because it races with output and leaves the old terminal contents visible while reconnecting.
### Durable source cursors make resumed file reads idempotent
Each process-output append carries its generation-local source-file cursor into the durable spool. The spool commits sequence allocation and the source cursor with the batch, restores both from pending segments, and treats a replayed cursor as already committed. This closes the crash window between a durable spool append and the process journal offset checkpoint.
The process journal retains superseded generations until both stdout and stderr files are fully drained. Starting generation B therefore cannot erase generation A recovery state if Run exits before A's final output is spooled. Existing journals without a session ID are upgraded in place for a still-running process so deployment of the new Run does not require restarting the game process.
### History is explicitly requested
The existing log-stream list/cursor contracts remain the history source. The terminal includes a deliberate history mode that reads selected historical stream cursors; it is not fed by the live SSE endpoint. This retains operator access without contaminating the live view.
## Risks / Trade-offs
- [Old Run versions emit no session ID] → Platform keeps their logs accessible in history but excludes them from live-follow selection; deployment is backward-safe and becomes live when Run is updated.
- [A process emits before the initial SSE replay completes] → line identity is stream ID plus sequence and client-side merge de-duplicates replay/live overlap.
- [Run exits after spooling a line but before checkpointing its file offset] → the durable source cursor makes the repeated read idempotent.
- [A new process starts before the old output files finish draining] → Run journals the retired generation until both channels are complete.
- [Session metadata tampering] → Platform accepts it only through the existing authenticated Run ingest channel and requires it to match the stream's immutable metadata.
- [Unbounded current logs] → live replay remains bounded while the full selected stream remains available through explicit history cursors.
## Migration Plan
1. Deploy Platform so it accepts session metadata and treats old streams as history.
2. Deploy Run so new managed process starts generate session-scoped stream IDs and resumptions preserve the persisted session.
3. Deploy the frontend terminal that understands `session` SSE boundaries and offers explicit history.
4. Rollback is safe: historical streams and batches remain immutable; an older frontend ignores the additional SSE event and Run can continue uploading session-scoped streams to the compatible Platform.
## Open Questions
None. The session scope is the plugin-declared process output, and the terminal's default is the current session.