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

221 lines
6.0 KiB
Markdown

# 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.