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

6.9 KiB

OpenSpec Change: network-plugin-migration

  • Change ID: network-plugin-migration
  • Package: @suaveplan/network-plugin-migration (new)
  • Status: PROPOSED
  • Date: 2026-05-01

Summary

Implement peer state migration between meshes. When network-plugin-assignment rebalances a peer from an overloaded mesh to an underloaded one, or when a peer manually requests to join a different mesh, there is currently no mechanism to transfer state or maintain message continuity during the handoff. This plugin provides the migration protocol: state snapshot transfer, graceful handoff with a forwarding tunnel, and session persistence across mesh boundaries.


Why

User requirement: "automatically place a new connection in a tiered mesh with available spaces" implies rebalancing, which implies migration.

Current state: No migration logic exists anywhere in the network suite.

What's missing:

  • State transfer when a peer moves from Mesh A to Mesh B
  • Graceful handoff: old host forwards messages to new host during transition
  • Session persistence across mesh boundaries

Core components

1. MigrationCoordinator

Orchestrates the end-to-end migration flow.

class MigrationCoordinator {
  constructor(options: {
    localPeerId: string;
    localMeshId: string;
    bus: NetworkBus;
  });
  initiateMigration(request: MigrationRequest): Promise<MigrationResult>;
  acceptMigration(request: MigrationRequest): Promise<MigrationResult>;
}

interface MigrationRequest {
  peerId: string;
  fromMeshId: string;
  toMeshId: string;
  reason: string; // e.g. "rebalance", "user-request", "region-optimization"
  priority: "graceful" | "urgent";
}

interface MigrationResult {
  success: boolean;
  peerId: string;
  newMeshId: string;
  transferredBytes: number;
  durationMs: number;
  forwardedMessages: number;
}

Migration flow:

  1. Request — MigrationCoordinator sends migration:request to the target mesh's assignment controller.
  2. Accept — Target mesh admits the peer via AdmissionController.
  3. Snapshot — Old host serializes peer state (registry entry, topology position, session memberships) and sends it to the new host.
  4. Transfer — New host installs the peer into its local registry and topology.
  5. Tunnel — Old host starts a forwarding tunnel for messages addressed to the migrating peer.
  6. Cutover — Peer disconnects from old mesh and connects to new mesh.
  7. Drain — Old host drains the forwarding tunnel (default 30s or until empty).
  8. Complete — Old host removes peer from its registry. migration:completed is emitted on both meshes' buses.

2. StateTransferProtocol

Serializes and deserializes peer state for migration.

class StateTransferProtocol {
  serialize(peerId: string, registry: RegistrySnapshot, topology: TopologyPosition, sessions: SessionMembership[]): Uint8Array;
  deserialize(bytes: Uint8Array): PeerStateSnapshot;
}

interface PeerStateSnapshot {
  peerId: string;
  registryEntry: PeerRegistryEntry;
  topologyPosition: TopologyPosition;
  sessionMemberships: SessionMembership[];
  version: number; // monotonic, for conflict detection
}

Uses @suaveplan/bitpacker for compact binary encoding. Max snapshot size: 64 KiB (peers with larger state must use lazy rehydration).

3. ForwardingTunnel

Temporary message forwarding from old host to new host during cutover.

class ForwardingTunnel {
  constructor(options: {
    fromHostId: string;
    toHostId: string;
    peerId: string;
    relay: RelayManager; // from network-plugin-relay
    drainTimeoutMs: number; // default 30000
    maxBufferedMessages: number; // default 1000
  });
  open(): void;
  close(): Promise<void>;
  forward(envelope: MessageEnvelope): void;
  getBufferedCount(): number;
}

Behaviour:

  • All messages addressed to peerId that arrive at the old host are forwarded to the new host via RelayManager.
  • If the buffer exceeds maxBufferedMessages, oldest messages are dropped (application is notified).
  • After drainTimeoutMs, the tunnel is closed regardless of buffer state.
  • ForwardingTunnel is only used for graceful migrations. urgent migrations skip the tunnel and accept message loss.

Error codes

const MIGRATE_CODES = defineCodes("MIGRATE", [
  "TARGET_MESH_REJECTED",
  "STATE_TRANSFER_FAILED",
  "TUNNEL_OVERFLOW",
  "TUNNEL_TIMEOUT",
  "CUTOVER_FAILED",
  "MIGRATION_ABORTED",
  "INVALID_SNAPSHOT",
] as const);

MigrationError extends SuaveplanError<MigrationErrorCode>.


Dependencies

  • Consumes: @suaveplan/network (NetworkNode, NetworkBus), @suaveplan/network-plugin-relay (RelayManager, cross-mesh forwarding), @suaveplan/network-plugin-assignment (AdmissionController, MeshBalancer), @suaveplan/network-plugin-registry (RegistrySnapshot), @suaveplan/network-plugin-topology (TopologyPosition), @suaveplan/network-plugin-session (SessionMembership), @suaveplan/bitpacker (snapshot encoding), @suaveplan/error, @suaveplan/types, @opentelemetry/api.
  • Blocked on: network-core (shipped), network-plugin-relay, network-plugin-assignment, network-plugin-registry, network-plugin-topology, network-plugin-session.

Verification approach

  • src/coordinator/coordinator.spec-conformance.test.ts — pins full migration flow: request → accept → snapshot → transfer → tunnel → cutover → drain.
  • src/state-transfer/state-transfer.spec-conformance.test.ts — pins serialize/deserialize roundtrip, version tracking, size limits.
  • src/tunnel/forwarding-tunnel.spec-conformance.test.ts — pins buffer limits, overflow drop, drain timeout.
  • tests/integration/migration.spec-conformance.test.ts — end-to-end: peer moves from Mesh A to Mesh B, messages continue to arrive.
  • 100/100/100/100 Istanbul coverage.

Conformance requirements (proposed)

  • NET-99 — MigrationCoordinator executes a 8-step migration flow (request, accept, snapshot, transfer, tunnel, cutover, drain, complete).
  • NET-100 — StateTransferProtocol serializes peer registry, topology, and session state using @suaveplan/bitpacker.
  • NET-101 — ForwardingTunnel buffers messages for the migrating peer and forwards them to the new host.
  • NET-102 — ForwardingTunnel drops oldest messages when buffer exceeds maxBufferedMessages.
  • NET-103 — ForwardingTunnel closes after drainTimeoutMs regardless of buffer state.
  • NET-104 — urgent migrations skip the forwarding tunnel and accept message loss.
  • NET-105 — migration:completed is emitted on both the source and target mesh buses.

Out of scope

  • Live migration without disconnect → v2; v1 requires a brief disconnect/cutover.
  • Migration of in-flight game state → application-level; v1 transfers session memberships only.
  • Rollback on failure → v2; v1 reports failure and leaves application to retry.