Skip to content

docs(spec): the backbone is a gateway property - #485

Merged
bahdotsh merged 4 commits into
mainfrom
docs/headless-backbone-gateway-property
Sep 30, 2026
Merged

bahdotsh merged 4 commits into
mainfrom
docs/headless-backbone-gateway-property

Conversation

@bahdotsh

@bahdotsh bahdotsh commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Summary

The gateway contract's backbone section was written for one backbone. It opened with "between gateways, the wide-area carrier is Reticulum", stated the Verdict rule for "a Reticulum gateway", and had one backbone token with no rule for a second. The device does none of that: it speaks the daemon contract over local IP, stores whatever tokens the gateway advertises, and reads none of them on any decision.

This PR restates the backbone as the gateway's property and records the decision in ADR 0026. No frame, verb or token spelling changes.

  • docs/spec/gateway-contract.md. ## The backbone now states what any backbone owes a gateway: gateway-to-gateway presence, arbitrary-size framing, a provisioned peer list, and a declared rate class (scarce or broad, a property of the kind; an unknown kind is scarce). Reticulum is the reference backbone in a subsection of its own, with the MDU, link-with-resources and one-destination-per-gateway mechanism moved there. Gateway-owned destinations are split into the kind-independent invariant (a gateway is reachable on the backbone under its own identity and only under it; no gateway holds a device key) and the Reticulum mechanism. The opener and the Verdict rule no longer name a backbone.
  • The token family backbone_<kind>_v1. A gateway advertises each backbone it reaches as one token. A client ignores a kind it does not know and keeps the session. A gateway may advertise no backbone token and is a conforming gateway. The token is advisory and never a routing input. backbone_reticulum_v1 is the first member, unchanged.
  • One attached daemon at a time. Every gateway answer is recorded against the carrier that gave it, and the daemon link is one carrier however many backbones stand behind it, so a second daemon on the same device would overwrite the first one's verdicts and presence answers recipient by recipient. The contract says so; ADR 0026 says why two daemons need two transport types and defers that.
  • ADR 0026 (0025 was taken this week by the native packages ADR), with an index row.
  • Doc-comment sweep. The engine's capability set and its setter, the GatewayCarrier::Reticulum variant, the transport module and type, the media fallback comment in send.rs, the FFI section banner and two reticulum_* headers, and their plain // twins in the UDL (which uniffi ignores, so no binding is regenerated) now say what they name: the gateway daemon carrier, after its reference backbone. The reticulum_* entry points, the transport variant and the manager file names keep their spelling. docs/reticulum.md gets the same sentence in its callout.
  • Threat model R10. The abuse-budget argument is per rate class, and says that a gateway declaring one class and applying another costs delay on its own backbone and nothing on the device.

What was verified against the tree, not just restated

  • Nothing in the engine reads a gateway capability token: the getter's only callers are tests in the engine and FFI crates, and the setter stores, bounds and clears. The mailbox_v1 behaviour is driven by the stored and pushed flags on each verdict.
  • Verdicts (three sites in send.rs) and presence answers (session.rs) are recorded through ReachabilityFacts::record keyed by TransportType, where the newest claim for a carrier replaces the last. The transport manager holds one transport per type and the FFI one ReticulumState.
  • The media path does not exclude the daemon carrier: select_media_transport pins to the current transport when it is available and leaves Reticulum out of the fallback order only. Retries are forced onto the pin (pinned_media_transport_for_message in the retry loop). The chapter says exactly that, makes the exclusion the gateway's obligation, and says what a scarce gateway's refusal does to the pinned transfer: each chunk gets a plain-failure verdict (budget_exceeded or frame_too_large, never recipient_unreachable) and the transfer fails on its retry ladder.
  • The bundled attach policies keep every string token they are given and act on none, so "ignore an unknown kind" is already their behaviour.

Validation

  • cargo fmt --all -- --check, cargo clippy --workspace -- -D warnings and RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps pass locally (the Rust changes are doc comments only).
  • No Rust guard, script or vector reads any file this PR touches; the spec-vector generator names the chapter in a description string only. The attach-proof vectors are untouched because the attach section is untouched.
  • Every anchor into the chapter from other documents (#attach, #gateway-daemon-contract-v1) still resolves; the new anchors (#the-backbone, #the-backbone-token, #what-a-backbone-owes-a-gateway, #the-reticulum-backbone) are the headings as written.
  • No em dashes in any added line.

Not in this PR

  • A second transport type for a second daemon. Deferred in ADR 0026 with the reason.
  • A rename of the reticulum_* API or the manager files. About forty source guards pin them by path and spelling, and the rename is a three-binding API break for no behaviour.
  • A Python daemon client. That is the next PR in this track and is independent of this one.
  • The docs/spec/README.md and docs/README.md scope text for the chapter still says "and the backbone", which remains accurate, so the rows are untouched.

Notes for reviewers

  • The broad rate class has no member kind in this revision. It exists so that the table has a second value to name and so that "an unknown kind is scarce" is a rule rather than the only case.
  • The chapter now says a device whose current carrier is the daemon link does hand it media, and that a scarce gateway's refusals fail the pinned transfer on its retry ladder. That is what the code does today (select_media_transport, and the forced pin in the retry loop), and the earlier text ("Media MUST be excluded") read as a device-side guarantee it never was. The obligation is placed on the gateway, where it always was enforceable, and the device's cost of not knowing the class is named rather than hidden.
  • The UDL edits are // comments only. ./scripts/generate-bindings.sh was deliberately not run: uniffi ignores them, and the generated bindings carry none of their text.

The gateway contract's backbone section said "between gateways, the
wide-area carrier is Reticulum" and stated the Verdict rule for "a
Reticulum gateway". The device never touches a backbone: it speaks the
daemon contract over local IP, stores the tokens the gateway advertises,
and reads none of them on any decision. Restate the section as what any
backbone owes a gateway (gateway-to-gateway presence, arbitrary-size
framing, a provisioned peer list, a declared rate class), with Reticulum
as the reference backbone in a subsection of its own. The token becomes
the family backbone_<kind>_v1: a client ignores a kind it does not know,
a gateway may advertise none, and the token is advisory and never a
routing input. backbone_reticulum_v1 keeps its spelling as the first
member. A device attaches to one gateway daemon at a time, because every
gateway answer is recorded against the one daemon carrier and a second
daemon would overwrite the first one's facts.

ADR 0026 records the decision and why two daemons need two transport
types. The reticulum_* entry points, the transport variant and the
manager files keep their names; their doc comments now say what they
name, the gateway daemon carrier after its reference backbone. Threat
model R10's abuse-budget argument is per rate class.

No frame, verb or token changes.
@bahdotsh
bahdotsh force-pushed the docs/headless-backbone-gateway-property branch from e6fa61b to 0dbe34b Compare September 30, 2026 15:36
…mments

The table pins the definition's SHA-256, and this branch rewords two
comments in it.
@bahdotsh
bahdotsh merged commit 9566dd8 into main Sep 30, 2026
22 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant