first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
# @suaveplan/network-plugin-assignment - Mesh auto-assignment and load balancing: capacity tracker, assignment-scoring engine, rebalance planner, admission controller
## Summary
Mesh auto-assignment and load balancing: live `MeshCapacityTracker`, weighted-multi-criteria `AssignmentEngine`, imbalance-detecting `MeshBalancer` planner, and admission-gating `AdmissionController`.
<!-- begin:auto-docs -->
## Documentation tree
- [balancer](./src/balancer/mesh-balancer.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [mesh-balancer](./src/balancer/mesh-balancer.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [admission-controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [assignment-engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [mesh-capacity-tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [error-codes](./src/error-codes.md) — Frozen registry of error codes for the network-plugin-assignment plugin. Three codes cover the surface: `NO_ELIGIBLE_ME…
<!-- end:auto-docs --> - [mesh-balancer](./src/balancer/mesh-balancer.md) — <!-- begin:auto-docs --> _(no submodules)_- [controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_- [admission-controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_- [engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_- [assignment-engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_- [tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_- [mesh-capacity-tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Frozen registry of error codes for the network-plugin-assignment plugin. Three codes cover the surface: `NO_ELIGIBLE_ME…
- [mesh-balancer](./src/balancer/mesh-balancer.md) — <!-- begin:auto-docs --> _(no submodules)_- [controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_- [admission-controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_- [engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_- [assignment-engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_- [tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_- [mesh-capacity-tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Frozen registry of error codes for the network-plugin-assignment plugin. Three codes cover the surface: `NO_ELIGIBLE_ME…
- [mesh-balancer](./src/balancer/mesh-balancer.md) — <!-- begin:auto-docs --> _(no submodules)_- [controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_- [admission-controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_- [engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_- [assignment-engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_- [tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_- [mesh-capacity-tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Frozen registry of error codes for the network-plugin-assignment plugin. Three codes cover the surface: `NO_ELIGIBLE_ME…
- [mesh-balancer](./src/balancer/mesh-balancer.md) — <!-- begin:auto-docs --> _(no submodules)_- [controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_ - [admission-controller](./src/controller/admission-controller.md) — <!-- begin:auto-docs --> _(no submodules)_- [engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_ - [assignment-engine](./src/engine/assignment-engine.md) — <!-- begin:auto-docs --> _(no submodules)_- [tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_ - [mesh-capacity-tracker](./src/tracker/mesh-capacity-tracker.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Frozen registry of error codes for the network-plugin-assignment plugin. Three codes cover the surface: `NO_ELIGIBLE_ME…

View File

@@ -0,0 +1,78 @@
# MeshBalancer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Detects mesh imbalances and proposes rebalance moves. The plan is advisory — the consumer is responsible for actually executing each move via `network-plugin-migration`. Imbalance is measured as the spread between the highest- and lowest-utilised meshes; below `imbalanceThreshold` the plan is empty.
## Features
- `computeImbalance()` returns the current `[0, 1]` spread between the busiest and least-busy meshes.
- `buildPlan(peerSelector)` returns a structured `RebalancePlan` with capped move count.
- Configurable `imbalanceThreshold` (default `0.3`) and `maxMovesPerPlan` (default `10`).
- Pluggable `peerSelector` — the consumer decides which peers can move from the overloaded mesh.
- Plan includes both pre and post-move imbalance estimates for observability.
## Usage: Basic Example
```ts
import {
MeshBalancer,
MeshCapacityTracker,
} from "@suaveplan/network-plugin-assignment";
const tracker = new MeshCapacityTracker();
const balancer = new MeshBalancer({ tracker });
const plan = balancer.buildPlan((overloadedMeshId) => [
"alice",
"bob",
]);
for (const move of plan.moves) {
await migrationCoordinator.initiateMigration({
peerId: move.peerId,
fromMeshId: move.fromMeshId,
toMeshId: move.toMeshId,
reason: move.reason,
priority: "graceful",
});
}
```
## Usage: Advanced Example
```ts
import {
MeshBalancer,
MeshCapacityTracker,
} from "@suaveplan/network-plugin-assignment";
const balancer = new MeshBalancer({
tracker,
imbalanceThreshold: 0.5,
maxMovesPerPlan: 3,
});
if (balancer.computeImbalance() >= 0.4) {
const plan = balancer.buildPlan((meshId) =>
registry.peersInMesh(meshId).map((p) => p.peerId)
);
metrics.gauge("mesh.imbalance.before", plan.imbalanceBefore);
metrics.gauge("mesh.imbalance.after", plan.imbalanceAfter);
}
```
## API Reference
- `interface RebalanceMove { peerId, fromMeshId, toMeshId, reason }`.
- `interface RebalancePlan { moves, imbalanceBefore, imbalanceAfter }`.
- `interface MeshBalancerConfig { tracker, imbalanceThreshold?, maxMovesPerPlan? }`.
- `computeImbalance(): number`.
- `buildPlan(peerSelector): RebalancePlan`.
## Implementation Notes
The balancer picks the single most-overloaded mesh and the single least-loaded mesh per plan rather than computing an N-way redistribution; this keeps each plan small enough to execute without congesting the migration plugin and lets repeated plan iterations converge on equilibrium across cycles. The peer selector is a closure rather than a tracked attribute because peer eligibility depends on mesh-membership data we deliberately do not duplicate inside the balancer — that lives in `network-plugin-registry`.

View File

@@ -0,0 +1,89 @@
# AdmissionController
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Gatekeeper that runs the `AssignmentEngine`, checks capacity against the `MeshCapacityTracker`, applies a pluggable `AdmissionPolicy`, and either records the placement or throws a typed error. Centralising admission here means there's one entry point for "place this peer somewhere" and the engine, balancer, and observability all see the same outcome.
## Features
- Pluggable `AdmissionPolicy` for non-capacity gates (capability mismatch, regional rules, operator quotas).
- Mesh enumeration via injected `meshes()` callback so the controller never has to keep a stale local copy.
- Three failure modes with distinct codes: `ADMISSION_DENIED` (policy), `NO_ELIGIBLE_MESH` (engine), `MESH_FULL` (tracker race).
- `release(peer, meshId)` for clean disconnect semantics.
- On success, records the join into the tracker so the next call sees the updated capacity.
## Usage: Basic Example
```ts
import {
AdmissionController,
AssignmentEngine,
MeshCapacityTracker,
} from "@suaveplan/network-plugin-assignment";
const tracker = new MeshCapacityTracker();
const engine = new AssignmentEngine({ tracker });
const controller = new AdmissionController({
engine,
tracker,
meshes: () => [
{ meshId: "mesh-A", tierFloor: 0 },
{ meshId: "mesh-B", tierFloor: 0 },
],
});
const result = controller.admit(peer);
```
## Usage: Advanced Example
```ts
import {
ASSIGN_CODES,
AdmissionController,
AssignError,
} from "@suaveplan/network-plugin-assignment";
const controller = new AdmissionController({
engine,
tracker,
meshes: () => fleetSnapshot,
policy: (peer, mesh) => {
if (peer.capabilities === "CONSTRAINED" && mesh.tierFloor < 2) {
return false;
}
return true;
},
});
try {
const placement = controller.admit(peer);
bus.emit("peer:placed", placement);
} catch (err) {
if (err instanceof AssignError) {
if (err.code === ASSIGN_CODES.MESH_FULL) {
await rebalanceAndRetry(peer);
return;
}
if (err.code === ASSIGN_CODES.ADMISSION_DENIED) {
return enterStandby(peer);
}
}
throw err;
}
```
## API Reference
- `type AdmissionPolicy = (peer, descriptor) => boolean`.
- `interface AdmissionControllerConfig { engine, tracker, meshes, policy? }`.
- `interface AdmissionResult { peerId, meshId, score }`.
- `admit(peer): AdmissionResult` — throws `ADMISSION_DENIED` / `NO_ELIGIBLE_MESH` / `MESH_FULL`.
- `release(peer, meshId)` — updates the tracker.
## Implementation Notes
`admit` does the policy check _before_ calling the engine so that policy violations are detected before we burn cycles scoring meshes the peer can't join anyway. The capacity check happens _after_ the engine picks a winner, because another concurrent admission could race the available-slots count down to zero between when the engine scored and when we record. Callers that need stricter ordering should serialize admissions through their own queue.

View File

@@ -0,0 +1,93 @@
# AssignmentEngine
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Scores candidate meshes for a placement decision along five axes (latency, capacity, load, region, tier-fit), combines them into a composite score, and returns the best mesh that still has room. Decoupling the scoring from the admission and balancer means consumers can run "what-if" scoring without committing to a placement, and the rebalancer can use the same scoring to evaluate moves.
## Features
- Five scoring axes, each normalised to `[0, 1]`.
- Configurable `AssignmentWeights` (defaults `0.35 / 0.25 / 0.2 / 0.1 / 0.1`).
- Configurable `minScoreThreshold` for "this mesh is good enough" decisions.
- Tier-fit pre-filter: peers below `tierFloor` are excluded from scoring entirely.
- Deterministic lex-tiebreak on `meshId` so independent observers reach the same winner.
- Throws `NO_ELIGIBLE_MESH` for empty candidate sets and below-threshold scores.
## Usage: Basic Example
```ts
import {
AssignmentEngine,
MeshCapacityTracker,
} from "@suaveplan/network-plugin-assignment";
const tracker = new MeshCapacityTracker();
tracker.recordMesh("mesh-A", 32);
tracker.recordMesh("mesh-B", 32);
const engine = new AssignmentEngine({ tracker });
const best = engine.bestFor(
{
peerId: "alice",
latencyMap: new Map([
["mesh-A", 50],
["mesh-B", 200],
]),
tierEligibility: 1,
capabilities: "STANDARD",
},
[
{ meshId: "mesh-A", tierFloor: 0 },
{ meshId: "mesh-B", tierFloor: 0 },
]
);
```
## Usage: Advanced Example
```ts
import {
AssignmentEngine,
MeshCapacityTracker,
} from "@suaveplan/network-plugin-assignment";
const engine = new AssignmentEngine({
tracker,
weights: {
latency: 0.5,
capacity: 0.3,
load: 0.1,
region: 0.1,
tierFit: 0,
},
minScoreThreshold: 0.4,
});
const scored = engine.score(peer, candidates);
for (const s of scored) {
console.log(s.meshId, s.score, s.factors);
}
try {
const best = engine.bestFor(peer, candidates);
} catch (err) {
// handle NO_ELIGIBLE_MESH
}
```
## API Reference
- `interface PeerInfo { peerId, latencyMap, region?, tierEligibility, capabilities }`.
- `interface MeshDescriptor { meshId, region?, tierFloor }`.
- `interface AssignmentWeights { latency, capacity, load, region, tierFit }`.
- `interface AssignmentScore { meshId, score, factors }`.
- `interface AssignmentEngineConfig { tracker, weights?, minScoreThreshold? }`.
- `score(peer, candidates): readonly AssignmentScore[]`.
- `bestFor(peer, candidates): AssignmentScore` — throws `NO_ELIGIBLE_MESH` on empty / below-threshold.
## Implementation Notes
The latency axis uses `1 / (1 + ms)` rather than min-max normalisation so a peer that has only one latency reading still produces a sensible score (no degenerate divide-by-zero). Capacity and load are deliberately separate axes — capacity is "how many free slots" while load is "how full"; they correlate but a tiny mesh and a huge mesh at the same utilisation should rank differently for placement, which the dual-axis lets us tune. Tier-fit is enforced as a _filter_ before scoring, then re-asserted as a binary axis, so we never score a peer against a mesh it cannot reach.

View File

@@ -0,0 +1,62 @@
# ASSIGN_CODES
## Purpose
Frozen registry of error codes for the network-plugin-assignment plugin. Three codes cover the surface: `NO_ELIGIBLE_MESH` when the engine cannot score any mesh above the threshold, `MESH_FULL` when the chosen mesh's capacity is exhausted at the moment of admission, and `ADMISSION_DENIED` when the admission controller refuses for a non-capacity reason (capability mismatch, regional policy, etc.). The companion `AssignError` extends `SuaveplanError<AssignErrorCode>` so consumers pattern-match on `code` rather than parse messages.
## Features
- Three frozen codes registered under the `NET_ASSIGN` namespace.
- `AssignError extends SuaveplanError<AssignErrorCode>`.
- `name: "AssignError"` on every instance — useful for log filters and dashboard alerts.
- `cause` and `context` propagation via `SuaveplanErrorOptions`.
- Codes are stable across versions; the registry only ever grows, never shrinks.
## Usage: Basic Example
```ts
import {
ASSIGN_CODES,
AssignError,
} from "@suaveplan/network-plugin-assignment";
throw new AssignError(
ASSIGN_CODES.NO_ELIGIBLE_MESH,
"assignment.no-eligible-mesh",
{ context: { peerId: "alice", knownMeshes: 4 } }
);
```
## Usage: Advanced Example
```ts
import {
ASSIGN_CODES,
AssignError,
} from "@suaveplan/network-plugin-assignment";
try {
const meshId = await admissionController.admit(peerInfo);
} catch (err) {
if (err instanceof AssignError) {
if (err.code === ASSIGN_CODES.MESH_FULL) {
await rebalanceAndRetry(peerInfo);
return;
}
if (err.code === ASSIGN_CODES.NO_ELIGIBLE_MESH) {
return enterStandbyMode(peerInfo);
}
}
throw err;
}
```
## API Reference
- `ASSIGN_CODES` — frozen `{ NO_ELIGIBLE_MESH, MESH_FULL, ADMISSION_DENIED }`.
- `type AssignErrorCode` — union of registered code values.
- `class AssignError extends SuaveplanError<AssignErrorCode>` — typed throwable.
## Implementation Notes
`MESH_FULL` is intentionally distinct from `NO_ELIGIBLE_MESH` because they imply different recovery profiles: the former says "the engine picked a mesh but admission lost the race — try again", while the latter says "no mesh in the fleet can host this peer at all — escalate". `ADMISSION_DENIED` covers everything else (capability mismatch, regional policy violation, operator quota cap) so callers don't have to multiplex unrelated codes onto the same handler. The package barrel side-effect-imports `./error-codes.js` so subpath consumers register the codes too.

View File

@@ -0,0 +1,59 @@
# MeshCapacityTracker
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Tracks `currentPeers / maxPeers` per mesh in real time. Push-driven: the consumer wires `peer:joined`/`peer:left` from `network-plugin-registry` into `recordJoin`/`recordLeave` and the tracker fires `mesh:full` / `mesh:available` listeners on the threshold transitions. Centralising the capacity view here means the assignment engine, balancer, and admission controller all read the same numbers.
## Features
- `recordMesh(meshId, maxPeers?)` upserts mesh capacity (default `defaultMaxPeers`, configurable; falls back to 64).
- `recordJoin(meshId, peerId)` and `recordLeave(meshId, peerId)` keep the live set per mesh.
- `getUtilization(meshId)` returns the `[0, 1]` ratio.
- `getAvailableSlots(meshId)` for hot-path scheduling.
- Listener-driven `mesh:full` / `mesh:available` transitions; unsubscribe handle returned from `on()`.
- `listMeshes()` and `getMaxPeers()` exposed for observability.
## Usage: Basic Example
```ts
import { MeshCapacityTracker } from "@suaveplan/network-plugin-assignment";
const tracker = new MeshCapacityTracker();
tracker.recordMesh("mesh-A", 32);
tracker.recordJoin("mesh-A", "alice");
tracker.recordJoin("mesh-A", "bob");
const u = tracker.getUtilization("mesh-A"); // 2 / 32 = 0.0625
```
## Usage: Advanced Example
```ts
import { MeshCapacityTracker } from "@suaveplan/network-plugin-assignment";
const tracker = new MeshCapacityTracker({ defaultMaxPeers: 16 });
const off = tracker.on((event, meshId) => {
if (event === "mesh:full") {
rebalancer.scheduleRebalance(meshId);
}
});
bus.on("peer:joined", (e) => tracker.recordJoin(e.meshId, e.peerId));
bus.on("peer:left", (e) => tracker.recordLeave(e.meshId, e.peerId));
```
## API Reference
- `interface MeshCapacityTrackerConfig { defaultMaxPeers? }`.
- `recordMesh(meshId, maxPeers?)` / `recordJoin(meshId, peerId)` / `recordLeave(meshId, peerId)`.
- `getUtilization(meshId): number`.
- `getAvailableSlots(meshId): number`.
- `listMeshes(): readonly string[]`.
- `getMaxPeers(meshId): number`.
- `on(listener) → () => void`.
## Implementation Notes
State is kept in two maps — the per-mesh peer set and a parallel `fullState` boolean — so `recheckFull` can observe a transition by comparing the previous and current values atomically. We only fire listeners on edge transitions (`true → false` or `false → true`), not on every record, to avoid storming subscribers when traffic is steady. `recordLeave` against an unknown mesh is a no-op rather than an error because clean-shutdown ordering can lose `recordMesh` events.