docs: finalize rvbox v1 design and implementation plan
This commit is contained in:
+101
-31
@@ -14,8 +14,10 @@ sizes before allocation/decompression.
|
||||
|
||||
The protobuf package is `rvbox.v1`. Registration negotiates a major/minor
|
||||
protocol range: incompatible majors are rejected; the highest shared minor is
|
||||
chosen. New fields are append-only. A peer ignores unknown optional fields but
|
||||
must respond with `PROTOCOL_ERROR` to an unknown required envelope feature.
|
||||
chosen. New fields are append-only. A peer ignores unknown optional fields. A
|
||||
new required behavior requires a negotiated minor-version change; an envelope
|
||||
with no recognized payload is a `PROTOCOL_ERROR` rather than an implicit
|
||||
“required unknown field” mechanism that protobuf cannot represent.
|
||||
|
||||
## Heartbeat and reconnect
|
||||
|
||||
@@ -33,11 +35,19 @@ reconciles only commands the server still considers non-terminal.
|
||||
|
||||
## Session fencing
|
||||
|
||||
`ClientHello` starts registration. `ServerWelcome` gives the selected version,
|
||||
random `session_id`, and `session_generation`. Except `ClientHello`, all
|
||||
envelopes carry those values. A new accepted registration fences and disconnects
|
||||
the prior one for the same client ID. The server accepts messages only from the
|
||||
current generation; command dispatches also identify their intended generation.
|
||||
`ClientHello` starts registration and includes a random `client_instance_id`
|
||||
generated once in the client state directory and retained across daemon restarts
|
||||
and reconnects. `ServerWelcome` gives the selected version, random `session_id`,
|
||||
and `session_generation`. Except `ClientHello`, all envelopes carry those
|
||||
values. A new accepted registration from the same instance fences and
|
||||
disconnects its prior session. A different instance claiming the same live
|
||||
client ID is rejected and recorded as the current pending claim unless an
|
||||
operator explicitly authorized that exact instance. The one-shot authorization
|
||||
expires after 5 minutes by default and is consumed by the matching reconnect.
|
||||
When no session for that client ID is live, a new instance is accepted normally.
|
||||
This is collision protection, not peer authentication. The
|
||||
server accepts messages only from the current generation; command dispatches
|
||||
also identify their intended generation.
|
||||
|
||||
## Reliable work flows
|
||||
|
||||
@@ -45,53 +55,113 @@ current generation; command dispatches also identify their intended generation.
|
||||
|
||||
The server persistently creates a command before dispatching `CommandDispatch`.
|
||||
It redelivers until it receives `CommandAccepted`. Clients durably deduplicate
|
||||
on `issue_uuid`. A client whose queue is full sends a capacity rejection.
|
||||
on `issue_uuid`. A client whose queue is full sends a transient capacity
|
||||
rejection, which the server requeues with backoff. A permanent validation or
|
||||
unsupported-platform rejection makes the server command terminal `rejected`;
|
||||
it is not retried or mislabeled as a launched-process failure.
|
||||
An exact UUID/request-hash hit in the compact tombstone ledger returns
|
||||
`CODE_ALREADY_EXECUTED`; the server suppresses dispatch and reconciles its stale
|
||||
state instead of representing the prior execution as a new rejection.
|
||||
|
||||
Following registration the server sends `ReconcileRequest` only for its
|
||||
non-terminal commands for that client. The client replies with a
|
||||
`ReconcileSnapshot` per requested known command, or `unknown_to_client`. It
|
||||
continues normal event retransmission from the server's last acknowledged event
|
||||
sequence. Terminal history already confirmed by the server is deliberately
|
||||
excluded.
|
||||
Following registration the server sends `ReconcileRequest` containing its
|
||||
non-terminal commands for that client. The client replies once with a complete,
|
||||
bounded `ReconcileSnapshot` of every command it still retains, including
|
||||
terminal-but-unacknowledged records and matching requested tombstones, then
|
||||
continues normal retransmission from the server's last acknowledged event
|
||||
sequence. The server does not dispatch new work until this snapshot is complete.
|
||||
For healthy client storage, absence means the client never durably accepted the
|
||||
UUID: server `queued`/`dispatched` work returns to `queued`, while absence of an
|
||||
`accepted`/`running` record is an invariant failure and becomes interrupted.
|
||||
A matching UUID/request-hash tombstone always suppresses replay. A client-known
|
||||
server-missing active command, or a contradiction with server-confirmed
|
||||
terminal history, is terminated locally after the server returns a durable
|
||||
`ReconcileResult` and creates a recovery incident rather than inventing server
|
||||
state. That result also identifies client-retained terminal records the server
|
||||
has already stored or deliberately tombstoned, allowing the client to discard
|
||||
them even when its last `EventAck` was lost. The result is idempotent and is
|
||||
written before any new dispatch on that session. A client
|
||||
with an unresolved essential-store incident must not claim a complete snapshot
|
||||
or accept work until repaired/acknowledged.
|
||||
|
||||
### Output and events
|
||||
|
||||
Client execution events use an increasing `event_seq`; retries reuse the same
|
||||
sequence and content. The server durably writes an event before sending
|
||||
`EventAck`. `EventAck` is cumulative through a sequence number. Output is
|
||||
Zstandard compressed with an explicit original-size field. Server storage can
|
||||
reuse the validated compressed bytes.
|
||||
Client execution entries first have a durable local order. They receive an
|
||||
increasing wire `event_seq` durably when admitted to the bounded send window;
|
||||
once assigned, the sequence/content is pinned until acknowledged and retries
|
||||
reuse it exactly. The server durably writes an event before sending `EventAck`.
|
||||
`EventAck` is cumulative through a sequence number. Output is Zstandard
|
||||
compressed with an explicit original-size field. Server storage can reuse the
|
||||
validated compressed bytes.
|
||||
|
||||
When rolling output removes old retained segments, the server writes
|
||||
`OutputTruncation` metadata containing the removed event/byte ranges. Queries
|
||||
must show that marker rather than silently presenting an apparently complete
|
||||
stream. An offline client that reaches a cap sends `ClientOutputTruncated`
|
||||
before replaying its retained tail on reconnect. The server records it, permits
|
||||
the named event-sequence gap, and exposes it in output queries. Truncation
|
||||
metadata is not an execution event and does not consume an `event_seq`.
|
||||
stream. Assigned but unacknowledged events occupy the pinned 1 MiB per-command
|
||||
send window and are not evicted. An offline client that reaches a cap replaces
|
||||
one or more still-unsequenced output runs in its durable local order with
|
||||
`OutputTruncation`; when admitted to the send window the marker receives the
|
||||
next normal `event_seq`. Its event-range fields are absent because the discarded
|
||||
bytes never had wire sequences. The normal cumulative `EventAck` acknowledges
|
||||
the marker. Server-created retention markers include their removed event range
|
||||
and remain query metadata because the server cannot allocate client sequences.
|
||||
|
||||
If a capture pipe cannot be drained to a provably complete EOF, the client emits
|
||||
a sequenced `OutputIncomplete` event before the terminal lifecycle event. It
|
||||
does not invent a missing sequence or byte count for bytes it never observed.
|
||||
|
||||
### Stdin and signals
|
||||
|
||||
`StdinWrite` is binary-safe and ordered by `write_seq`; the client durably
|
||||
deduplicates it and returns `StdinAck`. `append_newline` is true by default in
|
||||
the CLI but explicit on the wire. `CloseStdin` is a separate idempotent action.
|
||||
Signal and cancellation requests carry a command revision to settle start/kill
|
||||
races.
|
||||
deduplicates it and returns a sequenced stdin-acknowledgement command event.
|
||||
`append_newline` is true by default in the CLI but explicit on the wire.
|
||||
`CloseStdin` is a separate idempotent action. Signal and cancellation requests
|
||||
carry a command revision to settle start/kill races; the resulting lifecycle or
|
||||
signal-result event echoes that revision. Control-plane `request_id` values stay
|
||||
at the server and map to the assigned revision rather than crossing the agent
|
||||
protocol.
|
||||
|
||||
### Scripts
|
||||
|
||||
For a script command, dispatch first contains a `ScriptDescriptor`; then the
|
||||
server sends `ScriptChunk` messages and a commit. The client checks offset,
|
||||
chunk order, full length, and SHA-256 before it reports upload complete or
|
||||
launches the process. A retransmitted chunk is idempotent by offset/content.
|
||||
launches the process. `ScriptUploadStatus.received_bytes` is a cumulative
|
||||
durable progress acknowledgement: the server sends at most the command's
|
||||
unacknowledged window, waits for progress, and resumes from that offset. A
|
||||
retransmitted chunk is idempotent by offset/content.
|
||||
|
||||
Acceptance is durable queue admission, not proof that every later preparation
|
||||
step will succeed. A permanent script checksum/write error or failure to prepare
|
||||
the requested executable emits terminal `rejected` before any launch
|
||||
authorization. User cancellation in that interval emits `cancelled`; an
|
||||
uncertain crash window emits `interrupted`. None is mislabeled as process
|
||||
`failed`.
|
||||
|
||||
## Flow control and failure containment
|
||||
|
||||
No receive loop runs an executor, database write, decompressor, or slow socket
|
||||
operation inline. Each side has bounded staging queues. Durable spools are the
|
||||
source of truth and are charged to the 10 MiB/50 MiB/1 GiB compressed retention
|
||||
budgets described in the architecture document. A full staging queue pauses the
|
||||
related read/dispatch path and drains from disk; it never grows without bound.
|
||||
source of truth and are charged to the tiered unified command-storage budgets
|
||||
described in the architecture document. Senders keep a bounded unacknowledged
|
||||
window of encoded wire bytes (defaults: 1 MiB per command and 8 MiB per client
|
||||
session); unsent data
|
||||
waits durably. WebSocket Ping/Pong/Close plus fencing and protocol errors use a
|
||||
reserved priority lane and a dedicated writer with bounded data-frame size and
|
||||
write deadlines. A full essential ingress queue does not stall the sole
|
||||
WebSocket reader indefinitely:
|
||||
the receiver closes without acknowledgement and the durable sender retries
|
||||
after jittered reconnect. Droppable output uses the sequenced overload-loss
|
||||
path instead.
|
||||
|
||||
Before compression, raw output uses the architecture's high/low watermarks.
|
||||
Once a client high watermark is crossed, still-unsequenced droppable chunks are
|
||||
summarized in durable local order instead of consuming unbounded compression
|
||||
work. A server at its ingress high watermark does not acknowledge or discard an
|
||||
already sequenced event; it closes without acknowledgement if its bounded queue
|
||||
cannot admit the frame, and the client retries from durable spool. Fair
|
||||
scheduling prevents one verbose command from monopolizing workers. Reserved
|
||||
metadata capacity remains
|
||||
available to close each gap and emit the final lifecycle event.
|
||||
|
||||
Malformed protobuf, over-size payload, invalid compressed data, impossible
|
||||
sequence, bad session token, or protocol-version violation yields a structured
|
||||
|
||||
Reference in New Issue
Block a user