3.9 KiB
OpenSpec Change: service-discovery
- Change ID: service-discovery
- Package:
@suaveplan/service-discovery - Category: universal
- Target path:
packages/network/service-discovery - Status: PROPOSED
- Date: 2026-05-11
- Parent:
network-transport-family(2026-05-11) — Wave 1 - Migration source:
../suaveplan/packages/universal/service-discovery+../suaveplan/enterprise/service-discovery.md
Summary
Migrate @suaveplan/service-discovery into genesis as a universal-tier helper library backing every network-discovery-* plugin (Wave 2). Upstream ships at 97.76% coverage with a ServiceRegistry and supporting types. The migration ports the surface, hardens against genesis primitives, and provides the shared ServiceDescriptor / ServiceRegistry contract that all six Wave-2 discovery plugins consume.
Why this is needed
Every concrete discovery plugin (crdt, peerjs, trystero, webtorrent, gundb, hybrid) needs the same primitives: register a service, query for services by name/tag/region, watch for service-set changes, expire stale entries. Without a shared service-discovery library, each plugin would re-implement this differently and the hybrid orchestrator would have no canonical merge semantics. Landing service-discovery in Wave 1 keeps Wave 2 small.
Current-state audit
Upstream surface (../suaveplan/packages/universal/service-discovery/src/):
service-registry.ts— in-memoryServiceRegistrywith register/deregister/query/watchtypes.ts—ServiceDescriptor,ServiceQuery,RegistryEventservice-registry.test.ts+service-registry.md
Upstream coverage: 97.76% statement, no E2E. No structured errors, no Zod, no OTel.
Parity Checklist
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
ServiceDescriptor type |
✅ | ✅ | Considered for @suaveplan/types promotion if Wave 2 plugins import directly; default keep local. |
ServiceRegistry register/deregister |
✅ | ✅ | |
ServiceQuery by name/tag/region |
✅ | ✅ | |
watch(query) AsyncIterable |
✅ | ✅ | AbortSignal honoured on iterator. |
| TTL + stale-entry expiry | ✅ | ✅ | Injectable Clock per AGENTS.md. |
| Persistent backing | 🚫 | ⬆️ | Opt-in @suaveplan/storage AbstractStorage adapter for cross-restart persistence. |
| Structured errors | 🚫 | ⬆️ | defineCodes("SVC_DISC", [...]): DUPLICATE_REGISTRATION, QUERY_INVALID, REGISTRY_FULL, INVALID_OPTIONS. |
| Zod boundary validation | 🚫 | ⬆️ | Every public option struct + ServiceDescriptor. |
| OTel instrumentation | 🚫 | ⬆️ | Counters (svc_disc.registrations_total{outcome}, :queries_total), histogram (svc_disc.query_duration_ms), gauge (svc_disc.active_services). |
NetworkBus event integration |
🚫 | ⬆️ | Opt-in: emit service:registered/:deregistered/:expired topics when a bus is provided. |
| AbortSignal support | 🚫 | ⬆️ | Threaded everywhere. |
export * barrels |
check | ⬆️ | Convert to explicit named exports per §6a. |
| Test-runner imports | direct vitest | ⬆️ | Rewrite to @suaveplan/testing/runner per §7. |
| 100% test coverage | 🚫 (97.76%) | ⬆️ | Bring to 100/100/100/100. |
| Property tests | 🚫 | ⬆️ | fast-check property suite on TTL expiry monotonicity + query determinism. |
Available workspace deps on genesis
@suaveplan/types✅@suaveplan/error✅@suaveplan/events✅ (optional bus integration)@suaveplan/storage✅ (optional persistence)@suaveplan/testing✅
No blockers.
Acceptance gate
Identical to network-bootstrap (lint+typecheck+test green, 100% coverage, docs:check green, STATUS.md flipped, dep-graph cycle-free).
Out of scope
- Concrete transport-specific discovery (Wave 2).
- Cross-cluster service-mesh federation (future package; not in upstream).