Reference
Protocol
Frames and operations are JSON envelopes. The codec is the trust boundary for structure. Application adapters interpret extension payloads, stream units, and presence JSON.
Identifiers
clientId, domainId, mutationId, sliceId, and commit are branded strings or numbers from src/protocol. Ordinary slice and mutation ids are locally generated UUIDs. A mutation keeps its id across retries. Catalog slugs and reaction uniqueness keys in the demo are intentional deterministic exceptions.
ROOT_ID is the domain root. SYSTEM_ID is reserved for trusted migrations and is not a user client. The host also reserves bootstrap, delivery, and command for commits it authors; a hello claiming any of these is refused.
Operations
set_field { slice_id, field, value }
add_child { parent_id, slice_id, fields }
remove_child { parent_id, slice_id }
migrate { from, to }
set_handle { slice_id, handle }
extension { slice_id, extension, version, payload }
stream_end { slice_id, epoch, status, prefix, metadata }
extension names a locally installed adapter. Core checks that the slice's shape declares that extension, then delegates. stream_end.status is complete, aborted, or superseded. The server replaces client terminal data with its accepted prefix before committing. Durable replay uses the logged prefix and does not need live heap state.
apply requires commit === state.head + 1. A shape version below the floor or above the known version is rejected. Older operations may be upcast by the schema adapter.
Handles
inline
elided { hash, size }
external { key, hash, size } // blob in R2; not a domain
mounted { domain_id }
You cannot elide half a slice. Paging is a child-list window (keep_last), not a byte range inside one body. External keys are names, not expiring URLs. Mounted ids are names, not capabilities.
Frames
| Frame | Direction | Role |
|---|---|---|
hello | c → s | Open a subscription. Carries client_id, ack_commit, shape_version, and subscription_id. |
catch_up | c → s | Resume from ack_commit after a gap or reconnect. |
shapes | s → c | The live shape registry for this domain. |
snapshot | s → c | Projected tree at a commit. Used for bootstrap paging and compact catch-up. |
commit | s → c | An accepted operation with its commit number and last_mutation_id. |
synced | s → c | Catch-up barrier at commit. Not a freshness lease. |
mutate | c → s | mutation_id, advisory base_commit, shape_version, op. |
ack / nack | s → c | Terminal result for that mutation id. Nack includes a reason. |
denied | s → c | Subscription or session is not allowed to continue. |
want / have | both | Hydrate elided slice bodies. have includes fields, optional extension, and hash. |
stream_open / chunk / want / reject | both | Live prefix. Chunks never enter the commit log. |
presence / presence_gone | both | Last-write-wins ephemeral map keyed by client id. |
artifact | s → c | Replaceable derived cover for an elided prefix, or null. |
private_view | s → c | Full replacement of a derived permission view for this subscription. |
Sync loop
- Client calls
beginConnection(), then sendshello. - Server sends
shapes, then either a projectedsnapshotplus tail or the log sinceack_commit. - Live streams and current presence are included for catch-up.
- Server ends with
synced { commit }. - Client applies commits in order, buffering ahead-of-head frames, and becomes
synchronizedwhen head matches and the buffer has no gap.
If any descriptor names keep_last and the client is at ack 0, catch-up sends a snapshot of the current window and skips the log so replay cannot re-inflate elided children. A reconnect with a later ack still gets the log tail. This is bootstrap paging, not a complete partial-sync protocol. See Limits.
Mutation envelope
{ mutation_id, base_commit, shape_version, op }
The server applies against current head, not against base_commit. Base is advisory. Duplicate mutation ids return the original result. A nack is explicit so the client can distinguish rejection from loss. Denied or missing policy without a nack leaves the outbox in place for recovery.
Private views
{ type: "private_view", subscription_id, name, revision,
status: "ready", shape_id, value }
// or status: "unavailable" | "removed", without value
The revision belongs to the subscription, not the content commit clock. Session ignores another generation and old or equal revisions. Private values never enter shared trees, logs, snapshots, hashes, artifacts, policy caches, or Session.dump().
Shapes
A registry has version, floor, root, and descriptors. A descriptor lists field presence, an optional homogeneous child policy, and an optional extension reference. Child policy may set keep_last, an artifact reference, and elide_keep fields that survive elision (the canvas keeps user_id so erase can name a stroke in the hole).
Core ships a minimal record registry. The demo installs workspace, home, channel, document, and canvas descriptors at shape version 3. Changing a custom registry does not automatically migrate existing domains.
Codec
encodeFrame / decodeFrame and encodeOp / decodeOp accept JSON text or values. Unknown fields are preserved for forward compatibility. Numbers must be finite. The host rejects oversized inbound text (1 MiB) before decode. Decode failures are observed and do not apply.