Files
suaveplan/openspec/archive/2026-05-12-enterprise-browser-network-discovery-crdt/proposal.md
2026-05-27 23:29:53 +01:00

177 lines
5.6 KiB
Markdown

# OpenSpec Change: enterprise-browser-network-discovery-crdt
- **Change ID**: enterprise-browser-network-discovery-crdt
- **Package**: `@suaveplan/network-discovery-crdt`
- **Category**: browser
- **Target path**: `browser/network/network-discovery-crdt`
- **Status**: SUPERSEDED-BY-GENESIS-WAVE
- **Date**: 2026-04-24
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/network-discovery-crdt.md`
---
## Summary
Deliver `@suaveplan/network-discovery-crdt` on the genesis branch at enterprise quality. The package implements `DiscoveryStrategy` from `@suaveplan/network` using CRDT awareness layers (Yjs and Automerge) for eventually-consistent peer metadata exchange. Ships `CRDTDiscovery` backed by a clean `CRDTProviderAdapter` interface with `YjsProvider` and `AutomergeProvider` implementations. No mock global stubs — tests run against in-process signalling harnesses.
---
## Current-state audit (genesis)
Fresh build — does not exist on genesis yet.
---
## Available workspace deps on genesis
| Package | Role |
|---|---|
| `@suaveplan/network` (`workspace:^`) | `DiscoveryStrategy` interface, `PeerInfo` type |
| `@suaveplan/error` (`workspace:^`) | Typed CRDT errors |
| `@suaveplan/types` (`workspace:^`) | `LoggerLike` |
| `@suaveplan/crypto` (`workspace:^`) | Optional AES-GCM awareness encryption |
| `@suaveplan/schema-registry` (`workspace:^`) | Register awareness payload schema |
| `zod` (`catalog:forms`) | Awareness payload Zod schema |
| `@opentelemetry/api` (`catalog:observability`) | Peer/conflict/latency metrics |
| `tslib` (`catalog:build`) | Runtime helpers |
External: `yjs`, `y-webrtc`, `@automerge/automerge-repo` + network/storage adapters (Bun catalog).
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `@suaveplan/testing workspace:^`. React in `devDependencies` for stories.
---
## Preserved public API surface (from main branch)
```ts
class CRDTDiscovery implements DiscoveryStrategy {
constructor(config: CRDTDiscoveryConfig)
start(): Promise<void>
stop(): Promise<void>
getPeers(): PeerInfo[]
on(event: 'peer:discovered' | 'peer:left' | 'error', handler: Function): void
}
interface CRDTDiscoveryConfig {
provider: 'yjs' | 'automerge'
roomId: string
yjs?: YjsSignalingConfig // Required when provider === 'yjs'
automerge?: AutomergeConfig // Required when provider === 'automerge'
encrypt?: boolean // Optional AES-GCM via @suaveplan/crypto
strict?: boolean // Default true; refuses public signalling defaults
}
interface YjsSignalingConfig { signalingUrls: string[]; password?: string }
interface AutomergeConfig { serverUrl: string; indexedDB?: boolean }
// + CRDTConnectionStatus, CRDTMessage, CRDTMessageType, CRDTPeerMetadata, CRDTProvider, CRDTSyncStatus
```
---
## New additions for genesis
### 1. `CRDTProviderAdapter` interface
```ts
interface CRDTProviderAdapter {
init(config: CRDTDiscoveryConfig): Promise<void>
destroy(): Promise<void>
getAwareness(): Record<string, CRDTPeerMetadata>
setLocalAwareness(metadata: CRDTPeerMetadata): void
onAwarenessChange(handler: (changes: AwarenessChange[]) => void): void
}
```
Both `YjsProvider` and `AutomergeProvider` implement this interface; `CRDTDiscovery` no longer branches on provider string.
### 2. Strict-mode signalling gate
When `strict: true` (default), `YjsSignalingConfig.signalingUrls` must be non-empty; reject `wss://signaling.yjs.dev` without explicit opt-in.
### 3. Awareness payload Zod schema
`CRDTPeerMetadataSchema` validating `{ peerId, addresses, protocols, capabilities, timestamp }`; reject malformed on receive.
### 4. Optional AES-GCM encryption
When `config.encrypt: true`, wrap awareness payloads with `@suaveplan/crypto` AES-GCM before write; decrypt on read.
### 5. OTel metrics
Counter `discovery.crdt.peers_seen_total{provider}`, counter `discovery.crdt.conflicts_resolved_total`, histogram `discovery.crdt.awareness_propagation_ms`.
### 6. Structured errors
`CRDT_PROVIDER_UNAVAILABLE`, `CRDT_SIGNALING_FAILED`, `AWARENESS_REJECTED`.
---
## File layout
```text
browser/network/network-discovery-crdt/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
crdt-discovery/
crdt-discovery.ts
crdt-discovery.md
crdt-discovery.types.ts
crdt-discovery.test.ts
index.ts
provider/
provider.ts (CRDTProviderAdapter interface)
yjs/
yjs-provider.ts
yjs-provider.md
yjs-provider.test.ts
yjs-awareness.ts
yjs-sync.ts
index.ts
automerge/
automerge-provider.ts
automerge-provider.md
automerge-provider.test.ts
index.ts
managers/
crdt-peer-manager/
crdt-peer-manager.ts
crdt-peer-manager.md
crdt-peer-manager.test.ts
index.ts
crdt-state-manager/
crdt-state-manager.ts
crdt-state-manager.md
crdt-state-manager.test.ts
index.ts
crdt-initializer/
crdt-initializer.ts
crdt-initializer.md
crdt-initializer.test.ts
index.ts
crdt-intervals/
crdt-intervals.ts
crdt-intervals.md
crdt-intervals.test.ts
index.ts
schema/
awareness/
awareness.ts (CRDTPeerMetadataSchema)
awareness.md
index.ts
tests/
integration.test.ts
```
React dev dep declared for `crdt-discovery.stories.tsx` — story file lives in `src/` alongside the implementation.
---
## Impact
**Downstream**: `@suaveplan/network-discovery-hybrid` (optional peer dep).
**Prerequisites**: `@suaveplan/network`, `@suaveplan/error`, `@suaveplan/events` on Verdaccio.