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,202 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: api-ws (api-ws)
**Package**: `@suaveplan/api-ws`
**Status**: ACTIVE
> Every requirement carries a `**Verified by:**` line.
## ADDED Requirements
### Requirement: WsConnection contract
The package SHALL export `WsConnection` with: `id` (ULID), `principal: Principal`, `tenantId: TenantId`, `lastEventId: string | null`, `state: "open" | "closing" | "closed"`, methods `subscribe(topic, opts?)`, `unsubscribe(topic)`, `send(message)`, `close(code, reason?)`, `ack(id)`. The principal MUST be frozen at handshake for the connection's lifetime; token expiry mid-connection fires `connection.principal_expired` (the host emits this; the contract surfaces it).
#### Scenario: every method present
- **WHEN** `expectTypeOf<WsConnection>()` is checked
- **THEN** every documented method is declared with the documented signature
**Verified by:** `src/connection/connection.spec-conformance.test.ts::"WsConnection declares every documented method"`
---
### Requirement: `import z from "zod"` everywhere
Every Zod import MUST use the default form.
#### Scenario: no named import of z
- **WHEN** the test greps `import z` from `"zod"`
- **THEN** zero matches
**Verified by:** `tests/zod-import-form.test.ts::"every src file uses default-import z"`
---
### Requirement: ConnectionRegistry multi-index
The package SHALL export `ConnectionRegistry` with O(1) lookup by `tenantId` and by `(topic, tenantId)`. `add`, `remove`, `lookupByTenant(tenant)`, `lookupByTopic(topic, tenant?)`, `countByTenant(tenant)`. `remove` MUST be idempotent (removing an absent connection is a no-op).
#### Scenario: lookup by tenant returns matching set
- **WHEN** 3 connections are added for tenant T1 and 2 for tenant T2
- **THEN** `lookupByTenant("T1").length === 3` AND `lookupByTenant("T2").length === 2`
#### Scenario: remove is idempotent
- **WHEN** `remove(conn)` is called twice
- **THEN** the second call returns without throwing
**Verified by:** `src/connection-registry/connection-registry.test.ts::"lookupByTenant returns matching set"` AND `src/connection-registry/connection-registry.test.ts::"remove is idempotent"`
---
### Requirement: SubscriptionBus broadcast with filter
The package SHALL export `SubscriptionBus.broadcast(topic, { tenant, payload, filter? })` which delivers the payload to every connection registered against `(topic, tenant)` whose optional `filter(payload)` returns true. The payload MUST be encoded once and sent to each matching connection (no per-connection re-encoding).
#### Scenario: broadcast reaches matching connections
- **WHEN** two connections subscribe to `orders.created` for tenant T
- **THEN** `broadcast("orders.created", { tenant: T, payload })` reaches both
#### Scenario: filter excludes non-matching connections
- **WHEN** filter returns `false` for one of two matching connections
- **THEN** only the connection where filter returns true receives the payload
**Verified by:** `src/subscription-bus/subscription-bus.test.ts::"broadcast reaches all matching connections"` AND `src/subscription-bus/subscription-bus.test.ts::"filter predicate excludes non-matching connections"`
---
### Requirement: Cross-tenant broadcast requires explicit capability
`SubscriptionBus.broadcast(topic, { tenant, payload })` where `tenant` differs from the caller's `principal.tenantId` (or `tenant` is the wildcard "*") MUST throw `WS.CROSS_TENANT_BROADCAST_DENIED` unless the caller's `Caps` includes `"ws.broadcast-cross-tenant"`. Default is deny.
#### Scenario: same-tenant broadcast succeeds without capability
- **WHEN** caller's tenant is T AND broadcast is for tenant T
- **THEN** broadcast succeeds
#### Scenario: cross-tenant denied by default
- **WHEN** caller's tenant is T AND broadcast is for tenant T2 AND `Caps` does NOT include `ws.broadcast-cross-tenant`
- **THEN** broadcast throws `WS.CROSS_TENANT_BROADCAST_DENIED`
**Verified by:** `src/subscription-bus/cross-tenant.test.ts::"same-tenant broadcast succeeds without capability"` AND `src/subscription-bus/cross-tenant.test.ts::"cross-tenant denied without explicit capability"`
---
### Requirement: WsBroker contract
The package SHALL export `WsBroker` for multi-host fan-out: `publish(topic, envelope)`, `subscribe(topic, handler)`, `dispose()`. The broker MUST NOT migrate connections — it only makes broadcasts visible across host replicas. `dispose()` MUST close every subscription cleanly.
#### Scenario: every method present
- **WHEN** `expectTypeOf<WsBroker>()` is checked
- **THEN** all three methods are declared
**Verified by:** `src/broker/broker.spec-conformance.test.ts::"WsBroker declares publish subscribe dispose"`
---
### Requirement: Envelope Zod schema
The package SHALL export `wsEnvelopeSchema: z.ZodSchema<WsEnvelope>` describing the wire envelope: `{ id: string, type: string, topic?: string, payload?: unknown, ts?: string }`. Unknown top-level keys MUST be rejected.
#### Scenario: minimal envelope parses
- **WHEN** `{ id: "x", type: "msg" }` is parsed
- **THEN** it succeeds
#### Scenario: extra key rejected
- **WHEN** `{ id: "x", type: "msg", extra: 1 }` is parsed
- **THEN** Zod reports an issue at `extra`
**Verified by:** `src/envelope/envelope.test.ts::"minimal envelope parses"` AND `src/envelope/envelope.test.ts::"unknown keys rejected"`
---
### Requirement: Backpressure policy
The package SHALL export `BackpressurePolicy` with default `maxQueuedMessages = 256`. When a connection's send queue exceeds the cap, the connection MUST receive a `BACKPRESSURE_EVICTED` close frame and be dropped from the registry.
#### Scenario: queue saturation triggers eviction
- **WHEN** 257 messages are queued for a connection with default policy
- **THEN** the eviction handler fires AND `WS.BACKPRESSURE_EVICTED` is the close code
**Verified by:** `src/backpressure/backpressure.test.ts::"queue saturation triggers BACKPRESSURE_EVICTED"`
---
### Requirement: Heartbeat policy
The package SHALL export `HeartbeatPolicy` with default `heartbeatMs = 30000`. Two missed pongs MUST trigger `onClose` with code `ABNORMAL`.
#### Scenario: two missed pongs trigger close
- **WHEN** a fake clock advances past 2 × heartbeatMs without pong
- **THEN** the close handler fires with `code === "ABNORMAL"` AND `WS.HEARTBEAT_FAILED` is emitted
**Verified by:** `src/heartbeat/heartbeat.test.ts::"two missed pongs trigger ABNORMAL close"`
---
### Requirement: Reconnect-token shape
The package SHALL export `ReconnectToken` HMAC envelope carrying `{ tenant, principalId, lastEventId, exp }` with default 5-minute TTL. The HMAC verifier MUST reject expired tokens (`WS.RECONNECT_TOKEN_INVALID`) and tokens with bad MAC.
#### Scenario: valid token verifies
- **WHEN** a token signed within TTL is verified
- **THEN** the verifier returns the parsed payload
#### Scenario: expired token rejected
- **WHEN** the fake clock advances past TTL
- **THEN** the verifier throws `WS.RECONNECT_TOKEN_INVALID`
**Verified by:** `src/reconnect-token/reconnect-token.test.ts::"valid token verifies"` AND `src/reconnect-token/reconnect-token.test.ts::"expired token rejected"`
---
### Requirement: CapabilityId augmentation contributes "ws" and "ws.broadcast-cross-tenant"
The package SHALL augment the `CapabilityId` union to include both ids.
#### Scenario: both ids assignable to CapabilityId
- **WHEN** `expectTypeOf<"ws">().toMatchTypeOf<CapabilityId>()` AND `expectTypeOf<"ws.broadcast-cross-tenant">().toMatchTypeOf<CapabilityId>()` are checked
- **THEN** both pass
**Verified by:** `src/capability-id/capability-id.spec-conformance.test.ts::"ws and ws.broadcast-cross-tenant are valid CapabilityIds"`
---
### Requirement: Error codes registry frozen
The package SHALL export `WS_CODES` containing exactly: `HANDSHAKE_FAILED`, `PRINCIPAL_EXPIRED`, `BACKPRESSURE_EVICTED`, `HEARTBEAT_FAILED`, `CONNECTION_CAP_EXCEEDED`, `CROSS_TENANT_BROADCAST_DENIED`, `RECONNECT_TOKEN_INVALID`, `BROKER_UNAVAILABLE`.
#### Scenario: every code reachable
- **WHEN** the test enumerates `WS_CODES`
- **THEN** every code has a production throw site
**Verified by:** `src/error-codes/error-codes.spec-conformance.test.ts::"every WS_CODES entry thrown by production path"`
---
### Requirement: Conformance harness for engines
The package SHALL export `runWsEngineConformance({ factory, label })`. Every concrete engine + broker pair MUST pass.
#### Scenario: harness covers handshake, subscribe, broadcast, backpressure, heartbeat, reconnect
- **WHEN** the harness is run against a stub engine
- **THEN** test categories exist for every documented behavior
**Verified by:** `src/testing/conformance.test.ts::"harness covers handshake subscribe broadcast backpressure heartbeat reconnect"`