Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
# OpenSpec Change: network-plugin-assignment
- **Change ID**: network-plugin-assignment
- **Package**: `@suaveplan/network-plugin-assignment` (new)
- **Status**: PROPOSED
- **Date**: 2026-05-01
---
## Summary
Implement mesh auto-assignment and load balancing. When a new peer connects to the system, there is currently no algorithm to decide which mesh it should join. This plugin provides the "placement layer": tracking mesh capacity across the fleet, scoring candidate meshes for a new peer, rebalancing peers when meshes become overloaded, and admitting or rejecting connections based on global capacity.
This is a P0 gap blocking the user's stated requirement: *"automatically place a new connection in a tiered mesh with available spaces."*
---
## Why
**User requirement**: *"automatically place a new connection in a tiered mesh with available spaces"*
**Current state**: `enterprise-network-assignment` (dated 2026-04-24) provides consistent hashing and rendezvous hashing for **peer selection within a mesh**. It does NOT assign a new peer **to a mesh**.
**What's missing**:
- Mesh capacity tracking across the fleet
- Algorithm for placing a new peer into the "best" mesh (latency, geography, capacity, load)
- Rebalancing when meshes become imbalanced
- Handling of "mesh full" scenarios
---
## Core components
### 1. `MeshCapacityTracker`
Consumes `peer:joined` / `peer:left` / `peer:updated` events from `network-plugin-registry` to maintain a live view of `currentPeers / maxPeers` per mesh.
```ts
class MeshCapacityTracker {
recordMesh(meshId: string, maxPeers: number): void;
recordJoin(meshId: string, peerId: string): void;
recordLeave(meshId: string, peerId: string): void;
getUtilization(meshId: string): number; // 0.0 – 1.0
getAvailableSlots(meshId: string): number;
listMeshes(): string[];
on(event: "mesh:full" | "mesh:available", handler: (meshId: string) => void): void;
}
```
### 2. `AssignmentEngine`
Scores candidate meshes for a new peer and returns the best assignment.
```ts
interface PeerInfo {
peerId: string;
latencyMap: Map<string, number>; // meshId → RTT ms
region?: string;
tierEligibility: number; // max tier this peer can reach (0, 1, 2)
capabilities: DeviceProfile; // CONSTRAINED, STANDARD, POWERFUL, SERVER
}
interface AssignmentScore {
meshId: string;
score: number; // higher is better
factors: {
latencyScore: number;
capacityScore: number;
loadScore: number;
regionScore: number;
tierFitScore: number;
};
}
class AssignmentEngine {
constructor(options: {
tracker: MeshCapacityTracker;
weights: AssignmentWeights; // configurable
minScoreThreshold: number;
});
assign(peer: PeerInfo): AssignmentScore | null; // null = no suitable mesh
rankCandidates(peer: PeerInfo): AssignmentScore[];
}
```
**Scoring factors** (weights sum to 1.0, configurable):
- **Latency** (default 0.30): lower RTT to mesh host = higher score
- **Capacity** (default 0.25): more available slots = higher score
- **Load** (default 0.20): lower current CPU/bandwidth load = higher score
- **Region** (default 0.15): same region as peer = higher score
- **Tier fit** (default 0.10): mesh needs peers of this peer's capability level
**Fallback**: If all meshes are full, the engine returns `null`. The caller (`AdmissionController`) may then trigger mesh creation or reject the peer.
### 3. `MeshBalancer`
Periodic rebalancer that moves peers from overloaded meshes to underloaded ones.
```ts
class MeshBalancer {
constructor(options: {
tracker: MeshCapacityTracker;
imbalanceThreshold: number; // e.g. 0.20 = 20% variance triggers rebalance
rebalanceIntervalMs: number;
maxMovesPerCycle: number;
});
start(): void;
stop(): void;
evaluate(): RebalancePlan[];
executePlan(plan: RebalancePlan): Promise<void>;
}
interface RebalancePlan {
peerId: string;
fromMeshId: string;
toMeshId: string;
reason: string;
}
```
Rebalancing is **cooperative**: the target mesh must accept the peer, and the peer must confirm the move. Uses `network-plugin-migration` for the actual state transfer.
### 4. `AdmissionController`
Gatekeeper that accepts or rejects incoming peer connections.
```ts
class AdmissionController {
constructor(options: {
engine: AssignmentEngine;
balancer: MeshBalancer;
allowMeshCreation: boolean; // if true, creates new mesh when all full
globalMaxPeers: number;
});
admit(peer: PeerInfo): AdmissionResult;
}
interface AdmissionResult {
accepted: boolean;
meshId?: string;
reason?: string;
alternativeMeshIds?: string[]; // ranked alternatives if primary is full
}
```
---
## Error codes
```ts
const ASSIGN_CODES = defineCodes("ASSIGN", [
"MESH_FULL",
"NO_MESH_AVAILABLE",
"GLOBAL_CAPACITY_EXCEEDED",
"PEER_REJECTED",
"REBALANCE_FAILED",
"TRACKER_SYNC_ERROR",
"INVALID_PEER_INFO",
] as const);
```
`AssignmentError extends SuaveplanError<AssignmentErrorCode>`.
---
## Dependencies
- **Consumes**: `@suaveplan/network` (`NetworkNode`, `NetworkBus`), `@suaveplan/network-plugin-registry` (peer events for capacity tracking), `@suaveplan/network-plugin-topology` (tier eligibility, region data), `@suaveplan/network-plugin-relay` (cross-mesh routing for rebalanced peers), `@suaveplan/network-plugin-migration` (state transfer during rebalance), `@suaveplan/error`, `@suaveplan/types`, `@opentelemetry/api`.
- **Blocked on**: `network-core` (shipped), `network-plugin-registry`, `network-plugin-topology`.
---
## Verification approach
- `src/tracker/tracker.spec-conformance.test.ts` — pins capacity tracking, utilization math, full/available events.
- `src/engine/engine.spec-conformance.test.ts` — pins scoring algorithm, weights, fallback behaviour, tier fit.
- `src/balancer/balancer.spec-conformance.test.ts` — pins imbalance detection, plan generation, maxMovesPerCycle.
- `src/controller/controller.spec-conformance.test.ts` — pins admit/reject paths, global capacity enforcement.
- `tests/integration/assignment.spec-conformance.test.ts` — end-to-end: 3 meshes, 10 peers, verify balanced distribution.
- Property-based test: random peer latencies/regions/capabilities → assignment is deterministic for same inputs.
- 100/100/100/100 Istanbul coverage.
---
## Conformance requirements (proposed)
- `NET-71` — `MeshCapacityTracker` tracks `currentPeers / maxPeers` per mesh from registry events.
- `NET-72` — `AssignmentEngine` scores candidate meshes using latency, capacity, load, region, and tier fit.
- `NET-73` — `AssignmentEngine` returns `null` when no suitable mesh is found.
- `NET-74` — `MeshBalancer` detects imbalance and generates `RebalancePlan` objects.
- `NET-75` — `AdmissionController` rejects peers when global capacity is exceeded.
- `NET-76` — `AdmissionController` returns ranked alternative meshes when the primary mesh is full.
- `NET-77` — Rebalancing triggers `network-plugin-migration` for peer state transfer.
---
## Out of scope
- **Global DHT for mesh discovery** → handled by `network-plugin-relay` `MeshDirectory`.
- **Forced peer eviction** → v2; v1 only does cooperative rebalancing.
- **Real-time load metrics (CPU/bandwidth)** → consumes from `network-plugin-monitor` when available; v1 uses peer count as load proxy.

View File

@@ -0,0 +1,18 @@
# Tasks: network-plugin-assignment
- [x] 1. Write `openspec/changes/network-plugin-assignment/proposal.md`
- [x] 2. Write `openspec/changes/network-plugin-assignment/tasks.md`
- [x] 3. Scaffold `packages/network/network-plugin-assignment/package.json`, `tsconfig.json`, `vitest.config.ts`
- [x] 4. Implement `src/error-codes.ts` with `ASSIGN_CODES`
- [x] 5. Implement `src/tracker/mesh-capacity-tracker.ts` — consume registry events, track utilization
- [x] 6. Implement `src/engine/assignment-engine.ts` — scoring algorithm, weights, ranking
- [x] 7. Implement `src/balancer/mesh-balancer.ts` — imbalance detection, plan generation
- [x] 8. Implement `src/controller/admission-controller.ts` — admit/reject gatekeeper
- [x] 9. Write `src/tracker/tracker.spec-conformance.test.ts`
- [x] 10. Write `src/engine/engine.spec-conformance.test.ts`
- [x] 11. Write `src/balancer/balancer.spec-conformance.test.ts`
- [x] 12. Write `src/controller/controller.spec-conformance.test.ts`
- [x] 13. Write `tests/integration/assignment.spec-conformance.test.ts`
- [x] 14. Write property-based tests for scoring determinism
- [x] 15. Write `tests/dep-graph.test.ts`
- [x] 16. Run `bunx turbo lint typecheck test --filter=@suaveplan/network-plugin-assignment` and achieve 100/100/100/100 coverage