Initial Commit
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
# 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.
|
||||
|
||||
```ts
|
||||
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.
|
||||
|
||||
```ts
|
||||
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.
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Tasks: network-plugin-migration
|
||||
|
||||
- [x] 1. Write `openspec/changes/network-plugin-migration/proposal.md`
|
||||
- [x] 2. Write `openspec/changes/network-plugin-migration/tasks.md`
|
||||
- [x] 3. Scaffold `packages/network/network-plugin-migration/package.json`, `tsconfig.json`, `vitest.config.ts`
|
||||
- [x] 4. Implement `src/error-codes.ts` with `MIGRATE_CODES`
|
||||
- [x] 5. Implement `src/coordinator/migration-coordinator.ts` — 8-step migration flow
|
||||
- [x] 6. Implement `src/state-transfer/state-transfer-protocol.ts` — snapshot serialize/deserialize
|
||||
- [x] 7. Implement `src/tunnel/forwarding-tunnel.ts` — buffer, forward, drain
|
||||
- [x] 8. Write `src/coordinator/coordinator.spec-conformance.test.ts`
|
||||
- [x] 9. Write `src/state-transfer/state-transfer.spec-conformance.test.ts`
|
||||
- [x] 10. Write `src/tunnel/forwarding-tunnel.spec-conformance.test.ts`
|
||||
- [x] 11. Write `tests/integration/migration.spec-conformance.test.ts`
|
||||
- [x] 12. Write `tests/dep-graph.test.ts`
|
||||
- [x] 13. Run `bunx turbo lint typecheck test --filter=@suaveplan/network-plugin-migration` and achieve 100/100/100/100 coverage
|
||||
Reference in New Issue
Block a user