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

FrameDirectionRole
helloc → sOpen a subscription. Carries client_id, ack_commit, shape_version, and subscription_id.
catch_upc → sResume from ack_commit after a gap or reconnect.
shapess → cThe live shape registry for this domain.
snapshots → cProjected tree at a commit. Used for bootstrap paging and compact catch-up.
commits → cAn accepted operation with its commit number and last_mutation_id.
synceds → cCatch-up barrier at commit. Not a freshness lease.
mutatec → smutation_id, advisory base_commit, shape_version, op.
ack / nacks → cTerminal result for that mutation id. Nack includes a reason.
denieds → cSubscription or session is not allowed to continue.
want / havebothHydrate elided slice bodies. have includes fields, optional extension, and hash.
stream_open / chunk / want / rejectbothLive prefix. Chunks never enter the commit log.
presence / presence_gonebothLast-write-wins ephemeral map keyed by client id.
artifacts → cReplaceable derived cover for an elided prefix, or null.
private_views → cFull replacement of a derived permission view for this subscription.

Sync loop

  1. Client calls beginConnection(), then sends hello.
  2. Server sends shapes, then either a projected snapshot plus tail or the log since ack_commit.
  3. Live streams and current presence are included for catch-up.
  4. Server ends with synced { commit }.
  5. Client applies commits in order, buffering ahead-of-head frames, and becomes synchronized when 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.