docs: finalize v1 design and Windows service model
This commit is contained in:
+437
-194
@@ -11,12 +11,39 @@ binaries:
|
||||
spool, and reconnect/reconciliation owner.
|
||||
- `rvc`: local CLI over the server's Unix-domain gRPC socket.
|
||||
|
||||
The primary release target is a Linux server and Linux client. The Go client is
|
||||
structured behind OS interfaces from the outset; Windows implementations of
|
||||
process management, diagnostics, and resource controls are completed before a
|
||||
Windows client is declared supported. Windows v1 requires Windows 10 or Windows
|
||||
Server 2016 or newer. Do not claim Windows feature parity while those
|
||||
implementations are absent.
|
||||
The first supported client target is Windows connecting to a Linux server;
|
||||
Linux client support follows and remains a primary v1 goal. Build shared client
|
||||
runtime code behind OS interfaces, but complete and exercise Windows process
|
||||
management, diagnostics, resource controls, desktop hosting, and native CI
|
||||
before beginning the Linux supervisor. Windows v1 requires Windows 10 or newer,
|
||||
or Windows Server 2016 or newer. Desktop Experience is required only for the
|
||||
tray and active-session command contexts; the service and Session 0 execution
|
||||
contexts support headless Server Core.
|
||||
|
||||
The released `rvbox.exe` is one signed binary with explicit SCM-service, tray,
|
||||
installer/configurator, per-command launcher, and signal-helper modes.
|
||||
Installation is UAC-elevated and registers one machine-wide Automatic service
|
||||
running as LocalSystem. That single main service owns WSS, all accepted
|
||||
requests, durable state/history, logs, token policy, Job Objects, process
|
||||
handles, and recovery before any user logs in. There is no separate persistent
|
||||
worker or elevation broker.
|
||||
|
||||
The installer also registers an unelevated per-user tray through the machine-
|
||||
wide `Run` key. A tray is only a named-pipe frontend to the service: it may show
|
||||
health, open config/log, and request locally authorized service actions, but it
|
||||
never opens the store or owns remote work. Closing it affects neither service
|
||||
nor commands. Task Scheduler is not used anywhere in v1.
|
||||
|
||||
Use conventional machine-wide paths: `%ProgramData%\RVBox\client.toml`,
|
||||
`%ProgramData%\RVBox\state`, and `%ProgramData%\RVBox\logs\rvbox.log`, with
|
||||
state ACLs limited to Administrators and SYSTEM and separate read-only tray
|
||||
access for config/log. The menu opens the exact config or current log via a safe
|
||||
explicit file-opening path. SCM Automatic/Manual state is
|
||||
the only service-startup source of truth and is not mirrored into TOML. Create
|
||||
`%ProgramData%\RVBox\work` as a SYSTEM-owned root. For omitted CWDs, create an
|
||||
ACL-isolated child for the selected user SID, LocalService, or SYSTEM context;
|
||||
do not make one shared work directory writable by every identity. These
|
||||
requirements are part of Phase 4, not post-v1 polish.
|
||||
|
||||
This plan implements the already agreed v1 contract. In particular, it does
|
||||
not add authentication, client enrollment, mutual TLS, or a client allowlist.
|
||||
@@ -80,8 +107,10 @@ internal/
|
||||
runtime/ # reconnect loop, transport, dispatcher
|
||||
spool/ # active command/event/output durable spool
|
||||
supervisor/ # OS-neutral interface
|
||||
supervisor/unix/ # process groups, /proc, cgroup v2
|
||||
supervisor/windows/# Job Objects and Windows diagnostics
|
||||
supervisor/windows/# first target: tokens/sessions, launcher, Jobs, diagnostics
|
||||
supervisor/unix/ # later target: process groups, /proc, cgroup v2
|
||||
windowsservice/ # SCM install/control and service host
|
||||
windowstray/ # per-session named-pipe frontend and notification icon
|
||||
observability/ # logging, metrics, health/readiness
|
||||
testkit/ # clocks, fake transport, fault helpers
|
||||
gen/go/rvbox/v1/ # generated protobuf/grpc code
|
||||
@@ -101,6 +130,11 @@ versions in `go.mod`; record reasons for non-standard dependencies in
|
||||
toolchain for normal Linux/Windows client builds. Use a maintained,
|
||||
context-aware WebSocket implementation and a Zstandard implementation that
|
||||
supports bounded decompression. Do not rely on an archived WebSocket package.
|
||||
For Windows, prefer `golang.org/x/sys/windows` plus narrow reviewed wrappers for
|
||||
SCM, tokens/WTS, Job, console, named-pipe, shell, ACL, and notification-area APIs. Do not
|
||||
adopt an unmaintained tray abstraction merely to reduce Win32 code. Pin the
|
||||
resource compiler used for icon/version manifests and keep signing credentials
|
||||
out of build images and repository state.
|
||||
|
||||
### 3.2 Containerized developer tooling
|
||||
|
||||
@@ -134,9 +168,23 @@ Add CI (or a repository script ready for CI) that runs, in order:
|
||||
2. Generation freshness check.
|
||||
3. `go fmt`, `go vet`, static analysis, and unit tests.
|
||||
4. Race tests for server/client concurrency packages.
|
||||
5. Linux integration tests in Compose.
|
||||
6. Cross-compilation/build verification for Windows client packages; Windows
|
||||
runtime tests run on a Windows runner once available.
|
||||
5. Linux server/storage/session integration tests in Compose; no Linux client
|
||||
supervisor is required at this stage.
|
||||
6. Cross-compilation checks for Windows packages in the container plus native
|
||||
Windows build/runtime smoke tests on a Windows runner. A Windows runner is a
|
||||
Phase 0 prerequisite, not a later optional enhancement.
|
||||
|
||||
Provide two native Windows lanes before Phase 4: a clean noninteractive runner
|
||||
for unit/Job/process/storage tests, and a resettable interactive VM with Explorer,
|
||||
a split-token administrator, UAC enabled, and Desktop Experience for tray/WTS/
|
||||
token-session tests. Add repeatable test users/policy fixtures for a standard
|
||||
user, no logged-in user, multiple active sessions where supported, UAC disabled,
|
||||
and Administrator Protection enabled when the current Windows image exposes it.
|
||||
Tests that require the interactive desktop may be a
|
||||
release-candidate gate rather than per-commit, but cannot be replaced by mocks
|
||||
or a service-account runner. Add a Server Core service/Session-0 smoke lane.
|
||||
Exercise at least the oldest supported Windows baseline and one current desktop
|
||||
release before publishing.
|
||||
|
||||
**Exit criteria:** all three empty `main` packages build in the toolchain
|
||||
container; generated code is checked in; `make verify` works from a fresh clone.
|
||||
@@ -163,6 +211,11 @@ At the application boundary, validate what protobuf cannot express:
|
||||
<= 16 MiB, JSON-RPC HTTP bodies are <= 24 MiB, and decoded field limits remain
|
||||
identical across the two control transports.
|
||||
- shell type is explicit or assigned only to the documented platform default.
|
||||
- `elevated=false` is normal privilege; non-Windows clients reject true in v1.
|
||||
Windows applies the documented login-aware context hierarchy and records
|
||||
every attempted/effective context. Fallback ends before `launch_prepared`.
|
||||
- active Windows contexts bind one revalidated session ID, logon SID, and user
|
||||
SID; process creation is never retried under a different context.
|
||||
- CWD exists, is a directory, and is allowed by local daemon policy.
|
||||
- an execution request has exactly one source: command text or script descriptor.
|
||||
- script descriptors and payloads agree on SHA-256 and <= 10 MiB size.
|
||||
@@ -182,6 +235,9 @@ It contains:
|
||||
races;
|
||||
- typed domain errors mapped to gRPC status/structured details, JSON-RPC error
|
||||
objects, or agent-protocol `ControlError` as appropriate;
|
||||
- typed Windows pre-launch errors for a failed normal execution context or an
|
||||
exhausted elevated-context chain; ambiguity and per-attempt token/policy
|
||||
failures remain bounded selection detail rather than separate terminal codes;
|
||||
- event-sequence validation, duplicate equivalence checks, and declared gap
|
||||
validation through `OutputTruncation` metadata;
|
||||
- default values and hard-limit validation;
|
||||
@@ -214,7 +270,9 @@ Use strict TOML 1.0 and implement the normative contract in
|
||||
[`configuration.md`](configuration.md). Keep the annotated
|
||||
[`examples/server.toml`](examples/server.toml) and
|
||||
[`examples/client.toml`](examples/client.toml) synchronized with the Go config
|
||||
structs and compiled defaults.
|
||||
structs and compiled defaults. Keep
|
||||
[`examples/client.windows.toml`](examples/client.windows.toml) as the runnable
|
||||
Windows-first subset using those same defaults.
|
||||
|
||||
Implement configuration in this order:
|
||||
|
||||
@@ -265,6 +323,9 @@ takeover TTL, and observability. Client TOML covers WSS/TLS routing, durable
|
||||
identity/state, exact shell paths, allowed CWD roots, queue/concurrency,
|
||||
reconnect/liveness, spool/flow limits, execution diagnostics/grace periods,
|
||||
resource profiles, and observability.
|
||||
Windows client configuration additionally covers rotating service-log output.
|
||||
SCM startup type, service/launcher/tray identities, execution-context selection,
|
||||
conventional paths, and secure ACLs are mandatory policy rather than TOML knobs.
|
||||
|
||||
Do not add or claim application-level at-rest encryption in v1. Command-owned
|
||||
payloads are stored in plaintext under the private state directories; document
|
||||
@@ -274,8 +335,8 @@ Hard defaults are: 10-second heartbeat-idle interval, 30-second liveness
|
||||
timeout, 1–60-second full-jitter reconnect, reset after 60 stable seconds, 16
|
||||
running/100 queued client commands, 1,000 server-queued commands per client and
|
||||
10,000 server-wide, 5-minute one-shot live-conflict takeover grant, 15-minute
|
||||
queue TTL, 64 KiB raw stream
|
||||
chunk, 1 MiB decoded agent envelope, 768 KiB serialized execution spec, 16 MiB
|
||||
queue TTL, 64 KiB raw stream chunk, 1 MiB decoded agent envelope, 768 KiB
|
||||
serialized execution spec, 16 MiB
|
||||
decoded control request, 24 MiB JSON-RPC HTTP body, 10 MiB output window/raw
|
||||
script cap, 32 MiB total per command, 256 MiB per client/client daemon, 4 GiB
|
||||
server-wide command storage, 30-day terminal retention, 100 MiB audit storage
|
||||
@@ -286,18 +347,19 @@ plus unacknowledged send windows of 1 MiB per command and 8 MiB per session.
|
||||
Reserve 64 KiB within every accepted command's quota for terminal/loss closeout
|
||||
metadata and bound protocol detail/reason plus incident-note text to 4 KiB.
|
||||
Use emergency filesystem free-space floors of 256 MiB server-side and 64 MiB
|
||||
client-side by default.
|
||||
client-side by default. The Windows service installs as Automatic; service logs
|
||||
rotate at 10 MiB and retain five sealed files.
|
||||
|
||||
Test both annotated example files through the production decoder. Add table tests
|
||||
for every unknown key, invalid duration/size, high/low inversion, tier inversion,
|
||||
zero semantic, noncanonical path/device, unsupported default shell, conflicting
|
||||
profile combination, and flag-precedence case. Add a test proving a request-level
|
||||
`PATH` override cannot change the chosen shell executable. Golden-test the
|
||||
redacted effective configuration and keep its key order stable enough for
|
||||
operators to compare deployments.
|
||||
Test all annotated example files through the production decoder on their target
|
||||
platforms. Add table tests for every unknown key, invalid duration/size,
|
||||
high/low inversion, tier inversion, zero semantic, noncanonical path/device,
|
||||
unsupported default shell, conflicting profile combination, and flag-precedence
|
||||
case. Add a test proving a request-level `PATH` override cannot change the
|
||||
chosen shell executable. Golden-test the redacted effective configuration and
|
||||
keep its key order stable enough for operators to compare deployments.
|
||||
|
||||
**Exit criteria:** state-machine and configuration tests cover all legal/illegal
|
||||
transitions and boundary values; both examples parse to the documented defaults;
|
||||
transitions and boundary values; all examples parse to the documented defaults;
|
||||
generated protos compile and are the only DTOs crossing process boundaries.
|
||||
|
||||
## 5. Server persistence and retention (Phase 2)
|
||||
@@ -349,7 +411,7 @@ invariants must not.
|
||||
| --- | --- |
|
||||
| `clients` | client ID, most-recent platform/capabilities/CWD/version, durable instance ID, current generation, connection/last-seen timestamps, latest rejected live-conflict instance/time, unified charged command-storage total |
|
||||
| `sessions` | opaque session ID, client ID, durable client-instance UUID, generation, opened/fenced/closed times, close reason |
|
||||
| `commands` | UUID, client ID, indexed issue/queue-expiry/terminal times, lifecycle, revision, exit result, last event sequence, retention status, and a Zstandard-compressed immutable execution-spec payload including plaintext environment values |
|
||||
| `commands` | UUID, client ID, indexed issue/queue-expiry/terminal times, lifecycle, revision, exit result, last event sequence, retention status, a Zstandard-compressed immutable execution-spec payload including plaintext environment values, and optional Windows selection attempts/effective identity |
|
||||
| `command_payloads` | command UUID, payload kind (script or other command-owned blob), Zstandard compression, raw/stored sizes, digest, inline bytes or validated segment reference |
|
||||
| `command_events` | command UUID + event sequence unique key, observed and server receipt times, event type, payload metadata, immutable duplicate checksum |
|
||||
| `output_segments` | command UUID, segment ordinal/path, `committed_end_offset`, min/max event sequence, stream mix, compressed/raw byte totals, checksum, created time |
|
||||
@@ -740,13 +802,15 @@ slow client that cannot block another client or local control RPC.
|
||||
receive bounded dispatches, and recover connection faults without duplicate
|
||||
execution or server deadlock.
|
||||
|
||||
## 7. Client runtime, spool, and Unix supervisor (Phase 4)
|
||||
## 7. Windows-first client runtime, spool, and supervisor (Phase 4)
|
||||
|
||||
### 7.1 Client state and reconnect runtime
|
||||
|
||||
Keep client state private (mode `0700`): durable accepted-command records,
|
||||
active process metadata, command event journal, stdout/stderr spool segments,
|
||||
stdin write acknowledgements, and outstanding script upload state. It contains
|
||||
Keep client state private: mode `0700` on Unix; on Windows disable inherited
|
||||
ACLs and grant only SYSTEM and Administrators the required access. Store durable
|
||||
accepted-command records, active process metadata, command event journal,
|
||||
stdout/stderr spool segments, stdin write acknowledgements, and outstanding
|
||||
script upload state. It contains
|
||||
no terminal command history after server acknowledgement and local cleanup.
|
||||
It retains a separate compact FIFO of the most recent 1,000,000 command
|
||||
tombstones (binary UUIDv7, immutable request hash, and acknowledgement time) so
|
||||
@@ -775,6 +839,15 @@ first registration. Never regenerate it merely because the network/server
|
||||
rejects a session. If the identity file is corrupt, enter dirty local health and
|
||||
require the repair path rather than silently presenting a new installation.
|
||||
|
||||
Define durable-file primitives per platform and use them everywhere. Unix uses
|
||||
file `fsync`, atomic same-filesystem rename, then parent-directory `fsync`.
|
||||
Windows writes a same-directory temporary file with restrictive ACLs, calls
|
||||
`FlushFileBuffers`, atomically installs it with `ReplaceFileW` or
|
||||
`MoveFileExW(MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)` as applicable,
|
||||
and flushes a directory handle where supported. Treat an unsupported/failed
|
||||
required flush as a storage error; do not acknowledge durable state based only
|
||||
on buffered writes. Test power-loss boundaries on NTFS, not only process exit.
|
||||
|
||||
Give the client store the same committed-offset segment discipline as the
|
||||
server. Persist, at minimum, command phase/revision/hash, platform launch
|
||||
identity, local event ordinal, optional assigned wire sequence, payload digest,
|
||||
@@ -899,6 +972,10 @@ directory, and only then make the command launch-eligible. A permanent upload
|
||||
failure durably ends the command as `REJECTED` before launch authorization and
|
||||
releases every associated reservation.
|
||||
|
||||
On Windows create wrappers with an explicit non-inherited ACL, exclusive random
|
||||
basename, and delete-sharing suitable for terminal cleanup; use the common
|
||||
Windows durable replacement primitive rather than POSIX rename assumptions.
|
||||
|
||||
Materialize ordinary `command_text` through the same private generated-file
|
||||
machinery, while retaining its separate command-text metadata. Execute the file
|
||||
with exactly `sh`/`bash`, `cmd.exe /D /S /C`, or `powershell.exe` with
|
||||
@@ -916,7 +993,7 @@ reject if it is no longer the validated regular executable. Add tests with an
|
||||
attacker-controlled `PATH` entry and same-named fake shell to prove it cannot be
|
||||
selected.
|
||||
|
||||
### 7.4 Unix process supervisor
|
||||
### 7.4 Supervisor contract and Windows process implementation
|
||||
|
||||
Define a narrow interface used by the runtime:
|
||||
|
||||
@@ -929,107 +1006,229 @@ type Supervisor interface {
|
||||
}
|
||||
```
|
||||
|
||||
The Unix implementation starts an RVBox launcher as leader of a new
|
||||
session/process group; after authorization that launcher creates the selected
|
||||
`sh`/`bash` child inside the same group and remains as its watchdog. It rejects
|
||||
unsupported shell values. It applies daemon environment plus persisted
|
||||
overrides, validates the CWD, connects stdin/stdout/stderr pipes, and records
|
||||
launcher/root/group identities. Signal the full process group.
|
||||
On orderly shutdown and unclean-start recovery, terminate surviving managed
|
||||
groups and emit `interrupted` rather than pretending pipe monitoring survived.
|
||||
`StartSpec` includes the validated `elevated` intent; a successful `Process`
|
||||
exposes an immutable effective `WindowsExecutionIdentity` after preparation,
|
||||
and a context-selection error carries the same record without an effective
|
||||
context. The runtime persists the intent, attempted contexts, selection detail,
|
||||
and optional effective identity. It emits the record with a pre-launch
|
||||
rejection or with `running` before reporting requested code as started.
|
||||
|
||||
Treat root exit as the start of a configurable 5-second tree/output drain grace
|
||||
period. Wait for the cgroup/process group and capture readers; terminate residual
|
||||
descendants after the grace period, drain to EOF, and emit incomplete-output
|
||||
metadata as a sequenced `OutputIncomplete` event if handles still cannot be
|
||||
drained. The terminal lifecycle event must be sequenced and spooled only after
|
||||
all retained output/truncation events and is
|
||||
always the final client event.
|
||||
Implement Windows code in platform-specific files so non-Windows builds never
|
||||
import Windows APIs. Keep launch phases identical across platforms:
|
||||
`accepted -> launch_prepared -> launch_authorized -> running`, with no shortcut.
|
||||
|
||||
Implement launch through an internal blocked-launcher mode with a private
|
||||
release/watchdog channel. The launcher remains alive as a non-user-code
|
||||
supervisor for the process-tree lifetime. After the launcher reports ready,
|
||||
persist and flush
|
||||
its PID/process-group/platform birth identity as `launch_prepared`; persist and
|
||||
flush `launch_authorized` before releasing requested command code. EOF before
|
||||
release aborts without executing it. On Linux create a per-command cgroup v2
|
||||
for supervision regardless of resource-profile selection, place the blocked
|
||||
launcher into it before release (`clone3` with `CLONE_INTO_CGROUP` and
|
||||
`CLONE_PIDFD` where available), and persist the cgroup path plus
|
||||
`/proc/<pid>/stat` start time. Recovery prefers `cgroup.kill`; a process-group
|
||||
fallback is allowed only after positive birth-identity verification. Other Unix
|
||||
platforms use the watchdog/process-group fallback and document that descendants
|
||||
which deliberately create a new session may escape it.
|
||||
Implement one exhaustive token selector; do not scatter token fallback across
|
||||
launch code:
|
||||
|
||||
Encode launch as `accepted -> launch_prepared -> launch_authorized -> running`
|
||||
and permit no shortcut:
|
||||
1. Reject `elevated=true` on non-Windows in v1. On Windows, enumerate WTS
|
||||
sessions immediately before preparation. Prefer the physical console only
|
||||
when it is `WTSActive` with a valid user token; otherwise accept exactly one
|
||||
`WTSActive` interactive session. Zero candidates means logged out. Multiple
|
||||
candidates without a preferred console are recorded as ambiguous and treated
|
||||
as no *usable* active user, never selected nondeterministically.
|
||||
2. With a usable active user and `elevated=false`, attempt only `ACTIVE_USER`.
|
||||
Use an existing filtered/standard token. If Windows supplies only a full
|
||||
administrator token, call `CreateRestrictedToken` with LUA/max-privilege
|
||||
restriction, make administrator/privilege-bearing groups deny-only, set and
|
||||
verify medium integrity, and retain the user's SID/session. If a verified
|
||||
non-elevated token cannot be built, reject; do not use LocalService or SYSTEM
|
||||
while that selected active session remains valid.
|
||||
3. With a usable active user and `elevated=true`, attempt contexts in this exact
|
||||
order: `ACTIVE_USER_ELEVATED`, `ACTIVE_SYSTEM`, `LOCAL_SYSTEM`.
|
||||
`ACTIVE_USER_ELEVATED` queries `TokenElevationType`, uses a linked full token
|
||||
for a traditional limited administrator, and accepts an already-full admin
|
||||
token. A standard user/missing linked token records
|
||||
`CODE_ELEVATION_UNAVAILABLE`;
|
||||
Administrator Protection or another just-in-time approval policy records a
|
||||
bounded policy-specific attempt reason. V1 shows no UAC/Hello prompt and
|
||||
continues to `ACTIVE_SYSTEM`.
|
||||
4. `ACTIVE_SYSTEM` duplicates the service SYSTEM token, sets `TokenSessionId` to
|
||||
the selected session while `SeTcbPrivilege` is enabled only around that call,
|
||||
verifies SYSTEM SID/session, and hides all windows. A pre-preparation failure
|
||||
records its error and continues to Session 0 `LOCAL_SYSTEM`. This fallback
|
||||
intentionally bypasses user-scoped approval because the installed service
|
||||
already holds SYSTEM and must be conspicuous in status/audit history. The
|
||||
final `LOCAL_SYSTEM` fallback supplies elevation but no interactive-desktop
|
||||
access; session-scoped commands may launch successfully and then fail in the
|
||||
ordinary way in Session 0.
|
||||
5. With no usable active user, `elevated=false` attempts only `LOCAL_SERVICE`:
|
||||
passwordless `LogonUserW` for `NT AUTHORITY\LocalService` with
|
||||
`LOGON32_LOGON_SERVICE`, required SID `S-1-5-19`, and session 0. Do not use a
|
||||
restricted SYSTEM token as a substitute. `elevated=true` attempts only
|
||||
`LOCAL_SYSTEM` by duplicating the service token and requiring SID `S-1-5-18`
|
||||
plus session 0.
|
||||
6. Fallback decisions cover token/session capability only and all complete
|
||||
before `launch_prepared`. Never fall back after launcher creation, shell
|
||||
creation, CWD/executable/profile validation failure, authorization, or an
|
||||
uncertain outcome. If the selected user logs out during selection, close all
|
||||
provisional handles, enumerate once more, and apply the resulting row; never
|
||||
switch silently to another user.
|
||||
7. Verify the final user SID, session ID, token type, elevation state, and
|
||||
integrity level against the selected context. Close every token/profile
|
||||
handle on all paths. Persist the request's elevation bit, ordered attempted
|
||||
contexts with a total bounded selection detail, optional effective context/
|
||||
user SID, and target session/user/logon SIDs; never persist token handles or
|
||||
credentials. If every context fails, emit that decision record with no
|
||||
effective context on the terminal pre-launch rejection.
|
||||
|
||||
1. Create the private cgroup/process group, pipes, wrapper, and close-on-exec
|
||||
release/watchdog channel without executing user code.
|
||||
2. Start the blocked launcher and obtain a positive ready message containing
|
||||
the platform process identity; place and verify it in its command cgroup.
|
||||
3. Commit and fsync `launch_prepared` with that identity. Recheck the latest
|
||||
revision and pending cancellation while the launcher remains blocked.
|
||||
4. If still executable, commit and fsync `launch_authorized`, then send the
|
||||
one-byte release and keep the watchdog channel open until tree cleanup.
|
||||
Record `running` only after the launcher reports successful requested-shell
|
||||
creation/`exec`.
|
||||
5. A crash before durable preparation cleans an untrusted orphan and may retry.
|
||||
A prepared-but-unauthorized launcher must exit on channel EOF and may retry
|
||||
only after positive death verification. After release, channel EOF makes the
|
||||
launcher terminate its process group; an authorized but uncertain launch is
|
||||
killed as a tree and ends `interrupted`, never launched again.
|
||||
Build the base Unicode environment with `CreateEnvironmentBlock` for the
|
||||
effective token, then apply validated request overrides deterministically.
|
||||
Load/unload an active user's profile only when required and keep it loaded until
|
||||
the complete Job exits. Session 0 contexts use their service-account profile and
|
||||
cannot see interactive mapped drives. Validate an explicit CWD and wrapper ACL
|
||||
access while impersonating the effective token. For an omitted CWD, treat
|
||||
`%ProgramData%\RVBox\work` (or configured `daemon_cwd`) as a SYSTEM-owned root
|
||||
and create/open an ACL-isolated child keyed by the selected user SID,
|
||||
LocalService, or SYSTEM context. Reject insecure owners, inherited write grants,
|
||||
and reparse points on reuse. Generated wrappers grant only SYSTEM and the
|
||||
effective token SID the required access.
|
||||
|
||||
Make launch-journal and launcher-control records checksum-framed and bounded.
|
||||
Fault-inject process death before and after every fsync, ready/release, and exec
|
||||
acknowledgement; assert that user-visible side effects occur at most once and
|
||||
that recovery cannot confuse a reused PID.
|
||||
1. Create a non-inheritable per-command Job Object, enable
|
||||
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, and do not enable breakaway.
|
||||
2. Resolve the hierarchy's effective token/session as specified above. Create
|
||||
unpredictable local named pipes for control/stdin/stdout/stderr with
|
||||
`PIPE_REJECT_REMOTE_CLIENTS` and an ACL restricted to SYSTEM and the effective
|
||||
token SID. Do not rely on inherited service handles: Windows prohibits normal
|
||||
handle inheritance across Terminal Services sessions.
|
||||
3. Create a stateless per-command launcher mode of the canonical `rvbox.exe`
|
||||
suspended through `CreateProcessAsUser`, with no inherited handles and an
|
||||
opaque pipe-channel identifier. Assign this still-suspended launcher to the
|
||||
empty Job, then resume it. Verify the connecting pipe client's PID, creation
|
||||
`FILETIME`, token SID, session ID, and generation before sending any launch
|
||||
material.
|
||||
4. The launcher opens only the verified pipes and creates the requested shell
|
||||
suspended with `CREATE_NEW_CONSOLE`, `CREATE_SUSPENDED`,
|
||||
`CREATE_UNICODE_ENVIRONMENT`, and `EXTENDED_STARTUPINFO_PRESENT`. Use
|
||||
`STARTF_USESHOWWINDOW`/`SW_HIDE`; pass only standard-I/O handles with
|
||||
`PROC_THREAD_ATTRIBUTE_HANDLE_LIST`. Job membership propagates because
|
||||
breakaway is disabled. Report shell PID/creation time and wait for release.
|
||||
5. Persist and flush `launch_prepared` with requested elevation, attempted/
|
||||
effective contexts and reasons, token SID, session/user/logon SIDs when
|
||||
active, launcher/shell birth identities,
|
||||
Job generation, and console identity. Recheck revision/cancellation, WTS
|
||||
session identity, executable identity, CWD access, and requested Job limits
|
||||
while requested code remains suspended.
|
||||
6. Persist and flush `launch_authorized`, then send the authenticated release
|
||||
message. The launcher resumes the shell and acknowledges the attempt; record
|
||||
`running` with effective identity only after that acknowledgement. Failure or
|
||||
crash after authorization terminates the Job and becomes interrupted, never
|
||||
redispatched.
|
||||
7. The service retains the sole Job handle. Control EOF before authorization
|
||||
makes the launcher exit without executing; EOF afterward terminates the Job.
|
||||
Service exit therefore kills launcher, shell, and descendants in every crash
|
||||
window. Recovery never signals or kills by PID alone.
|
||||
|
||||
Implement the portable Unix set `HUP`, `INT`, `TERM`, `KILL`, `USR1`, and
|
||||
`USR2`, accepting optional `SIG` prefixes in the CLI and mapping only through
|
||||
the `SignalKind` enum. Reject arbitrary native numbers and unsupported names.
|
||||
Never allow signal zero or arbitrary PID targeting. The process's group ID
|
||||
comes only from durable supervisor metadata, never a request field.
|
||||
Do not combine `CREATE_NEW_PROCESS_GROUP` with `CREATE_NEW_CONSOLE`: Windows
|
||||
ignores the former, and it is unnecessary because every command owns a distinct
|
||||
hidden console. For TERM, start a short-lived private signal-helper mode of the
|
||||
same canonical `rvbox.exe` using the command's effective token/session. Pass the
|
||||
target identity through a separate unpredictable, PID/token/session-verified
|
||||
local named pipe rather than inherited cross-session handles or the command
|
||||
line. Prefer the root shell while it is live;
|
||||
otherwise select a live PID from the Job's process list and verify its creation
|
||||
`FILETIME`/generation before use. The helper calls `AttachConsole(target_pid)`,
|
||||
installs a control handler that consumes its own CTRL_BREAK, then calls
|
||||
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0)` so only processes attached to
|
||||
that command's console receive it. It reports a checksum-framed result and
|
||||
detaches. The helper receives neither the Job handle nor command stdio.
|
||||
|
||||
### 7.5 Linux diagnostics and resource profiles
|
||||
Apply `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` to every same-session child creation.
|
||||
Every database, log, listener, Job, unrelated pipe, and SCM handle is non-
|
||||
inheritable. Length/type/generation/checksum-frame launcher/signal-helper
|
||||
control messages and reject mismatched generations.
|
||||
|
||||
Poll active processes at a configurable interval outside pipe/network loops.
|
||||
Read readable `/proc` values for CPU time, RSS, I/O, state, CWD, and wait reason;
|
||||
aggregate only clearly associated group/child data. Missing/unreadable values
|
||||
remain absent. Set `suspected_hung` only after default 10 minutes without
|
||||
observable progress and label it diagnostic, not lifecycle.
|
||||
Open the configured shell by canonical absolute path and pass it as explicit
|
||||
`lpApplicationName`. Use one reviewed Windows argument-quoting routine and an
|
||||
explicit Unicode environment block. Never invoke `%COMSPEC%`, search
|
||||
`PATH`/file associations, or let command environment overrides choose the
|
||||
executable.
|
||||
|
||||
Make requested resource-profile handling explicit. On Linux the supervisor
|
||||
uses a delegated writable cgroup v2 for tree supervision whenever available,
|
||||
even without a profile. Profiles add administrator-defined CPU/memory/disk/
|
||||
process controls to that cgroup. Without delegation, no-profile execution uses
|
||||
the watchdog/process-group fallback; a profile whose essential control cannot
|
||||
be applied is rejected as unsupported. No profile means no resource restriction,
|
||||
even when a supervisory cgroup exists.
|
||||
Associate the Job with an I/O completion port. Use
|
||||
`JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO` plus capture-pipe EOF to establish complete
|
||||
tree/output drain. Persist shell creation `FILETIME` before trusting a PID.
|
||||
Recovery may reopen a process only to compare creation time/generation;
|
||||
inability to prove ownership creates a dirty incident rather than risking
|
||||
termination of a reused PID.
|
||||
|
||||
Compile TOML resource profiles into immutable validated launch policies during
|
||||
startup. Enforce `LIGHT` as exclusive; otherwise allow at most one CPU, one
|
||||
memory, and one disk profile. On Linux, translate configured policy into the
|
||||
per-command cgroup's `cpu.max`/`cpu.weight`, `memory.max`/`memory.high`,
|
||||
`pids.max`, and per-device `io.max` controls as applicable. Write and read back
|
||||
all essential controls while the launcher is blocked. If any essential control
|
||||
is unsupported or cannot be applied, destroy the empty command cgroup and
|
||||
permanently reject the dispatch; never run partially constrained.
|
||||
Accept only `TERM`/`SIGTERM` and `KILL`/`SIGKILL`. TERM runs the verified
|
||||
console-attach helper, waits the configured 10 seconds, then terminates the Job
|
||||
if necessary. KILL terminates it immediately. Emit the actual attach, delivery,
|
||||
wait, and escalation outcome. Root exit starts the common 5-second drain grace;
|
||||
the terminal lifecycle event remains last.
|
||||
|
||||
Define diagnostic progress as a change in process-tree CPU ticks, cumulative
|
||||
I/O counters, retained output/input activity, or lifecycle state. Persist only
|
||||
the latest sample and no-progress start time, clear `suspected_hung` on the next
|
||||
observed progress, and tolerate counter reset/process exit. Diagnostics must not
|
||||
keep a command alive, change terminal status, or block the supervisor.
|
||||
Compile requested TOML profiles before launch and apply CPU, memory, active
|
||||
process, and supported I/O rate limits to the empty Job. Read back every required
|
||||
limit before authorization. Permanently reject `UNSUPPORTED` rather than run
|
||||
partially constrained. Use process/Job accounting for CPU/RSS/I/O and omit
|
||||
Linux-only wait reasons.
|
||||
|
||||
### 7.6 Client tests
|
||||
### 7.5 Windows desktop host and lifecycle
|
||||
|
||||
Use fake transport/clock plus real subprocess tests for shell defaults, CWD/env
|
||||
overlays, at-most-once duplicate dispatch, concurrent output with no newlines,
|
||||
stdin ordering/close, process-group termination, reconnect/replay, offline
|
||||
rolling truncation before sequence assignment, assigned-window pinning, script
|
||||
progress/checksum failure/cleanup, atomic tombstone replacement, queue limits,
|
||||
shutdown interruption, and `/proc` absence. Run race tests with multiple
|
||||
Build the executable with the Windows GUI subsystem so service/helper/tray modes
|
||||
never flash an unwanted console. Human-invoked `--help`, `--check-config`,
|
||||
install, uninstall, and configuration modes call
|
||||
`AttachConsole(ATTACH_PARENT_PROCESS)`, reopen the inherited standard handles,
|
||||
and use UTF-8 terminal diagnostics when attached; define native-dialog plus exit-
|
||||
code behavior when no console exists and test both paths. `install-service` and
|
||||
`uninstall-service` use `ShellExecuteEx(..., "runas", ...)` only when their caller
|
||||
is not already elevated, validate the canonical executable/config paths, and
|
||||
make idempotent SCM changes. The installed service command line selects only the internal
|
||||
`service` mode and exact config path. At service entry, verify SCM startup rather
|
||||
than accepting that mode from an ordinary process invocation. Hold one machine-
|
||||
wide service/state lock and report SCM start/stop checkpoints within their
|
||||
deadlines; lengthy spool recovery remains asynchronous and does not block
|
||||
service start.
|
||||
|
||||
Register the tray with the machine-wide `Run` key, not Task Scheduler and not
|
||||
service-created cross-session injection. Use one ACL-protected mutex per WTS
|
||||
session so each logged-in user gets at most one tray while multiple RDP/console
|
||||
sessions remain independent. Run its Win32 message pump on one locked OS thread,
|
||||
recreate the icon after `TaskbarCreated`, and use bounded nonblocking IPC/UI
|
||||
channels. The tooltip shows connection/readiness/dirty state without payloads.
|
||||
`Exit` closes only that tray. The service continues through tray exit, user
|
||||
logoff, Explorer restart, and periods with no interactive user.
|
||||
|
||||
Expose a versioned, length-bounded local named-pipe tray protocol with
|
||||
`PIPE_REJECT_REMOTE_CLIENTS`, explicit SYSTEM/Administrators/interactive-user
|
||||
ACLs, peer PID/session/token inspection, deadlines, and no command payloads.
|
||||
The tray never reads the spool/database. Read-only status may be served to a
|
||||
local interactive user. For a mutating service/config action, the tray launches
|
||||
the narrow canonical `configure-service` mode with UAC; that helper sends one
|
||||
authenticated request and exits. The service rechecks the helper token rather
|
||||
than trusting a tray-supplied administrator claim.
|
||||
|
||||
Resolve defaults under `%ProgramData%\RVBox`; create the config, state, and log
|
||||
directories with explicit Administrators/SYSTEM ACLs and reject insecure
|
||||
existing objects. Create `%ProgramData%\RVBox\work` as a SYSTEM-owned root and
|
||||
create identity-scoped child directories on demand with non-inherited ACLs for
|
||||
SYSTEM plus the effective non-SYSTEM SID; no command identity receives state-
|
||||
directory access. Give tray users only the documented read access to config/
|
||||
current logs. On first installation, copy the annotated Windows
|
||||
TOML template and leave the service live/not-ready until routing is valid. `Open
|
||||
config` and `Open log` use exact resolved regular paths—never an unquoted shell
|
||||
command—and show actionable errors. Config edits are administrator-only and
|
||||
take effect after explicit service restart; v1 has no hot reload or TOML rewrite.
|
||||
|
||||
Install the service with `SERVICE_AUTO_START`. `configure-service` maps the tray
|
||||
Automatic/Manual control directly to SCM start type; there is no
|
||||
`windows.start_on_boot` TOML key or second desired-state store. Stop/restart is a
|
||||
separate administrator action and reports that stopping the service terminates
|
||||
all supervised Jobs. Uninstall first stops admission, performs bounded orderly
|
||||
shutdown, removes the Run entry and service registration, and preserves config/
|
||||
state/log data unless a separately confirmed purge operation is designed later.
|
||||
|
||||
Use bounded rotating service file logs because SCM has no useful interactive
|
||||
stderr. Flush warnings/errors promptly, redact as elsewhere, and make `Open log`
|
||||
target the current file after rotation. Add native icon/version/service metadata;
|
||||
release signing and checksum publication are Phase 8 gates.
|
||||
|
||||
### 7.6 Windows-first client tests
|
||||
|
||||
Use fake transport/clock tests for shared runtime logic and real Windows
|
||||
subprocess tests for both shells, CWD/env overlays, at-most-once duplicates,
|
||||
concurrent output without newlines, stdin ordering/close, Job tree termination,
|
||||
reconnect/replay, offline truncation, script verification/cleanup, tombstone
|
||||
rotation, queue limits, and shutdown interruption. Run race tests with multiple
|
||||
commands and forced network churn.
|
||||
|
||||
Add a table-driven crash suite for every acceptance, script-upload, launch,
|
||||
@@ -1038,15 +1237,45 @@ the same duplicate dispatch after each restart and assert exactly one of:
|
||||
durable rejection before authorization, one supervised process, or an
|
||||
`interrupted` uncertain launch—never a second execution.
|
||||
|
||||
**Exit criteria:** a Linux client can stay alive through server loss/restart,
|
||||
execute up to its capacity at most once, preserve/replay bounded history, and
|
||||
cleanly manage full command process trees.
|
||||
Fault-inject daemon death around Job creation, suspended shell creation,
|
||||
launcher pipe authentication, prepared/authorized fsync, release/resume, and
|
||||
terminal drain; fault the signal helper before/after attach, control-event
|
||||
delivery, and result. Test paths with spaces/non-ASCII, malicious
|
||||
`PATH`/`COMSPEC`, nested descendants, hidden-console behavior, CTRL_BREAK
|
||||
refusal/escalation, Job-limit rejection, cross-session output/stdin, and
|
||||
inherited-handle leaks.
|
||||
|
||||
Add a native table for every hierarchy row: active standard user, traditional
|
||||
split-token administrator, already-full administrator/UAC-off token, enabled
|
||||
Administrator Protection where available, logged-out machine, ambiguous RDP
|
||||
sessions, and logout between selection/revalidation. Assert normal active-user
|
||||
execution is medium/non-admin; elevated attempts are ordered
|
||||
`ACTIVE_USER_ELEVATED -> ACTIVE_SYSTEM -> LOCAL_SYSTEM`; no-user normal/elevated
|
||||
use `LOCAL_SERVICE`/`LOCAL_SYSTEM`; and attempted/effective identities are
|
||||
persisted and displayed. Inject failure at each token attempt and prove fallback
|
||||
occurs only before `launch_prepared`, never as a process retry.
|
||||
|
||||
Test idempotent UAC installation/uninstallation, SCM Automatic/Manual/start/
|
||||
stop/restart, preservation of state on uninstall, service boot before login,
|
||||
service continuity through logon/logoff/tray exit, one tray per session, all tray
|
||||
actions and authorization checks, malicious named-pipe clients, Explorer
|
||||
restart, Run-key registration/removal, first-install bootstrap, ProgramData/work
|
||||
ACLs, Server Core headless behavior, and log rotation on each supported Windows
|
||||
version. Assert no Task Scheduler object is created or required.
|
||||
|
||||
**Exit criteria:** the native Windows client can stay alive through server
|
||||
loss/restart, execute up to capacity at most once, preserve/replay bounded
|
||||
history, manage complete Job trees, and satisfy tray/elevation/autostart behavior
|
||||
on Windows CI, including all execution-hierarchy rows. Do not begin the Linux
|
||||
supervisor phase until this gate passes.
|
||||
|
||||
## 8. End-to-end agent protocol (Phase 5)
|
||||
|
||||
Wire the server session layer and client runtime together before adding the CLI.
|
||||
Use real protobuf bytes through an in-memory WebSocket test server first, then
|
||||
through Docker Compose with nginx proxying a WSS endpoint.
|
||||
Wire the server session layer and Windows client runtime together before adding
|
||||
the CLI. Use real protobuf bytes through an in-memory test transport first.
|
||||
Then run Linux server/nginx/storage containers and connect a native Windows CI
|
||||
runner to their short-lived WSS endpoint; do not substitute Wine or a
|
||||
cross-compiled binary that was never executed on Windows.
|
||||
|
||||
Implement flows in this order:
|
||||
|
||||
@@ -1087,9 +1316,10 @@ the visible CLI result. Include these cross-cutting cases:
|
||||
- heartbeat control traffic remains serviceable while both data lanes are full
|
||||
over a slow, intermittently writable WebSocket.
|
||||
|
||||
**Exit criteria:** Compose tests demonstrate a complete background command,
|
||||
foreground follow, stdin interaction, signal, reconnect, and restart recovery
|
||||
through the nginx WebSocket path.
|
||||
**Exit criteria:** a native Windows client against the Compose-hosted Linux
|
||||
server demonstrates a complete background command, history/follow behavior,
|
||||
stdin interaction, signal, reconnect, and restart recovery through nginx WSS.
|
||||
This is the first supported-client milestone and gates Linux supervisor work.
|
||||
|
||||
## 9. Server control plane and `rvc` (Phase 6)
|
||||
|
||||
@@ -1221,70 +1451,72 @@ structured RVBox detail as gRPC.
|
||||
stack; the Unix socket has mode `0600`; JSON-RPC behavior matches gRPC unary
|
||||
semantics and is off unless explicitly enabled.
|
||||
|
||||
## 10. Windows client implementation (Phase 7)
|
||||
## 10. Linux client implementation (Phase 7)
|
||||
|
||||
Keep Windows code in platform-specific files/build tags so Linux builds never
|
||||
import Windows APIs. Implement the same supervisor interface and event/spool
|
||||
runtime; only OS execution/diagnostics/resource enforcement differ.
|
||||
Begin this phase only after the Windows Phase 4/5 release gates pass. Keep Unix
|
||||
code in platform-specific files/build tags and reuse the proven runtime/store/
|
||||
protocol contracts without changing their wire semantics to suit Linux.
|
||||
|
||||
1. Create a non-inheritable per-command Job Object, enable
|
||||
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, and do not enable breakaway.
|
||||
2. Start an RVBox launcher suspended with `CREATE_NEW_CONSOLE`,
|
||||
`CREATE_UNICODE_ENVIRONMENT`, and `EXTENDED_STARTUPINFO_PRESENT`. Assign it
|
||||
to the Job at creation through `PROC_THREAD_ATTRIBUTE_JOB_LIST`; inherit only
|
||||
an explicit standard-I/O/control handle list and never the Job handle.
|
||||
3. Persist and flush `launch_prepared` with launcher PID, `GetProcessTimes`
|
||||
creation `FILETIME`, and launch generation; persist and flush
|
||||
`launch_authorized` before `ResumeThread`. The launcher then creates the
|
||||
requested shell suspended in its console with `CREATE_NEW_PROCESS_GROUP`,
|
||||
reports its PID/group, connects the allowlisted pipes, and resumes it. A
|
||||
failure after authorization terminates the Job and becomes interrupted.
|
||||
4. Keep the launcher as an in-console signal proxy. This separation is
|
||||
mandatory: Windows ignores `CREATE_NEW_PROCESS_GROUP` when combined with
|
||||
`CREATE_NEW_CONSOLE`, and `GenerateConsoleCtrlEvent` reaches only groups
|
||||
sharing the caller's console. On daemon crash, closing the sole Job handle
|
||||
terminates the launcher and complete tree; restart never signals by PID alone.
|
||||
5. Expose Job Object accounting snapshots. Children normally remain in the Job.
|
||||
6. Accept only `SIGTERM` and `SIGKILL`. For TERM, have the launcher call
|
||||
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, shell_group_id)`, wait 10
|
||||
seconds, then terminate the Job. For KILL, terminate the Job immediately.
|
||||
Report graceful-attempt/escalation outcome in the event.
|
||||
7. Apply requested profile limits through Job Object limits. If the requested
|
||||
control cannot be applied, reject the dispatch with structured `UNSUPPORTED`.
|
||||
8. Use available process/Job Object telemetry for CPU/RSS/I/O; do not emit a
|
||||
Linux-style wait reason.
|
||||
The Unix supervisor starts an RVBox launcher as leader of a new session/process
|
||||
group; after authorization the launcher creates the selected `sh`/`bash` child
|
||||
inside the same group and remains its watchdog. It applies daemon environment
|
||||
plus persisted overrides, validates the CWD, connects stdin/stdout/stderr, and
|
||||
records launcher/root/group identities. On shutdown or unclean-start recovery,
|
||||
terminate managed groups and emit `interrupted` rather than claiming pipe
|
||||
monitoring survived.
|
||||
|
||||
Implement the Windows launcher as a small mode of the same RVBox binary, not a
|
||||
searchable external helper. Pass it an inherited, random per-launch control
|
||||
pipe and fixed-size launch-generation token. Apply an explicit
|
||||
`PROC_THREAD_ATTRIBUTE_HANDLE_LIST` so only stdin/stdout/stderr and that control
|
||||
pipe cross creation; make every database, log, listener, Job, and unrelated
|
||||
pipe handle non-inheritable. Frame ready/release/exec/error messages with length,
|
||||
type, generation, and checksum, and reject any mismatched generation.
|
||||
Implement launch with a private release/watchdog channel. Persist and fsync
|
||||
launcher PID/process-group/platform birth identity as `launch_prepared`; recheck
|
||||
revision/cancellation, persist and fsync `launch_authorized`, then release user
|
||||
code. Keep the channel open through tree cleanup. EOF before authorization exits
|
||||
without execution; EOF after release terminates the group. An authorized but
|
||||
uncertain launch is killed and interrupted, never retried.
|
||||
|
||||
Open the configured shell executable by canonical absolute path and pass it as
|
||||
the explicit `lpApplicationName`. Produce the command line with one reviewed
|
||||
Windows argument-quoting routine and construct an explicit Unicode environment
|
||||
block. Do not invoke `%COMSPEC%`, search `PATH`/file associations, or let command
|
||||
environment overrides influence executable selection.
|
||||
On Linux create a per-command cgroup v2 for supervision whenever a delegated
|
||||
writable cgroup exists, even without a resource profile. Put the blocked
|
||||
launcher into it before release using `clone3(CLONE_INTO_CGROUP |
|
||||
CLONE_PIDFD)` where available; otherwise migrate only the still-blocked launcher
|
||||
through `cgroup.procs`. Persist cgroup path, pidfd-derived identity where
|
||||
available, PID/process group, `/proc/<pid>/stat` start time, and launch
|
||||
generation. Recovery prefers `cgroup.kill`; a PID/process-group fallback is
|
||||
allowed only after positive birth-identity verification.
|
||||
|
||||
Associate the Job with an I/O completion port and use
|
||||
`JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO` plus pipe EOF for tree/drain completion.
|
||||
Persist the launcher and shell creation `FILETIME` identities before trusting
|
||||
any PID. On recovery, reopen a process only to compare creation time and
|
||||
generation evidence; if ownership cannot be proven, record a dirty incident
|
||||
rather than terminating an unrelated reused PID.
|
||||
Other Unix platforms use the same barrier plus watchdog/process-group fallback.
|
||||
Document that descendants deliberately creating a new session may escape that
|
||||
fallback. The at-most-once authorization rule still applies.
|
||||
|
||||
Fault-inject daemon/launcher death around Job creation, attribute-list process
|
||||
creation, prepared/authorized fsync, resume, requested-shell creation, and
|
||||
terminal drain. Test both supported shells, paths with spaces/non-ASCII,
|
||||
malicious `PATH`/`COMSPEC`, nested descendants, CTRL_BREAK refusal/escalation,
|
||||
Job limit rejection, and inherited-handle leaks on supported Windows versions.
|
||||
Treat root exit as the configured 5-second tree/output drain grace. Wait for
|
||||
cgroup/process-group emptiness and capture EOF; terminate residual descendants,
|
||||
drain, and sequence `OutputIncomplete` if EOF remains unprovable. Emit terminal
|
||||
lifecycle last.
|
||||
|
||||
Run build checks on every platform and dedicated Windows integration tests for
|
||||
shell selection, process-tree kill, forced termination, profile rejection,
|
||||
reconnect, and output/spool behavior. No Windows release is supported until
|
||||
these tests run on Windows CI.
|
||||
Implement portable `HUP`, `INT`, `TERM`, `KILL`, `USR1`, and `USR2`, accepting
|
||||
optional `SIG` prefixes and mapping only through `SignalKind`. Reject native
|
||||
numbers, signal zero, unsupported names, and arbitrary PID targets. Derive group
|
||||
identity only from durable supervisor metadata.
|
||||
|
||||
Poll readable `/proc` data outside pipe/network loops for CPU time, RSS, I/O,
|
||||
state, CWD, and wait reason, aggregating only clearly associated descendants.
|
||||
Missing data remains absent. Define diagnostic progress as changes in tree CPU
|
||||
ticks, cumulative I/O, retained input/output activity, or lifecycle. Clear
|
||||
`suspected_hung` on progress and never let diagnostics alter lifecycle.
|
||||
|
||||
Compile profile policy before launch. A no-profile supervisory cgroup imposes
|
||||
no limit. Profiles apply required `cpu.max`/`cpu.weight`,
|
||||
`memory.max`/`memory.high`, `pids.max`, and per-device `io.max` values while the
|
||||
launcher is blocked, then read them back. Without required delegation/control,
|
||||
permanently reject as `UNSUPPORTED`; never run partly constrained.
|
||||
|
||||
Run native Linux integration/race/crash tests mirroring the shared Windows
|
||||
suite: exact shell resolution, CWD/env, duplicate dispatch, output/stdin,
|
||||
process-group/cgroup tree cleanup, launcher death at every durable boundary,
|
||||
reconnect/replay, truncation, scripts, tombstones, queue limits, `/proc`
|
||||
absence, profile failure, and shutdown interruption. Add explicit tests for
|
||||
pidfd/`clone3` availability fallbacks and deliberate `setsid` escape behavior.
|
||||
|
||||
**Exit criteria:** the Linux client passes the same protocol/durability suite as
|
||||
Windows, plus cgroup/process-group tests, without weakening the already shipped
|
||||
Windows behavior or changing the v1 wire contract.
|
||||
|
||||
## 11. Reliability, observability, and operational delivery (Phase 8)
|
||||
|
||||
@@ -1313,6 +1545,9 @@ Provide:
|
||||
policy, working directory, file descriptor limits, and least privilege;
|
||||
- example server/client configuration files with every default and an explicit
|
||||
JSON-RPC exposure warning;
|
||||
- Windows `rvbox.exe` GUI/icon/version resources, SCM service installation,
|
||||
per-session tray registration, first-run TOML template, ACL setup, rotating-
|
||||
file-log support, code-signing hook, and SHA-256 manifest;
|
||||
- a backup/restore procedure for SQLite plus output/audit segment directories;
|
||||
- an upgrade procedure that stops dispatch safely, snapshots data, migrates,
|
||||
and verifies recovery;
|
||||
@@ -1324,8 +1559,9 @@ operationally important identity/listener/state/TLS/quota keys first in each
|
||||
section. Add `--check-config` to both daemons: it must run the production strict
|
||||
decoder, defaulting, cross-field/profile/path/shell validation, print a redacted
|
||||
normalized summary, and exit without opening stores/listeners or changing
|
||||
state. CI parses both examples with this path on Linux; Windows CI additionally
|
||||
validates the documented Windows path/shell variant.
|
||||
state. Linux CI parses the server and all-knob client reference without starting
|
||||
a client. Native Windows CI parses and normalizes `client.windows.toml` with the
|
||||
production Windows path/shell/ACL checks.
|
||||
|
||||
Use `Delegate=yes` in the Linux client systemd unit when cgroup supervision is
|
||||
enabled and create a private writable cgroup subtree for the service. Refuse a
|
||||
@@ -1340,7 +1576,10 @@ Before v1 release, review path traversal, script temp-file permissions, command
|
||||
logging, environment override redaction, malformed compression, decompression
|
||||
bombs, oversized frames, Unicode/ASCII ID validation, Unix socket ownership,
|
||||
JSON-RPC external bind warnings, SQL injection (all parameterized), segment
|
||||
record corruption, and process-group PID reuse. Add fuzz tests for envelope
|
||||
record corruption, process-group PID reuse, UAC relaunch argument handling,
|
||||
SCM service/Run-key executable and config quoting/ownership, ProgramData ACLs,
|
||||
tray file-opening paths, global single-instance behavior, and Windows inherited
|
||||
handles. Add fuzz tests for envelope
|
||||
decode, compressed output validation, segment-tail recovery, pagination tokens,
|
||||
and JSON-RPC parsing.
|
||||
|
||||
@@ -1356,10 +1595,12 @@ The recommended merge order is deliberately vertical:
|
||||
2. Phase 1: domain/config validation and state machine.
|
||||
3. Phase 2: SQLite/segments/audit/retention with recovery tests.
|
||||
4. Phase 3: server registration, fencing, heartbeat, and persisted dispatch.
|
||||
5. Phase 4: Linux client spool/supervisor and at-most-once execution.
|
||||
6. Phase 5: full WSS protocol, fault injection, and nginx end-to-end tests.
|
||||
5. Phase 4: shared client spool plus Windows supervisor/tray and at-most-once
|
||||
execution on native Windows CI.
|
||||
6. Phase 5: full WSS protocol and fault injection from native Windows to the
|
||||
Compose-hosted Linux server/nginx stack.
|
||||
7. Phase 6: gRPC Unix socket, `rvc`, and optional JSON-RPC.
|
||||
8. Phase 7: Windows supervisor and Windows CI.
|
||||
8. Phase 7: Linux supervisor/cgroup implementation and Linux client parity.
|
||||
9. Phase 8: operational assets, stress/fuzz/recovery testing, release review.
|
||||
|
||||
Do not merge a later vertical slice by stubbing a durability/safety invariant.
|
||||
@@ -1368,8 +1609,10 @@ not bypass persistence; client output may be truncated under the documented
|
||||
caps, but must never block a child pipe; and a reconnection may replay work,
|
||||
but may never re-execute an already accepted UUID.
|
||||
|
||||
The v1 release is ready only after all release-gate tests pass on clean Docker
|
||||
environments, Linux server/client end-to-end behavior matches the design docs,
|
||||
Windows support is either fully tested or explicitly not shipped, and every
|
||||
accepted limitation (self-reported identity and unauthenticated optional
|
||||
JSON-RPC) is conspicuous in deployment documentation.
|
||||
The Windows-client milestone is releasable only after Phases 0–6 plus its
|
||||
applicable Phase 8 packaging/security gates pass on native Windows and a clean
|
||||
Linux server environment. Windows support cannot be marked optional or replaced
|
||||
by cross-compilation-only checks. Full v1 is ready after the later Linux client
|
||||
also passes the common protocol/durability suite and Linux-specific cgroup/
|
||||
process tests. Every release must conspicuously document self-reported identity,
|
||||
the elevated Windows execution authority, and unauthenticated optional JSON-RPC.
|
||||
|
||||
Reference in New Issue
Block a user