# Distributed Operation (`gemel` Phase 6)
Native synchronization is **separate from Git interchange** (brief §47): Git remains a
deterministic projection; this document specifies how Gemel repositories exchange the
canonical object graph directly. A second transport implementation must be possible
from this specification alone.
## 1. Principles
- **Content addressing is the negotiation.** An object exists iff its identity exists
locally. "What do you have / what do you want" are id-set questions, never byte
comparisons. Re-push transfers nothing; a resumed fetch re-negotiates from the new
have-set (resumability falls out of negotiation).
- **Refs are the only mutable state exchanged**, and every ref update is validated
before publication: names must be public refs, and the referenced object closure
must be fully present (a ref never dangles).
- **Integrity is per-record and end-to-end.** Every envelope is re-verified against its
advertised identity by the receiver before insertion; the store rejects same-id
different-bytes as a fatal conflict (THREAT_MODEL.md §11).
- **Transport success is not trust.** A transport may be hostile; integrity
verification is the receiver's job. Authentication/authorization is the transport's
concern (§6).
- **Full content travels.** Unlike the Git-carried exchange profile (carrier-backed),
native sync carries source blobs: the destination reconstructs the complete graph.
- **Pull never overwrites divergence.** `pull` is fetch + fast-forward; a diverged
local head is preserved and reconciliation is the explicit next step.
## 2. Refs policy
Refs are partitioned into **public** (sync) and **local-only** (never sync):
| `refs/head` | `refs/mappings/*` (interchange bookkeeping) |
| `refs/state/head` | `refs/exchange/*` (imported-frontier markers) |
| `refs/config` | `refs/remotes/*` (tracking, local) |
| `refs/names/*` | workspace state files (not refs) |
| `refs/trajectories/*` (incl. `current`) | |
| `refs/cases/*`, `refs/releases/*` | |
| `refs/checkpoints/*`, `refs/reconciliations/*` | |
| `refs/semantic/*` (per-state, current, head) | |
The remote advertises its public refs. `fetch` records them under
`refs/remotes/<name>/<flat-name>` (e.g. `refs/names/C1` → `refs/remotes/origin/names/C1`)
without touching local refs. `push` updates the remote's public refs. `pull` fast-forwards
local public refs to the remote values only when the remote head descends from (or
equals) the local head.
## 3. Transfer pack format — `gemlpack` (GMLP v1)
Distinct from the Git-carried `GXPK` exchange pack: GMLP carries full canonical
envelopes (including blobs) and is the native wire format.
```
MAGIC "GMLP" 4 bytes
FORMAT_VERSION 0x01 u8
OBJECT_COUNT u64 (LE)
TOTAL_BYTES u64 (LE) (envelope bytes only)
RECORD 1..N
object_id 33 bytes (family code + BLAKE3 digest)
envelope_length u64 (LE)
canonical_envelope_bytes [envelope_length]
```
Invariants:
- Records appear in ascending canonical id byte order.
- The advertised id must equal `BLAKE3(envelope)`; the decoded family must equal the
id's family.
- Duplicate ids are rejected.
- Limits (defaults): 4 GiB per pack, 10,000,000 objects per pack, 1 GiB per object —
enforced during decode before allocation.
- A single failing record invalidates the whole pack: refs are never published over a
partially verified transfer.
The pack bytes themselves receive `PackId = BLAKE3(exact_pack_bytes)` for caching and
resume bookkeeping; the pack id is transport metadata, not a Gemel object identity.
## 4. Protocol messages
The transport trait (Rust `sync::Transport`) defines six operations. Message
parameters are id sets and refs; there are no executable payloads (SEC; THREAT_MODEL
§10).
| `list_refs` | remote → client | the remote's public refs |
| `reachable_ids(seeds)` | remote → client | the remote's reachable closure of the seeds (ids only) |
| `missing_ids(ids)` | remote → client | which of the ids the remote lacks |
| `fetch_objects(ids)` | remote → client | verified envelopes for the ids |
| `push_objects(records)` | client → remote | envelopes; the remote verifies each identity and rejects conflicts |
| `update_refs(refs)` | client → remote | atomically publish refs (names validated, closures present) |
### 4.1 Fetch
1. `list_refs` → remote public refs.
2. `reachable_ids(ref values)` → remote closure.
3. `want = closure − local present` (by identity).
4. `fetch_objects(want)` → envelopes; verify `BLAKE3(envelope) == id` and insert
(dedup/conflict-safe).
5. Publish `refs/remotes/<name>/<flat>` for every remote ref — only after the whole
transfer verified.
### 4.2 Push
1. Local public refs.
2. `reachable_ids(local ref values)` → local closure (computed locally).
3. `missing_ids(closure)` → what the remote lacks.
4. `push_objects(missing)` → remote verifies and stores.
5. `update_refs(local public refs)` → remote validates names + presence, applies
journaled-atomically.
### 4.3 Pull
`pull = fetch + fast-forward`: after fetch, the remote head must equal or descend from
the local head (causal-parent walk, bounded). On divergence the pull refuses and
preserves local refs; the fetched tracking refs remain available for `gemel
reconcile`.
## 5. Concurrency and locking
- Object insertion is lock-free and content-addressed: concurrent writers cannot
conflict on objects (identical ids deduplicate; different ids coexist).
- Refs updates use the journaled ref transaction under the repository's write lock on
each side (STORAGE.md §4). The remote's own lock protects it from concurrent local
writers.
## 6. Transports and security
- `FileTransport` (the shipped transport) addresses a local filesystem path to an
initialized Gemel repository; `gemel remote add --init` creates one. Authentication
is the filesystem's; the protocol still verifies every byte.
- Network transports (SSH/HTTP) implement the same six operations; TLS is mandatory for
non-local remotes (THREAT_MODEL.md §10); credentials never travel in packs.
- Integrity ≠ authenticity: a verified object is byte-exact, but the producer's
identity is carried by the object's own producer field, not by the transport.
- Git-only remotes: `gemel push`/`pull` against a path that is a Git repository (not a
Gemel one) use the deterministic export/import projections (GIT_INTEROP.md §3–§4);
the loss documented there applies.
## 7. CLI surface
```
gemel remote list configured remotes
gemel remote add <name> <path> [--init]
gemel remote remove <name>
gemel fetch <remote> transfer objects + write refs/remotes/<name>/*
gemel push <remote> update the remote's public refs
gemel pull <remote> fetch + fast-forward (never overwrites divergence)
```
All commands support `--json` with the `gemel.query.v1` envelope. A remote argument
may be a configured name or a direct path.