Initial Commit
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
# 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<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).
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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.
|
||||
|
||||
```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<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
|
||||
|
||||
```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<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.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Tasks: network-plugin-relay
|
||||
|
||||
- [x] 1. Write `openspec/changes/network-plugin-relay/proposal.md`
|
||||
- [x] 2. Write `openspec/changes/network-plugin-relay/tasks.md`
|
||||
- [x] 3. Scaffold `packages/network/network-plugin-relay/package.json`, `tsconfig.json`, `vitest.config.ts`
|
||||
- [x] 4. Implement `src/error-codes.ts` with `RELAY_CODES`
|
||||
- [x] 5. Implement `src/router/relay-router.ts` — routing table, add/remove/resolve
|
||||
- [x] 6. Implement `src/backbone/host-backbone-transport.ts` — Tier 0 transport abstraction
|
||||
- [x] 7. Implement `src/envelope/cross-mesh-envelope.ts` — Zod schema, validation rules
|
||||
- [x] 8. Implement `src/directory/mesh-directory.ts` — mesh registration, host lookup
|
||||
- [x] 9. Implement `src/manager/relay-manager.ts` — plugin entry point, outbound intercept, inbound delivery
|
||||
- [x] 10. Implement `src/loop-prevention/loop-guard.ts` — hopCount, ttl, visitedNodes checks
|
||||
- [x] 11. Write `src/router/router.spec-conformance.test.ts`
|
||||
- [x] 12. Write `src/backbone/backbone.spec-conformance.test.ts`
|
||||
- [x] 13. Write `src/envelope/envelope.spec-conformance.test.ts`
|
||||
- [x] 14. Write `src/directory/directory.spec-conformance.test.ts`
|
||||
- [x] 15. Write `src/manager/manager.spec-conformance.test.ts`
|
||||
- [x] 16. Write `src/loop-prevention/loop-prevention.spec-conformance.test.ts`
|
||||
- [x] 17. Write `tests/integration/relay.spec-conformance.test.ts` — end-to-end two-mesh relay
|
||||
- [x] 18. Write `tests/dep-graph.test.ts`
|
||||
- [x] 19. Run `bunx turbo lint typecheck test --filter=@suaveplan/network-plugin-relay` and achieve 100/100/100/100 coverage
|
||||
Reference in New Issue
Block a user