166 lines
8.0 KiB
Markdown
166 lines
8.0 KiB
Markdown
# 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 `2` accepts
|
|
only major `2`; 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. Assignment is represented solely by
|
|
the durable assignment `CommandAck`; it produces no `JobEvent`. The control
|
|
daemon sends only one active step command for a job and waits for its persisted
|
|
completion event before commanding the next participant.
|
|
|
|
## Events, progress, and reconciliation
|
|
|
|
Each client-originated job event has a unique ID, monotonically increasing
|
|
global per-job `sequence`, resulting job revision, and the `command_id` of its
|
|
owning `ExecuteStepCommand` or `CancelJobCommand`. Duplicate IDs/sequences are
|
|
idempotent only when their complete content matches. Control verifies the
|
|
client, acknowledged command lease, expected cursor, and active step before
|
|
accepting it. A gap, conflict, or stale lease triggers a durable authoritative
|
|
`ReconcileJobCommand`: the client retires only the named stale command leases
|
|
and restores that job cursor, without deleting resource data or unrelated job
|
|
state.
|
|
|
|
`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 an authoritative reconciliation command.
|
|
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.
|