Routing Redesign Phase 2 Wire-Spec Plan
Status: documentation-only preparation. This note intentionally changes no packet layout, codec, golden bytes, firmware behavior, daemon behavior, or tests. It is the approval checklist for the later Phase 2.1/2.2/2.3 implementation pass.
Phase 2 is a deliberate LPP wire event. Do not implement it until the PR records:
/lrc-wire-changecompletion.- Human RF sign-off for ERB/RACK/TX-contention behavior per
AGENTS.mdrule 5. - The
docs/PROTOCOL.mdLPP v2 migration note described below before any golden bytes move.
Versioning Recommendation
Section titled “Versioning Recommendation”Implement Phase 2 as an LPP v2 event (VT version bits 01), not as a
same-version v1.1 break. Reusing bit 3 with a different optional-block layout is
exactly the compatibility hazard the wire-change checklist calls out: old v1
nodes parse the ERB head as path_len, so old and new nodes can disagree about
packet boundaries on the same channel.
Approval target:
docs/PROTOCOL.mdstates that v2 replaces v1.1FLAGS.PATHwithFLAGS.ERB, and that mixed v1/v2 edge relaying is not guaranteed for ERB frames unless a later compatibility shim is deliberately designed.core/src/packet.cpp::encode()emitsVT=01for the new layout, anddecode()accepts that version only for the v2 ERB/RACK layout.- Existing path-block behavior is either removed with the v2 migration or kept explicitly fenced as legacy v1 decode/relay behavior; it must not be silently reinterpreted as ERB in v1.1.
Open Decision: Observed Successful Delivery
Section titled “Open Decision: Observed Successful Delivery”Recommendation for Phase 2.3: define “observed successful delivery” as a
router-local overhear event, not a new relay-sourced confirmation frame for
the first implementation. A relay is admitted to RELAY_VIA only when the router
overhears that relay advance one of the router’s own downlink frames toward the
client, with attribution derived from the router’s receiver state and the ERB
state of that downlink.
Tradeoff:
- Pro: no new trust-bearing wire claim; works for keyless foreign relays; avoids
adding another R6 freshness/Tag8 surface; keeps
ERB.TRAIL[]non-trust data. - Con: slower admission under asymmetric or hidden-node geometry where the client hears the relay but the router cannot overhear the relay. Those cases stay in BROADCAST/UNKNOWN until a router-observed event occurs.
Defer relay-sourced delivery confirmations. If they are later needed for the hidden-node case, they need a separate wire-change approval: exact packet type or flag, freshness binding, authenticated source rule for registered relays, and the keyless fallback behavior. A keyless relay confirmation must not become a trust input.
Phase 2.1: Edge Routing Block Layout
Section titled “Phase 2.1: Edge Routing Block Layout”Future approval target: replace FLAGS.PATH with FLAGS.ERB and replace the
path block with the ERB slot described in ASYNC_ROUTING_DESIGN.md section 6.2:
byte 0: ERB_VER[7..6] | mode[5..4] | rprog[3..0]byte 1: HINTbyte 2: RACK_RTR1byte 3: reserved[7..4] | TRAIL_LEN[3..0]byte 4..: TRAIL[TRAIL_LEN]Files and functions that must be touched once approved:
docs/PROTOCOL.md: commonFLAGStable, Path block section replacement, Tag8/canonical-image text, ACK/HELLO path language, MTU/versioning notes.docs/IDENTITY.md: registration path text in section 2 and canonical-image language in section 7.docs/ROUTING.mdanddocs/TELEMETRY.md: replace path-pinning/store-forward text and stable counter names with ERB/RD terms.core/include/lrc/packet.h:flags::Path,flags::kMutableMask,Packet::path_len,Packet::path[], and any ERB replacement struct/accessors.core/src/packet.cpp:Offsets,layout(),encode(),decode(),canonical_image(), and comments describing mutable-region exclusions.core/src/reg.cppandcore/include/lrc/reg.h:RegClient::make_hello(),RegClient::handle_welcome(),Registrar::handle_hello(), and therouter_path_*state/accessors.core/src/node.cpp:append_path_discovery_hop(),reset_path_block_to_discovery(),Node::remote_privmsg(),Node::send_dccctl(),Node::send_dccdata(),Node::handle_keyreq(),Node::service_key_lookup(),Node::send_ack(),Node::handle_ack(),Node::retry_pending_acks(),Node::reroute_pending_path_ack(),Node::handle_frame(),Node::apply_path_pin(),Node::note_reverse_path_pin(), andNode::clear_path_pin().core/include/lrc/node.h:PendingAck::path_pinned,PathPin,path_pins_, and the path helper declarations.firmware/src/main.cpp:handle_radio_frame()and any firmware-owned HELLO, WELCOME, beacon, or relay behavior that reads or emits the old path slot.
Tests that must move or be added:
tests/test_packet.cpp:packet_dm_with_path,packet_router_restamp_keeps_tag_valid,packet_golden_bytes,packet_decode_rejects_garbage,packet_decode_fuzz_smoke, plus new hand-built ERB golden bytes and canonical-image mutation coverage.tests/test_reg.cpp:reg_welcome_returns_reverse_path_and_client_pins_router_pathandreg_hello_retry_budget_clears_pinned_router_path.tests/test_node.cpp:node_path_block_relays_matching_next_hop,node_path_relay_store_forwards_when_egress_returns,node_path_discovery_appends_relay_uid_and_preserves_tag,node_path_ack_pins_discovered_route_for_next_dm,node_path_ack_timeout_clears_pin_and_rediscovers_next_dm,node_standby_failover_reroutes_pinned_dm_retry_to_discovery,node_path_block_does_not_flood_unlisted_relay,node_path_block_blind_relays_unknown_version_matching_next_hop,node_path_discovery_blind_relays_unknown_version,node_path_block_unknown_version_does_not_flood_unlisted_relay, and the TX-off relay-store tests around lines 8130-8172.tests/test_chaos.cpp: add ERB/RD multi-hop cases only after the host decision layer exists; these belong naturally with Phase 3.1.
Golden-byte/versioning implications:
- Reusing bit 3 with a different optional-block layout is not wire-compatible
with current v1 nodes. Old nodes will parse the ERB head as
path_len, which can make old and new nodes disagree about packet boundaries. This is a/lrc-wire-changered flag. - The approval PR must bump the
VTversion and document that mixed v1/v2 edge relaying is not guaranteed for ERB frames unless explicit legacy interop code is added. tests/test_packet.cpp::packet_golden_bytesand every new ERB golden frame must be rebuilt by hand, not copied from encoder output.
Phase 2.2: RACK Control Frame
Section titled “Phase 2.2: RACK Control Frame”Future approval target: reserve an exact group-C RACK packet type, preferably the
next unused group-C value 0x2B, with PRIO, TTL=1, ERB mode=RACK, and no
relay past one hop. LANEGRANT remains unchanged. Resolve the “empty payload”
draft conflict by carrying a freshness-bound target in the payload:
u64 target_dedup_key_leu32 router_boot_id_or_session_counter_leThe authenticated trust input is the tagged packet type, SRC, target/freshness
payload, and the normal dedup gate. ERB mode and RACK_RTR1 stay mutable
hints and must not be the sole authority for canceling a relay.
Files and functions that must be touched once approved:
docs/PROTOCOL.md: Packet type table, RACK frame layout, RACK authentication, freshness, replay, and “never relayed past one hop” rules.core/include/lrc/packet.h:PacketType::Rackand any ERB mode constants.core/src/packet.cpp:known_type()and any length-exact RACK/ERB decode constraints.core/src/node.cpp: add RACK emission/handling on the normalhandle_frame()path afterdecode(), tag/signature checks, and dedup; do not add a fast path that bypassesdedup_.seen_or_insert(). UseNode::emit_lane_grant()andNode::handle_lane_grant()as the local Tag8/src-check pattern.core/include/lrc/node.h: declare any RACK handler/emitter state.firmware/src/main.cpp: add firmware RACK RX/TX only after RF sign-off; it must respect CAD, duty-cycle, and local TX enable policy.core/include/lrc/telemetry.handdocs/TELEMETRY.md: add stable counters for RACK TX/RX, bad authentication, cancel, and shorten-only fallback if the behavior is observable.
Tests that must move or be added:
tests/test_packet.cpp: newpacket_rack_golden_bytes, strict freshness-payload decode tests, and bad-length rejection.tests/test_node.cpp: registered relay verifies SK-Tag8 andsrc, duplicate RACK is deduped, wrong source/bad tag cannot cancel, and keyless relay can shorten but never cancel.tests/test_chaos.cpp: suppression-abuse scenarios belong with Phase 3.1 once Phase 1 host decision logic exists.
Red flag resolved for the next implementation pass: NEXT_PASSES.md originally
said RACK has an empty payload, while Rule C requires a freshness binding
(boot_id or a monotone per-session counter) and Rule A needs the signal to name
the logical frame being suppressed. Use the target/freshness payload above unless
a later wire-change review deliberately replaces it with an equally explicit
covered binding.
Phase 2.3: Observed Successful Delivery
Section titled “Phase 2.3: Observed Successful Delivery”Future approval target: with the recommended router-overhear choice, this is a
normative spec and state-machine change, not an additional packet layout beyond
ERB/RACK. docs/PROTOCOL.md must say that no relay is admitted to RD
RELAY_VIA from TRAIL[]; the admission event is the router overhearing a
relay advance one of the router’s own downlink frames.
Files and functions that must be touched once approved:
docs/PROTOCOL.md: define the observed-delivery event and state that it is router-local authority, not a TRAIL byte or relay claim.docs/ROUTING.mdanddocs/TELEMETRY.md: document RD admission, candidate aging, and counters.core/include/lrc/node.h: add bounded RD and return-candidate state once the Phase 1 host decision layer has landed.core/src/node.cpp: integrate the RD gate inNode::handle_frame(), ACK/DM delivery handling (Node::send_ack(),Node::handle_ack()), relay emission, and candidate aging.TRAIL[]may propose/order only already gated candidates.core/include/lrc/telemetry.h: add counters for observed delivery, candidate admission, candidate expiry, and rejected TRAIL-only attempts if surfaced through stats.
Tests that must move or be added:
tests/test_node.cpp: first contact is BROADCAST or UNKNOWN, neverRELAY_VIA; router-overheard relay forwarding admits a candidate; TRAIL-only candidates age out; poisoned TRAIL cannot pin return; RD decays to UNKNOWN.tests/test_chaos.cpp: asymmetric-return correctness and no permanent black-hole under suppression.
If the approval decision instead chooses relay-sourced delivery confirmations, Phase 2.3 becomes another packet-layout event: add the packet/flag, freshness, Tag8/source rules, golden bytes, and keyless fallback to this plan before code.
Wire-Change Checklist Red Flags
Section titled “Wire-Change Checklist Red Flags”Stop and ask before coding if any of these remain unresolved:
- Old and new nodes disagree about packet boundaries after bit-3 reuse.
- RACK freshness depends on an unstated receiver-side fact.
- Any ERB field starts feeding a trust decision.
- Any Tag8/canonical-image coverage changes beyond masking the ERB slot.
- Any UID derivation, dedup key, or
SEQ3canonical stamping changes. - Any firmware TX/contention behavior lands without human RF sign-off.
Minimum approval-time verification:
git diff --checkcmake --build build -j./build/lrc_testspython3 tests/smoke_lrcd.py(cd firmware && pio run)