docs: publish archive control design set

This commit is contained in:
2026-07-23 13:39:09 +00:00
parent 6ca94a546e
commit 7e295cff3a
12 changed files with 1960 additions and 7 deletions
+162
View File
@@ -0,0 +1,162 @@
# Daemon protocol
The source of truth is `archive_control.v1` in the sibling
`archive-control-proto` repository. This document defines behavior that cannot
be expressed by field declarations alone. There is no separately maintained
JSON schema.
## Framing and encoding
- Each WebSocket text frame contains exactly one `Envelope` encoded with the
canonical protobuf JSON mapping.
- Binary frames, batches, unknown top-level payloads, malformed JSON, duplicate
JSON keys, non-finite numbers, invalid timestamps, and over-limit envelopes
are protocol errors.
- UUID fields use lowercase canonical UUID text. Hash fields use validated
lowercase hexadecimal text.
- Protobuf `uint64` values use JSON strings according to the protobuf JSON
mapping. Enum names are emitted symbolically.
- Receivers may ignore unknown JSON fields only after a compatible minor
version has been negotiated. An unknown required command or job operation is
rejected explicitly.
- Implementations apply negotiated envelope and inventory chunk limits before
allocation and use bounded outbound queues.
## Registration and connection ownership
`RegisterRequest` must be the first application message on every new
connection. It carries protocol version, stable client ID/name, role,
connection-instance UUID, capabilities, current active-job cursors, and the
single shared token.
The server validates the entire request and constant-time token comparison
before accepting other messages. A rejected token or malformed first message
causes a rejection when safe to send and then immediate connection closure. The
token is forbidden in all later messages and is redacted before any structured
logging, error rendering, metrics label, trace, or database write.
A valid new registration for an already connected client atomically becomes
the current connection generation. The old connection is closed, and messages
arriving from its stale generation cannot mutate state.
Clients retry registration indefinitely until accepted or the process exits.
The default delay begins at one second, doubles with jitter, and caps at 60
seconds. A connection stable for 60 seconds resets the backoff.
## Version and feature negotiation
The envelope and registration both state the sender version. Major `1` accepts
only major `1`; a different major is rejected. The negotiated minor is the
highest mutually supported minor no greater than either endpoint's advertised
minor.
Capabilities, not version guesses, gate optional behavior such as route
provisioning, sparse files, partfile migration, or eviction. A required missing
feature fails the job preflight with `UNSUPPORTED`. Compatible changes add
fields or enum values; released field numbers and enum values are never reused,
and removals reserve them. Buf breaking checks enforce file-level compatibility.
## Heartbeats and liveness
Default settings are:
| Setting | Default |
| --- | ---: |
| Registration deadline | 10 s |
| Heartbeat interval | 15 s |
| Offline threshold | 45 s |
| Unacknowledged command retry | 15 s |
| Maximum command send attempts | 3 total |
| Reconnect initial/cap | 1 s / 60 s |
| Stable reset window | 60 s |
The server communicates the effective heartbeat values in `RegisterResponse`.
Heartbeat sequence acknowledgements prove application-level responsiveness.
Missing the offline threshold marks the client offline, releases connection
ownership, and moves affected jobs to waiting; it does not fail them.
## Reliable commands
Control generates a UUID `command_id`, persists the command and intended
client, then sends it at least once. A client transactionally inserts an unseen
ID and its immutable payload before replying `ACCEPTED`. A repeat with the same
payload returns the stored acknowledgement as `DUPLICATE`; the same ID with a
different payload is a protocol conflict.
`CommandAck` confirms durable acceptance or rejection only. It never means the
step completed. Completion is represented by ordered `JobEvent`,
`InventoryChunk`, `RouteUpdate`, or `JobSnapshot` messages. Control retries an
unacknowledged command with its original ID after reconnect or timeout, up to
the configured total attempt limit. A late acknowledgement remains valid after
automatic delivery stops.
Route updates use a stable update UUID and a sequence monotonic within their
route-creation command. Conflicting duplicates or gaps are reconciled with the
client state snapshot before a route can become ready.
Every mutating command includes an expected job revision or immutable job
definition plus the expected last global event sequence. A stale revision or
sequence is rejected without side effects. The control daemon sends only one
active step command for a job and waits for its persisted completion event
before commanding the next participant. This single-writer lease lets whichever
client owns the current step allocate the next global per-job event sequence;
the following command starts from the sequence control has durably accepted.
## Events, progress, and reconciliation
Each client-originated job event has a unique ID, monotonically increasing
global per-job `sequence`, and resulting job revision. Duplicate IDs/sequences
are idempotent only when their complete content matches. Control never grants
concurrent event-writer leases for one job. A gap or conflicting duplicate
pauses destructive orchestration and requests snapshots.
`fraction_complete` is current-step progress and
`overall_fraction_complete` is the weighted five- or three-step job progress;
both are clamped to `[0,1]`. Byte counts are authoritative when known.
`STALLED` retains both fractions, byte counters, and current step. Progress
timestamp movement alone does not prove content movement.
On registration, active-job cursors provide the client's revision, last event
sequence, state, and commit flag. Reconciliation applies these rules:
1. Equal cursors resume normal delivery.
2. A client behind receives safe replay/snapshot commands.
3. Control behind requests and validates the client's full job snapshot.
4. Conflicting commit evidence reserves the resource and requires manual
reconciliation; neither side performs cleanup.
5. In-flight command IDs are compared before any resend.
## Inventory protocol
Inventory is requested only for a user flow, job preflight, active-job
monitoring, route discovery, or reconciliation. Registration does not upload a
full torrent inventory.
Queries declare a scope, resource IDs/filter, page bounds, and optional expected
revision. Responses form an atomic snapshot stream identified by query ID,
snapshot ID, revision, monotonically increasing chunk index, and final marker.
Control commits the result only after every chunk through `last_chunk` is
present. A disconnect or error discards the partial snapshot. Full content
trees are fetched for one chosen resource and from both relevant endpoints
before staging. File entries carry index, logical/allocated/completed bytes,
selection, sparse/padding flags, and the resource content revision required to
validate the preview.
## Stable errors
Machine behavior uses `ErrorCode`, `retryable`, and structured details; the
human message explains the concrete condition without secrets. Transient
unavailability/timeouts may wait or retry. Invalid input, stale fingerprints,
path conflicts, permissions, unsupported formats, integrity failures, and
unsafe state are permanent for the current attempt. Unknown internal errors
must not be mislabeled retryable.
## Manifest encoding
`TransferManifest` and `ReadyMarker` use the same canonical protobuf JSON
mapping, written atomically. The manifest describes immutable source/target
identity, route, requested set, target baseline, transfer delta, file paths and
sizes, sparse flags, materialization provenance, fingerprints, and auxiliary
artifacts. The ready marker is written last and includes a SHA-256 digest of the
exact manifest bytes. A receiver ignores a job directory until both exist and
the digest matches.