Files
2026-05-27 23:29:53 +01:00

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 + 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.

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

  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

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 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.