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,56 @@
# `@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.