ADR-002: Naming Matrix for Protocol Types
- Date: 2025-11-22
- Status: Accepted
- Tags: protocol, transport, runtime
Context
- Names for ids, timestamps, frame kinds, and wire keys have drifted across packages, which makes interop harder and confuses new contributors.
- We need a single source of truth that pins type names, property names, and wire-level keys, including how higher-level SDK concepts map onto protocol frames.
- The matrix should be concise enough to live near specs and code but explicit enough to guide future changes and AI-assisted edits.
Decision
- Canonicalize on Peer as the intrinsic identity, using
PeerIdfor type names andpeerIdfor properties. - Adopt the following naming matrix as the canonical reference for protocol-level types, wire keys, and SDK-facing naming.
- Wire keys (
t,id,peerId,caps,s,b) are internal to encode/decode; public TypeScript types expose descriptive names. - Use these names in code, docs, tests, and public APIs; deviations require an ADR update.
Rationale: PeerId avoids collision with Node.js ecosystem terminology and aligns with modern P2P standards (libp2p, IPFS, WebRTC). NodeId created semantic ambiguity in browser contexts and confused process identity with network-visible peer identity.
1. Identity, timestamps, correlation
| Domain concept | Type / Enum name | Field / Property | Wire key | Notes / invariants |
|---|---|---|---|---|
| Peer identity | PeerId | peerId | peerId | Stable identity of a peer node; survives reconnects |
| Connection identity | ConnectionId | connectionId | - | Transient transport link identity; new per TCP/WebSocket |
| Session identity | SessionId | sessionId | - | Optional higher-level session across reconnects |
| Frame identity | FrameId | frameId | id | 16 opaque bytes; identifies this frame instance on wire |
| Correlation identity | CorrelationId | correlationId | - | Reserved for v2. |
| Trace identity (req) | TraceId | traceId | - | Optional, spans multi-frame flows |
frameId— In v1, used for request/response correlation and ACK linkage. Always present on every frame (16 bytes, binary); auto-generated at construction to eliminate defensive checks in runtime/RPC layers.correlationId— Reserved for v2 when explicit tracing or multi-hop flows require separate correlation semantics. Helper:generateFrameId()creates a 128-bit (16 bytes) FrameId viacrypto.getRandomValues(). Human-readable representation (for logs/JSON) usesframeIdToHex()→ lowercase hex (32 chars).
2. Frames and variants
| Domain concept | Type / Enum name | Field / Property | Wire key | Notes |
|---|---|---|---|---|
| Frame discriminant | FrameKind | kind | t | Enum: Control, Message, Ack, Error |
| Base frame | Frame | - | - | Union of all frame variants |
| Control frame | ControlFrame | kind: FrameKind.Control | t=0 | Handshake / Ping / Pong / Close |
| Message frame | MessageFrame | kind: FrameKind.Message | t=1 | Carries routable application data (RPC + pub/sub)¹ |
| Ack frame | AckFrame | kind: FrameKind.Ack; ackFrameId | t=2 | Acknowledges another frame² |
| Error frame | ErrorFrame | kind: FrameKind.Error | t=3 | Represents protocol or app errors |
- Message frames carry routable application data (RPC + events). The peer SDK works with frames directly — there is no separate wrapper type.
ackFrameIdreferences the target'sframeId.
3. Control operations and payloads
| Domain concept | Type / Enum name | Field / Property | Wire key | Notes |
|---|---|---|---|---|
| Control op | ControlOp | op | c | Handshake, Ping, Pong, Close |
| Control data | - | data | - | Optional opaque binary |
| Handshake payload | HandshakePayload | - | - | Contains peerId, caps, metadata |
cis scoped toControlFramepayloads only; never used in other frame types.datais an optional binary blob used for control frame metadata or handshake extensions.
4. Message frame and subjects
| Domain concept | Type / Enum name | Field / Property | Wire key | Notes |
|---|---|---|---|---|
| Routing key | Subject | subject | s | UTF-8 string; see channel subject rules below |
| Raw message bytes | - | data | b | Uint8Array; wire is opaque bytes |
| Wire message (frame) | MessageFrame | subject, data | t=1 | Routable, identity-bearing unit on the wire |
Channel subject rules (see ADR-006 for full RPC envelope spec):
| Subject | Purpose | Dispatch by | Semantics |
|---|---|---|---|
rpc | All RPC request/response | envelope.m | Request/Response |
event | All fire-and-forget events | envelope.e | Notification |
stream | Streaming (reserved for v2) | — | (future) |
app/* | Vendor-specific / custom | Subject/custom | Custom |
Note: rpc, event, and stream are exact-match channels. The app/ prefix supports arbitrary sub-paths. Subjects are transport-level mux keys; method and event identity MUST live in the envelope (m, e), not in the subject.
5. Errors
| Domain concept | Type / Enum name | Field / Property | Wire key | Notes |
|---|---|---|---|---|
| Error code enum | ErrorCode | code | - | Protocol range 1000–1099; RPC range 1100–1199 |
| RPC error code enum | RpcErrorCode | code | - | RPC-layer codes in @sideband/rpc |
| Error frame details | ErrorFrame | message, details | - | Optional details (binary); see note below |
detailsis an optional binary/structured payload (e.g. JSON, CBOR) carried alongside the error message. Application errors use code range 2000+.
6. Capabilities and metadata
| Domain concept | Type / Enum name | Field / Property | Wire key | Notes |
|---|---|---|---|---|
| Capabilities list | - | capabilities | caps | string[], feature flags or protocol extensions |
| Peer metadata | - | metadata | - | Record<string, string> namespaced keys |
7. Protocols and packages
| Layer | Protocol | Abbreviation | Package | Notes |
|---|---|---|---|---|
| App Framing | Sideband Protocol | SBP | @sideband/protocol | Topology-agnostic application frames |
| Relay Session | Sideband Relay Protocol | SBRP | @sideband/secure-relay | E2EE tunnel via relay server |
| Direct Session | Sideband Direct Protocol | SBDP | @sideband/direct (future) | P2P session via DTLS or similar |
SBP is the base protocol used by all session layers. SBRP wraps SBP frames with E2EE for relay mode; SBDP (future) will secure SBP frames for direct P2P.
| Package | Notes |
|---|---|
@sideband/transport | Shared interfaces, no env-specific logic |
@sideband/transport-ws | WebSocket transport (Browser/Node/Bun) |
@sideband/rpc | Typed RPC envelope layer over SBP frames |
@sideband/runtime | Peer lifecycle, routing, subscriptions |
@sideband/peer | High-level publish/subscribe/RPC API |
8. RPC and pub/sub (higher level)
| Domain concept | Type / Enum name | Field / Property | Notes |
|---|---|---|---|
| RPC envelope (union) | RpcEnvelope | t discriminant | Discriminated union in MessageFrame.data (ADR-006) |
| RPC request | RpcRequest | t: "r", m, cid, p? | Method + params; cid = frame's frameId |
| RPC success response | RpcSuccessResponse | t: "R", cid, result? | Correlation via cid (ADR-010) |
| RPC error response | RpcErrorResponse | t: "E", cid, code, message | data? for structured error details |
| RPC response (union) | RpcResponse | — | RpcSuccessResponse | RpcErrorResponse |
| RPC notification | RpcNotification | t: "N", e, d? | Fire-and-forget; no cid |
9. Helper API verbs (for AI assistants)
| Operation | Function name | Notes |
|---|---|---|
| Generate frame ID | generateFrameId | () -> FrameId (16-byte binary) |
| Frame ID to hex | frameIdToHex | (FrameId) -> string (32-char lowercase) |
| Frame ID from hex | frameIdFromHex | (string) -> FrameId (validates 32 hex) |
| Encode frame to bytes | encodeFrame | Frame -> Uint8Array |
| Decode bytes to frame | decodeFrame | ArrayBufferView -> Frame |
| Create message frame | createMessageFrame | (subject, data, opts?) -> MessageFrame |
| Create error frame | createErrorFrame | (code, message, details?, opts?) -> ErrorFrame |
| Create handshake frame | createHandshakeFrame | (data: Uint8Array, opts?) -> HandshakeControlFrame |
AI usage hints (suitable as code comments)
- Use
Frame/FrameKind/kindfor protocol-level variants.- Use
peerIdfor stable peer identity;connectionIdis per link,sessionIdspans reconnects.- Use
frameIdto identify frames;cidin RPC envelopes for request/response correlation (ADR-010).- Map wire fields (
t,id,peerId,caps,s,b) only inside encode/decode; never expose them in public TS types.
Consequences
- All packages align on shared names and wire keys with PeerId as the intrinsic identity, reducing ambiguity in docs, code, and AI-assisted edits.
- Eliminates naming collision with Node.js ecosystem and aligns with libp2p/IPFS/WebRTC conventions.
- Every frame has a
frameIdcreated bygenerateFrameId()at construction. This eliminates defensiveframeId === undefinedchecks throughout runtime and RPC layers, improving code clarity and reducing bugs. - Future additions (new frame kinds, error codes, capability keys) must extend this matrix and update the ADR.
- Tests and examples should assert the public property names while keeping wire keys scoped to codecs.
- Adding
connectionId/sessionIdis optional but recommended when distinguishing links or resumable sessions; codecs should keep those out-of-band unless explicitly encoded.