# 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.