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

5.6 KiB

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)

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

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

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.