Initial Commit
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
# OpenSpec Change: enterprise-browser-network-libp2p
|
||||
|
||||
- **Change ID**: enterprise-browser-network-libp2p
|
||||
- **Package**: `@suaveplan/network-libp2p`
|
||||
- **Category**: browser
|
||||
- **Target path**: `browser/network/network-libp2p`
|
||||
- **Status**: SUPERSEDED-BY-GENESIS-WAVE
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/network-libp2p.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/network-libp2p` on the genesis branch from scratch at enterprise quality. The package provides the browser-side libp2p transport for `@suaveplan/network`, stitching together the canonical libp2p stack (WebSockets, WebRTC, Noise, Yamux, identify, ping, bootstrap, circuit-relay-v2) into a `Libp2pTransport` class with connection lifecycle, peer addressing, streaming messaging, and metrics. No React dependencies — demo UI moves to a companion package.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package does not exist on genesis yet — fresh build.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/network` (`workspace:^`) | `Transport` interface, peer/connection types |
|
||||
| `@suaveplan/error` (`workspace:^`) | Typed libp2p errors |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike` |
|
||||
| `@suaveplan/events` (`workspace:^`) | Event maps |
|
||||
| `@suaveplan/serialize` (`workspace:^`) | Wire message encoding |
|
||||
| `@suaveplan/schema-registry` (`workspace:^`) | Config schema registration |
|
||||
| `zod` (`catalog:forms`) | `Libp2pConfig` schema |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Connection/stream counters, latency histograms |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
External libp2p deps (from Bun catalog): `libp2p`, `@libp2p/websockets`, `@libp2p/webrtc`, `@libp2p/bootstrap`, `@libp2p/circuit-relay-v2`, `@libp2p/identify`, `@libp2p/ping`, `@libp2p/interface`, `@chainsafe/libp2p-noise`, `@chainsafe/libp2p-yamux`, `@multiformats/multiaddr`.
|
||||
|
||||
No `react` / `react-dom` in runtime deps.
|
||||
|
||||
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `@playwright/test` (`catalog:testing`), `@suaveplan/testing workspace:^`.
|
||||
|
||||
---
|
||||
|
||||
## Preserved public API surface (from main branch)
|
||||
|
||||
```ts
|
||||
class Libp2pTransport {
|
||||
constructor(config: Libp2pConfig)
|
||||
start(): Promise<void>
|
||||
stop(): Promise<void>
|
||||
dial(addr: Multiaddr | string): Promise<Libp2pConnection>
|
||||
send(peerId: PeerId, protocol: string, message: Uint8Array): Promise<void>
|
||||
request(peerId: PeerId, protocol: string, message: Uint8Array): Promise<Uint8Array>
|
||||
registerHandler(protocol: string, handler: StreamHandler): void
|
||||
unregisterHandler(protocol: string): void
|
||||
getConnectedPeers(): PeerId[]
|
||||
getMetrics(): Libp2pMetrics
|
||||
on(event: 'peer:connect', handler: (peer: Libp2pPeerInfo) => void): void
|
||||
on(event: 'peer:disconnect', handler: (peer: Libp2pPeerInfo) => void): void
|
||||
on(event: 'error', handler: (error: Error) => void): void
|
||||
}
|
||||
|
||||
interface Libp2pConfig {
|
||||
listenAddresses?: string[]
|
||||
bootstrapPeers?: string[] // Must be explicit; no public defaults unless strict: false
|
||||
relayAddrs?: string[]
|
||||
enableWebRTC?: boolean
|
||||
enableWebSockets?: boolean
|
||||
strict?: boolean // Default true; rejects public bootstrap peers
|
||||
}
|
||||
interface Libp2pMetrics { connectionsOpened: number; streamsOpened: number; bytesIn: number; bytesOut: number }
|
||||
interface Libp2pPeerInfo { peerId: PeerId; multiaddrs: Multiaddr[]; protocols: string[] }
|
||||
type StreamHandler = (stream: Stream, connection: Connection) => Promise<void>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
### 1. Structured errors via `@suaveplan/error`
|
||||
|
||||
Error codes: `LIBP2P_DIAL_FAILED`, `LIBP2P_STREAM_RESET`, `LIBP2P_UNSUPPORTED_TRANSPORT`, `LIBP2P_BOOTSTRAP_REJECTED`, `LIBP2P_RELAY_INVALID`.
|
||||
|
||||
### 2. Strict-mode bootstrap gate
|
||||
|
||||
When `strict: true` (default), `Libp2pConfig.bootstrapPeers` must be explicitly supplied; throw `LIBP2P_BOOTSTRAP_REJECTED` if the list is empty. Users who want public defaults must set `strict: false`.
|
||||
|
||||
### 3. Transport presets
|
||||
|
||||
```ts
|
||||
const presets = {
|
||||
browserP2P(): Libp2pConfig // WebRTC + WebSocket + Yamux + Noise + identify + ping
|
||||
browserBrowser(): Libp2pConfig // WebRTC only
|
||||
browserNode(): Libp2pConfig // WebSocket + circuit-relay
|
||||
}
|
||||
```
|
||||
|
||||
### 4. `Libp2pConfig` Zod schema
|
||||
|
||||
Full runtime validation of config; registered in `@suaveplan/schema-registry`.
|
||||
|
||||
### 5. OTel metrics
|
||||
|
||||
Counters `libp2p.connections_opened_total`, `libp2p.streams_opened_total`, `libp2p.bytes_in_total`, `libp2p.bytes_out_total`; histograms `libp2p.dial_ms`, `libp2p.stream_ttfb_ms`.
|
||||
|
||||
### 6. Relay-addr validator
|
||||
|
||||
`validateRelayAddrs(addrs)` checks multiaddr format and rejects obviously insecure patterns.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
browser/network/network-libp2p/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
lib/
|
||||
libp2p-connection/
|
||||
libp2p-connection.ts
|
||||
libp2p-connection.md
|
||||
libp2p-connection.test.ts
|
||||
index.ts
|
||||
libp2p-messaging/
|
||||
libp2p-messaging.ts
|
||||
libp2p-messaging.md
|
||||
libp2p-messaging.test.ts
|
||||
index.ts
|
||||
peer-message/
|
||||
peer-message.ts
|
||||
peer-message.md
|
||||
peer-message.test.ts
|
||||
index.ts
|
||||
send-to-peer/
|
||||
send-to-peer.ts
|
||||
send-to-peer.md
|
||||
send-to-peer.test.ts
|
||||
index.ts
|
||||
stream-handler/
|
||||
stream-handler.ts
|
||||
stream-handler.md
|
||||
stream-handler.test.ts
|
||||
index.ts
|
||||
transport/
|
||||
libp2p-transport/
|
||||
libp2p-transport.ts
|
||||
libp2p-transport.md
|
||||
libp2p-transport.types.ts
|
||||
libp2p-transport.test.ts
|
||||
index.ts
|
||||
presets/
|
||||
presets/
|
||||
presets.ts
|
||||
presets.md
|
||||
presets.test.ts
|
||||
index.ts
|
||||
config/
|
||||
config-schema/
|
||||
config-schema.ts
|
||||
config-schema.md
|
||||
index.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
e2e/
|
||||
libp2p.spec.ts (two real libp2p nodes, echo protocol)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream**: P2P networking layers, drive distribution, real-time collaboration.
|
||||
|
||||
**Breaking changes**: `react` / `react-dom` removed from runtime deps (demo relocated).
|
||||
|
||||
**Prerequisites**: `@suaveplan/network`, `@suaveplan/serialize`, `@suaveplan/error`, `@suaveplan/events` on Verdaccio.
|
||||
@@ -0,0 +1,125 @@
|
||||
# Tasks: enterprise-browser-network-libp2p
|
||||
|
||||
**Change ID**: enterprise-browser-network-libp2p
|
||||
**Package**: `@suaveplan/network-libp2p`
|
||||
**Target path**: `browser/network/network-libp2p`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/network`, `@suaveplan/serialize`, `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] Create `browser/network/network-libp2p/` directory tree per layout in `proposal.md`.
|
||||
- [x] Write `package.json` at version `0.1.0` — no `react` / `react-dom` in runtime deps.
|
||||
- `dependencies`: `@suaveplan/network workspace:^`, `@suaveplan/serialize workspace:^`, `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/events workspace:^`, all libp2p packages from Bun catalog, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
|
||||
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `@playwright/test catalog:testing`, `@suaveplan/testing workspace:^`
|
||||
- [x] Write `tsconfig.json` and `vitest.config.ts` (pool: forks).
|
||||
|
||||
---
|
||||
|
||||
## 2. Error codes (`src/error-codes.ts`)
|
||||
|
||||
- [x] Define `Libp2pErrorCode`: `LIBP2P_DIAL_FAILED`, `LIBP2P_STREAM_RESET`, `LIBP2P_UNSUPPORTED_TRANSPORT`, `LIBP2P_BOOTSTRAP_REJECTED`, `LIBP2P_RELAY_INVALID`.
|
||||
- [x] Export `registerLibp2pErrorCodes()` and call eagerly.
|
||||
|
||||
---
|
||||
|
||||
## 3. Config schema (`src/config/config-schema/`)
|
||||
|
||||
- [x] Define `Libp2pConfigSchema` (Zod):
|
||||
- `listenAddresses?: string[]`
|
||||
- `bootstrapPeers?: string[]` — required when `strict: true`
|
||||
- `relayAddrs?: string[]` — validated by `validateRelayAddrs()`
|
||||
- `enableWebRTC?: boolean`, `enableWebSockets?: boolean`, `strict?: boolean` (default `true`)
|
||||
- [x] Export `validateLibp2pConfig(config)` throwing `LIBP2P_BOOTSTRAP_REJECTED` when strict + empty bootstrap list.
|
||||
- [x] Register schema in `@suaveplan/schema-registry`.
|
||||
- [x] Write `config-schema.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Presets (`src/presets/presets/`)
|
||||
|
||||
- [x] Implement `presets.browserP2P()`, `presets.browserBrowser()`, `presets.browserNode()` returning valid `Libp2pConfig` objects.
|
||||
- [x] Write `presets.test.ts` asserting each preset passes schema validation and has non-empty transport list.
|
||||
- [x] Write `presets.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Peer message (`src/lib/peer-message/`)
|
||||
|
||||
- [x] Define `PeerMessage` wire format with `protocol`, `payload`, `from` fields.
|
||||
- [x] Zod schema for `PeerMessage`.
|
||||
- [x] Write `peer-message.test.ts`, `peer-message.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Stream handler (`src/lib/stream-handler/`)
|
||||
|
||||
- [x] Implement `StreamHandler` registration: `registerProtocol(protocol, handler)`, `unregisterProtocol(protocol)`, dispatch incoming streams.
|
||||
- [x] Write `stream-handler.test.ts`, `stream-handler.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Messaging (`src/lib/libp2p-messaging/`)
|
||||
|
||||
- [x] Implement `sendMessage(peerId, protocol, payload)` + `requestResponse(peerId, protocol, payload)` over a libp2p stream.
|
||||
- [x] Wire OTel histograms `libp2p.stream_ttfb_ms`.
|
||||
- [x] Write `libp2p-messaging.test.ts` (≤500 lines, split by responsibility), `libp2p-messaging.md`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Send-to-peer helpers (`src/lib/send-to-peer/`)
|
||||
|
||||
- [x] Implement convenience helpers: `sendToPeer`, `broadcastToPeers`, `sendToAll`.
|
||||
- [x] Write `send-to-peer.test.ts`, `send-to-peer.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Connection (`src/lib/libp2p-connection/`)
|
||||
|
||||
- [x] Implement `createLibp2pNode(config: Libp2pConfig): Promise<Libp2p>` — calls `createLibp2p()` with transport/muxer/encryption assembled from config.
|
||||
- [x] Apply bootstrap gate: refuse public default bootstrap peers when `strict: true`.
|
||||
- [x] Write `libp2p-connection.test.ts`, `libp2p-connection.md`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Transport (`src/transport/libp2p-transport/`)
|
||||
|
||||
- [x] Implement `Libp2pTransport` composing all lib/ modules.
|
||||
- [x] Wire OTel counters `libp2p.connections_opened_total`, `libp2p.streams_opened_total`, `libp2p.bytes_in_total`, `libp2p.bytes_out_total`.
|
||||
- [x] Write `libp2p-transport.test.ts` (unit: wires components together), `libp2p-transport.md`, `libp2p-transport.types.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Integration test (`tests/integration.test.ts`)
|
||||
|
||||
- [x] Two in-process libp2p nodes using the echo protocol; assert message round-trip.
|
||||
- [x] Strict-mode gate: config with empty bootstrapPeers + strict: true throws `LIBP2P_BOOTSTRAP_REJECTED`.
|
||||
- [x] Relay-addr validation: invalid multiaddr format throws `LIBP2P_RELAY_INVALID`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Playwright E2E (`e2e/libp2p.spec.ts`)
|
||||
|
||||
- [x] Two Chromium browser contexts; each runs a Vite-bundled test page with a real `Libp2pTransport` node.
|
||||
- [x] Nodes discover each other via a shared bootstrap addr.
|
||||
- [x] Echo protocol: send message from A to B, assert received at B.
|
||||
|
||||
---
|
||||
|
||||
## 13. Docs
|
||||
|
||||
- [x] Write `.md` per module (≥200 words, examples, API reference).
|
||||
- [x] Write `README.md` with transport-preset table, strict-mode guidance, companion-package link.
|
||||
|
||||
---
|
||||
|
||||
## 14. Verify and publish
|
||||
|
||||
- [x] `bunx turbo lint typecheck test --filter=@suaveplan/network-libp2p` — green.
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/network-libp2p` — 100%.
|
||||
- [x] `bun run docs:check --filter=@suaveplan/network-libp2p` — zero errors.
|
||||
- [x] No `react` in runtime deps; no `vi.mock`, `any`, `ts-ignore`, `.skip`.
|
||||
- [x] Playwright E2E two-node echo round trip passes.
|
||||
- [x] `bun scripts/publish.ts --package @suaveplan/network-libp2p`.
|
||||
- [x] Archive to `openspec/archive/2026-04-24-enterprise-browser-network-libp2p/`.
|
||||
Reference in New Issue
Block a user