Initial Commit
This commit is contained in:
73
openspec/archive/2026-05-25-api-sse/proposal.md
Normal file
73
openspec/archive/2026-05-25-api-sse/proposal.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# `@suaveplan/api-sse` — abstract SSE Connection + Broker + Last-Event-Id resume contract
|
||||
|
||||
**Package**: `@suaveplan/api-sse`
|
||||
**Status**: PROPOSED
|
||||
**Type**: new-package
|
||||
**Stage**: 1 (Contracts) — see umbrella `openspec/changes/api-plugin-foundation/`
|
||||
|
||||
## Why
|
||||
|
||||
SSE is the simpler cousin of WebSocket (design §11.6): same subscription model, no client→server channel, no ack semantics. Useful for activity feeds, dashboards, notifications, and as a fallback on networks that block WebSocket. Plugins declare SSE endpoints in the manifest; the host runs the engine, manages connection lifecycle, fans out subscriptions, replays buffered events on `Last-Event-Id` reconnect. Concrete engines (`api-sse-bun`) plug behind this contract.
|
||||
|
||||
Per design §11.6: "Reuses the same bus + broker; just a different transport." The bus shape is shared with `api-ws`'s `SubscriptionBus` — this package re-declares the SSE-specific connection/broker surface and the `Last-Event-Id` replay contract.
|
||||
|
||||
## What changes
|
||||
|
||||
Net-new package. Contract types + the replay-buffer interface + the conformance harness.
|
||||
|
||||
- ADDED: `./connection` — abstract `SseConnection`: `id`, `principal`, `tenantId`, `lastEventId: string | null`, `state`, `send(event: SseEvent)`, `close()`.
|
||||
- ADDED: `./event` — `SseEvent`: `{ id: string, event?: string, data: string, retry?: number }` (standard SSE event field shape).
|
||||
- ADDED: `./broker` — abstract `SseBroker`: same shape as `WsBroker` (publish/subscribe/dispose) but distinct type so engines can supply different transports.
|
||||
- ADDED: `./replay-buffer` — abstract `ReplayBuffer`: per-`(tenant, topic)` ring buffer (`eventBufferSize`, `eventBufferMs`); `append(event)`, `since(lastEventId)`, `evictExpired(now)`. The host's `Last-Event-Id` reconnect contract reads from here.
|
||||
- ADDED: `./connection-registry` — abstract `SseConnectionRegistry`: same multi-index shape as `ConnectionRegistry` in `api-ws` but typed for `SseConnection`.
|
||||
- ADDED: `./capability-id` — adds `"sse"` to the `CapabilityId` union via type-level augmentation.
|
||||
- ADDED: `./error-codes` — frozen `SSE_CODES`: `HANDSHAKE_FAILED`, `PRINCIPAL_EXPIRED`, `BUFFER_OVERFLOW`, `LAST_EVENT_ID_UNKNOWN`, `CONNECTION_CAP_EXCEEDED`, `BROKER_UNAVAILABLE`.
|
||||
- ADDED: `./testing` — `runSseEngineConformance(factory)` reusable harness.
|
||||
|
||||
## CapabilityId additions
|
||||
|
||||
This package contributes `"sse"` to the `CapabilityId` union. (No "broadcast-cross-tenant" SSE variant — SSE never accepts client input, so the host owns broadcast tenant scoping uniformly.)
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Concrete `api-sse-bun` engine — Stage 7.
|
||||
- WebSocket — separate contract in `api-ws`.
|
||||
- The shared `SubscriptionBus` semantics — re-used type lives in `api-ws`; this package's broker is engine-side.
|
||||
|
||||
## Source mapping
|
||||
|
||||
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
|
||||
|
||||
| Requirement | Design doc section |
|
||||
|---|---|
|
||||
| SSE simpler-cousin shape | §11.6 |
|
||||
| `Last-Event-Id` replay buffer | §11.5 (last paragraph) + §11.6 |
|
||||
| OTel surface (`sse.connections`, `sse.events_sent_total`) | §11.7 |
|
||||
| Worker-tier proxying | §11.8 |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| `runSseEngineConformance` harness | Stage-5 deliverable (§13.4). |
|
||||
|
||||
## Verification approach
|
||||
|
||||
- `src/connection/connection.spec-conformance.test.ts` — `expectTypeOf<SseConnection>()` declares every documented field.
|
||||
- `src/event/event.test.ts` — SseEvent shape; `id`/`event`/`data`/`retry` field handling.
|
||||
- `src/broker/broker.spec-conformance.test.ts` — `SseBroker` interface declared.
|
||||
- `src/replay-buffer/replay-buffer.test.ts` — append, query `since(id)`, time-based eviction, size cap.
|
||||
- `src/replay-buffer/last-event-id.test.ts` — unknown `Last-Event-Id` → `SSE.LAST_EVENT_ID_UNKNOWN` AND fall back to live subscription (no replay).
|
||||
- `src/connection-registry/connection-registry.test.ts` — multi-index lookup; idempotent remove.
|
||||
- `src/capability-id/capability-id.spec-conformance.test.ts` — `"sse"` in `CapabilityId`.
|
||||
- `tests/dep-graph.test.ts` — deps: `@suaveplan/api-types`, `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/api-ws` (for shared `SubscriptionBus` type only, peer dep), `zod`.
|
||||
|
||||
## Migration impact
|
||||
|
||||
None — additive only.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
- (none) — [x] no action needed (reason: net-new; engines land Stage 7)
|
||||
Reference in New Issue
Block a user