@@ -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. C lient runtime, spool, and Unix supervisor (Phase 4)
## 7. Windows-first c lient 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 launch er c reates 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` aft er p reparation,
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-outp ut
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 shortc ut.
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 lifecyc le.
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
executab le.
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 c ontrols 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, cumula tive
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 no t
keep a command alive, change terminal status, or block the supervisor .
Compile requested TOML profiles before laun ch and apply CPU, memory, ac tive
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 omi t
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
o verlays, 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
ne ver 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 o nce , p reserve/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 fsy nc, release/resume , and
terminal drain; fa ult 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. W indows client implementation (Phase 7)
## 10. L inux 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 enforc eme nt 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 s ema ntics 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 review ed
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 block ed
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: W indows supervisor and W indows CI .
8. Phase 7: L inux supervisor/cgroup implementation and L inux 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.