8.1 KiB
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 + peerIdvs justpeerId) - 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.
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<string, string>;
}
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).
interface HostBackboneTransport {
connect(hostId: string, address: string): Promise<void>;
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:
interface CrossMeshEnvelope {
// Inherited from MessageEnvelope
id: string;
type: string;
senderId: string;
senderMeshId: string;
targetId: string;
targetMeshId: string;
payload: Uint8Array;
timestamp: number;
headers: Record<string, string>;
// 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.
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).
class MeshDirectory {
registerMesh(meshId: string, tier0HostIds: string[], hostAddresses: Map<string, string>): 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
- Same mesh —
RelayManagerreturnsnull; message stays local. - Different mesh, direct backbone exists — forward to connected Tier 0 host that advertises the target mesh.
- Different mesh, multi-hop required —
RelayRouterselects next hop from its table;hopCountincrements;visitedNodesappends current node. - Broadcast across meshes —
targetMeshId = "*",targetId = "*". Relayed to ALL known meshes. Deduplicated at each mesh boundary viamessageIdBloom filter. - Loop prevention — Hard drop if
hopCount >= maxHopsorvisitedNodescontains current node.
Error codes
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<RelayErrorCode>.
Dependencies
- Consumes:
@suaveplan/network(NetworkNode,NetworkBus,MessageEnvelope,NetworkPlugin),@suaveplan/network-plugin-topology(tier info for backbone eligibility),@suaveplan/network-plugin-registry(peer lists forMeshDirectory),@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—RelayRoutermaintains a routing table from(destMeshId, destPeerId)tonextHopHostId.NET-62—HostBackboneTransportconnects Tier 0 hosts with capability negotiation.NET-63—CrossMeshEnvelopeextendsMessageEnvelopewithsenderMeshId,targetMeshId,hopCount,maxHops,visitedNodes, andttl.NET-64— Cross-mesh messages are dropped whenhopCount >= maxHops.NET-65— Cross-mesh messages are dropped whenttl <= 0.NET-66— Cross-mesh messages are dropped whenvisitedNodescontains the current node.NET-67—MeshDirectoryregisters 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—RelayManagerintercepts outbound messages wheretargetMeshId !== localMeshIdand forwards them.NET-70— Inbound cross-mesh envelopes are delivered to the localNetworkBusscoped to the target peer.
Out of scope
- Cross-mesh state synchronization →
network-plugin-statehandles 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.