57 lines
4.4 KiB
Markdown
57 lines
4.4 KiB
Markdown
# `@suaveplan/api-router-worker-bridge` — request/response marshalling across the worker boundary
|
||
|
||
**Package**: `@suaveplan/api-router-worker-bridge`
|
||
**Status**: PROPOSED
|
||
**Type**: new-package
|
||
**Stage**: 3 (Worker isolation infrastructure)
|
||
|
||
## Why
|
||
|
||
Per design doc §6: when a plugin runs in `isolated` or `sandboxed` tier, the per-request pipeline executes steps 1–7 in the main process (intake, route match, auth, tenant, rate-limit, RBAC, validation, capability scoping), then marshals the result across `MessagePort` to the worker for step 8 (the user-code handler). Critically: the raw `Request` object is NEVER marshalled. Only the already-validated `body` / `params` / `query` cross the boundary. This is what lets worker-tier plugins gain isolation without losing any of the host-enforced security guarantees.
|
||
|
||
`api-router-worker-bridge` is the pair of half-bridges (main-side + worker-side) that implement that envelope. It sits between the main-process router (`api-router-hono` in Stage 4) and the worker-side request dispatcher (in `api-worker-host`).
|
||
|
||
## What changes
|
||
|
||
Net-new package. Two halves:
|
||
|
||
**ADDED exports:**
|
||
|
||
- `./main-side` — `BridgeMain`: accepts a route hit from `api-host`'s pipeline; constructs an `HttpDispatchEnvelope` (validated body, params, query, principal, tenantId, requestId, trace context); sends across the worker port via `api-worker-rpc`; awaits the response envelope; rehydrates the response into a `Response` object for the main-side response-validation + compose steps.
|
||
- `./worker-side` — `BridgeWorker`: receives `HttpDispatchEnvelope` on the worker port; reconstructs a `RequestContext` (capability proxies already wired by `api-worker-host`); invokes the route handler; serialises the return value as an `HttpResponseEnvelope`; supports streaming via `Transferable` `ReadableStream`.
|
||
- `./envelope-schemas` — Zod schemas for `HttpDispatchEnvelope` and `HttpResponseEnvelope`. Validated on both sides.
|
||
- `./streaming-marshal` — request-body streams AND response-body streams via `Transferable`; back-pressure propagated.
|
||
- `./header-allowlist` — only headers in the bridge's allowlist (or matching the configurable per-route allowlist) cross the boundary; the rest are dropped on the main side so worker code cannot read `Authorization` / `Cookie` etc.
|
||
- `./error-codes` — `BRIDGE_*` codes (`BRIDGE_ENVELOPE_INVALID`, `BRIDGE_HANDLER_THROW`, `BRIDGE_HEADER_NOT_ALLOWED`, `BRIDGE_RESPONSE_NOT_SERIALISABLE`, `BRIDGE_REQUEST_BODY_STREAM_CLOSED`).
|
||
|
||
## Out of scope
|
||
|
||
- **Marshalling the raw `Request`** — explicitly forbidden by §6. The bridge marshals only the validated parts.
|
||
- **Routing itself** — `api-router` / `api-router-hono` own URL matching. This package picks up after step 2.
|
||
- **Capability proxies** — `api-db-worker-proxy` etc. live in their own packages. This package only marshals the request envelope.
|
||
- **Auth / RBAC** — those run in the main process before the bridge. The bridge merely receives the resolved `principal` and `tenantId`.
|
||
|
||
## Source mapping
|
||
|
||
Genesis-original. Design doc sections: §6 (per-request data flow, especially the "isolated / sandboxed" paragraph), §5.2a.
|
||
|
||
| Requirement | Design doc section |
|
||
|---|---|
|
||
| Raw `Request` NEVER marshalled — only validated body/params/query | §6 |
|
||
| Steps 1–7 + 9–11 always main-process | §6 |
|
||
| Streams transferred via `Transferable` | §6, §5.2a |
|
||
| Auth / RBAC / tenant resolution never run inside worker | §6 |
|
||
|
||
## Verification approach
|
||
|
||
- `src/main-side/main-side.test.ts` — given a fake `RouteMatch` + validated body, envelope is sent across paired `MessageChannel`; response rehydrates.
|
||
- `src/worker-side/worker-side.test.ts` — given an envelope, handler is invoked with reconstructed `ctx.request`; response envelope serialises.
|
||
- `src/envelope-schemas/envelope-schemas.test.ts` — Zod schemas reject malformed envelopes; valid envelopes round-trip.
|
||
- `src/streaming-marshal/streaming-marshal.test.ts` — large request body stream + large response body stream both transfer; back-pressure ack works.
|
||
- `src/header-allowlist/header-allowlist.spec-conformance.test.ts` — `Authorization`, `Cookie`, `Set-Cookie` MUST NOT cross by default; configurable allowlist works.
|
||
- `tests/no-raw-request.test.ts` — pin the invariant: the wire envelope never contains a `Request` object reference; the raw header bag is filtered.
|
||
|
||
## Migration impact
|
||
|
||
None — additive only. `api-host` will wire this in Stage 4 once `api-router-hono` lands.
|