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,145 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: api-sse (api-sse)
**Package**: `@suaveplan/api-sse`
**Status**: PROPOSED
> Every requirement carries a `**Verified by:**` line.
## ADDED Requirements
### Requirement: SseConnection contract
The package SHALL export `SseConnection` with: `id` (ULID), `principal: Principal`, `tenantId: TenantId`, `lastEventId: string | null`, `state: "open" | "closing" | "closed"`, methods `send(event: SseEvent)`, `close()`. SSE is uni-directional (host → client); there is no `onMessage`.
#### Scenario: every documented field present
- **WHEN** `expectTypeOf<SseConnection>()` is checked
- **THEN** every documented field is declared
**Verified by:** `src/connection/connection.spec-conformance.test.ts::"SseConnection declares every documented field"`
---
### 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: SseEvent shape
The package SHALL export `sseEventSchema: z.ZodSchema<SseEvent>` describing `{ id: string, event?: string, data: string, retry?: number }`. Unknown keys MUST be rejected.
#### Scenario: minimal event parses
- **WHEN** `{ id: "1", data: "hello" }` is parsed
- **THEN** it succeeds AND `event` is undefined (not defaulted)
#### Scenario: unknown key rejected
- **WHEN** `{ id: "1", data: "x", extra: 1 }` is parsed
- **THEN** Zod reports an issue at `extra`
**Verified by:** `src/event/event.test.ts::"minimal event parses"` AND `src/event/event.test.ts::"unknown keys rejected"`
---
### Requirement: SseBroker contract
The package SHALL export `SseBroker` with `publish(topic, envelope)`, `subscribe(topic, handler)`, `dispose()`. Same shape as `WsBroker` but a distinct type so engines can supply different transports.
#### Scenario: every method present
- **WHEN** `expectTypeOf<SseBroker>()` is checked
- **THEN** all three methods are declared
**Verified by:** `src/broker/broker.spec-conformance.test.ts::"SseBroker declares publish subscribe dispose"`
---
### Requirement: Replay buffer with Last-Event-Id resume
The package SHALL export `ReplayBuffer` with: `append(event: SseEvent)`, `since(lastEventId: string): readonly SseEvent[]`, `evictExpired(now: number)`. The buffer is per `(tenant, topic)` and bounded by `eventBufferSize` (default 256) and `eventBufferMs` (default 30000). `since` SHALL return every event whose id sorts AFTER `lastEventId` in append order. An unknown `lastEventId` (evicted or never seen) MUST surface `SSE.LAST_EVENT_ID_UNKNOWN` so the engine can fall back to live subscription.
#### Scenario: since(id) returns events after the marker
- **WHEN** events A, B, C are appended in order AND `since(B.id)` is called
- **THEN** the result is `[C]`
#### Scenario: unknown id surfaces error code
- **WHEN** `since("never-seen-id")` is called
- **THEN** the call throws `SSE.LAST_EVENT_ID_UNKNOWN`
#### Scenario: time-based eviction drops expired entries
- **WHEN** the fake clock advances past `eventBufferMs`
- **THEN** the expired entries are dropped on the next `evictExpired(now)`
**Verified by:** `src/replay-buffer/replay-buffer.test.ts::"since returns events after marker"` AND `src/replay-buffer/last-event-id.test.ts::"unknown lastEventId surfaces LAST_EVENT_ID_UNKNOWN"` AND `src/replay-buffer/replay-buffer.test.ts::"time-based eviction drops expired entries"`
---
### Requirement: Connection registry multi-index
The package SHALL export `SseConnectionRegistry` with O(1) lookup by `tenantId` and by `(topic, tenantId)`. `add`, `remove`, `lookupByTenant`, `lookupByTopic`, `countByTenant`. `remove` MUST be idempotent.
#### Scenario: lookup returns matching set
- **WHEN** 2 SSE connections subscribe to tenant T
- **THEN** `lookupByTenant("T").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: CapabilityId augmentation contributes "sse"
The package SHALL augment the `CapabilityId` union to include `"sse"`.
#### Scenario: sse is assignable to CapabilityId
- **WHEN** `expectTypeOf<"sse">().toMatchTypeOf<CapabilityId>()` is checked
- **THEN** the assertion passes
**Verified by:** `src/capability-id/capability-id.spec-conformance.test.ts::"sse is a valid CapabilityId after augmentation"`
---
### Requirement: Error codes registry frozen
The package SHALL export `SSE_CODES` containing exactly: `HANDSHAKE_FAILED`, `PRINCIPAL_EXPIRED`, `BUFFER_OVERFLOW`, `LAST_EVENT_ID_UNKNOWN`, `CONNECTION_CAP_EXCEEDED`, `BROKER_UNAVAILABLE`.
#### Scenario: every code reachable
- **WHEN** the test enumerates `SSE_CODES`
- **THEN** every code has a production throw site
**Verified by:** `src/error-codes/error-codes.spec-conformance.test.ts::"every SSE_CODES entry thrown by production path"`
---
### Requirement: Conformance harness for engines
The package SHALL export `runSseEngineConformance({ factory, label })`. Every concrete SSE engine MUST pass.
#### Scenario: harness covers handshake, send, last-event-id replay, eviction
- **WHEN** the harness runs against a stub engine
- **THEN** test categories exist for each documented behavior
**Verified by:** `src/testing/conformance.test.ts::"harness covers handshake send replay eviction"`