status & errors — the cross-cutting result taxonomy

In one paragraph

Two closed sets, one per direction. tr::graph::status_t is what a local call answers with: every read / write / await and every field operation returns tr::graph::result_t<T>, which is std::expected<T, status_t>. tr::wire::err_t is what a peer is told: a registered u16 code carried in the ERROR TLV, plus registry-side severity and disposition that never travel on the wire. Neither set is extensible — an application’s own failures are application data, not protocol errors.

What it does

Every other module on this site depends on this one, and nothing here depends on anything else: the interface map draws status as the sink of the dependency graph. That is the whole point of keeping it separate — the codec, the graph and the routing plane can all name a failure without agreeing on anything else.

The two taxonomies answer different questions:

  • status_t answers a caller. It is the L4 control-surface code: the path did not resolve, the payload’s type does not fit the vertex, the queue is full, the await deadline expired, the ACL refused. It is returned by value, never thrown, which is what lets the whole runtime build with -fno-exceptions.

  • err_t answers a peer. It is the frozen tr::<concept>::<error> registry: a stable u16 that identifies the failure across implementations and languages, so a node written in Rust and a node written in C++ report a truncated frame with the same bytes. err_severity_t and err_disposition_t are registry-side metadata — how bad, and whether a retry could help — and are deliberately not on the wire, so a receiver’s policy can differ without a protocol change.

Both sets are closed. An application that wants to report “the sensor is unplugged” writes that into a vertex; it does not mint a protocol error. The wire codes and their meanings are specified in reference §protocol TLVs §error codes; this page is the C++ surface for them.

Interface

A local call, a peer-visible failure, and the conversion between the two:

namespace tr::graph {
enum class status_t { NOT_FOUND, INVALID_PATH, TYPE_MISMATCH, /* … */ };
template <class T> using result_t = std::expected<T, status_t>;
constexpr const char* to_string(status_t) noexcept;
}

namespace tr::wire {
enum class err_t : std::uint16_t { FRAME_TRUNCATED = 0x0001, /* … */ };
constexpr std::string_view    err_path(err_t) noexcept;        // "tr::frame::truncated"
constexpr err_severity_t      err_severity(err_t) noexcept;
constexpr err_disposition_t   err_disposition(err_t) noexcept;
}

err_path is the bridge to the human-readable registry name — the same tr::<concept>::<error> string the reference suite and the Wireshark dissector use, so a log line and a spec section can be matched by eye.

Checksums

The frame trailer’s integrity codes live beside the taxonomy because they are the other thing every layer needs and no layer owns: CRC-32C (Castagnoli) for the 32-bit trailer and CRC-16-CCITT for the narrow one. Both are incremental — a state object fed span by span — because a rope is decoded link by link and the CRC must cover bytes that are never contiguous in memory. Which one a frame carries is an opt-bit decision described in frame-codec and specified in reference §data format.

Pitfalls

  • status_t is not err_t renamed. They overlap in meaning, not in membership: BACKPRESSURE is a local answer, FLOW_BACKPRESSURE is a wire code, and a forwarder that maps one to the other does so explicitly. Do not cast between them.

  • Severity and disposition are receiver policy, not protocol. They are compiled into the registry so every node agrees on a default reading; they are not transmitted, so a node is free to treat a TRANSIENT code as fatal.

  • result_t<void> is the success-only shape. A call that returns nothing on success still returns a result_t — discarding it discards the failure.

  • A link that did not come up is TRANSPORT_DOWN, not NOT_FOUND. The two read alike locally and diverge on the wire: NOT_FOUND becomes tr::path::not_found, whose registry disposition is PERMANENT, while tr::transport::down is TRANSIENT. Spending NOT_FOUND on a refused dial or a listener that could not bind therefore tells a correct peer to stop retrying a link that would have come back — which is what the built-in transport factories did until #929.

API reference

enum class tr::graph::status_t

The L4 control-surface error code — a closed, protocol-only set (ADR-0010).

The read/write/await surface returns std::expected<T, status_t>. These names mirror the documented protocol error codes (docs/reference/05 §error codes) and reconcile with the tr:: error namespace when RFC-0002 lands. Not for user error codes.

Values:

enumerator NOT_FOUND

Path doesn’t resolve / no last-known-value.

enumerator INVALID_PATH

Malformed path or non-UTF-8 NAME segment.

enumerator TYPE_MISMATCH

Payload type incompatible with the vertex/field.

enumerator BACKPRESSURE

Queue full / dispatch-depth cap hit.

enumerator TIMEOUT

Await deadline expired.

enumerator SCHEMA_NOT_FOUND

Field read/write on a vertex that doesn’t expose it.

enumerator PERMISSION_DENIED

ACL rejected (ALLOW-only ACEs + INHERIT, core subset).

enumerator PATH_IN_USE

Registration collided with an existing vertex.

enumerator TRANSPORT_DOWN

A link could not be brought up — a dial, bind, or handshake that failed.

The transport-plane counterpart of NOT_FOUND, and deliberately NOT the same member: NOT_FOUND says this address does not resolve, which the wire registry reads as PERMANENT, while a refused connect or a listener that could not bind is TRANSIENT — retrying it may succeed (wire::err_t::TRANSPORT_DOWN, wire::err_disposition_t). Collapsing the two told a peer to stop retrying a link that would have come back (#929).

template<class T>
using tr::graph::result_t = std::expected<T, status_t>

The L4 result type: std::expected<T, status_t>.

Success is the value side of the expected; an empty STATUS=OK on the wire maps to a result_t with a value (or result_t<void> success).

constexpr const char *tr::graph::to_string(status_t s) noexcept

The stable lower-case wire name of a status_t (e.g. "not_found").

Structurally exhaustive, and deliberately so (#876): the switch carries neither a default: label nor a fall-through tail, so -Wswitch — an error under the -Werror=switch the library compiles with — names this switch the moment an enumerator is added to status_t without a name here. The retired return "unknown"; tail turned that into a nameless status at runtime, the reporting twin of the wire mislabel the error_code bridge carried.

Warning

s must be a status_t enumerator. Every status the library produces is one — a status is minted from the enumerators and is never cast in from wire bytes (the wire’s own registry is wire::err_t) — so the end of this function is unreachable by construction, and a call that reaches it in a constant expression is a compile error.

enum class tr::wire::err_t : std::uint16_t

Registered protocol error codes (RFC-0002 §D) — the u16 wire identity of each built-in tr::… error path, carried LE in the ERROR TLV’s first-child VALUE.

Values:

enumerator FRAME_TRUNCATED

tr::frame::truncated

enumerator FRAME_INVALID

tr::frame::invalid

enumerator FRAME_CRC_FAIL

tr::frame::crc_fail

enumerator TLV_NESTING_TOO_DEEP

tr::tlv::nesting_too_deep — exceeds this RECEIVER’s decode resources (RFC-0006; depth is resource-bounded, never a constant).

enumerator PATH_NOT_FOUND

tr::path::not_found

enumerator PATH_INVALID

tr::path::invalid

enumerator PATH_IN_USE

tr::path::in_use

enumerator SCHEMA_TYPE_MISMATCH

tr::schema::type_mismatch

enumerator SCHEMA_NOT_FOUND

tr::schema::not_found

enumerator FLOW_BACKPRESSURE

tr::flow::backpressure

enumerator FLOW_TIMEOUT

tr::flow::timeout

enumerator FLOW_ADDRESS_SHIFT_GAP

tr::flow::address_shift_gap

enumerator ACCESS_DENIED

tr::access::denied

enumerator TRANSPORT_DOWN

tr::transport::down

enumerator VERSION_MISMATCH

tr::version::mismatch

enum class tr::wire::err_severity_t : std::uint8_t

Registry-side severity of a protocol error (RFC-0002 §D) — advisory metadata that never travels on the wire.

Values:

enumerator WARN

Expected-in-operation outcome (e.g. a missing path).

enumerator ERROR

A genuine protocol-level failure.

enumerator CRITICAL

The peer relationship itself is unsound.

enum class tr::wire::err_disposition_t : std::uint8_t

Registry-side disposition of a protocol error (RFC-0002 §D) — what a caller should do next; never travels on the wire.

Values:

enumerator TRANSIENT

Retry may succeed (congestion, timing, lossy link).

enumerator PERMANENT

Don’t retry this request as-is.

enumerator FATAL

Tear down the peer relationship.

constexpr std::string_view tr::wire::err_path(err_t e) noexcept

The canonical tr::… namespace path of a registered error code (RFC-0002 §A/§D) — the string identity a NAME-form ERROR would carry.

Parameters:

e – the registered error code

Returns:

the frozen tr::<concept>::<error> path (empty for an unregistered value)

constexpr err_severity_t tr::wire::err_severity(err_t e) noexcept

The registry severity of a registered error code (RFC-0002 §D).

Parameters:

e – the registered error code

Returns:

WARN / ERROR / CRITICAL per the frozen registry table

constexpr err_disposition_t tr::wire::err_disposition(err_t e) noexcept

The registry disposition of a registered error code (RFC-0002 §D).

Parameters:

e – the registered error code

Returns:

TRANSIENT / PERMANENT / FATAL per the frozen registry table

struct crc32c_state

A running CRC-32C accumulator — the one home of the init/final-xor constants.

Feed the covered bytes as any number of contiguous chunks (a rope crossing link boundaries, a payload-plus-trailer region), then read value(): byte-identical to crc32c over the concatenation (the CRC is associative over the feed), with no intermediate buffer. The single-/two-span crc32c below delegate to it.

Public Functions

inline constexpr void feed(std::span<const std::byte> data) noexcept

Feed one contiguous chunk of covered bytes.

inline constexpr std::uint32_t value() const noexcept

The finalized CRC-32C over everything fed so far.

Public Members

std::uint32_t c = 0xFFFFFFFFu

Running state; init per RFC 3720.

struct crc16_ccitt_state

A running CRC-16-CCITT (FALSE) accumulator — the crc16 twin of crc32c_state (init 0xFFFF, no final xor). Same feed-chunks-then-read-value contract.

Public Functions

inline constexpr void feed(std::span<const std::byte> data) noexcept

Feed one contiguous chunk of covered bytes.

inline constexpr std::uint16_t value() const noexcept

The finalized CRC-16-CCITT over everything fed so far.

Public Members

std::uint16_t c = 0xFFFFu

Running state; init 0xFFFF, no final xor.

constexpr std::uint32_t tr::crc::crc32c(std::span<const std::byte> data) noexcept

CRC-32C (Castagnoli) over data.

constexpr std::uint16_t tr::crc::crc16_ccitt(std::span<const std::byte> data) noexcept

CRC-16-CCITT (FALSE) over data.

See: frame-codec (where the codes are produced), graph (where result_t is returned), interface map, reference §protocol TLVs.