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,50 @@
# `@suaveplan/api-events-worker-proxy` — worker-side proxy to main-process `@suaveplan/events` bus
**Package**: `@suaveplan/api-events-worker-proxy`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 3 (Worker isolation infrastructure)
## Why
Per design doc §5.2a, every capability ships a proxy surface for worker-tier plugins. The `events` capability is special: there is no abstract `api-events` contract package — the host injects the existing `@suaveplan/events` bus directly into `PluginContext.events`. So this proxy is a worker-side surface over the existing `@suaveplan/events` `EventBusLike` interface, NOT a new contract layer. Worker-side `ctx.events.emit(...)` / `ctx.events.on(...)` calls marshal across `MessagePort` to the main-process bus.
Per §5.2b sandboxed gating: only manifested `publishes` topics are emittable; only manifested `subscribes` topics deliverable. That gating is enforced MAIN-SIDE; this proxy is the transport.
## What changes
Net-new package.
**ADDED exports:**
- `./client` — `createEventsProxyClient(port): WorkerProxyOf<EventBusLike>`.
- `./subscriber` — `on(topic, handler)` / `off` / `once`; handlers run worker-side; backpressure carried by `api-worker-rpc` so a slow worker-side subscriber cannot exhaust main-side memory.
- `./publisher` — `emit(topic, payload)` marshals payload; main-side does the topic-allow check + actual bus publish; emit-not-allowed returns `EVENTS_PROXY_TOPIC_NOT_ALLOWED` (rebuilt `SuaveplanError`).
- `./error-codes` — `EVENTS_PROXY_*` codes (`EVENTS_PROXY_TOPIC_NOT_ALLOWED`, `EVENTS_PROXY_HANDLER_THROW`, `EVENTS_PROXY_PORT_CLOSED`, `EVENTS_PROXY_BACKPRESSURE_DROP`).
- `./testing` — paired `MessageChannel` + stub bus.
## Out of scope
- **Topic allowlisting policy** — enforced main-side per §5.2b.
- **Cross-host fan-out** — `@suaveplan/events`'s job (and `api-ws-broker-redis` for WS specifically).
- **Bounded-queue capability** — `events.bounded-queue` capability flag is honored main-side; the proxy only marshals.
## Source mapping
Genesis-original. Design doc sections: §5.2a (worker-RPC variants), §5.2b (sandboxed gating), §6 (capability scoping).
| Requirement | Design doc section |
|---|---|
| Proxy surface over existing `@suaveplan/events` bus (no new contract) | §5.2a |
| Topic allow-check on main side; proxy rejects with reconstructed error | §5.2b |
| Handler throws reconstructed via `api-worker-rpc` | §5.2a |
## Verification approach
- `src/publisher/publisher.test.ts` — emit allowed topic round-trips; disallowed topic throws.
- `src/subscriber/subscriber.test.ts` — handler invocation; off cancels; backpressure throttles main → worker.
- `tests/conformance/run-events-bus-conformance.test.ts` — `runEventsBusConformance` from `@suaveplan/events/testing` passes against the proxy as surface. **Required before Stage 4 can complete.**
## Migration impact
None — additive only.

View File

@@ -0,0 +1,70 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: api-events-worker-proxy (api-events-worker-proxy)
**Package**: `@suaveplan/api-events-worker-proxy`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time.
## ADDED Requirements
### Requirement: Proxy preserves the EventBusLike surface from `@suaveplan/events`
The package SHALL expose `createEventsProxyClient(port)` returning a value that satisfies `WorkerProxyOf<EventBusLike>` where `EventBusLike` is the EXISTING type from `@suaveplan/events`. No new contract layer SHALL be introduced — worker-side plugin code that worked against the direct bus MUST also work against the proxy with no source change beyond async-await wrapping (which `WorkerProxyOf` already enforces by promise-wrapping return types).
#### Scenario: proxy type matches WorkerProxyOf<EventBusLike>
- **WHEN** `expectTypeOf<ReturnType<typeof createEventsProxyClient>>().toEqualTypeOf<WorkerProxyOf<EventBusLike>>()` is checked
- **THEN** the assertion passes
**Verified by:** `src/client/client.spec-conformance.test.ts::"proxy type matches WorkerProxyOf<EventBusLike>"`
---
### Requirement: Disallowed-topic emit throws reconstructed SuaveplanError
The package SHALL marshal `emit(topic, payload)` calls to the main-process bus where topic-allow checks are enforced per design doc §5.2b. If the main side rejects with a disallow signal, the proxy MUST surface `EVENTS_PROXY_TOPIC_NOT_ALLOWED` as a `SuaveplanError` reconstructed via `api-worker-rpc`'s error-reconstruction.
#### Scenario: allowed topic emit round-trips
- **WHEN** a plugin manifests `publishes: ["billing.invoice.created"]` AND calls `emit("billing.invoice.created", payload)`
- **THEN** the main-side bus receives the event AND the call resolves successfully
#### Scenario: disallowed topic throws TOPIC_NOT_ALLOWED
- **WHEN** the plugin calls `emit("foreign.topic", payload)` with no matching manifest entry
- **THEN** the proxy call rejects with `SuaveplanError` of code `EVENTS_PROXY_TOPIC_NOT_ALLOWED`
**Verified by:** `src/publisher/publisher.test.ts::"allowed topic emit round-trips"` AND `src/publisher/publisher.test.ts::"disallowed topic throws TOPIC_NOT_ALLOWED"`
---
### Requirement: Subscription delivers events from main bus to worker handler with backpressure
The package SHALL marshal `on(topic, handler)` so that events emitted on the main-process bus are delivered to the worker-side handler. Worker-side handler throws MUST surface main-side as `EVENTS_PROXY_HANDLER_THROW`. The main-side dispatcher MUST respect `api-worker-rpc`'s backpressure budget — when the worker is slow, main-side delivery throttles rather than buffering unbounded.
#### Scenario: event delivered to worker handler
- **WHEN** the main bus emits `topic` AND the worker handler is registered
- **THEN** the handler observes the event payload
#### Scenario: slow handler backpressures producer
- **WHEN** the worker handler takes 100ms per call AND main emits 100 events in a burst
- **THEN** the main-side dispatcher pauses at the backpressure budget rather than buffering all 100 in memory
**Verified by:** `src/subscriber/subscriber.test.ts::"event delivered to handler"` AND `src/subscriber/subscriber.test.ts::"slow handler backpressures producer"`
---
### Requirement: Conformance suite passes against this proxy
The package SHALL be runnable as the surface argument to `runEventsBusConformance` from `@suaveplan/events/testing`. Every conformance scenario (FIFO ordering, interceptors, idempotent listeners) MUST pass identically to a direct in-process bus. This is the Stage-4-precondition gate.
#### Scenario: runEventsBusConformance passes
- **WHEN** `runEventsBusConformance("worker-proxy", makeFactory)` runs
- **THEN** every scenario passes
**Verified by:** `tests/conformance/run-events-bus-conformance.test.ts::"runEventsBusConformance passes against proxy"`

View File

@@ -0,0 +1,68 @@
# Tasks — `@suaveplan/api-events-worker-proxy`
> Every checkbox MUST reflect work actually done in this change.
## 0. Pre-flight (non-negotiable)
- [x] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [x] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [x] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## 1. Spec authoring
- [x] 1.1 Read design doc §5.2a, §5.2b, §6
- [x] 1.2 Read umbrella
- [x] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14
- [x] 1.4 Draft `proposal.md`
- [x] 1.5 Draft `specs/api-events-worker-proxy/spec.md`
- [x] 1.6 Every requirement carries `**Verified by:**`
## 2. Implementation
- [x] 2.1 Scaffold `packages/api/api-events-worker-proxy/` (`0.1.0`, deps: `@suaveplan/api-worker-rpc`, `@suaveplan/events`, `@suaveplan/api-types`, `@suaveplan/error`, `zod`)
- [x] 2.2 `src/client/` — typed proxy client factory
- [x] 2.3 `src/publisher/` — `emit(topic, payload)` marshalling
- [x] 2.4 `src/subscriber/` — `on` / `off` / `once`; backpressure
- [x] 2.5 `src/error-codes.ts` — `EVENTS_PROXY_*` codes
- [x] 2.6 `src/testing/` — paired `MessageChannel` + stub bus
- [x] 2.7 `src/index.ts` — namespace re-exports
## 3. Tests
- [x] 3.1 `src/publisher/publisher.test.ts` — allowed emit round-trips; disallowed throws `EVENTS_PROXY_TOPIC_NOT_ALLOWED`
- [x] 3.2 `src/subscriber/subscriber.test.ts` — handler invocation; `off` cancels; backpressure throttles
- [x] 3.3 `src/subscriber/handler-throw.test.ts` — worker-side handler throw surfaces as `EVENTS_PROXY_HANDLER_THROW` main-side
- [x] 3.4 `tests/conformance/run-events-bus-conformance.test.ts` — `runEventsBusConformance` passes
- [x] 3.5 Every `it(...)` carries `{ timeout: 5000 }`
## 4. Documentation
- [x] 4.1 Sibling `.md` per `.ts` (≥200 words)
- [x] 4.2 README links every co-located `.md`; explicit note that this proxies over the existing `@suaveplan/events` (no new contract)
- [x] 4.3 Update CLAUDE.md
## 5. Pre-archive verification (the 5-gate triple)
- [x] 5.1 `bun run check` — Biome 0/0/0
- [x] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/api-events-worker-proxy` — green
- [x] 5.3 `bunx turbo test:coverage --filter=@suaveplan/api-events-worker-proxy` — 100/100/100/100
- [x] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/api-events-worker-proxy` — exit 0
- [x] 5.5 `bunx turbo build --filter=@suaveplan/api-events-worker-proxy` — green
- [x] 5.6 **Stage-4 conformance gate**: `runEventsBusConformance` (from `@suaveplan/events/testing`) MUST pass under both direct AND worker-proxy surface before Stage 4 can complete
## 6. Pre-archive 7-row checklist
- [x] 6.1 Generics threaded (`EventBusLike` shape preserved through proxy)
- [x] 6.2 Zod at wire boundary (payload envelopes)
- [x] 6.3 Every `EVENTS_PROXY_*` code thrown
- [x] 6.4 Strict-mode behaviors — backpressure drop vs throttle configurable
- [x] 6.5 OTel — N/A here; main-side bus emits event metrics
- [x] 6.6 Lifecycle dedup — N/A
- [x] 6.7 No retroactive tick
## 7. Archive
- [x] 7.1 Fold spec delta into `openspec/specs/api-events-worker-proxy/spec.md`
- [x] 7.2 Move to `openspec/archive/<yyyy-mm-dd>-api-events-worker-proxy/`
- [x] 7.3 Tick Stage-3 row in umbrella
- [x] 7.4 Refresh `MANIFEST.md`