Skip to content

Routing & federation

chIRpChat’s topology is deliberately cellular, not mesh-egalitarian: clients register with routers; routers form a backbone. Meshtastic floods because every node is a peer; MeshCore floods to discover then routes directly. chIRpChat goes further: flooding is reserved for discovery only, and the backbone is allowed to use the internet when it has it.

Binary Runs on Does
lrc-node XIAO, other ESP32/nRF52 client gateway: USB/WiFi IRC server, registers with routers, relays opportunistically
lrc-router high-placed ESP32/nRF52, or lrcd on Linux/Pi with a LoRa HAT sequencing, store, federation, lane scheduling. Build flag RELAY_ONLY strips it down to a lane-translator

No 30-role taxonomy. A relay is a router with the chat brain compiled out. lrcd is the same router code with the embedded HAL swapped for sockets/files — see ARCHITECTURE.md.

v2 credit routing and the edge mesh (Wave 2)

Section titled “v2 credit routing and the edge mesh (Wave 2)”

Everything below this section describes the v1 wire mechanics still in force today (FLAGS.PATH source routing, TH TTL/hops). LPP v2 (PROTOCOL.md §Versioning) replaces those specifically for the edge mesh — the opportunistic multi-hop relay layer between a client and its router — without changing anything about routers, federation, or the backbone described in the rest of this document:

  • TH (TTL+hops) → AC (airtime credit). The loop guard is the existing (SRC,MSGID) dedup ring plus a new gradient-descent forwarding predicate (relay only if it strictly lowers router-distance); the resource bound is AC, an airtime-denominated budget debited per hop at the relay’s actual time-on-air. PROTOCOL.md §The AC byte (v2); CREDIT.md is the normative host decision layer (core/include/lrc/credit.h).
  • The Path block → the Edge Routing Block (ERB). rprog (router-distance progress), HINT (soft contention bias), RACK_RTR1, and TRAIL[] (diagnostics-only relay breadcrumbs — never a trust input) replace the 1-byte-per-hop pinned path. PROTOCOL.md §Edge Routing Block; design rationale in docs/research/routing-redesign/CONSOLIDATED_ROUTING_PLAN.md.
  • Return path is a Return Descriptor, not a reversed path. The router that terminates a client’s uplink owns a per-client RD (BROADCAST/RELAY_VIA/LANE_SCHED/UNKNOWN, core/include/lrc/return_path.h) that only ever enters RELAY_VIA after the router has observed a specific relay actually deliver — never seeded from the unauthenticated TRAIL[] bytes. This is the “observed-delivery gate” that keeps a poisoned forward discovery from seeding a trusted return.
  • RACK (PROGRESS-ACK) is the suppression-collapse control frame (PROTOCOL.md §RACK) a router emits once it has ingested a frame, so relays still holding it pending can cancel (if they hold the issuing router’s SK) or shorten (if they don’t) their contention-window wait instead of always burning the full window.

Status: the wire layout (ERB, AC, RACK’s frame format) is landed and golden-byte-frozen; Node’s relay-side credit debit/gradient decision and RACK authentication are wired into core/src/node.cpp. The pending-forward suppression scheduler that actually cancels/shortens/fires a queued retransmit using that classification is not yet built — it needs TX-timing behavior that belongs to firmware and needs human RF sign-off (AGENTS.md rule 5) before it drives real transmit timing. Constants (AC’s unit, per-class budgets) are provisional pending the scenario-testbed sweep gate.

  1. Router emits BEACON on the anchor lane (slowest preset, widest reach) every 60–600 s (config + load adaptive), carrying: RK fingerprint, rtr_short (2-byte id, rendered as e.g. rt1), lane plan, current load, federation id, and the router boot_id incarnation.
  2. Client picks the best-heard router, registers (IDENTITY.md §2), probing upward through the lane ladder during the exchange so both ends learn the fastest preset that actually works between them (RADIO.md §Lane probing).
  3. Downlink: the router owns delivery. It knows exactly which channels its clients joined and the last SEQ3 each client acked (piggy-backed on client uplinks), so it can replay gaps without being asked, batching during its scheduled lane slots.
  4. Uplink: client → router, possibly via relays (path block). The client learns its uplink path from the first successful exchange (path recording, MeshCore-style: relays append their UID byte to a discovery packet, the WELCOME carries the reverse path back). The path is then pinned — no flooding after discovery — and re-discovered only after the relevant control-plane retry budget expires.

The core enforces pinned path blocks today: a relay forwards a FLAGS.PATH packet only when path[hops_taken] matches the first byte of its router UID, then increments hops_taken. Discovery recording is also built: a discovery packet carries path_len == hops_taken, and each relay appends its UID byte before forwarding. Online DMs now use that path policy: an unpinned DM starts with an empty path block, the recipient ACK returns the discovered path in reverse, and the sender pins the reversed-back path for the next DM to that UID. Registration uses the same policy: HELLO starts discovery with an empty path block or uses the pinned router path, WELCOME returns the discovered path in reverse, and the client pins the reversed-back path only after validating the signed WELCOME. If a pinned DM exhausts its ACK retry budget, or a pinned HELLO exhausts its retry budget, the sender clears the stale pin and the next packet starts discovery again. A path-pinned online DM can fail over sooner when router liveness has already marked the pinned router unreachable and a different reachable same-UID attachment is known: the pending retry is retargeted to that attachment and its mutable path block is compacted back to discovery form, preserving the original (SRC, MSGID) reliability identity. A relay that is allowed to forward a path frame but has no current peer/RF egress keeps a bounded, TTL-limited copy of the already-mutated relay frame and forwards it when an egress returns; expired or oldest entries are dropped rather than changing PATH semantics or generating relay ACKs. The default buffer is 8 frames for 30 s, and signed Admin safe keys can adjust or disable it with relay.store_forward_limit and relay.store_forward_ttl_ms.

Asymmetry is embraced: downlink may be 0 hops (router is loud, on a hill) while uplink takes 2 relay hops. The two directions maintain independent paths.

A moving client that starts hearing rt7 better than its primary rt1 HELLOs to rt7 while still registered to rt1 (make-before-break). rt7 announces the registration in RTRSYNC; rt1 forwards queued DMs over the backbone and demotes itself to standby; the IRC layer notices nothing — the user keeps their nick and channels because identity is the key, not the attachment point. When the new attachment registers, the router restores channels learned from reachable same-UID RTRSYNC directory records through the ordinary IRC JOIN path, then the existing max-seq-vector/CHANSYNC recovery loop backfills any channel messages missed before the roam completed.

Routers hold links to each other over whatever they have, in preference order: TCP (internet/WiFi/Ethernet) > fast LoRa lane > anchor lane.

  • A TCP link carries the same LPP packets as the air. When lrcd is started with one or more --peer-pin UID8HEX entries, the socket first exchanges authenticated control frames ("LA", little-endian magic 0x414C): HELLO carries (rtr, RK pubkey, nonce), and PROOF signs LRC-PEER-AUTH-v1 || signer_pub || verifier_pub || signer_nonce || verifier_nonce. The peer’s RK UID must match a configured pin. Both sides derive the base key from X25519(local_RK, peer_RK) and the two nonces ordered by RK UID, then expand directional SipHash keys for A->B and B->A. After that, data frames are "LR" (little-endian magic 0x524C), u16 len, u32 seq, packet bytes, Tag8; the tag covers the header, sequence, and payload. len=0 is a MACed link-local keepalive and is not an LPP packet. Unpinned lrcd sockets and ESP32 firmware lanes keep the legacy unauthenticated LR | len | packet framing for lab/back-compat only. ESP32 firmware redials a bounded list of stored TCP targets; when its stored peer policy contains pins, outbound lanes complete the same LA HELLO/PROOF exchange, reject unpinned RK UIDs, then send only sequenced/MACed LR frames. No TLS, no encryption — authenticated plaintext, consistent with project policy. Operators crossing hostile middleboxes should wrap the TCP socket outside lrcd.
  • No MQTT, no broker, no SPOF. The backbone is a peer mesh: each router config lists peers = [host:port or UID-via-LoRa], links gossip their liveness in RTRSYNC, and the routing rule is dumb and robust: prefer the best link class first (TCP > fast LoRa lane > anchor lane), then the lowest measured RTT inside that class. lrcd reconnects configured TCP peers with per-target jittered exponential backoff, sends idle TCP keepalives, echoes received keepalives, measures RTT from those MACed zero-length frames, times out silent peer sockets, and sends only once per directly connected peer router using the best sampled duplicate link. It reports authenticated or legacy-compatible link-up events through fed.links, RTT observations through fed.rtt_sample, last-link drops through fed.link_down, and rejected auth/MAC/sequence attempts through fed.auth_fail. Link selection is fully transport-agnostic core policy: each link carries an explicit BackboneLinkClass (TCP today; FastRf/AnchorRf for a future RF backbone lane), and the host-tested backbone_best_link / backbone_router_reachable / backbone_reachable_routers helpers pick the best ready link to a router and decide reachability — a peer is marked down only after its last ready link drops, so a router reachable both by TCP and by RF survives either one flapping. lrcd carries the class on every peer link and routes single-target peer sends through backbone_best_link; the RF backbone transport (actually shipping a framed peer frame over a radio lane) remains device work. The same policy module now has a host-tested bounded anti-entropy fanout selector for RTRSYNC-style gossip: it caps the number of ready peers per interval, prioritizes never-synced or stale peers, and rotates the remaining candidates by deterministic epoch score. lrcd applies that selector to RTRSYNC peer broadcasts after first choosing the best duplicate TCP link per router; normal channel, DM, ACK, admin, and DCC traffic still goes to every directly connected peer router. Core RTRSYNC senders now retain changed optional directory, channel-record, tombstone, liveness, identity, and revoke rows for a bounded rtrsync_delta_ttl_ms window, send those compact rows between full snapshots, and use periodic full snapshots as the repair path for peers that miss a delta. fed.gossip_defer counts skipped ready routers, while fed.sync_delta counts compact RTRSYNC sends.
  • FLAGS.VIA_TCP enforces the user’s rule: a packet that traveled by TCP is not re-emitted on LoRa except by the destination’s own access router (the egress), which clears the flag and schedules it on the proper lane. Today the host-testable egress predicate is deliberately narrow: a channel packet may egress only where that scope has local members, and a DM may egress only where the destination identity is locally online. Unknown-version packets can be blind-relayed over TCP/path, but never RF-egressed because the destination semantics are unknowable. Suppressed RF transits increment tx.via_tcp_suppress. RF spectrum is spent exactly once per access network, never for transit that copper already did.

Federation: shared channels without collisions

Section titled “Federation: shared channels without collisions”

Channel identity is the pair (CHAN4, scope):

  • Solo scope (default): #pizza on router rt1 is rendered to IRC clients as #pizza.rt1. The same name on rt2 is #pizza.rt2 — a different channel. No global coordination needed, no collisions, ever. The gateway enforces explicit .rtN isolation: a local member of #pizza.rt2 does not render or chase rt1 traffic even though both names share the same suffix-free CHAN4. Solo deployments can accept the friendly /join #pizza form and canonicalize it to their local primary router scope before the IRC channel is created. /msg *lrc namespace reports the active local policy and examples; adding a channel argument, e.g. /msg *lrc namespace #pizza, shows how that name resolves and which local federated or .rtN views currently exist for the same suffix-free CHAN4. /msg *lrc namespace views [#pizza] lists the currently open local views across all channels, or filters that listing to one suffix-free CHAN4. /msg *lrc namespace join #pizza applies that resolution and performs the ordinary local JOIN to the exact view, without changing routing state. /msg *lrc namespace merge #from #to previews a possible local migration: it resolves both exact views, reports local member counts and whether the current IRC session is already in each view, and reminds operators that history, topic, modes, keys, and channel records stay separate. /msg *lrc namespace move #from #to first joins the policy-resolved target view for the current IRC session and then parts the source view, giving users an explicit, reversible local migration between solo and .rtN views without merging channel history or router ownership. A solo scope is deliberately owned by its router: if rt1 dies, #pizza.rt1 is unavailable until rt1 returns with its persisted sequence/channel state or users explicitly move to another view such as #pizza.rt7. Other routers do not auto-promote themselves to sequence #pizza.rt1, because that would fork the (CHAN4,RTR2) stream and make backfill ambiguous.
  • Federated scope: routers in the same federation_id (a 4-byte value + shared roster of RK fingerprints in config) host channels jointly: the channel renders without suffix (#pizza), every member router sequences its own local members’ messages in its own (CHAN4,RTR2) stream, and members merge all streams. Total order doesn’t exist (this is physics, not a bug); display order is (origin timestamp, RTR2) with the IRC layer emitting in arrival order like any netsplit-healing IRC network. History replay exposes server-time only when the local clock is disciplined and the stored origin timestamp is within the default 10-minute skew guard; otherwise the timestamp is hidden and time.skew_drop counts the guard. Reconciliation is the per-stream max-seq vector from PROTOCOL.md §Recovery — each router’s stream is linear, so “what am I missing” is a handful of integers compared per RTRSYNC, then ranged backfill. (IBLT/minisketch is reserved for DM mailboxes, below, where no sequencer exists.) Router failure in a federated channel is a netsplit, not a sequencer election: reachable routers keep sequencing their own streams, liveness records mark the missing router unreachable, and CHANSYNC fills that router’s stream only after it returns or another node with retained history can serve the exact range.

RTRSYNC cadence: 15 s over TCP links, 120 s over LoRa backbone links, immediate flush on roaming events and channel-admin changes. Optional directory, channel-record, tombstone, liveness, identity, and revoke rows may be sent as TTL-retained compact deltas between full snapshots; receivers treat omitted rows as “unchanged in this packet” and rely on the next full snapshot to repair missed deltas. The access-network spine gossips a bounded online directory/member snapshot in RTRSYNC: one record per (UID,current router) online attachment, with a liveness generation, current nick, and channel display names where that attachment is presently seen. This is enough for cross-router DMs to route without a shared-channel warm-up, for make-before-break roaming to keep two routers live for one UID, and for a router that missed a JOIN or PART edge to repair the presence state used by later BYE/QUIT rendering. The same channel lists are used to restore a reconnecting same-UID roaming session on a new router; JOIN validation remains centralized in the IRC layer, and sequenced channel history still arrives through CHANSYNC rather than the directory record itself. Each node bounds this per-federation cache with directory_limit (default 1024 remote attachments; 0 disables the cap). When the cap is exceeded the node evicts unreachable-router attachments first, older same-UID attachments when a fresher reachable attachment exists, then the least-recently-refreshed attachment by the local touch order. fed.directory_drop counts those local evictions, and later RTRSYNC gossip can rehydrate the entry. Eviction is not a BYE or tombstone; local IRC clients that had seen the attachment in a channel may still receive QUIT cleanup so their presence view matches the bounded cache. RTRSYNC also carries short-lived tombstones for missed BYE edges; a tombstone suppresses directory snapshots for the same UID and router at the same or lower liveness generation, while another router’s live attachment remains routable. When multiple reachable attachments match the same nick or UID, outbound lookup paths prefer the most recently heard attachment, the local refresh order when no clock timestamp is available, then the higher liveness generation. This steers path pins, ACK tracking, DCC control/data, KEYREQ, listener adverts, and WHOIS toward the fresh router while preserving make-before-break fanout for the same UID. Router liveness records in RTRSYNC carry monotone reachability generations: an unreachable router attachment renders as an IRC QUIT/netsplit locally without being deleted from the directory, and a newer reachable record renders the corresponding JOIN/netjoin and makes that attachment eligible for DM egress again. The gateway also emits one *lrc channel NOTICE per affected channel (rtN.lrc netsplit / rtN.lrc netjoin, with the liveness reason when present) so clients have a stable split/heal label even though IRC JOIN has no reason field. Trusted TCP peer ingress can also carry RTRSYNC revoke-state rows. They are generationed UID denylist state, not presence state: receivers ignore them on radio ingress and for local identities, then apply only newer (generation, writer_rtr) rows. Accepted revoke rows clear any cached identity record and suppress later KEYRESP/RTRSYNC cache refreshes or chat from that UID. Accepted clear rows persist as tombstones, so stale revoke rows from an old peer snapshot cannot re-denylist a UID after key unrevoke or signed Admin identity.unrevoke has converged.

Channel administration (the “who owns #foo” problem)

Section titled “Channel administration (the “who owns #foo” problem)”

IRC’s classic failure: ops evaporate when the last op leaves. chIRpChat routers are services:

  • First JOIN of a nonexistent channel creates it; the creator’s IK (not nick!) is recorded as founder in the router’s channel record. The access-network spine persists this record today so a rebooted router restores founder ownership, op grants, ban lists, simple modes, topic, and channel key before admitting the first post-boot JOIN.
  • Founder/ops manage the channel with normal IRC commands (/mode +o, /kick, /topic); the gateway translates these to CHANCTL ops that the client signs (SIGNED_FULL for grants, Tag8 for routine kicks) and the router enforces by identity. Op rights survive disconnects, reboots, and device changes, because they bind to the key. When the node has a signed IdentityRecord cached for a loaded founder/op UID, that record is pinned against identity-cache eviction so accepted ROTATE overlays continue to validate authority traffic under cache churn.
  • Channel records (founder, op list as UIDs, modes +t +m +i +A +S +k, topic, keyed-JOIN secret, ban list) are persisted by the owning router and replicate across the federation in bounded RTRSYNC snapshots. Receivers apply only newer (record_rev, record_writer_rtr) snapshots and persist them under their local router scope before first JOIN enforcement, so a banned UID is silenced at the edge and never wastes backbone airtime. The gateway enforces +t topic control, blocks non-op local speakers under +m, requires a pending operator INVITE or durable op/founder status for local joins under +i, and suppresses non-op remote CHANMSG frames under +m using the sender UID in the channel record. Under +A, remote CHANMSG frames are rendered only after the sender has a learned router-attested directory attachment for that router. Under +S, remote CHANMSG frames are rendered only after the sender’s latest retained checkpoint archive row is verified and non-mismatching. Under +k, non-op local joins must present the matching JOIN key; founders and durable ops can still recover a keyed channel after reboot without typing the key. Newer replicated channel records fan out topic, simple mode, and currently local op/ban nick changes to already-open IRC channel state; replicated keys update the live JOIN gate without rendering the secret back to clients.
  • /mode extensions surfaced through standard IRC syntax: +S requires Tier-3 checkpoints for remote speakers; +A (router-attested users only) drops unregistered drive-by traffic today.

DMs route sender → sender's router → (backbone) → recipient's router → recipient using registration records gossiped in RTRSYNC (UID → current router(s), a DHT-free directory that fits comfortably in a Pi’s RAM and in an ESP32 router’s PSRAM LRU for the active subset). The current implementation gossips online attachments keyed by (UID,current_rtr), so the same UID can stay reachable through another router when one attachment quits; tombstones remove only the matching attachment. Online DMs use explicit ACK: the sender retries the same MSGID if either the DM frame or the ACK is lost, while the receiver re-ACKs duplicates without rendering a second IRC line. A router that can deliver the DM to a local attachment still relays the frame when it has learned another online attachment for the same UID, so a make-before-break roam can receive on both routers. If the recipient is offline but known locally and no reachable remote attachment for that UID is known, the recipient’s router now mailboxes the DM with TTL (default 7 days, config), ACKs only after storage, and flushes it when that IRC identity reconnects. An old standby router that still knows the identity suppresses a new mailbox copy when RTRSYNC has already shown a reachable newer attachment; the active attachment is left to ACK the DM. The core has a MailboxStore seam, so a configured warm-tier adapter can reload those records after reboot; lrcd --state-dir stores them in per-recipient mailbox files. Duplicate retries are deduped by (SRC, MSGID) and re-ACKed without delivering a second IRC line. Reloaded mailbox snapshots are normalized the same way before delivery: the destination UID is restored from the snapshot key, duplicate live rows collapse first-wins, and any tombstone for (SRC, MSGID) wins over a live row. Delivered DMs become bounded deletion tombstones retained until their mailbox TTL horizon; live rows that expire locally become tombstones with a fresh suppression horizon, while expired tombstones are pruned. Standby routers now reconcile dirty mailboxes over target-aware authenticated peer links with the MAILBOXSYNC control packet. The sender targets a reachable same-UID remote attachment learned from RTRSYNC; no broadcast peer egress is used, radio ingress is dropped, and the receiver processes only a locally known UID or already materialized mailbox. Dirty mailboxes first send an LCMI inventory: row key, expiry, tombstone state, and 64-bit row digest, with no message text. The peer answers with LCMR for rows it is missing or where the remote row is newer, and the sender responds with an LCMB row subset. Node::import_mailbox_snapshot merges those rows into the local mailbox using the same deterministic set-union rule, persists the result when a mailbox store is configured, delivers imported live rows once on reconnect, and keeps replayed stale live rows suppressed by tombstones. Large inventories, requests, or row subsets use bounded LPP fragmentation over the same target peer path.

Anti-congestion (“virtual radio traffic jams”)

Section titled “Anti-congestion (“virtual radio traffic jams”)”
  • Single-sequencer channels mean fan-out is a router broadcast per access network — one transmission serves every local member. This is the single biggest airtime win over flood meshes: N members cost 1 TX, not N.
  • Routers shape per-lane duty cycle (regulatory + configured budget) with a token bucket; PRIO packets (ACK, admin, DCC control) preempt.
  • Channel-activity detection (CAD) before every TX; CAD-fail backoff is randomized 120–480 ms (we copied MeshCore’s constants, they’re field-proven).
  • Relays add jitter U(0, airtime×2.5) before re-TX so parallel relays don’t collide twice (MeshCore’s delay model).
  • Backpressure: a router whose TX queue passes high-water emits BEACON with a busy bit; clients stretch their uplink pacing (and the TUI shows it, so humans stop typing into a wall).
  • Anchor offload (client lane promotion). The anchor lane is the one channel every client must hear, so it is also the scaling chokepoint. The core Node implements both ends of promotion so it is identical on lrcd and firmware (and host-tested in test_node.cpp); the decision is the pure LaneSchedule::promote_client policy described in RADIO.md §Lane promotion, which this layer only carries:
    • Router side. A promotion-enabled router Node owns a Registrar (the HELLO/WELCOME state machine) and an advertised lane plan (Node::set_lane_plan). The embedding records the SNR it measured on each client’s uplink — Node::record_client_uplink_snr(uid, snr_x10, now) on firmware from the radio, on lrcd from a link-quality estimate — or a failed/absent uplink (Node::record_client_uplink_fail(uid, now)), which stamps the per-client Registration (SNR + the recent-uplink health runs the hysteresis reads back) and immediately re-runs the lifecycle for that client. A periodic sweep (Node::promote_clients, also driven from tick) re-evaluates every measured client.
      • The decision is the full promotion lifecycle (LaneSchedule::promote_lifecycle, RADIO.md §Lane promotion), not a one-shot promote: GRANT a first fast lane, RENEW the live grant once the clock enters the renew lead window (default 30 s before its TTL lapses) so a sustained client never silently falls back, DEMOTE a decaying client back to the anchor (a sustained uplink-SNR drop past the lane’s sustain floor with hysteresis, a run of failed/absent uplinks, or a lapsed lease), or HOLD. The lifecycle thresholds are Node::Config::lane_lifecycle_policy (ClientPromotionLifecyclePolicy): renew lead, demote SNR margin + run, failure run.
      • GRANT/RENEW emit a LANEGRANT unicast Tag8’d with that client’s session key (lane.grant_tx) and re-stamp the live grant on the Registration (lane, TTL, expiry) so the next sweep can renew or demote it. The lease length is the embedding’s lane_grant_ttl_s; the controller’s per-decision TTL is only a floor/term marker.
      • DEMOTE does not put any new frame on the air. The wire LANEGRANT only ever carries a promotion lane, and “return to the anchor” is exactly the absence of a live grant: the router clears the client’s promoted state (Registrar::note_demote), and the client’s own TTL countdown drops it back to the anchor lane. Silence is the correct, lowest-airtime demotion; a demote bumps lane.retune for visibility.
      • A router on the anchor-only plan, below the congestion floor, or with promotion disabled emits nothing.
    • Client side. The embedding drives its own RegClient handshake and, once registered, hands the Node the derived 16-byte session key and the issuing router’s UID (Node::set_session_key(local_uid, sk, router_uid)). The Node accepts an inbound LANEGRANT only when it is addressed to a local identity, its src is the router that issued that session key, and its Tag8 verifies under that identity’s recorded session key; it then records the granted lane + TTL (Node::granted_lane, plus an optional LaneGrantSink) and bumps lane.grant_rx. An off-target grant, a grant from a sender other than the issuing router, a bad/missing tag, or a grant for an identity with no recorded key is dropped silently — promotion can never be forced onto a client. The router-src check is defense-in-depth: a session key is shared with exactly one router, so a frame tagged with a leaked/forged key from any other sender is still rejected. Applying the granted lane to the radio (the actual retune) stays in firmware behind its lane retune on RF opt-in; the grant simply lapses back to the anchor when the TTL expires.
    • Presence (BYE) authenticity. A BYE evicts a presence, so a forged one is a denial-of-service against the named user. When the BYE’s src is a client this router registered, the router holds its session key (via the Registrar) and requires a valid Tag8 before acting — a forged or untagged eviction is dropped (rx_badtag). A BYE for a UID this router did not register carries no key it could check; it is a federation announcement from the router that owns that user, and its authority is the federation link itself (the same trust boundary every other cross-router frame rides), so it is acted on as before. The tightening applies only to the locally-registered case.
  • No global flood routing of chat traffic. Discovery floods are TTL≤3 and rate-limited per node.
  • No DHT. Directory = federation gossip; it’s small data.
  • No store-everything-forever; stores are rings with TTLs (STORAGE.md).
  • No automatic transmission of GPS position, ever. (/msg *lrc loc set … exists for operators who want a router to advertise siting info.)