Initial Commit
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
# OpenSpec Change: enterprise-network-quality
|
||||
|
||||
- **Change ID**: enterprise-network-quality
|
||||
- **Package**: `@suaveplan/network-quality`
|
||||
- **Category**: universal/network
|
||||
- **Target path**: `universal/network/network-quality`
|
||||
- **Status**: SUPERSEDED-BY-GENESIS-WAVE
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/network-quality.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/network-quality` on the genesis branch from scratch at enterprise quality. The package provides network quality monitoring: continuous measurement of RTT, jitter, packet loss, and throughput; quality scoring with configurable thresholds; trend analysis (sliding window, exponential smoothing); degradation alerts; and a quality-aware selector for choosing the best peer/transport. Genesis adds structured errors via `@suaveplan/error`, Zod schemas for measurement and threshold types, OTel instrumentation on all quality metrics, `AbortSignal` on collection tasks, injectable `Clock` for measurement timestamps, event-bus emission for quality degradation and recovery, and property-based tests for scoring invariants.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package does not exist on genesis yet — this is a fresh build.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/error` (`workspace:^`) | Base class for `NetworkQualityError`; stable error codes |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `EventBusLike` |
|
||||
| `@suaveplan/events` (`workspace:^`) | Event bus for quality change events |
|
||||
| `@suaveplan/logger` (`workspace:^`) | Structured logging |
|
||||
| `@suaveplan/timing` (`workspace:^`) | Stopwatch and interval timer for measurement |
|
||||
| `zod` (`catalog:forms`) | Zod schemas |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Deep OTel metric emission for network quality |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`).
|
||||
|
||||
---
|
||||
|
||||
## Public API surface
|
||||
|
||||
### Core
|
||||
|
||||
```ts
|
||||
function createQualityMonitor(options?: QualityMonitorOptions): QualityMonitor
|
||||
|
||||
interface QualityMonitor {
|
||||
readonly score: number
|
||||
readonly grade: QualityGrade
|
||||
start(signal?: AbortSignal): Promise<void>
|
||||
stop(): void
|
||||
measure(probe: QualityProbe): Promise<QualityMeasurement>
|
||||
report(): QualityReport
|
||||
on(event: QualityEvent, handler: (report: QualityReport) => void): () => void
|
||||
}
|
||||
```
|
||||
|
||||
### Measurement types
|
||||
|
||||
```ts
|
||||
interface QualityMeasurement {
|
||||
readonly rttMs: number
|
||||
readonly jitterMs: number
|
||||
readonly packetLoss: number // 0–1
|
||||
readonly throughputBps?: number
|
||||
readonly timestamp: number
|
||||
}
|
||||
|
||||
interface QualityReport {
|
||||
readonly score: number // 0–100
|
||||
readonly grade: QualityGrade
|
||||
readonly current: QualityMeasurement
|
||||
readonly trend: QualityTrend
|
||||
readonly window: QualityWindowStats
|
||||
readonly thresholdViolations: ReadonlyArray<ThresholdViolation>
|
||||
}
|
||||
|
||||
interface QualityWindowStats {
|
||||
readonly avgRttMs: number
|
||||
readonly avgJitterMs: number
|
||||
readonly avgPacketLoss: number
|
||||
readonly p95RttMs: number
|
||||
readonly sampleCount: number
|
||||
readonly windowMs: number
|
||||
}
|
||||
|
||||
type QualityTrend = 'improving' | 'stable' | 'degrading'
|
||||
type QualityGrade = 'excellent' | 'good' | 'fair' | 'poor' | 'critical'
|
||||
|
||||
interface ThresholdViolation {
|
||||
readonly metric: string
|
||||
readonly threshold: number
|
||||
readonly actual: number
|
||||
readonly severity: 'warning' | 'critical'
|
||||
}
|
||||
```
|
||||
|
||||
### Probe and selector
|
||||
|
||||
```ts
|
||||
type QualityProbe = (signal?: AbortSignal) => Promise<QualityMeasurement>
|
||||
|
||||
function createRttProbe(url: string, options?: RttProbeOptions): QualityProbe
|
||||
function createBandwidthProbe(url: string, options?: BandwidthProbeOptions): QualityProbe
|
||||
|
||||
function selectBestPeer<T extends { id: string; score?: number }>(
|
||||
peers: T[],
|
||||
monitor: QualityMonitor
|
||||
): T | null
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```ts
|
||||
interface QualityMonitorOptions {
|
||||
measureIntervalMs?: number
|
||||
windowSize?: number // number of samples in sliding window
|
||||
smoothingFactor?: number // 0–1 for exponential smoothing
|
||||
thresholds?: QualityThresholds
|
||||
probe?: QualityProbe
|
||||
logger?: LoggerLike
|
||||
clock?: Clock
|
||||
eventBus?: EventBusLike
|
||||
}
|
||||
|
||||
interface QualityThresholds {
|
||||
maxRttMs?: number
|
||||
maxJitterMs?: number
|
||||
maxPacketLoss?: number
|
||||
minThroughputBps?: number
|
||||
degradationScore?: number
|
||||
criticalScore?: number
|
||||
}
|
||||
|
||||
type QualityEvent = 'measurement' | 'degradation' | 'recovery' | 'critical'
|
||||
```
|
||||
|
||||
### Error codes
|
||||
|
||||
```ts
|
||||
type NetworkQualityErrorCode =
|
||||
| 'QUALITY_PROBE_FAILED'
|
||||
| 'QUALITY_MONITOR_RUNNING'
|
||||
| 'QUALITY_NO_MEASUREMENTS'
|
||||
| 'QUALITY_ABORT'
|
||||
|
||||
function registerNetworkQualityErrorCodes(): void
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Genesis additions
|
||||
|
||||
1. **Structured `NetworkQualityError`** with stable `QUALITY.*` codes
|
||||
2. **Zod schemas** for `QualityMonitorOptions`, `QualityThresholds`
|
||||
3. **Exponential smoothing + sliding window** statistics
|
||||
4. **Trend detection** — compares recent half of window against older half
|
||||
5. **Quality scoring** — weighted composite (RTT 40%, jitter 20%, packet loss 30%, throughput 10%)
|
||||
6. **`createRttProbe` / `createBandwidthProbe`** factory utilities
|
||||
7. **OTel metric emission** — gauges for `net.quality.rtt`, `net.quality.jitter`, `net.quality.packet_loss`, `net.quality.score`
|
||||
8. **Property-based tests** — score monotonically decreases as RTT increases; grade ordering matches score ordering
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
universal/network/network-quality/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
constants/
|
||||
index.ts
|
||||
types/
|
||||
common.ts
|
||||
common.md
|
||||
core/
|
||||
monitor/
|
||||
monitor.ts
|
||||
monitor.md
|
||||
monitor.types.ts
|
||||
monitor.test.ts
|
||||
index.ts
|
||||
scoring/
|
||||
scorer.ts
|
||||
scorer.md
|
||||
scorer.test.ts
|
||||
index.ts
|
||||
stats/
|
||||
window-stats.ts
|
||||
window-stats.md
|
||||
window-stats.test.ts
|
||||
index.ts
|
||||
probes/
|
||||
rtt-probe.ts
|
||||
rtt-probe.md
|
||||
rtt-probe.test.ts
|
||||
bandwidth-probe.ts
|
||||
bandwidth-probe.md
|
||||
bandwidth-probe.test.ts
|
||||
index.ts
|
||||
__benchmarks__/
|
||||
quality.bench.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
property.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/timing` must be published.
|
||||
102
openspec/archive/2026-05-12-enterprise-network-quality/tasks.md
Normal file
102
openspec/archive/2026-05-12-enterprise-network-quality/tasks.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Tasks: enterprise-network-quality
|
||||
|
||||
**Change ID**: enterprise-network-quality
|
||||
**Package**: `@suaveplan/network-quality`
|
||||
**Target path**: `universal/network/network-quality`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/timing`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] Create `universal/network/network-quality/` with `src/`, `tests/`, `src/__benchmarks__/`, and all feature subdirectories.
|
||||
- [x] Write `package.json` at version `0.1.0`: `"sideEffects": false`, exports map, deps per `proposal.md`.
|
||||
- [x] Write `tsconfig.json`, `vitest.config.ts`, scripts (including `bench`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Types and error codes
|
||||
|
||||
- [x] Define all public types in `src/types/common.ts`: `QualityMeasurement`, `QualityReport`, `QualityWindowStats`, `QualityTrend`, `QualityGrade`, `ThresholdViolation`, `QualityMonitorOptions`, `QualityThresholds`, `QualityProbe`, `QualityEvent`.
|
||||
- [x] Write `src/types/common.md` (≥200 words, simple + advanced examples).
|
||||
- [x] Define error codes in `src/error-codes.ts`.
|
||||
- [x] Write Zod schemas in `src/types/schemas.ts` + `src/types/schemas.md`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Window statistics (`src/stats/`)
|
||||
|
||||
- [x] Implement `computeWindowStats(measurements: QualityMeasurement[], windowMs: number): QualityWindowStats`:
|
||||
- Filter measurements within the last `windowMs`.
|
||||
- Compute avg RTT, avg jitter, avg packet loss, p95 RTT.
|
||||
- Uses insertion-sort for p95 on small sample sets (≤100).
|
||||
- [x] Implement `exponentialSmooth(values: number[], alpha: number): number[]`: EWMA.
|
||||
- [x] Write `window-stats.test.ts` + `window-stats.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Scorer (`src/scoring/`)
|
||||
|
||||
- [x] Implement `computeQualityScore(measurement: QualityMeasurement, thresholds?: QualityThresholds): number`:
|
||||
- RTT factor (40%): 100 when rtt ≤ 50ms; 0 when rtt ≥ thresholds.maxRttMs (default 500ms); linear between.
|
||||
- Jitter factor (20%): 100 when jitter ≤ 10ms; 0 when jitter ≥ 100ms.
|
||||
- Packet loss factor (30%): 100 when 0%; 0 when ≥ thresholds.maxPacketLoss (default 0.1).
|
||||
- Throughput factor (10%): 0 if below threshold; 100 if above.
|
||||
- Return weighted sum (0–100).
|
||||
- [x] Implement `gradeScore(score: number): QualityGrade`.
|
||||
- [x] Implement `detectTrend(recent: number[], older: number[]): QualityTrend`:
|
||||
- Compare averages; `improving` if delta > +5, `degrading` if delta < -5, else `stable`.
|
||||
- [x] Write `scorer.test.ts` (≤500 lines): boundary cases, all four factors.
|
||||
- [x] Write `scorer.md` (≥200 words).
|
||||
|
||||
---
|
||||
|
||||
## 5. Probe factories (`src/probes/`)
|
||||
|
||||
- [x] Implement `createRttProbe(url, options?)`: HTTP HEAD probe with clock-measured RTT.
|
||||
- [x] Implement `createBandwidthProbe(url, options?)`: fetch known-size payload, compute bytes/elapsed.
|
||||
- [x] Write `rtt-probe.test.ts` + `rtt-probe.md`.
|
||||
- [x] Write `bandwidth-probe.test.ts` + `bandwidth-probe.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Quality monitor (`src/core/monitor/`)
|
||||
|
||||
- [x] Implement `createQualityMonitor(options?)`:
|
||||
- Validates options with Zod.
|
||||
- `start(signal?)` — runs measurement loop via `@suaveplan/timing` interval.
|
||||
- Each tick: call `options.probe ?? createRttProbe(options.rttUrl)`.
|
||||
- Store in sliding window `measurements[]` (capped at `windowSize`, default 20).
|
||||
- Apply exponential smoothing before scoring.
|
||||
- Detect trend comparing newest half vs oldest half of window.
|
||||
- Compute threshold violations.
|
||||
- Emit `measurement` event. On grade transition, emit `degradation` or `recovery`.
|
||||
- OTel: gauge `net.quality.rtt_ms`, gauge `net.quality.score`, gauge `net.quality.packet_loss`.
|
||||
- [x] Implement `selectBestPeer(peers, monitor)` — returns peer with lowest `score` gauge if custom probe; otherwise highest `score` field.
|
||||
- [x] Write `monitor.test.ts` (≤500 lines): start/stop, degradation event fired, recovery event, threshold violation.
|
||||
- [x] Write `monitor.md` (≥200 words).
|
||||
|
||||
---
|
||||
|
||||
## 7. Benchmarks (`src/__benchmarks__/quality.bench.ts`)
|
||||
|
||||
- [x] `computeWindowStats_20`, `computeWindowStats_100`: 20 and 100 measurement samples.
|
||||
- [x] `scorer_hot`: 10k `computeQualityScore` calls.
|
||||
|
||||
---
|
||||
|
||||
## 8. Property-based tests (`tests/property.test.ts`)
|
||||
|
||||
- [x] Score monotonicity: increasing RTT (all else equal) never increases the score.
|
||||
- [x] Grade boundary consistency: score ≥ 80 → grade is `excellent`.
|
||||
- [x] Trend stability: stable measurements produce `stable` trend.
|
||||
|
||||
---
|
||||
|
||||
## 9. Docs and verify
|
||||
|
||||
- [x] All per-file `.md` files per layout in `proposal.md`.
|
||||
- [x] `universal/network/network-quality/README.md`.
|
||||
- [x] `bunx turbo lint typecheck test test:coverage --filter=@suaveplan/network-quality` — all green, 100%.
|
||||
- [x] `bun scripts/publish.ts --package @suaveplan/network-quality`.
|
||||
- [x] Update `CLAUDE.md`. Archive change folder.
|
||||
Reference in New Issue
Block a user