docs: publish archive control design set
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user