Files
suaveplan/openspec/archive/2026-05-11-service-discovery/proposal.md
2026-05-27 23:29:53 +01:00

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-memory ServiceRegistry with register/deregister/query/watch
  • types.ts — ServiceDescriptor, ServiceQuery, RegistryEvent
  • service-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).