Completed the transport-relayed Link three-tier unit.

Key findings:

Valid established-Link traffic uses HEADER_1 and link_table.
HEADER_2 Link traffic can cross the addressed relay, then is dropped by the next node.
Relay forwarding requires correct interface and hop count.
Relay forwarding does not require IDX_LT_VALIDATED or destination_type=LINK.
Endpoint Link delivery does require destination_type=LINK.
Link-addressed PROOF uses link_table; ordinary DATA proofs use reverse_table.
Added:

Tier 1 audit
Transport-Link flow
Verifier
Deterministic vectors
Updated SPEC.md, playbook.md, README files, and existing Link flow documentation.

Verification:

Deterministic vector regeneration: identical SHA-256
Full pinned suite: 21 passed, 0 failed
git diff --check: passed
No commit created.
This commit is contained in:
John Poole 2026-06-08 17:50:52 -07:00
commit 4a14dca3a4
11 changed files with 633 additions and 20 deletions

View file

@ -143,7 +143,9 @@ so the LXMessage advances `SENT → DELIVERED` when the recipient's PROOF for th
### 6. Wire bytes leave (KISS / HDLC framing)
Same as `send-opportunistic-lxmf.md` step 9 — the framed link DATA packet leaves the interface. Note that, in contrast to opportunistic DATA, **`Transport.outbound` does NOT apply the HEADER_1→HEADER_2 conversion for link-addressed packets**, regardless of how many transport hops the link traverses. The HEADER_1→HEADER_2 path is keyed on a `path_table` lookup against `dest_hash`; a link-addressed packet's `dest_hash` is the `link_id`, which lives in `link_table`, not `path_table`. Relays then forward it via `link_table` forwarding — which preserves the header bytes verbatim — so emitting HEADER_2 with `transport_id` set on a link-addressed packet would have it dropped at the destination's `packet_filter` as "for another transport instance" (SPEC.md §6.4.3). The next-hop interface for a link is cached on the link object (`link.attached_interface`) so all subsequent traffic uses that same interface without further path-table lookup.
Same as `send-opportunistic-lxmf.md` step 9 — the framed link DATA packet leaves the interface. Note that, in contrast to opportunistic DATA, **`Transport.outbound` does NOT apply the HEADER_1→HEADER_2 conversion for link-addressed packets**, regardless of how many transport hops the link traverses. The HEADER_1→HEADER_2 path is keyed on a `path_table` lookup against `dest_hash`; a link-addressed packet's `dest_hash` is the `link_id`, which lives in `link_table`, not `path_table`. Relays forward it via `link_table`, preserving the header layout and changing only hops. If a clean-room sender emits HEADER_2, the addressed relay can forward it, but the next node drops it because the retained `transport_id` still names the prior relay (SPEC.md §6.4.3). The next-hop interface for a link is cached on the link object (`link.attached_interface`) so all subsequent traffic uses that same interface without further path-table lookup.
See [`transport-link.md`](transport-link.md) for direction-specific relay checks and failure diagnostics.
### 7. PROOF receipt arrives → `__mark_delivered` fires

49
flows/transport-link.md Normal file
View file

@ -0,0 +1,49 @@
# Flow: Transport-Relayed Link
This flow follows a Link across one transport relay, pinned to RNS 1.2.4.
It focuses on behavior that direct-Link testing cannot expose.
## Establishment
1. The initiator sends LINKREQUEST toward the responder's destination hash.
As normal destination-routed traffic, a multi-hop LINKREQUEST can be
HEADER_2 with `transport_id` naming the relay.
2. The relay matches that `transport_id` and the destination's `path_table`
entry. On the last hop it strips the transport slot, emits HEADER_1 toward
the responder, and creates a `link_table[link_id]` entry.
3. The responder emits LRPROOF as HEADER_1 toward `link_id`.
4. The relay accepts LRPROOF only from the responder-side interface at the
recorded remaining-hop count and only after validating the responder's
signature. It forwards LRPROOF toward the initiator and marks the table
entry validated.
## Established Traffic
Post-handshake packets use HEADER_1 and are addressed to `link_id`.
- Initiator to responder: packet arrives on `IDX_LT_RCVD_IF`; after inbound
hop increment it must equal `IDX_LT_HOPS`; relay sends on `IDX_LT_NH_IF`.
- Responder to initiator: packet arrives on `IDX_LT_NH_IF`; after inbound hop
increment it must equal `IDX_LT_REM_HOPS`; relay sends on
`IDX_LT_RCVD_IF`.
- The relay preserves every byte except the hops byte.
- DATA, Resource packets, control packets, and Link-addressed PROOF packets all
use this link-table path.
The forwarding branch is not gated by `IDX_LT_VALIDATED`. Validation affects
entry lifetime, while interface and hop-count checks determine whether a
packet is forwarded.
## Failure Diagnostics
| Observation | Meaning |
|---|---|
| LINKREQUEST crosses relay, LRPROOF does not | Check LRPROOF signature, ingress interface, and expected hop count. |
| Traffic works direct but not through relay | Check that post-handshake packets are HEADER_1 and use `link_id`; then check relay interface/hop direction. |
| Relay forwards DATA but endpoint silently ignores it | Confirm `destination_type == LINK` and that the endpoint Link is attached to the receiving interface. |
| First relay forwards HEADER_2 Link DATA, next node drops it | The stale `transport_id` still names the first relay; established-Link traffic must be HEADER_1. |
| Link traffic forwards before LRPROOF | Expected upstream behavior; `IDX_LT_VALIDATED` is not checked by the forwarding branch. |
Executable evidence: `tools/verify_transport_link.py` and
`test-vectors/transport-link.json`.