7. Frame Types
All frames are JSON objects with a type field (string).
Implementations MUST silently ignore frames with unrecognised type values to allow forward
compatibility.
7.1 Frame Type Registry
The table is rendered from the normative machine-readable frame registry; its own shape is validated by frame-registry.schema.json.
| Type | Layer | Gated | Status | Schema | Fields |
|---|---|---|---|---|---|
| client-hello | 2 | No | core | handshake.schema.json | First authenticated handshake offer. |
| server-hello | 2 | No | core | handshake.schema.json | Server offer, negotiated extensions, transcript proof and key confirmation. |
| client-finish | 2 | No | core | handshake.schema.json | Transcript hash, client proof and key confirmation. |
| cmb | 3/4 | SVAF | core | cmb-frame.schema.json | Signed CAT7 record assertion. |
| cmb-encrypted | 2/3 | After decrypt | core | encrypted-cmb-frame.schema.json | Directional ChaCha20-Poly1305 envelope with session, sequence, routing metadata and sealed record bytes. |
| control-encrypted | 2 | After decrypt | core | control-encrypted.schema.json | Directional ChaCha20-Poly1305 envelope sealing one control frame; shares the session's per-direction sequence with cmb-encrypted. |
| cmb-anchors | 4 | Sealed only | core | control-frame.schema.json | The first sealed frame a node sends on every Core Secure session it admits: the cognition keys of the sender's own records about to be replayed as context, or none (§9.4); changes no verification or admission. |
| cmb-fetch | 3 | No | core | cmb-fetch.schema.json | Request a record by exact content address. |
| cmb-fetch-result | 3 | No | core | cmb-fetch-result.schema.json | Correlation id with the keys returned and the keys not found; each returned record travels before it as its own cmb-encrypted frame. |
| authority-statement | 3 | Authority | core | authority-frame.schema.json | One signed grant, revoke or endorse statement, identified by the hash of its canonical bytes; it counts only as §6.6 resolution places it. |
| authority-digest | 3 | No | core | authority-frame.schema.json | The sender's authority root and in-force count, for anti-entropy (§6.6.8). |
| authority-fetch | 3 | No | core | authority-frame.schema.json | Asks for statements by id, with their chains, or for a page of the responder's live set in authority order (§6.6.8). |
| authority-set | 3 | Authority | core | authority-frame.schema.json | Answers an authority-fetch with statements in authority order; each is verified as gossip is, never on the session's word. |
| mood | 4 | Sealed only | core | control-frame.schema.json | Session-scoped mood text and context, sealed in control-encrypted on a confirmed Core Secure session; attributed to the session's proven peer; never stored, relayed or remixed (§9.3). |
| room-join | 2 | Sealed only | core | room-join.schema.json | Owner-signed room-join grant presented after the handshake; admits the session to a gated room when it binds the proven key (§5.8.1). |
| peer-info | 2 | No | core | control-frame.schema.json | Authenticated-room peer and wake-channel gossip. |
| wake-channel | 2 | No | core | control-frame.schema.json | Platform wake-channel registration. |
| error | 2 | No | core | control-frame.schema.json | Protocol error code, message and optional bounded detail. |
| ping | 2 | No | core | control-frame.schema.json | Peer keepalive request. |
| pong | 2 | No | core | control-frame.schema.json | Peer keepalive response. |
| relay-auth | 1 | Relay auth | transport | relay-frame.schema.json | Authenticate a node to a relay. |
| relay-peers | 1 | No | transport | relay-frame.schema.json | Relay peer directory. |
| relay-ping | 1 | No | transport | relay-frame.schema.json | Relay keepalive request. |
| relay-pong | 1 | No | transport | relay-frame.schema.json | Relay keepalive response. |
| relay-reauth | 1 | No | transport | relay-frame.schema.json | Request a fresh relay-auth frame. |
| relay-peer-joined | 1 | No | transport | relay-frame.schema.json | Relay presence notification. |
| relay-peer-left | 1 | No | transport | relay-frame.schema.json | Relay departure notification. |
| relay-error | 1 | No | transport | relay-frame.schema.json | Relay-level error: message, with kind, code (the close that follows) and reason (§4.4.9). |
| role-grant | legacy | No | legacy | — | Retired time-replayed role grant (sym 0.14.0 and earlier drafts). Confers nothing; MUST NOT emit; ignore on receipt. See §6.6.11 migration. |
| role-revoke | legacy | No | legacy | — | Retired time-replayed role revoke with a cutoff (sym 0.14.0 and earlier drafts). MUST NOT emit; ignore on receipt. See §6.6.11 migration. |
| state-sync | deprecated | No | legacy | — | Reserved legacy type. MUST NOT emit; ignore on receipt. |
| message | legacy | No | legacy | — | Legacy unsigned application message. |
| xmesh-insight | legacy | No | legacy | — | Legacy runtime projection. |
All cognitive content — observations, decisions, feedback, directives — MUST be sent
as cmb frames. Only cmb frames
enter SVAF evaluation, produce anchor weights, and modulate CfC state.
The registry distinguishes Core Secure wire types from retained legacy and runtime-extension
types. A Core Secure participant MUST complete the three-frame authenticated handshake before it
accepts cmb or cmb-encrypted.
It MUST NOT silently downgrade to the legacy one-frame handshake
or to an unsigned application frame.
The relay-* types are transport-scope (Section 4.4):
they are exchanged between a node and a relay, never between peers, and never reach the application
layer. The relay forwards peer frames as opaque payloads (Section 4.4.4) and does not originate any
of the peer-scope types above.
Sealed control frames. Only the frames a session protects are bound to it. A frame in clear can be
read and injected by whatever sits on the path — over a relay, the relay itself and, before relay
authentication proves keys, any client that can claim a nodeId there (§4.4.1, §4.4.4).
In a Core Secure session that is CONNECTED (§5.3), a sender MUST therefore send every peer-scope frame
other than ping and pong sealed:
a record in cmb-encrypted, and every other frame —
peer-info, wake-channel,
mood, cmb-fetch,
cmb-fetch-result, cmb-anchors (§9.4), room-join (§5.8.1), the four authority frames
(authority-statement, authority-digest,
authority-fetch, authority-set, §6.6.8),
error and every negotiated extension frame — as the inner frame of a
control-encrypted envelope (§18.2.1). A receiver MUST discard any of
those frames that arrives in clear on a CONNECTED session, with one exception: it MAY read an
error in clear, as the informational frame it always is (§7.2), but a clear
error changes nothing. It closes no session, moves no sequence and changes no state, because whatever sits on the
path can forge it, and a receiver cannot tell whether its sender still holds a session. The one clear error a node
acts on is 1011 UNKNOWN_SESSION, which MAY prompt a new handshake and nothing
else (§5.2.2). A sealed error is the peer’s own: one whose action is Close (§7.2) ends the session. ping
and pong MAY travel in clear or sealed, and a receiver handles a sealed one
as liveness, exactly as a clear one, and answers a sealed ping with a
pong, in clear or sealed (§5.4). The inner frame of a control-encrypted
envelope MUST NOT be a handshake frame (including the legacy handshake),
cmb, cmb-encrypted,
control-encrypted, state-sync or a
relay-* frame. A sealed frame that opens advances its direction’s sequence
(§18.2.1) whether or not its inner frame is then accepted; refusing the inner frame changes nothing else.
Session-bound frames speak only for the session’s peer. wake-channel,
mood, cmb-fetch,
cmb-fetch-result, cmb-anchors,
room-join, authority-digest and
authority-fetch take their sender from the session: the proven nodeId of
the peer whose keys opened them. A receiver MUST attribute them to that nodeId and the name the handshake bound
to it (§5.2). None of them carries a sender field, and a receiver MUST NOT take a sender from inside a
frame. peer-info describes other nodes;
its entries are hints. A receiver MAY use a gossiped wake channel to try a wake, but MUST NOT let it replace
a wake channel the named node registered over its own session, and MUST NOT bind an identity or a key from it
(§18.3). Sealing proves who sent a frame, not that what it says about third parties is true.
Author-signed frames carry their own authority. A frame that carries a signed statement takes its authority
from that signature, never from the session that delivered it: a signed statement may be relayed over any session,
and the session only transports it. Sealing it still keeps it from the relay, and from injection. The key it is
verified against depends on what it is. An authority statement, carried in authority-statement
or authority-set (§6.6.3), is verified by §6.6.9 and §18.3.2: against the
subject key of the grant its authorisedBy names, or the pinned anchor keys, under the one
verification rule, and never against a key a registry or a session binds to a nodeId. A record (§18.3.1), and an
extension’s author-signed frame such as a sym-attest-v1 attestation (§16.4), is
verified against the key the receiver binds to the nodeId it names (§3.4), never against the key of the session
that delivered it.
Deprecated — state-sync. The
state-sync frame carried a node’s hidden-state vectors
(h₁, h₂). Per the hidden-state locality invariant
(Section 2.7),
hidden state MUST NOT cross the wire. Implementations MUST NOT emit state-sync
and SHOULD ignore it on receipt. It is retained in this registry only to reserve the type and document the
deprecation; all peer influence flows through cmb frames evaluated by SVAF.
7.2 Error Frame
When a node encounters a protocol-level error, it SHOULD send an error frame
before closing the connection (if applicable). Error frames are informational — the receiving
node MUST NOT treat them as commands. A clear error changes nothing (§7.1). A sealed error is the session
peer’s own notice: one whose Action is Close ends that session at the receiver too; any other sealed error is
information.
| Code | Name | Action | Description |
|---|---|---|---|
| 1001 | VERSION_MISMATCH | Close | Peer version is incompatible |
| 1002 | DIMENSION_MISMATCH | Reject frame | Vector dimension mismatch (legacy state-sync; deprecated — see §2.7) |
| 1003 | FRAME_TOO_LARGE | Close | Frame exceeds MAX_FRAME_SIZE |
| 1004 | HANDSHAKE_TIMEOUT | Close | No handshake within deadline |
| 1005 | DUPLICATE_NODE | Close | nodeId already connected (Legacy Import only: Core Secure never refuses a session that proves the bound key, §3.4) |
| 2001 | SVAF_REJECTED | None | Memory-share rejected by SVAF (informational) |
| 1006 | AUTHENTICATION_FAILED | Close | Transcript signature or key confirmation failed |
| 1007 | ROOM_MISMATCH | Close | Authenticated room does not match the local room |
| 1008 | REPLAY_DETECTED | Close | Encrypted-frame counter repeated or moved backwards. Retained for compatibility: a receiver now discards a replayed frame and keeps the session (§18.2.1), so a conformant node does not send it |
| 1009 | IDENTITY_CONFLICT | Close | A nodeId bound to one identity key proved a different key (§3.4); recorded and reported. The refused side does not retry automatically |
| 1010 | SESSION_CLOSED | Close | The sender closed the session for a reason no other code names; the reason is in message (§5.2.2) |
| 1011 | UNKNOWN_SESSION | None | The sender holds no session for the frame it received; prompts the client to re-handshake, never closes a session (§5.2.2) |
Codes 1xxx are connection-level: they concern a connection or a session, and most close it; the Action column says which. Codes 2xxx are evaluation-level (informational). Error-frame codes are a registry of their own, separate from the relay’s WebSocket close codes (§4.4.9): an implementation MUST NOT send an error frame with a code in the 4000–4999 range, which those close codes occupy. Error frames MUST NOT contain sensitive information.
7.3 Type Naming and Extensions
Frame types are identified by their type string value.
Core types (this specification) MUST NOT be redefined by extensions.
Extension types MUST use <extension>-<name> format.
Vendor types MUST use x-<vendor>-<name> format
and MUST be silently ignored by non-supporting nodes.
Q&A
Why MUST nodes silently ignore unknown frame types?
Without this rule, you can never add new features to the protocol. If a node crashes or rejects unknown frame types, then deploying a new extension (like mesh rooms) requires upgrading every node on the mesh simultaneously — impossible in a peer-to-peer system. Silent ignore means old nodes and new nodes coexist: a node running a new extension sends its frames, and nodes that don’t support the extension simply ignore them. No crash, no error, the mesh keeps working. When a node adds support later, it handles the frame. No coordinated upgrade needed. This is the same principle used by HTTP (unknown headers ignored), TCP (unknown options skipped), and HTML (unknown tags ignored). Every successful protocol is evolvable because of this rule.
What happens if a relay receives an unknown frame type?
The relay forwards it. The relay is a dumb transport pipe — it wraps the payload in a { from, fromName, payload } envelope and sends it to the target or broadcasts it. It never inspects the payload type. This means extension frames (room, vendor, future types) flow through the relay without any relay changes. The intelligence is at the endpoints, not the transport.
Can an extension frame break an existing node?
No, if the node follows Section 7. The frame handler switches on msg.type. Unknown types fall through with no match and no action. The node’s cognitive state, memory, and coupling are unaffected. This is a hard requirement — implementations that reject or error on unknown types are non-conformant.