Wire protocol¶
The wire protocol defines how two TOVIO replicas — a client and a relay, or two peers — negotiate, authenticate, and exchange content-addressed objects and refs over a network. It governs the on-wire bytes and message exchange; the objects in flight are framed exactly as in the storage format, so an object hashes the same on disk and in transit.
This page summarizes wire major tovio-wire/1 — frozen and Stable, with later
additions carried as byte-compatible minor extensions.
Scope: sync, not authorization¶
The protocol covers the sync surface only: discovering which objects a peer lacks, transferring them, and converging mutable refs. It deliberately does not cover policy evaluation, key distribution, or the collaboration UI. A bare server that speaks this protocol is a relay: it authenticates sessions and enforces the wire's admission and write gates, but it is not the Key Authority and cannot turn protected ciphertext into plaintext.
The same logical protocol runs in three roles:
- client ↔ relay — the primary hosted-server case;
- peer ↔ peer — two clients syncing directly, symmetric, with no privileged server;
- bridge — import/export against a Git remote (a translation layer in front of the protocol, not part of it).
Transport and security¶
Every transport runs over TLS 1.3 or higher. There is no plaintext mode, even on localhost. An endpoint implements two transports and can add a third:
| Mode | Scheme | Notes |
|---|---|---|
| Native binary | tovio://host:7743 |
Length-delimited binary frames over one TLS 1.3 stream. High-throughput path; required. |
| TOVIO-over-HTTPS | https://host/tovio |
HTTP/2 request/response, for restricted networks; required. |
| gRPC mirror | tovio+grpc://host:port |
Same message set; optional. |
A hosted Forge behind a CDN can additionally carry the same four canonical messages as discrete REST calls (advertise, want, objects, refs), driving the identical algorithm so a REST peer converges byte-for-byte with a binary peer. Implemented transports preserve the same logical protocol semantics; availability still depends on the endpoint's supported profile, authorization, admission limits, and transport configuration.
TLS provides confidentiality and MITM resistance for the transport. It is not the object-integrity mechanism — that is the per-object re-hash on arrival (see below).
The handshake¶
Every connection begins with a HELLO / HELLO_ACK exchange. No data message may be
sent before it completes.
HELLOcarries the supported wire versions, the 32-byterepo_id(the BLAKE3 address of the repository's genesis marker — stable across every replica), the connecting identity, an identity proof, and a fresh anti-replay nonce. Optional, additive fields — each omitted-when-absent, so a peer that skips them produces byte-identical frames — carry a path scope, a promisor filter, the ABS write proofs for a push, a Forge connect token, an agent session grant, and the post-handshake intent. There is no capability-set field; capability negotiation is deferred.HELLO_ACKselects the connection version, echoes therepo_id, says whether the connection was accepted (with an error code when not), and presents the responder's pinned public key, its own nonce, and a channel-bound proof.
Authentication is of identities, not accounts. The IdentityProof is an Ed25519
signature over a domain-separated tuple that includes a TLS-exporter value, binding the
proof to this TLS session (channel binding) so a captured proof cannot be replayed onto
another connection. When the identity is an agent, HELLO carries its signed
agent session grant, and the responder verifies the agent's
registration, the grant's repository and time bindings, the root-to-leaf capability chain,
and the declared fetch/push operation before servicing any request.
A crucial boundary: authentication is not decryption authority. Completing the handshake is only the first gate. The responder may still apply repository, path-scope, agent-session, quota, closure, and other admission checks before transferring an object. Receiving protected ciphertext grants no ability to decrypt it; recipient eligibility and key distribution are outside this protocol.
The sync algorithm: HAVE → WANT → OBJECTS → REFS¶
Sync proceeds in four phases after the handshake, realizing sparse-by-default transfer. The provider — the side holding the objects — always advertises first, and the requester replies with what it lacks:
HAVE— advertise reachable state. The provider advertises its live ref tips and the addresses of its reachable objects (plus any active metadata objects, such as the policy manifest), so the requester can compute exactly what it is missing.WANT— select missing. The requester diffs the advert against its own store and returns the set of addresses it lacks.OBJECTS— selective fetch. The provider streams only the wanted objects, in one or more batches, dependencies-first where possible. Transfer is resumable per batch.REFS— verify and merge. Each object is verified on arrival; only once all objects for a ref are present and verified does the receiver apply the ref update — so a ref never points at a commit whose objects are absent.
Sparse and path-scoped transfer¶
A sync transfers only the HAVE→WANT set difference; sending the full object graph
when a sparse difference suffices is non-conformant. A requester may further declare a
path scope (["src/**", "docs/api/**"]) on HELLO, and the provider then transfers only
the trees, blobs, chunks, and manifests reachable under those paths — a subset, never a
rewrite, and still verify-on-arrival and hash-exact. A promisor-backed partial view (a
blobless or shallow clone) rides a separate, orthogonal HELLO filter that selects
object classes, sizes, and history depth rather than paths; the two compose, and the objects
it leaves out are backfilled by single-object WANTs on first read.
Set reconciliation is exact. Today's HAVE carries the enumerated reachable address
list directly and the requester diffs it locally; the compact frontier-walk and
range-digest summaries are specified as future accelerations, and any acceleration falls
back to exact resolution for a range whose fingerprints differ. No optimization may ever
omit a needed object.
Object framing and verify-on-arrival¶
Objects move in a thin transfer frame wrapping the canonical storage-format bytes:
ObjectFrame {
type_tag: u8 // object type tag from the data-model registry; 0 = unknown/relayed
length: u32 (BE) // byte length of payload
hash: bytes(32) // BLAKE3-256 address of payload
payload: bytes // the stored bytes — exactly what the receiver hashes and stores
}
The payload is exactly the bytes the receiver will hash and store — no transport-level
re-encoding of the object itself. Every received frame is re-hashed with BLAKE3 and
compared to its claimed address before it is written or acknowledged. On mismatch the
object is discarded and E_HASH_MISMATCH is raised. Integrity therefore does not depend
on trusting the transport or the relay: a tampered object — even from a compromised relay
— fails the check and is rejected.
Objects with an unknown type_tag are relayed verbatim (forward compatibility): the
receiver still verifies the hash but never decodes them.
Refs are CRDTs, not file overwrites¶
Lane tips and tags sync as CRDT state, not as overwrites:
- Lanes are Last-Writer-Wins registers keyed by a Hybrid Logical Clock; on merge, the maximal HLC wins, ties break deterministically by actor. Merge is commutative, associative, and idempotent, so reordered or replayed exchanges converge.
- Tags are immutable; a
REFSmessage that re-points an existing tag is rejected withE_TAG_IMMUTABLE, and a fetching peer keeps its own tag on a divergent re-point. The spec reserves an audited, ABS-authorized force re-point, but that over-the-wire path is not built yet — tags distribute relay → peer, and a push never auto-bundles local tags. - Deletions travel as tombstones on the same register, so a concurrent delete and update resolves deterministically rather than silently resurrecting or losing a ref.
No divergence error exists. The protocol never defines, returns, or relies on a "push rejected — non-fast-forward" condition. Divergent lane tips are a normal state: the CRDT merge converges the pointer at the wire layer, and the client then reconciles the divergence on the lane into a two-parent merge — or a first-class conflict object carried by the reconciliation commit — so neither tip is left off-lane. Work continues without blocking.
Push, fetch, sync, and write enforcement¶
All three operations are the same four-phase algorithm with different directions:
- fetch pulls objects and refs the client lacks; it does not advance server refs.
- push uploads objects and advances server refs.
- sync is fetch + push in one session — and, by the no-divergence guarantee, never fails because "the other side moved."
For content writes, the wire adds gates that do not require decryption. They operate alongside session authentication, repository admission, agent scope, quotas, ref preconditions, and any deployment-level authorization:
- Write-policy proof. A push to a path governed by a
write_policymust carry an Attribute-Based Signature proving the pusher holds satisfying attributes without revealing which. The proofs ride inHELLO; the relay verifies them against the clear-readable policy manifest and refuses a missing or invalid proof withE_WRITE_DENIED. - Protected-lane conflict gate. A push that would advance a protected lane to a
commit whose tree is not conflict-free is rejected with
E_CONFLICTED_LAND, mirroring the client-side land gate so it cannot be bypassed by pushing directly. Conflict-freeness is read from object structure alone — no decryption. - Change-control advance gate. On a Forge, a push that would advance a served lane
with a change touching a
change_controlpath in the manifest is refused withE_REVIEW_REQUIREDand routed to a proposal. This check is content-dependent, so it runs as a Forge-edge admission step before the ref merge; a bare relay does not run it.
A push may also carry an obliteration with its authorization proof; the relay verifies the proof, replaces the payload with the tombstone at the same address, and audits the action. Agent tokens never authorize obliteration.
Policy objects transfer as ciphertext only¶
policy-blob and policy-chunk objects move with their ciphertext intact. A relay without recipient
keys cannot decrypt those protected payloads. The envelope and policy-manifest expose the metadata
needed to store, route, and evaluate the object, including the fact that a path is protected. Public
content, repository/ref metadata, operational records, runner inputs, and agent context explicitly
disclosed to a service have different trust boundaries and are not made secret merely by using TOVIO.
See the crypto envelope and policy & tokens for the
cryptographic and authorization detail.
Errors and resumability¶
Any phase may end with an ERROR carrying a code, a retriable flag, and context. The
protocol-level codes include E_VERSION_UNSUPPORTED, E_REPO_UNKNOWN, E_AUTH_FAILED,
E_TOKEN_INVALID, E_FRAME_INVALID, E_HASH_MISMATCH, E_TAG_IMMUTABLE,
E_WRITE_DENIED, E_CONFLICTED_LAND, E_SCOPE_VIOLATION, E_RATE_LIMITED,
E_TRANSFER_INTERRUPTED, and the additive E_REVIEW_REQUIRED. The shipped ErrorCode
enum implements eight of those twelve, plus E_REVIEW_REQUIRED and
E_PROTECTED_CLEARTEXT (a push carrying a clear object at a read-protected path);
E_TOKEN_INVALID,
E_SCOPE_VIOLATION, E_RATE_LIMITED, and E_TRANSFER_INTERRUPTED are reserved for
surfaces not yet wired. Notably absent: any code for ref divergence — divergence is
data, not an error.
Transfer is resumable at object granularity. Because every object is content-addressed
and independently verified, an interrupted transfer leaves only fully verified objects in
the store; on reconnect the requester re-runs HAVE→WANT and the already-stored objects
simply fall out of the new WANT set. No partial or unverified object is ever committed,
so recovery never produces corruption.
Byte-level precedence
Where this page and the storage format appear to differ on the bytes of an object, the storage format governs.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure