Files
2026-05-27 23:29:53 +01:00

6.0 KiB

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

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

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

interface ProtocolOffer {
  name: string
  versions: number[]
  capabilities?: string[]
}

interface ProtocolNegotiationResult {
  readonly agreed: boolean
  readonly version: number
  readonly capabilities: string[]
  readonly reason?: string
}

Types

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

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

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.