221 lines
6.0 KiB
Markdown
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.
|