NACK and GOODBYE codes#
There is one code space, not two. A GOODBYE code is drawn from this
same table.
A receiver that meets an unknown code treats it as the generic code of its range, taken from the high byte. That fallback is why a second, overlapping space was rejected. With two spaces, the range of an unknown code is ambiguous.
0x00xx: protocol#
The frame itself is unusable.
| Code | Name | Meaning |
|---|---|---|
0x0000 |
MALFORMED |
undecodable frame/CBOR |
0x0001 |
UNSUPPORTED_VERSION |
HELLO proto_ver not servable |
0x0002 |
FRAME_TOO_LARGE |
exceeds negotiated max_frame |
0x0003 |
PROFILE_VIOLATION |
CBOR not in deterministic profile |
0x01xx: session and authorization#
The session cannot proceed as asked.
| Code | Name | Meaning |
|---|---|---|
0x0100 |
BUSY |
refused per-request work (concurrent blob transfers, §8.4 row 4); carries retry_after_ms. RFC-055 moved session admission to HUB_AT_CAPACITY; a client meeting BUSY on a HELLO (a pre-RFC-055 hub) treats it as HUB_AT_CAPACITY. |
0x0101 |
UNAUTHORIZED |
token invalid/revoked |
0x0102 |
NOT_CONTROLLER |
control op without controller role |
0x0103 |
PAIRING_REQUIRED |
controller requested, no token, pairing window closed |
0x0104 |
PAIRING_DENIED |
bad pin_proof or pairing window closed |
0x0105 |
SESSION_EVICTED |
admin kick (GOODBYE code). RFC-051 narrowed this from slow-consumer stalls, which now PARK the session instead of evicting it (see never_shed_stall_eviction_ms) — distinct from DUPLICATE_INSTANCE, which already had its own code for the other eviction door. |
0x0106 |
DUPLICATE_INSTANCE |
instance_id already in live session; old session evicted instead: see §6.8 |
0x0107 |
NORMAL_CLOSURE |
clean voluntary teardown (GOODBYE code, either direction): not an error |
0x0108 |
DEADMAN_TIMEOUT |
hub-initiated session teardown: silence exceeded the deadman window (§11.3, GOODBYE code) |
0x0109 |
REBOOTING |
hub is committing a change by rebooting and is closing every session first (RFC-020/022.2, GOODBYE code). Preceded by an ECHO carrying reboot_in_ms; on return the changed boot_id tells clients what happened. |
0x010A |
READY_TIMEOUT |
session never sent CATALOG_READY within catalog_ready_timeout_ms (RFC-015, GOODBYE code). Needed because liveness reaping NEVER fires on a client that PINGs happily but never finishes adopting the catalog: it would hold a slot forever with both planes gated shut. |
0x010B |
NOT_READY |
frame refused because the session has not sent CATALOG_READY yet (RFC-015). READY gates BOTH planes: pre-READY INTENTs are NACK'd, not queued, because a client acting before it has adopted the retained safety latch breaks §11.5(2). |
0x010C |
IDLE_REAPED |
RFC-039.4: hub-initiated teardown of a NON-OWNING session that fell silent past idle_reap_multiplier x ping_interval_idle_ms (RFC-024, GOODBYE code). Distinct from DEADMAN_TIMEOUT on purpose: reaping a dark viewer is housekeeping with zero motion consequence, and before this code existed it was reported with the motion-safety code: a reaped dashboard read as a deadman event in every log and client. RFC-042: silence no longer reaches this code directly; it marks a session STALE instead (session_event_kinds.4), so the reference hub no longer emits DEADMAN_TIMEOUT or IDLE_REAPED for silence; both stay registered for a hub/policy combination that still wants to terminate outright. |
0x010D |
SLOT_RECLAIMED |
RFC-042: a HELLO that would otherwise NACK HUB_AT_CAPACITY (BUSY before RFC-055) instead evicted a STALE session to make room (lowest access tier first, tie-break longest continuously stale): best-effort GOODBYE code, since the reclaimed session was stale for a reason and may never receive it. Distinguishable from SESSION_EVICTED (admin kick only, since RFC-051) and from DEADMAN_TIMEOUT/IDLE_REAPED (which no longer fire for silence at all). |
0x010E |
HUB_AT_CAPACITY |
RFC-055 (§6.3): HELLO refused because max_sessions are in use (incumbents are never degraded to admit a newcomer). REQUIRES retry_after_ms; usable as a GOODBYE code. A client MUST NOT retry sooner and MUST apply jitter. |
0x010F |
HUB_SHEDDING |
RFC-055 (§6.3): HELLO or connection refused because the hub is protecting itself (resource pressure, stalled handshakes, a shedding policy). REQUIRES retry_after_ms; usable as a GOODBYE code. Never applies to ESTOP or its connectionless forms. |
0x02xx: subscription and QoS#
The subscription request is refused.
| Code | Name | Meaning |
|---|---|---|
0x0200 |
UNKNOWN_CHANNEL |
channel id not in catalog |
0x0201 |
ACCESS_DENIED |
channel access level above session role |
0x0202 |
CLASS_MISMATCH |
e.g. SUBSCRIBE to an INTENT channel |
0x0203 |
SUB_LIMIT |
per-session subscription cap reached |
0x0204 |
SUBSCRIBE_REJECTED |
RFC-033.2: the SUBSCRIBE frame as a WHOLE could not be processed (undecodable, or more wishes than max_subscriptions_per_frame): as opposed to the per-channel codes above, which reject one wish and grant the rest. detail carries the reason. Exists because the alternative was observed silence: a dropped SUBSCRIBE leaves a healthy-looking LIVE session with zero STATE, which presents as a client rendering bug and cost two debugging nights. |
0x0205 |
CHANNEL_WITHDRAWN |
RFC-077 (§8.6): UNSOLICITED, one per withdrawn subscription or publication grant, carrying channel_id, when a user-space channel is removed (an accessory forgotten or its declaration replaced). Silence is not an option: a subscription that silently stops presents as a rendering bug. |
0x03xx: intent#
The intent is refused on its own merits.
| Code | Name | Meaning |
|---|---|---|
0x0300 |
CONFLICT |
precondition (cfg_gen CAS) failed |
0x0301 |
RATE_LIMITED |
ingress intent rate exceeded |
0x0302 |
INVALID_VALUE |
outside schema min/max or wrong type; also a store import whose kind or size the hub refuses (RFC-021.5) |
0x0303 |
UNSUPPORTED_OP |
intent op not implemented on this hub |
0x0304 |
NETWORK_JOIN_FAILED |
RFC-069: a provisioning wifi_join (§13.9) did not join (wrong passphrase, no such network, timeout). detail MUST NOT contain either credential. The hub's prior network configuration stays in effect. |
0x0305 |
ACCESSORY_OFFLINE |
RFC-076 (§8.10): a write to a paired accessory that is not reachable right now (absent, or no answer after the §13.3 retransmits). Its channels stay in the catalog; it is offline, not gone. |
0x0306 |
ACCESSORY_CAPACITY |
RFC-077 (§8.10): accessory-admin window_open refused because the host has no free slice, no free peer entry, or less budget than the smallest legal declaration (the status entry plus one channel). |
0x0307 |
TRIAL_CONFLICT |
RFC-099 (§9.3): a write, trial or durable, to a key in ANOTHER session's open trial set. The whole intent is refused, no key applied. Retry after that trial is committed or reverted (meta.trial_pending shows it). Not SOURCE_CONFLICT: that is a safety-range code naming a motion source. |
0x04xx: safety refusal#
The machine refuses on safety grounds. A client SHOULD render these distinctly.
| Code | Name | Meaning |
|---|---|---|
0x0400 |
ESTOP_ACTIVE |
refused while e-stop latched |
0x0401 |
NOT_HOMED |
motion intent before homing |
0x0402 |
INTERLOCK |
hub-specific safety interlock |
0x0403 |
SOURCE_CONFLICT |
another session owns this arbiter source |
0x0404 |
TAKEOVER_REQUIRED |
control exists; retry with takeover flag |
0x0405 |
CLEAR_REFUSED |
e-stop clear conditions not met (§11.2) |
0x05xx: transfer#
A chunked transfer failed.
| Code | Name | Meaning |
|---|---|---|
0x0500 |
CHUNK_UNAVAILABLE |
blob chunk index out of range, or a store/slot that does not exist WITHIN a registered namespace (generalized from 'catalog chunk' by RFC-021; the catalog is now namespace 0). An unregistered NAMESPACE itself is the more specific INVALID_NAMESPACE (0x0504, RFC-049e): this code no longer covers that case. |
0x0501 |
REASSEMBLY_TIMEOUT |
fragment reassembly abandoned (5 s) |
0x0502 |
ETAG_MISMATCH |
static-profile client etag != hub catalog etag |
0x0503 |
BLOB_REFUSED |
RFC-039.2: a RECEIVER refusing a declared blob (total_bytes over its reassembly budget), sent as a GOODBYE code by the client rather than idling in a half-session. The observed failure: a client's DoS-guard cap silently refused a grown catalog's transfer header and the session went LIVE WITH NO CATALOG: no error anywhere, every STATE frame undecodable, READY_TIMEOUT eventually killing it 15 s later and blaming the client. Refusal is legal; SILENT refusal is not. |
0x0504 |
INVALID_NAMESPACE |
RFC-049e: a BLOB_REQ naming a blob.ns value not in the registered blob_namespaces table (0 catalog / 1 store; 128-255 device-defined are legal too: only a value truly outside every registered/device range trips this). Split out of CHUNK_UNAVAILABLE so a client can tell 'namespace does not exist' from 'namespace exists, item does not' (§18-8's panel-flagged gap) instead of receiving the same code for both. Same range as its siblings because it is a transfer-request refusal, not a new error class. Implementation: Phase D. |