16. Errors and Diagnostics (normative)#
16.1 NACK and GOODBYE#
NACK (CBOR): code (16) from the registry's ranged taxonomy, optional channel_id (15), intent_id (18), intent_seq (41), detail (17), and retry_after_ms (31) with BUSY, HUB_AT_CAPACITY and HUB_SHEDDING (REQUIRED on the last two, RFC-055).
- Ranges:
0x00xxprotocol,0x01xxsession/auth,0x02xxsubscription/QoS,0x03xxintent,0x04xxsafety refusals,0x05xxtransfer. UIs SHOULD render0x04xxdistinctly: a refusal because the machine is e-stopped is user-meaningful, not an "error". - Unknown code → treat as its range generic (§4.3).
intent_seqcorrelates a NACK to the frame that provoked it. Hubs SHOULD populate it whenever a specific inbound frame provoked the NACK; clients MUST tolerate its absence. Without it, a client with two intents in flight on the same channel cannot tell which one was refused, and must guess.detailis diagnostic, never required for machine handling. A sender truncates it tonack_detail_max_bytes(48); an over-length detail MUST NOT cause the NACK itself to vanish (§5.8-4).- NACK never closes the session by itself; GOODBYE does.
GOODBYE draws its code from the same nack_codes table. A separate code space was considered and rejected: §4.3's unknown-code handling is a range fallback, and two overlapping spaces would make the range of an unknown code ambiguous, so a forward-compatible receiver could not classify it. Codes usable as a GOODBYE reason are marked as such in the registry: NORMAL_CLOSURE, SESSION_EVICTED, DUPLICATE_INSTANCE, READY_TIMEOUT, SLOT_RECLAIMED (RFC-042, §6.6), REBOOTING, UNAUTHORIZED, and the client-sent BLOB_REFUSED (§4.5). DEADMAN_TIMEOUT and IDLE_REAPED remain registered but, since RFC-042, silence produces no GOODBYE at all (a session goes STALE, not gone) — a hub/policy combination that still wants to terminate outright on silence remains free to emit them.
16.2 Observability#
The hub exposes its own health as ordinary channels, dogfooding the protocol:
hub-status— heap, uptime, per-binding client counts,events_dropped, shed and eviction counters. It carries no firmware version: identity has exactly one home (§4.2-4, §6.3).session-rosterand the session-events channel — §12.7.- The log channel — a spec-core EVENT channel carrying
{level, tag, hub-ms, message}in itsbodysub-map, withlevelfrom the registry'slog_levels. Bounded drop-oldest with the §9.4 visible counter,backgroundpriority,watchaccess. It declares areplay_depth(defaultlog_replay_depth_default, 32), which is the named exception to §9.4's no-replay rule: on grant the hub MAY replay its ring tail, so "what went wrong just before I connected" is answerable. A hub whose logging back-end drops records before they become wire events SHOULD report that loss as a field on the next published entry rather than inventing a second event kind — there is one home for drop counters. Where a hub previously demoted its serial console on first diagnostic HTTP fetch, that handoff re-binds to the first log-channel grant.
Diagnostic verbosity beyond these channels is hub-implementation territory.