# Capability Spec Delta: network-plugin-monitor (complete-network-plugins) **Package**: `@suaveplan/network-plugin-monitor` **Status**: PROPOSED > Every requirement below carries a `**Verified by:**` line at authoring time. ## ADDED Requirements ### Requirement: MonitorPlugin conforms to NetworkPlugin lifecycle The package SHALL export a `MonitorPlugin implements NetworkPlugin` and SHALL provide an integrated metric collector, alert engine, tracer, history store, and sampler on install. #### Scenario: install / uninstall lifecycle - **WHEN** a host installs then uninstalls the plugin - **THEN** the plugin reaches `INSTALLED` then `UNINSTALLED` and releases timers **Verified by:** `src/plugin/monitor-plugin.spec-conformance.test.ts::"install / uninstall lifecycle is clean"` --- ### Requirement: MetricCollector supports the four metric types The package SHALL export `MetricCollector`, `MetricCollectorConfig`, and `MetricDefinition`. The collector SHALL support Counter, Gauge, Histogram, and Summary metric types and SHALL throw `NetworkMonError(METRIC_NOT_FOUND)` / `METRIC_TYPE_MISMATCH` on misuse. #### Scenario: unknown metric raises typed error - **WHEN** `collector.record("not-registered", 1)` is called - **THEN** the call throws `NetworkMonError` with `code === "NET_MON.METRIC_NOT_FOUND"` **Verified by:** `src/collector/metric-collector.spec-conformance.test.ts::"throws METRIC_NOT_FOUND on unknown metric"` --- ### Requirement: AlertEngine enforces unique rule ids The package SHALL export `AlertEngine`, `AlertEngineConfig`, and `AlertBus`. Adding two rules with the same id SHALL throw `NetworkMonError(ALERT_RULE_DUPLICATE)`; operating on an unknown id SHALL throw `NetworkMonError(ALERT_RULE_NOT_FOUND)`. #### Scenario: duplicate rule id - **WHEN** `engine.addRule({ id: "x", … })` is called twice - **THEN** the second call throws `NetworkMonError(ALERT_RULE_DUPLICATE)` **Verified by:** `src/alert/alert-engine.spec-conformance.test.ts::"throws ALERT_RULE_DUPLICATE on repeat id"` --- ### Requirement: GrafanaDashboardGenerator validates row titles with typed error The package SHALL export `GrafanaDashboardGenerator`, `GrafanaDashboardOptions`, `GrafanaDashboardOptionsSchema`, `DashboardJson`, `GrafanaPanelType`, `GridPos`, and `GridPosSchema`. `addRow(title)` SHALL throw `NetworkMonError(DASHBOARD_ROW_INVALID)` when `title` is empty or whitespace-only. The class SHALL NOT throw bare `Error`. #### Scenario: empty title raises typed error - **WHEN** `generator.addRow("")` is called - **THEN** the call throws `NetworkMonError` with `code === "NET_MON.DASHBOARD_ROW_INVALID"` and `context.field === "title"` **Verified by:** `src/grafana/dashboard-generator.spec-conformance.test.ts::"throws DASHBOARD_ROW_INVALID on empty title"` --- ### Requirement: BandwidthMonitor validates sample config with typed error The package SHALL export `BandwidthMonitor` (in the `quality` module). The monitor SHALL throw `NetworkMonError(BANDWIDTH_MONITOR_CONFIG_INVALID)` whenever `maxSamples` is less than 1 or `smoothingFactor` is outside `(0, 1]`. The class SHALL NOT throw bare `RangeError`. #### Scenario: invalid maxSamples - **WHEN** `new BandwidthMonitor({ maxSamples: 0 })` is called - **THEN** the call throws `NetworkMonError` with `code === "NET_MON.BANDWIDTH_MONITOR_CONFIG_INVALID"` and `context.field === "maxSamples"` #### Scenario: invalid smoothingFactor - **WHEN** `new BandwidthMonitor({ smoothingFactor: 0 })` is called - **THEN** the call throws `NetworkMonError` with `code === "NET_MON.BANDWIDTH_MONITOR_CONFIG_INVALID"` and `context.field === "smoothingFactor"` **Verified by:** `src/quality/bandwidth-monitor.spec-conformance.test.ts::"throws BANDWIDTH_MONITOR_CONFIG_INVALID on maxSamples < 1"` AND `src/quality/bandwidth-monitor.spec-conformance.test.ts::"throws BANDWIDTH_MONITOR_CONFIG_INVALID on smoothingFactor out of range"` --- ### Requirement: DegradationManager validates hysteresis and window config with typed error The package SHALL export `DegradationManager` (in the `quality` module). The manager SHALL throw `NetworkMonError(DEGRADATION_CONFIG_INVALID)` whenever `hysteresisMs` is negative or `windowSize` is less than 1. #### Scenario: negative hysteresisMs - **WHEN** `new DegradationManager({ hysteresisMs: -1 })` is called - **THEN** the call throws `NetworkMonError` with `code === "NET_MON.DEGRADATION_CONFIG_INVALID"` and `context.field === "hysteresisMs"` #### Scenario: zero windowSize - **WHEN** `new DegradationManager({ windowSize: 0 })` is called - **THEN** the call throws `NetworkMonError` with `code === "NET_MON.DEGRADATION_CONFIG_INVALID"` and `context.field === "windowSize"` **Verified by:** `src/quality/degradation-manager.spec-conformance.test.ts::"throws DEGRADATION_CONFIG_INVALID on negative hysteresisMs"` AND `src/quality/degradation-manager.spec-conformance.test.ts::"throws DEGRADATION_CONFIG_INVALID on zero windowSize"` --- ### Requirement: NetworkTracer propagates span context across mesh hops The package SHALL export a tracer compatible with `@opentelemetry/api`. Operating on a closed span SHALL throw `NetworkMonError(SPAN_NOT_OPEN)`. #### Scenario: closed span raises typed error - **WHEN** `tracer.addEvent(closedSpanId, …)` is called - **THEN** the call throws `NetworkMonError(SPAN_NOT_OPEN)` **Verified by:** `src/tracer/network-tracer.spec-conformance.test.ts::"throws SPAN_NOT_OPEN on closed span"` --- ### Requirement: HistoricalDataStore enforces retention with typed error The package SHALL export a historical store and an adaptive sampler. Invalid retention SHALL throw `NetworkMonError(RETENTION_INVALID)`. #### Scenario: invalid retention raises typed error - **WHEN** `new HistoricalDataStore({ retentionMs: -1 })` is called - **THEN** the call throws `NetworkMonError(RETENTION_INVALID)` **Verified by:** `src/history/historical-data-store.spec-conformance.test.ts::"throws RETENTION_INVALID on negative retention"` --- ### Requirement: Error-code registry is frozen and namespaced NET_MON `NETWORK_MON_CODES` SHALL be frozen under the `NET_MON` namespace and SHALL include at minimum: `METRIC_NOT_FOUND`, `METRIC_TYPE_MISMATCH`, `ALERT_RULE_DUPLICATE`, `ALERT_RULE_NOT_FOUND`, `SPAN_NOT_OPEN`, `RETENTION_INVALID`, and the new `DASHBOARD_ROW_INVALID`, `BANDWIDTH_MONITOR_CONFIG_INVALID`, `DEGRADATION_CONFIG_INVALID`. #### Scenario: registry contains the three new codes - **WHEN** `NETWORK_MON_CODES` is inspected - **THEN** the three new codes map to their `"NET_MON."` values **Verified by:** `src/error-codes.spec-conformance.test.ts::"NETWORK_MON_CODES includes DASHBOARD_ROW_INVALID, BANDWIDTH_MONITOR_CONFIG_INVALID, DEGRADATION_CONFIG_INVALID"` --- ### Requirement: No bare-Error throws in production code `src/` SHALL contain zero bare `throw new (Error|TypeError|RangeError|SyntaxError)\(` sites outside test files. #### Scenario: source grep returns zero hits - **WHEN** the grep is run - **THEN** the output is empty **Verified by:** `tests/no-bare-throws.test.ts::"network-plugin-monitor src/ has no bare Error throws"` --- ### Requirement: README is auto-docs idempotent The package `README.md` SHALL contain exactly one balanced auto-docs block; the generator SHALL produce a byte-identical file on a second run. #### Scenario: marker count is exactly one - **WHEN** `grep -c "begin:auto-docs" packages/network/network-plugin-monitor/README.md` - **THEN** output is `1` **Verified by:** `scripts/generate-package-module-tree.test.ts::"README regen is idempotent for network-plugin-monitor"`