# OpenSpec Change: network-plugin-relay - **Change ID**: network-plugin-relay - **Package**: `@suaveplan/network-plugin-relay` (new) - **Status**: PROPOSED - **Date**: 2026-05-01 --- ## Summary Implement cross-mesh message relay — the "federation layer" that enables Tier 0 hosts of different meshes to route messages to one another. Without this plugin, the network suite only supports intra-mesh communication; peers in Mesh A cannot send messages to peers in Mesh B. This is a P0 gap blocking the user's stated requirement: *"message relaying through the hosts through the nominated tier host to be able to transmit in any pocket mesh on any tier to any other pocket mesh on any tier."* --- ## Why **User requirement**: *"message relaying through the hosts through the nominated tier host to be able to transmit in any pocket mesh on any tier to any other pocket mesh on any tier"* **Current state**: ALL existing network proposals handle **intra-mesh** communication only (full mesh within one group). No package addresses **inter-mesh** routing. **What's missing**: - Host-to-host backbone network between Tier 0 hosts of different meshes - Message forwarding/relay logic at the host level - Cross-mesh addressing scheme (`meshId + peerId` vs just `peerId`) - Route discovery between meshes - Concept of a "super-peer" or "federation backbone" --- ## Core components ### 1. `RelayRouter` Maintains a routing table: `(destinationMeshId, destinationPeerId) → nextHopHostId`. Runs on every Tier 0 host. Updates dynamically from `MeshDirectory` sync events. ```ts class RelayRouter { addRoute(destMeshId: string, destPeerId: string, nextHopHostId: string): void; removeRoute(destMeshId: string, destPeerId: string): void; resolve(targetMeshId: string, targetPeerId: string): string | null; // returns nextHopHostId getRoutesForMesh(meshId: string): Map; } ``` ### 2. `HostBackboneTransport` Dedicated transport for Tier 0 ↔ Tier 0 communication. Reuses `WebSocket` or `RTCDataChannel` but is logically separate from peer mesh transport. Negotiates capabilities (max message size, supported compression, crypto suites). ```ts interface HostBackboneTransport { connect(hostId: string, address: string): Promise; disconnect(hostId: string): void; send(hostId: string, envelope: CrossMeshEnvelope): void; onMessage(handler: (envelope: CrossMeshEnvelope, fromHostId: string) => void): void; getConnectedHosts(): string[]; } ``` ### 3. `CrossMeshEnvelope` Extends `MessageEnvelope` with cross-mesh routing metadata: ```ts interface CrossMeshEnvelope { // Inherited from MessageEnvelope id: string; type: string; senderId: string; senderMeshId: string; targetId: string; targetMeshId: string; payload: Uint8Array; timestamp: number; headers: Record; // Relay-specific hopCount: number; // incremented per hop maxHops: number; // default 16 visitedNodes: string[]; // peerIds / hostIds visited ttl: number; // ms remaining } ``` Zod-validated. Dropped when `hopCount >= maxHops`, `ttl <= 0`, or `visitedNodes` contains current node. ### 4. `RelayManager` Plugin entry point. Intercepts outbound messages where `targetMeshId !== localMeshId` and forwards them via `RelayRouter` → `HostBackboneTransport`. On inbound cross-mesh messages, delivers to local `NetworkBus` scoped to the target peer. ```ts class RelayManager { constructor(options: { router: RelayRouter; backbone: HostBackboneTransport; localMeshId: string; localHostId: string; maxHops: number; defaultTtlMs: number; }); start(): void; stop(): void; onOutbound(envelope: MessageEnvelope): CrossMeshEnvelope | null; // null = not relayed onInbound(envelope: CrossMeshEnvelope): void; } ``` ### 5. `MeshDirectory` Discovery/registration of known meshes and their Tier 0 hosts. Can be seed-based (static config), server-assisted (via `network-plugin-server-bridge`), or gossip-based (Tier 0 hosts exchange mesh advertisements). ```ts class MeshDirectory { registerMesh(meshId: string, tier0HostIds: string[], hostAddresses: Map): void; unregisterMesh(meshId: string): void; getTier0Hosts(meshId: string): string[]; getHostAddress(hostId: string): string | undefined; listKnownMeshes(): string[]; on(event: "mesh:added" | "mesh:removed" | "mesh:updated", handler: Function): void; } ``` --- ## Routing rules 1. **Same mesh** — `RelayManager` returns `null`; message stays local. 2. **Different mesh, direct backbone exists** — forward to connected Tier 0 host that advertises the target mesh. 3. **Different mesh, multi-hop required** — `RelayRouter` selects next hop from its table; `hopCount` increments; `visitedNodes` appends current node. 4. **Broadcast across meshes** — `targetMeshId = "*"`, `targetId = "*"`. Relayed to ALL known meshes. Deduplicated at each mesh boundary via `messageId` Bloom filter. 5. **Loop prevention** — Hard drop if `hopCount >= maxHops` or `visitedNodes` contains current node. --- ## Error codes ```ts const RELAY_CODES = defineCodes("RELAY", [ "ROUTE_NOT_FOUND", "BACKBONE_CONNECT_FAILED", "BACKBONE_DISCONNECTED", "MAX_HOPS_EXCEEDED", "TTL_EXPIRED", "LOOP_DETECTED", "INVALID_ENVELOPE", "MESH_NOT_REGISTERED", "HOST_NOT_FOUND", "RELAY_REJECTED", ] as const); ``` `RelayError extends SuaveplanError`. --- ## Dependencies - **Consumes**: `@suaveplan/network` (`NetworkNode`, `NetworkBus`, `MessageEnvelope`, `NetworkPlugin`), `@suaveplan/network-plugin-topology` (tier info for backbone eligibility), `@suaveplan/network-plugin-registry` (peer lists for `MeshDirectory`), `@suaveplan/network-plugin-crypto-messages` (signature verification on cross-mesh envelopes), `@suaveplan/error`, `@suaveplan/events`, `@suaveplan/types`, `zod`, `@opentelemetry/api`. - **Blocked on**: `network-core` (shipped), `network-plugin-topology`, `network-plugin-registry`. --- ## Verification approach - `src/router/router.spec-conformance.test.ts` — pins routing table add/remove/resolve, multi-hop resolution. - `src/backbone/backbone.spec-conformance.test.ts` — pins HostBackboneTransport connect/send/onMessage. - `src/envelope/envelope.spec-conformance.test.ts` — pins Zod validation, hopCount/ttl/visitedNodes rules. - `src/manager/manager.spec-conformance.test.ts` — pins outbound intercept, inbound delivery, same-mesh bypass. - `src/directory/directory.spec-conformance.test.ts` — pins mesh registration, host lookup, event emission. - `src/loop-prevention/loop-prevention.spec-conformance.test.ts` — pins maxHops drop, ttl drop, visitedNodes drop. - `tests/integration/relay.spec-conformance.test.ts` — end-to-end: Mesh A peer → Mesh B peer via Tier 0 backbone. - 100/100/100/100 Istanbul coverage. --- ## Conformance requirements (proposed) - `NET-61` — `RelayRouter` maintains a routing table from `(destMeshId, destPeerId)` to `nextHopHostId`. - `NET-62` — `HostBackboneTransport` connects Tier 0 hosts with capability negotiation. - `NET-63` — `CrossMeshEnvelope` extends `MessageEnvelope` with `senderMeshId`, `targetMeshId`, `hopCount`, `maxHops`, `visitedNodes`, and `ttl`. - `NET-64` — Cross-mesh messages are dropped when `hopCount >= maxHops`. - `NET-65` — Cross-mesh messages are dropped when `ttl <= 0`. - `NET-66` — Cross-mesh messages are dropped when `visitedNodes` contains the current node. - `NET-67` — `MeshDirectory` registers known meshes and their Tier 0 hosts. - `NET-68` — Broadcast across meshes (`targetMeshId = "*"`) is relayed to all known meshes and deduplicated at mesh boundaries. - `NET-69` — `RelayManager` intercepts outbound messages where `targetMeshId !== localMeshId` and forwards them. - `NET-70` — Inbound cross-mesh envelopes are delivered to the local `NetworkBus` scoped to the target peer. --- ## Out of scope - Cross-mesh **state synchronization** → `network-plugin-state` handles state sync within a mesh; cross-mesh state is application-level. - Global DHT for mesh discovery → future hardening; seed-based and server-assisted discovery are sufficient for v1. - Cross-mesh **host election** → each mesh governs itself; no global host.