Contents Menu Expand Light mode Dark mode Auto light/dark, in light mode Auto light/dark, in dark mode Skip to content
libtracer
libtracer

Getting started

  • Getting started
  • Examples
    • In-process pub/sub
    • Pub/sub fan-out & dispatch cost
    • Wire codec round-trip
    • Wire codec deep-dive & throughput
    • Rope scatter-gather
    • Two nodes over a wire — FWD delivery
    • Composition axes
    • Register a vertex, and address it
    • Read and write
    • await
    • Write-creates
    • :children[]
    • Retirement
    • A HANDLER vertex
    • A STREAM vertex
    • Subscribe to one vertex
    • One edge, a whole subtree
    • Delivery terminates at the target
    • The delivery policy is per subscription
    • Unsubscribe & the release hook
    • Unsubscribing from inside a delivery
    • Retire drops a producer's subscriptions
    • The TLV header and the opt byte
    • Structured or opaque — one bit decides
    • A PATH body is packed segment records
    • The escape record
    • The trailer: CRC and timestamp
    • What the frame reader refuses
    • decode_into: a flat arena
    • tlv_view_t: a scattered frame
    • The segment, and its refcount
    • subview
    • borrow: the app's own bytes
    • The rope
    • subrope and the iovec egress
    • A bounded backend
    • A DEVICE link
    • A shared seam needs a thread-safe backend
    • The failable block seam
    • Two L0 seams, and the question that picks
    • A bump source
    • The upstream decides what the buffer means
    • A long-lived seam has to recycle
    • Classes do not share
    • A container that fails by value
    • The std::pmr adapter
    • Open by default, and the first ACE is the lock
    • The caller context is not the subject
    • access_mask is a bitfield
    • EVERYONE@ is reserved both ways
    • Effective ACL = own + inherited
    • expires_ns is checked against your clock
    • Two evaluators, and DENY
    • An ACL is a security document
    • The dst is a source route
    • Terminus or forward — one test decides
    • One NAME, one slot
    • A mount run is consumed whole
    • Three nodes, and a forwarder that stores nothing
    • The src you accumulated is the way home
    • A repeating flow buys its route back
    • A stale label is dropped and NACK'd
    • One seam, every wire technology
    • A kind is a NAME, resolved twice
    • DIAL and LISTEN are two constructors
    • A datagram already has boundaries
    • A stream has none, so the kind supplies them
    • No frame crosses until the Upgrade completes
    • The one BUS kind
    • One listener, many slots
    • Rust: a VALUE frame and its CRC trailer
    • Rust: an address is packed segment records
    • Rust: a remote write is one FWD frame
    • Rust: subscribing is a write
    • Rust: reading a node's :stats
    • TypeScript: a VALUE frame and its CRC trailer
    • TypeScript: write a remote vertex, then read it
    • TypeScript: subscribe to a remote producer
    • TypeScript: a remote failure is a typed error
    • TypeScript: dial a WebSocket link

Specification

  • The specification
    • Protocol v1 — the wire format

RFCs and decisions

  • ADR and RFC index
    • RFC 0001 — Protocol-v1 wire-format consistency consolidation
    • RFC 0002 — Protocol error model: the tr:: concept namespace
    • RFC 0003 — Concrete-path delivery for bridged wildcard subscriptions
    • RFC 0004 — Remote operation addressing: path-as-route + the FWD/FIELD frames
    • RFC 0005 — Subtree subscriptions: vertical bubbling, branch-write decomposition, write-creates
    • RFC 0006 — Nesting depth is receiver-resource-bounded: the fixed cap of 32 is removed
    • RFC 0007 — SUBSCRIBER delivery terminates at the target: no automatic re-dispatch to the target’s subscribers
    • RFC 0008 — Vertex operations: assign and propagate; structural selective propagation; value-agnostic per-vertex delivery_mode
    • RFC 0009 — Vertex removal and subscriber eviction
    • RFC 0010 — Owner-writable application property fields: the field descriptor table, the reserved settings.app namespace, and owner-defined :schema
    • RFC 0011 — Node identity facet: a wire-readable, pre-auth :identity field serving the ADR-0045 ed25519 TOFU public key at every vertex
    • RFC 0013 — Readable creatable-child-type catalog: the :children.schema read
    • RFC 0014 — Creator endpoint: connection lifecycle and link liveness
    • RFC 0016 — Composed branch read: a plain READ of a branch serves the folded POINT tree of its registered subtree
    • RFC 0017 — Element addressing: [n] on the value plane, and per-element delivery
    • RFC 0018 — Packed path segments: a PATH body becomes length-prefixed records
    • RFC 0019 — Path depth is bounded by bytes: the 32-segment PATH cap is deleted
    • RFC 0020 — A bus link’s connection NAME is not a routable next-hop (reject, never broadcast, on the request plane)
    • RFC 0021 — The frame of reference of a wire SUBSCRIBER’s PATH target
    • RFC 0022 — Delivery policy is per-subscription; settings_t dissolves
    • RFC 0023 — The path segment cap is repriced: 32 → 255, derived from the wire’s own widths
    • RFC 0024 — Bound paths: node-scoped vertex-ref source routing
    • RFC 0025 — Stream-class values: delivery classes over the rope primitive
    • RFC 0026 — The ACE access_mask canonical wire width is u32
    • RFC 0027 — Label-switched path compression: minting a per-host path label across the wire
    • RFC 0028 — The lean value path: one block per publish, copy-or-share by size, retention per vertex, sync as a trait
    • RFC 0029 — One path primitive: the owner-issued (index, generation) pair, carried per hop, local = forwarded
    • RFC 0030 — The host API walks the graph: a graph-owned path object, creation refused by default, the reply as a remote write, AWAIT and REPLY retired

Reference

  • Reference (descriptive)
    • Overview — the six-layer model
    • Module catalog & composition
    • Deployment profiles
    • Concurrency & scaling
    • Reclamation policy
    • Memory substrate
    • Views & ownership
    • Data format
    • Protocol-defined TLVs
    • Graph model
    • Addressing
    • Communication flows
    • User data packing
    • Vertex roles & aggregation
    • Host embedding
    • Network formation
    • CAN transport
    • WebSocket session authentication
    • Composition over the network
    • Transports are vertices
    • Bindings map
    • ROS 2 integration (rmw_tracer)
    • Backpressure & sizing
  • Design notes
    • Concurrency & scaling
      • Scaling and serialization
      • Write and delivery path
    • Zero-copy and flatten
    • Build configuration
      • The configuration space
    • Failable allocation and backpressure

C++ API reference

  • C++ API reference
    • Interface map
    • File map — header to page
    • status & errors — result taxonomy
    • config — the build's traits type
    • instrumentation — reachability counters
    • segment — refcounted bytes
    • backends — allocators
    • views — view_t / rope_t / cast
    • frame-codec — TLV codec + CRC
    • Wire format, bit by bit
    • path — addressing
    • graph — vertices & dispatch
    • security & ACL — access control
    • fwd-router — FWD routing and the /net plane
    • transport — the wire
    • connection config — the SPEC config keys
    • can — the header-elided CAN stack

Interoperate

  • Interoperability
  • Build a custom device
  • A production ESP32 node
  • Capability matrix
  • Implementation registry

Evidence

  • Performance & conformance
  • Test report

Glossary

  • Context glossary

Start here

  • Route by intent
Back to top

RFC 0020 — A bus link’s connection NAME is not a routable next-hop (reject, never broadcast, on the request plane)¶

Note

Status: accepted. This page is an accepted change proposal, kept as the record of why the specification reads as it does. RFCs are proposals and history, not the standard. The normative specification is Protocol v1 and the annexes its §3 incorporates; where an RFC and the specification differ, the specification wins. All RFCs, with their status, are listed in the ADR and RFC index.

Field

Value

RFC

0020

Title

A bus link’s connection NAME is not a routable next-hop (reject, never broadcast, on the request plane)

Status

accepted — 14-day comment window waived by sole maintainer, 2026-08-01

Author(s)

AvatarSD (maintainer)

Created

2026-08-01

Comment window

waived by the sole maintainer, 2026-08-01 (GOVERNANCE.md §”Errata, amendments, and the comment window”); docs/implementations.md still reads _(none yet)_, so the waiver’s revert trigger has not fired.

Instrument

Amendment / clarification of RFC-0004 §B. The forward-resolution step gains a MUST-reject for one hop shape a conforming peer could previously observe being broadcast — the normative surface changes, so this is not an erratum (GOVERNANCE.md excludes by name any change that alters what a conforming implementation does).

Tracking issue

#741 (rfc-labelled)

Target spec version

v1 itself — still DRAFT (docs/spec/v1.md:1), the immutability clause has never triggered. Same route RFC-0006 / RFC-0018 / RFC-0019 took.

Numbering note. Numbering gaps and why they are not reused are recorded in the ADR and RFC index.


1. Summary¶

This RFC records ADR-0073 §3 as normative text, amending RFC-0004 §B’s forward-resolution step for multi-peer (bus) links:

An inbound FWD whose next hop resolves to a bus link’s own connection NAME — the net/<module>/<name> mount itself, with a residual dst below it that names no current peer — MUST be rejected, never broadcast. The rejection is a single directed FWD{ op=REPLY, kind=ERROR } carrying STATUS{ ERROR{ tr::path::invalid (0x0021) } } back along the request’s accumulated src (RFC-0004 §D shape); a frame with no trustworthy return route (malformed at the level that carries src, or itself a REPLY) is dropped by value instead.

Only a bus link’s peer names are routable next-hops below its mount (/net/<module>/<name>/<peer>/... — universally legal since ADR-0073 §2’s node-assigned p<slot> names, #426). Two addressings are explicitly unchanged:

  • a dst naming the mount exactly still addresses the connection vertex itself (its :children[], :settings, liveness value) and terminates locally (ADR-0038 §3a);

  • a peer-directed hop (/net/<module>/<name>/<peer>/...) still strips net/<module>/<name>/<peer> and forwards over that peer’s directed endpoint (ADR-0061).

The only shape this RFC forbids is the broadcast shape: bus NAME + residual, no resolvable peer segment.

2. Motivation¶

2.1 The defect: one directed request drew N replies¶

RFC-0004 §B’s forward step says a hop “re-emits FWD over that link”. For a point-to-point link that is well-defined. For a multi-peer link, “that link” is ambiguous — and the reference implementation resolved the ambiguity in the worst way: the fall-through egressed over the bus transport’s send(), which fans out to every open peer. One directed request drew N replies and scrambled FIFO reply correlation — the failure that broke the #409 topology walk (two replies for one request; nodes bound to wrong identities). The spec text never said “broadcast”; it simply never said what a multi-peer next hop means, and the gap shipped.

2.2 The model: a dst is a directed route to one terminus¶

Everything else in RFC-0004 §B assumes exactly one delivery per route: dst shrinks monotonically to one terminus; src accumulates one return route; a REPLY is one frame routed back along it. A hop that multiplies one frame into N breaks the one-request-one-reply correlation model by construction — there is no spelling of “which of the N replies is mine” in the frame grammar, and adding one would be request state the stateless forwarder is forbidden to hold (§A, and the 2026-07-31 erratum’s reasoning).

2.3 Fan-out is not lost — it lives where it belongs¶

The subscription plane (RFC-0004 §D/§E, ADR-0026) delivers to N peers because N peers subscribed, each delivery a separate directed FWD along a separately accumulated route. That is the protocol’s one sanctioned fan-out, and it is unaffected. What this RFC removes is only the accidental addressing-plane fan-out that no client could correlate.

3. Normative change to RFC-0004 §B¶

Appended to §B’s forward-resolution bullet list:

  • Multi-peer next-hop resolution. When the resolved child is a multi-peer (bus) link and dst continues below its mount, the next segment MUST be resolved in that link’s own peer table (never across buses — ADR-0061). If it names a current peer, the hop consumes the peer segment too and re-emits over that peer’s directed endpoint. If it does not, the node MUST reject the frame: reply kind=ERROR with STATUS{ ERROR{ tr::path::invalid (0x0021) } } along the accumulated src for a request with an intact return route; drop by value for a malformed frame or a REPLY. A node MUST NOT emit the frame over the bus link’s shared (fan-out) endpoint, and MUST NOT resolve the residual against its local graph (a WRITE would materialize a shadow vertex under the connection mount). A dst naming the mount exactly is unaffected — it addresses the connection vertex locally.

Error code. The issue left the exact code to this RFC: it is tr::path::invalid (0x0021), not tr::path::not_found (0x0020). Below a bus mount the only legal next segment is a peer name; peer names are session-scoped (ADR-0073 §2 — “a fallback bus peer name identifies a session, not a device”), so a segment naming a departed session is indistinguishable from one that never named anything: the dst is unroutable as an address at this hop, the same family every unroutable spelling answers (path_t::parse, subscribe_toward). not_found would misdescribe the failure as a resolvable path whose target is missing.

Route-handle plane (§E.1). An ADVERTISE whose route’s next hop is a bus NAME with a residual binds nothing — neither a downstream swap (re-advertising over the bus is the same broadcast) nor a terminus binding (which would absorb every COMPACT locally). The peer’s COMPACTs then draw the ordinary HANDLE_NACK a stale label draws, and the flow stays on the full-route FWD form, where the rejection above answers.

4. Rejected alternatives (recorded per #741)¶

  1. Keep broadcast, document it. Ratifying the current behavior breaks the one-request-one-reply correlation model by construction (§2.2): every client on every bus pays a dedup-and-guess protocol that cannot be written correctly, because the frame grammar carries no reply-to-request identity beyond FIFO order — the exact thing N replies scramble.

  2. Verb-dependent routing (broadcast for a WRITE without reply, reject for READ/AWAIT). This makes addressability depend on the operation — a dst that is a legal route for one op and an error for another — which no other hop in the addressing model does, and it still leaves the WRITE fan-out uncorrelatable (WRITE has a reply, §D). Fan-out semantics belong to the subscription plane, not to an op-conditional reading of PATH bytes.

5. Reference implementation and conformance¶

  • core/src/fwd_router.cpp — resolve_mount_segs marks the bus-NAME-plus-residual descent rejected instead of falling through to the bus child’s link; both forward arms (span and rope) answer via reject_bus_name_hop (assembled-error grammar identical to the terminus resolver’s); on_advertise binds nothing for a rejected route.

  • core/tests/mount_routing_test.cpp — drives the production wiring: rejection (no fan-out, no peer delivery, one directed 0x0021 ERROR reply), peer-originated rejection routed back to the sender only, and the two positive controls (peer-directed hop still forwards; exact-mount addressing still terminates and answers).

  • tests/conformance/vectors/v1/fwd/fwd-bus-name-reject — codec-layer vector pinning the rejected shape (round-trip-safe bytes; the routing-layer MUST is carried in its note, the fwd-wildcard-reject precedent).

6. Consequences¶

  • The #409 topology walk’s routable: false special case for bus links becomes removable (ADR-0073 §Consequences): every enumerable name below a bus mount is now either routable (a peer) or deterministically rejected (anything else).

  • Wire-visible change: a frame that used to fan out (and draw N replies) now draws exactly one ERROR reply — the protocol is DRAFT, so this rides the ordinary amendment route.

  • Depends on ADR-0073 §2 / #426 (closed — the fix merged in PR #744): peers are universally addressable (p<slot>) before the NAME hop may reject, so no reachable terminus is lost.

Relates¶

  • ADR-0073 §3 — the grill ruling this RFC records (PR #740).

  • RFC-0004 §B — the amended forward-resolution step.

  • ADR-0061 — strip-K descent, per-endpoint peer tables.

  • ADR-0044 — bus peer names as routable next-hop segments.

  • #426 (p<slot> peer names), #409 (the broadcast failure), #741 (tracking).

Next
RFC 0021 — The frame of reference of a wire SUBSCRIBER’s PATH target
Previous
RFC 0019 — Path depth is bounded by bytes: the 32-segment PATH cap is deleted
Copyright © 2026, avatarsd LLC
Made with Sphinx and @pradyunsg's Furo
On this page
  • RFC 0020 — A bus link’s connection NAME is not a routable next-hop (reject, never broadcast, on the request plane)
    • 1. Summary
    • 2. Motivation
      • 2.1 The defect: one directed request drew N replies
      • 2.2 The model: a dst is a directed route to one terminus
      • 2.3 Fan-out is not lost — it lives where it belongs
    • 3. Normative change to RFC-0004 §B
    • 4. Rejected alternatives (recorded per #741)
    • 5. Reference implementation and conformance
    • 6. Consequences
    • Relates