Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,220 @@
# OpenSpec Change: enterprise-network-protocol
- **Change ID**: enterprise-network-protocol
- **Package**: `@suaveplan/network-protocol`
- **Category**: universal/network
- **Target path**: `universal/network/network-protocol`
- **Status**: SUPERSEDED-BY-GENESIS-WAVE
- **Date**: 2026-04-24
---
## Summary
Deliver `@suaveplan/network-protocol` on the genesis branch from scratch at enterprise quality. The package provides custom application-level protocol definitions: message framing (length-prefix, delimiter, fixed-length), codec registry (encode/decode for message types), protocol versioning and negotiation, session handshake lifecycle, and flow-control primitives. Genesis adds structured errors via `@suaveplan/error`, Zod schemas for all protocol types, OTel instrumentation, `AbortSignal` on handshake and negotiation, injectable `Clock` for session timing, and property-based tests for codec round-trip invariants.
---
## 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 `NetworkProtocolError`; stable error codes |
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `EventBusLike` |
| `@suaveplan/events` (`workspace:^`) | Event bus for session lifecycle events |
| `@suaveplan/logger` (`workspace:^`) | Structured logging |
| `@suaveplan/serialize` (`workspace:^`) | MessagePack binary serialization |
| `@suaveplan/crypto-utils` (`workspace:^`) | Byte utilities (concat, i2osp, os2ip) for framing |
| `zod` (`catalog:forms`) | Zod schemas |
| `@opentelemetry/api` (`catalog:observability`) | Spans + counters on encode/decode |
| `tslib` (`catalog:build`) | Runtime helpers |
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`).
---
## Public API surface
### Core
```ts
function defineProtocol(definition: ProtocolDefinition): Protocol
interface Protocol {
readonly name: string
readonly version: number
createSession(options?: SessionOptions): ProtocolSession
negotiate(offered: ProtocolOffer, signal?: AbortSignal): Promise<ProtocolNegotiationResult>
}
interface ProtocolSession {
readonly id: string
readonly state: SessionState
encode(message: ProtocolMessage): Uint8Array
decode(data: Uint8Array): ProtocolMessage
handshake(signal?: AbortSignal): Promise<void>
close(): void
on(event: SessionEvent, handler: (data: unknown) => void): () => void
}
```
### Protocol definition
```ts
interface ProtocolDefinition {
name: string
version: number
framing: FramingStrategy
codecs: ReadonlyArray<MessageCodec<unknown>>
handshake?: HandshakeDefinition
maxMessageSize?: number
}
type FramingStrategy =
| { type: 'length-prefix'; lengthBytes: 2 | 4; bigEndian?: boolean }
| { type: 'delimiter'; delimiter: Uint8Array }
| { type: 'fixed'; size: number }
interface MessageCodec<T> {
readonly type: string
readonly version?: number
encode(message: T): Uint8Array
decode(data: Uint8Array): T
schema?: { parse: (data: unknown) => T }
}
interface HandshakeDefinition {
timeout?: number
clientHello(session: ProtocolSession): Uint8Array
serverHello(clientData: Uint8Array): Uint8Array
verify?(data: Uint8Array): boolean
}
```
### Negotiation
```ts
interface ProtocolOffer {
name: string
versions: number[]
capabilities?: string[]
}
interface ProtocolNegotiationResult {
readonly agreed: boolean
readonly version: number
readonly capabilities: string[]
readonly reason?: string
}
```
### Types
```ts
interface ProtocolMessage {
type: string
version?: number
payload: unknown
id?: string
sessionId?: string
}
type SessionState = 'idle' | 'handshaking' | 'established' | 'closing' | 'closed'
type SessionEvent = 'established' | 'message' | 'close' | 'error'
interface SessionOptions {
logger?: LoggerLike
clock?: Clock
eventBus?: EventBusLike
}
```
### Error codes
```ts
type NetworkProtocolErrorCode =
| 'PROTO_FRAMING_ERROR'
| 'PROTO_CODEC_NOT_FOUND'
| 'PROTO_DECODE_FAILED'
| 'PROTO_ENCODE_FAILED'
| 'PROTO_HANDSHAKE_FAILED'
| 'PROTO_VERSION_MISMATCH'
| 'PROTO_MESSAGE_TOO_LARGE'
| 'PROTO_ABORT'
function registerNetworkProtocolErrorCodes(): void
```
---
## Genesis additions
1. **Structured `NetworkProtocolError`** with stable `PROTO.*` codes
2. **Zod schemas** for `ProtocolDefinition`, `ProtocolOffer`, `ProtocolMessage`
3. **Three framing strategies** with length-prefix and delimiter implementations using `@suaveplan/crypto-utils` byte helpers
4. **Codec registry** — look up codec by `message.type`; throw `PROTO_CODEC_NOT_FOUND` if missing
5. **Protocol negotiation** — version intersection + capability set intersection
6. **Message size guard** — throw `PROTO_MESSAGE_TOO_LARGE` before encoding
7. **OTel instrumentation** — counter `proto.messages_encoded_total`, `proto.messages_decoded_total`, histogram `proto.message_size_bytes`
8. **Property-based tests** — encode/decode round-trip for arbitrary message payloads; framing idempotency
---
## File layout
```text
universal/network/network-protocol/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/
index.ts
types/
common.ts
common.md
core/
protocol/
protocol.ts
protocol.md
protocol.types.ts
protocol.test.ts
index.ts
session/
session.ts
session.md
session.test.ts
index.ts
framing/
length-prefix.ts
length-prefix.md
length-prefix.test.ts
delimiter.ts
delimiter.md
delimiter.test.ts
fixed.ts
fixed.md
fixed.test.ts
codecs/
codec-registry.ts
codec-registry.md
codec-registry.test.ts
index.ts
tests/
integration.test.ts
property.test.ts
```
---
## Impact
**Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/serialize`, `@suaveplan/crypto-utils` must be published.

View File

@@ -0,0 +1,99 @@
# Tasks: enterprise-network-protocol
**Change ID**: enterprise-network-protocol
**Package**: `@suaveplan/network-protocol`
**Target path**: `universal/network/network-protocol`
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/serialize`, `@suaveplan/crypto-utils`
---
## 1. Scaffold
- [x] Create `universal/network/network-protocol/` with `src/`, `tests/`, and all feature subdirectories.
- [x] Write `package.json` at version `0.1.0`: `"sideEffects": false`, exports map, deps per `proposal.md`.
- [x] Write `tsconfig.json`, `vitest.config.ts`, scripts.
---
## 2. Types and error codes
- [x] Define all public types in `src/types/common.ts`: `ProtocolDefinition`, `FramingStrategy`, `MessageCodec`, `HandshakeDefinition`, `ProtocolMessage`, `ProtocolOffer`, `ProtocolNegotiationResult`, `SessionState`, `SessionEvent`, `SessionOptions`.
- [x] Write `src/types/common.md` (≥200 words, simple + advanced examples).
- [x] Define error codes in `src/error-codes.ts`.
- [x] Write Zod schemas in `src/types/schemas.ts` + `src/types/schemas.md`.
---
## 3. Framing implementations (`src/framing/`)
### 3a. Length-prefix framing
- [x] Implement `encodeLengthPrefix(data, opts)` — prepend 2 or 4-byte length header (big-endian or little-endian) using `@suaveplan/crypto-utils` `i2osp`.
- [x] Implement `decodeLengthPrefix(buffer, opts)` — parse header, extract payload bytes.
- [x] Write `length-prefix.test.ts` + `length-prefix.md`.
### 3b. Delimiter framing
- [x] Implement `encodeDelimiter(data, delimiter)` — append delimiter to message bytes.
- [x] Implement `decodeDelimiter(buffer, delimiter)` — split on delimiter bytes.
- [x] Write `delimiter.test.ts` + `delimiter.md`.
### 3c. Fixed-length framing
- [x] Implement `encodeFixed(data, size)` — pad or throw if `data.length !== size`.
- [x] Implement `decodeFixed(buffer, size)` — slice `size` bytes.
- [x] Write `fixed.test.ts` + `fixed.md`.
---
## 4. Codec registry (`src/codecs/`)
- [x] Implement `createCodecRegistry()` — stores `Map<string, MessageCodec<unknown>>`.
- [x] Implement `register(codec)` / `get(type)` / `encode(message)` / `decode(type, data)`.
- [x] `encode` throws `NetworkProtocolError(PROTO_CODEC_NOT_FOUND)` if codec missing.
- [x] Write `codec-registry.test.ts` + `codec-registry.md`.
---
## 5. Protocol session (`src/core/session/`)
- [x] Implement `ProtocolSession` class:
- Stores `framing`, `codecs` from parent protocol.
- `encode(message)`: check `maxMessageSize`; look up codec; apply framing.
- `decode(data)`: strip framing; look up codec by message type header.
- `handshake(signal?)`: call `definition.handshake.clientHello()`, await response, call `verify()`.
- Emit lifecycle events via event bus.
- [x] Write `session.test.ts` (≤500 lines): encode/decode round-trip, handshake, too-large message throws.
- [x] Write `session.md` (≥200 words).
---
## 6. Protocol factory (`src/core/protocol/`)
- [x] Implement `defineProtocol(definition)`:
- Validates definition with Zod.
- Returns `Protocol` object.
- [x] Implement `protocol.createSession(options?)` — creates `ProtocolSession`, returns it.
- [x] Implement `protocol.negotiate(offered, signal?)`:
- Compute version intersection; take highest common version.
- Compute capability intersection.
- Return `ProtocolNegotiationResult`.
- [x] Write `protocol.test.ts` + `protocol.md` (≥200 words).
---
## 7. Property-based tests (`tests/property.test.ts`)
- [x] Encode/decode round-trip: for arbitrary `ProtocolMessage` with registered codec, `decode(encode(m))` deep-equals `m`.
- [x] Framing round-trip: `decode(encode(payload))` equals payload for all three strategies.
- [x] Negotiation symmetry: `negotiate(A, B)` produces same version as `negotiate(B, A)`.
---
## 8. Docs and verify
- [x] All per-file `.md` files per layout in `proposal.md`.
- [x] `universal/network/network-protocol/README.md`.
- [x] `bunx turbo lint typecheck test test:coverage --filter=@suaveplan/network-protocol` — all green, 100%.
- [x] `bun scripts/publish.ts --package @suaveplan/network-protocol`.
- [x] Update `CLAUDE.md`. Archive change folder.