# `@suaveplan/api-worker-rpc` — MessagePort protocol for capability marshalling **Package**: `@suaveplan/api-worker-rpc` **Status**: PROPOSED **Type**: new-package **Stage**: 3 (Worker isolation infrastructure) ## Why Every capability adapter ships two callable surfaces: a direct in-process one for `trusted` plugins and a `MessagePort`-marshalled one for `isolated` / `sandboxed` plugins (design doc §2.2, §5.2a). Hand-rolling that marshalling once per capability is how plugin frameworks accrete subtly-different RPC layers that diverge on cancellation, streaming, and error reconstruction. `api-worker-rpc` is the single, shared protocol every worker-proxy package builds on. Getting it right once means every capability — `db`, `events`, `secrets`, `jobs`, `logger`, future ones — gets cancellation, backpressure, streaming, and error reconstruction for free. Per design doc §5.2a: every conformance suite runs against both surfaces. The proxy surface MUST behave identically to the direct surface from the plugin's perspective. ## What changes Net-new package. Universal-tier (uses `MessagePort` from `worker_threads` on Node/Bun; the same `MessagePort` shape is also available in browsers, so the protocol is portable even though Stage-3 callers are all server-side). **ADDED exports:** - `./protocol` — wire types: `RpcRequest`, `RpcResponse`, `RpcError`, `RpcStreamChunk`, `RpcAbort`. All Zod-validated at the receive boundary. - `./correlation` — monotonic message-id generator (`bigint`); request/response matching table; orphan-response detection. - `./abort` — `AbortSignal` forwarding: caller-side `AbortController.signal` triggers an `RpcAbort` message; worker-side handler receives an `AbortSignal` that fires accordingly. - `./streaming` — `ReadableStream` marshalling via `Transferable` (zero-copy chunk transfer); back-pressure carried in `RpcStreamChunk.ack`. - `./error-reconstruction` — `SuaveplanError` rebuilt on the receiving side with the same `code`, `cause` chain (depth-bounded), and serialisable `details`. Non-`SuaveplanError` throws are wrapped in `RPC_REMOTE_THROW`. - `./backpressure` — bounded outstanding-message budget per port; exceeding triggers `RPC_BACKPRESSURE` at the caller BEFORE exhausting memory. - `./cloneable-guard` — pre-send check for structured-clone safety; non-cloneable values (functions, class instances without `serialize` hooks) throw `RPC_NOT_CLONEABLE` at the call site, not silently corrupting the wire. - `./caller` — `createRpcCaller(port)` returns a typed proxy of `T` whose method signatures are `WorkerProxyOf` (return types wrapped in `Promise`). - `./callee` — `serveRpc(port, impl)` mounts an implementation on the receiving side; handles dispatch, abort wiring, and stream lifecycle. - `./error-codes` — `RPC_*` codes via `defineCodes` (`RPC_BACKPRESSURE`, `RPC_NOT_CLONEABLE`, `RPC_REMOTE_THROW`, `RPC_TIMEOUT`, `RPC_PORT_CLOSED`, `RPC_ORPHAN_RESPONSE`, `RPC_PROTOCOL_VERSION_MISMATCH`). - `./testing` — paired `MessageChannel` factory for in-process conformance suites. ## Out of scope - **Capability-specific marshalling logic** — query-cursor handling for DB, log-record batching for logger, etc. Each worker-proxy package layers its own semantics on top of this protocol. - **Process-level isolation** — `api-worker-rpc` is transport-only; spawning the worker is `api-worker-host`'s job. - **Cross-host transport** — this is in-process `MessagePort` only. Cross-host fan-out is `api-ws-broker-*`'s domain. ## Source mapping Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`. | Requirement | Design doc section | |---|---| | Request/response correlation via monotonic ids | §5.2a | | `AbortSignal` forwarding | §5.2a | | Streaming via `Transferable` `ReadableStream` | §5.2a | | Error reconstruction (`SuaveplanError` rebuilt) | §5.2a | | Backpressure → `RPC_BACKPRESSURE` | §5.2a | | Structured-clone-safe boundary → `RPC_NOT_CLONEABLE` | §5.2a | ## Verification approach - `src/correlation/correlation.test.ts` — monotonic ids; request/response match; orphan detection. - `src/abort/abort.test.ts` — caller-side abort propagates to worker `AbortSignal`; in-flight call rejects with `AbortError`. - `src/streaming/streaming.test.ts` — large stream marshals in zero-copy chunks; backpressure ack throttles producer. - `src/error-reconstruction/error-reconstruction.test.ts` — `SuaveplanError` round-trips with code + cause depth ≤ N; non-Suaveplan throw → `RPC_REMOTE_THROW`. - `src/backpressure/backpressure.test.ts` — outstanding budget enforced; exceeding → `RPC_BACKPRESSURE`. - `src/cloneable-guard/cloneable-guard.test.ts` — function argument → `RPC_NOT_CLONEABLE` at call site. - `src/caller/caller.spec-conformance.test.ts` — `expectTypeOf` proves caller methods return `Promise`. - `tests/conformance/round-trip.test.ts` — pair `createRpcCaller` + `serveRpc` over `MessageChannel`; 50+ scenarios; every code in `error-codes.ts` reachable from at least one path. ## Migration impact None — additive only. Stage-3 worker-proxy packages and `api-worker-host` will depend on this.