# Spec — network-discovery-hybrid - **Capability**: `@suaveplan/network-discovery-hybrid` - **Tier**: browser - **Status**: SHIPPED 2026-05-11 - **Genesis path**: `packages/network/network-discovery-hybrid` ## ADDED Requirements ### Requirement 1 — Multi-Strategy Composition The package SHALL ship a `HybridDiscoveryManager` that composes any subset of the five Wave-2 sibling discovery strategies (peerjs / crdt / trystero / webtorrent / gundb) plus arbitrary caller-supplied strategies behind a single `DiscoveryStrategy` contract from `@suaveplan/network-bootstrap`. #### Scenario 1: Composite Discovery - **WHEN** a `HybridDiscoveryManager` is configured with two strategies and `start()` + `discover(timeoutMs)` is called - **THEN** the manager fans `discover()` out across every active strategy and returns the merged + deduplicated peer set. ### Requirement 2 — Priority-Based Strategy Selection The orchestrator SHALL support a three-bucket strategy priority — `primary`, `fallback`, `backup` — and SHALL initialise strategies in priority order. #### Scenario 1: Priority-Ordered Initialisation - **WHEN** strategies are registered with mixed priorities - **THEN** `initializeAll()` runs every primary first, then every fallback, then every backup. ### Requirement 3 — Per-Strategy Health Tracking The orchestrator SHALL maintain a `StrategyHealth` record for every registered strategy with rolling success-rate (EMA, α = 0.3), consecutive-failure counter, last-error string, and active flag. ### Requirement 4 — Automatic Fallback The orchestrator SHALL, when `autoFallback` is enabled and a primary becomes unavailable, initialise the next dormant non-primary strategy in priority order. #### Scenario 1: Fallback Promotion - **WHEN** a primary strategy reports `isAvailable() === false` - **AND** at least one non-primary strategy is dormant - **THEN** the orchestrator initialises the dormant strategy AND emits `hybrid:fallback{from,to}` AND increments `hybrid.fallback_total{from,to}`. ### Requirement 5 — Deduplicated Peer Set with PeerSource Provenance The orchestrator SHALL maintain a deduplicated peer set keyed by `PeerInfo.peerId` with `PeerSource.discoveredBy` recording the ordered set of every strategy that has observed the peer. #### Scenario 1: Append-Only Provenance - **WHEN** the same peer is observed by two different strategies - **THEN** the second observation appends to `discoveredBy` AND retains the original `primaryStrategy`. ### Requirement 6 — Optional Peer-Deps with Graceful Degradation The package SHALL declare every sibling discovery strategy as `peerDependenciesMeta..optional: true`. A `StrategyConfig.factory` whose dynamic `import()` rejects SHALL be recorded as `STRATEGY_LOAD_FAILED` and the strategy MUST be marked unhealthy WITHOUT aborting `start()` for the remaining strategies. ### Requirement 7 — Lifecycle FSM The orchestrator SHALL expose idempotent `start()`, `stop()`, and `dispose()` methods. After `dispose()` every method SHALL throw `NetDiscHybridError(DISPOSED)`. ### Requirement 8 — Error Registry The package SHALL emit the six frozen codes `NO_STRATEGIES`, `ALL_STRATEGIES_DEGRADED`, `STRATEGY_LOAD_FAILED`, `DUPLICATE_STRATEGY`, `INVALID_OPTIONS`, `DISPOSED` via `defineCodes("NET_DISC_HYBRID", [...])` and surface them through `NetDiscHybridError extends SuaveplanError`. ### Requirement 9 — OpenTelemetry Instrumentation The orchestrator SHALL emit the following instruments under tracer + meter name `@suaveplan/network-discovery-hybrid`: - `hybrid.peer_sources_total{strategy}` counter - `hybrid.strategy_health_score{strategy}` histogram - `hybrid.fallback_total{from,to}` counter - `hybrid.active_strategies{strategy}` up-down counter - `hybrid.discover_duration_ms{outcome}` histogram - `hybrid.start` and `hybrid.discover` spans ### Requirement 10 — Test-Time Real-Implementation Fixture The package SHALL ship a real-implementation `TestStrategy` fixture under `tests/setup/test-strategy.ts` that fully implements the `DiscoveryStrategy` contract deterministically. The package SHALL NOT contain any `vi.mock`, `jest.mock`, `__mocks__/` directory, or `: any` cast in production `src/` code. ### Requirement 11 — 100% Coverage The package SHALL pass `bunx turbo test:coverage --filter=@suaveplan/network-discovery-hybrid` at 100% statements, branches, functions, and lines.