# 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()` 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` 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()` 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()` 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"`