204 lines
5.6 KiB
Markdown
204 lines
5.6 KiB
Markdown
# OpenSpec Change: enterprise-network-registry
|
||
|
||
- **Change ID**: enterprise-network-registry
|
||
- **Package**: `@suaveplan/network-registry`
|
||
- **Category**: universal/network
|
||
- **Target path**: `universal/network/network-registry`
|
||
- **Status**: SUPERSEDED-BY-GENESIS-WAVE
|
||
- **Date**: 2026-04-24
|
||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/network-registry.md`
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
Deliver `@suaveplan/network-registry` on the genesis branch from scratch at enterprise quality. The package provides a network service and peer registry: register/deregister services with metadata and capabilities, health-status tracking, TTL-based expiry, lookup by capability/tag/region, and change-notification subscriptions. Genesis adds structured errors via `@suaveplan/error`, Zod schemas for all registry types, OTel instrumentation on register/lookup operations, `AbortSignal` on async lookup, injectable `Clock` for TTL expiry, event-bus emission for registration changes, and property-based tests for lookup correctness.
|
||
|
||
---
|
||
|
||
## 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 `NetworkRegistryError`; stable error codes |
|
||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `EventBusLike` |
|
||
| `@suaveplan/events` (`workspace:^`) | Event bus for registration changes |
|
||
| `@suaveplan/logger` (`workspace:^`) | Structured logging |
|
||
| `zod` (`catalog:forms`) | Zod schemas |
|
||
| `@opentelemetry/api` (`catalog:observability`) | Spans + counters on register/lookup |
|
||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||
|
||
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`).
|
||
|
||
---
|
||
|
||
## Public API surface
|
||
|
||
### Core
|
||
|
||
```ts
|
||
function createNetworkRegistry(options?: NetworkRegistryOptions): NetworkRegistry
|
||
|
||
interface NetworkRegistry {
|
||
register(entry: RegistryEntry): RegistrationHandle
|
||
deregister(entryId: string): boolean
|
||
lookup(query: RegistryQuery): ReadonlyArray<RegistryEntry>
|
||
get(entryId: string): RegistryEntry | null
|
||
list(filter?: RegistryFilter): ReadonlyArray<RegistryEntry>
|
||
updateHealth(entryId: string, status: HealthStatus): boolean
|
||
subscribe(query: RegistryQuery, handler: RegistryChangeHandler): () => void
|
||
purgeExpired(): number
|
||
}
|
||
```
|
||
|
||
### Entry types
|
||
|
||
```ts
|
||
interface RegistryEntry {
|
||
id: string
|
||
name: string
|
||
type: 'peer' | 'service' | 'relay' | 'bootstrap'
|
||
address?: string
|
||
port?: number
|
||
region?: string
|
||
capabilities: string[]
|
||
tags?: Record<string, string>
|
||
metadata?: Record<string, unknown>
|
||
ttlMs?: number
|
||
registeredAt?: number
|
||
expiresAt?: number
|
||
health?: HealthStatus
|
||
}
|
||
|
||
interface HealthStatus {
|
||
healthy: boolean
|
||
lastCheckedAt: number
|
||
latencyMs?: number
|
||
load?: number // 0–1
|
||
}
|
||
|
||
interface RegistrationHandle {
|
||
readonly entryId: string
|
||
renew(ttlMs?: number): void
|
||
deregister(): void
|
||
}
|
||
```
|
||
|
||
### Query types
|
||
|
||
```ts
|
||
interface RegistryQuery {
|
||
type?: RegistryEntry['type'] | Array<RegistryEntry['type']>
|
||
capabilities?: string[] // entry must have ALL listed capabilities
|
||
anyCapabilities?: string[] // entry must have AT LEAST ONE
|
||
tags?: Record<string, string> // all tag key/value pairs must match
|
||
region?: string
|
||
healthyOnly?: boolean
|
||
limit?: number
|
||
}
|
||
|
||
interface RegistryFilter {
|
||
type?: RegistryEntry['type'] | Array<RegistryEntry['type']>
|
||
healthyOnly?: boolean
|
||
region?: string
|
||
}
|
||
|
||
type RegistryChangeHandler = (change: RegistryChange) => void
|
||
|
||
interface RegistryChange {
|
||
type: 'registered' | 'deregistered' | 'health-changed' | 'expired'
|
||
entry: RegistryEntry
|
||
previous?: RegistryEntry
|
||
}
|
||
```
|
||
|
||
### Options
|
||
|
||
```ts
|
||
interface NetworkRegistryOptions {
|
||
ttlDefaultMs?: number
|
||
maxEntries?: number
|
||
gcIntervalMs?: number
|
||
logger?: LoggerLike
|
||
clock?: Clock
|
||
eventBus?: EventBusLike
|
||
}
|
||
```
|
||
|
||
### Error codes
|
||
|
||
```ts
|
||
type NetworkRegistryErrorCode =
|
||
| 'REGISTRY_ENTRY_NOT_FOUND'
|
||
| 'REGISTRY_DUPLICATE_ID'
|
||
| 'REGISTRY_CAPACITY_EXCEEDED'
|
||
| 'REGISTRY_INVALID_ENTRY'
|
||
| 'REGISTRY_EXPIRED'
|
||
|
||
function registerNetworkRegistryErrorCodes(): void
|
||
```
|
||
|
||
---
|
||
|
||
## Genesis additions
|
||
|
||
1. **Structured `NetworkRegistryError`** with stable `REGISTRY.*` codes
|
||
2. **Zod schemas** for `RegistryEntry`, `RegistryQuery`, `NetworkRegistryOptions`
|
||
3. **TTL-based expiry** with periodic GC via injectable `Clock`; `purgeExpired()` callable manually
|
||
4. **Subscription-based change notifications** with query-scoped filtering
|
||
5. **Capability AND/OR query operators** — `capabilities` (AND), `anyCapabilities` (OR)
|
||
6. **Health status tracking** with `updateHealth()` API
|
||
7. **OTel instrumentation** — counter `registry.register_total`, `registry.deregister_total`, `registry.lookup_total`, histogram `registry.lookup_results`
|
||
8. **Property-based tests** — lookup(query) results are always a subset of `list()`; TTL expiry correctness
|
||
|
||
---
|
||
|
||
## File layout
|
||
|
||
```text
|
||
universal/network/network-registry/
|
||
package.json
|
||
tsconfig.json
|
||
vitest.config.ts
|
||
src/
|
||
index.ts
|
||
error-codes.ts
|
||
constants/
|
||
index.ts
|
||
types/
|
||
common.ts
|
||
common.md
|
||
core/
|
||
registry/
|
||
registry.ts
|
||
registry.md
|
||
registry.types.ts
|
||
registry.test.ts
|
||
index.ts
|
||
query-engine/
|
||
query-engine.ts
|
||
query-engine.md
|
||
query-engine.test.ts
|
||
index.ts
|
||
ttl-manager/
|
||
ttl-manager.ts
|
||
ttl-manager.md
|
||
ttl-manager.test.ts
|
||
index.ts
|
||
tests/
|
||
integration.test.ts
|
||
property.test.ts
|
||
```
|
||
|
||
---
|
||
|
||
## Impact
|
||
|
||
**Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger` must be published.
|