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,59 @@
# `@suaveplan/api-worker-host` — worker-side runtime hosting one plugin
**Package**: `@suaveplan/api-worker-host`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 3 (Worker isolation infrastructure)
## Why
`isolated` and `sandboxed` plugins run in their own `Worker` thread (design doc §2.2). Something inside that worker has to: load the plugin module, build a `PluginContext` whose capability slots are RPC proxies back to the main host, drive the plugin's own lifecycle hooks, and surface crashes / OOM / wall-clock-budget exhaustion back to the main host's `LifecycleDriver`. `api-worker-host` is that something. It's the worker-side counterpart to `api-host`: where `api-host` runs in the main process and orchestrates plugins, `api-worker-host` runs inside each worker and hosts exactly one plugin.
Splitting this out from `api-host` keeps the main-process bundle free of worker-only code and lets us evolve worker-side concerns (heap limits, AbortController plumbing, crash telemetry) independently.
## What changes
Net-new package. Server-tier (`worker_threads` API). Single binary entry-point: a Worker boot script that the main host's loader spawns with `new Worker(...)`.
**ADDED exports:**
- `./worker-boot` — script entry; reads `workerData` (manifest, plugin module specifier, capability port table); imports the plugin module via `await import(specifier)`; wires every capability slot from its `MessagePort` via `api-worker-rpc`'s `createRpcCaller`; constructs `PluginContext` + `RequestContext`; signals readiness back to main.
- `./lifecycle-receiver` — receives lifecycle commands (`install`, `start`, `stop`, `uninstall`, `reload`) over a dedicated control port; dispatches to the plugin's hooks; propagates outcomes (success / `SuaveplanError` / unhandled throw) back to main.
- `./crash-detector` — `process.on("uncaughtException")` / `process.on("unhandledRejection")` handlers; converts to a final `WORKER_CRASHED` envelope sent over the control port; calls `process.exit(1)` so the main host sees the worker die.
- `./budget-guard` — periodic check of `process.memoryUsage().heapUsed` against `host.config.worker.heapLimitBytes`; CPU sampling via `performance.now()` deltas; wall-clock-budget enforcement on `start`/`stop`/`install`/`uninstall` hooks. Exceeding any budget triggers a controlled `WORKER_BUDGET_EXCEEDED` exit.
- `./request-dispatcher` — receives marshalled requests from `api-router-worker-bridge`; constructs a per-request `RequestContext`; runs the handler under the plugin's `AbortSignal`; serialises the response back.
- `./error-codes` — `WORKER_*` codes (`WORKER_CRASHED`, `WORKER_BUDGET_EXCEEDED`, `WORKER_HEAP_LIMIT`, `WORKER_CPU_BUDGET`, `WORKER_WALL_CLOCK_BUDGET`, `WORKER_BOOT_FAILED`, `WORKER_MODULE_IMPORT_FAILED`).
- `./testing` — in-process worker simulator (uses `MessageChannel` instead of a real `Worker`) so conformance suites run without spawning real OS threads.
## Out of scope
- **Sandbox isolation primitives** — `vm.Context` guards and WASI permission gates live in `api-sandbox-vm` / `api-sandbox-wasm` (Stage 8). This package hosts the plugin module; the sandbox wraps the host.
- **Capability marshalling protocol** — that's `api-worker-rpc`. This package CONSUMES it.
- **Main-host spawning** — `new Worker(...)` and worker pool management live in `api-host`'s `LifecycleDriver`. This package only provides the script that runs INSIDE the worker.
- **HTTP request marshalling** — `api-router-worker-bridge` owns the request/response boundary; this package only receives the already-marshalled handler invocation.
## Source mapping
Genesis-original. Design doc sections: §2.2 (isolation tiers), §5.2a (worker-RPC variants), §4 (`crashed` state propagation), §8.5 (`forceTimeoutMs` on stragglers).
| Requirement | Design doc section |
|---|---|
| Worker hosts exactly one plugin | §2.2 |
| Plugin author writes the same `definePlugin` regardless of tier | §2.2 |
| Crash detection propagates to main → `crashed` state | §2.2, §4 |
| Auto-restart `crashRestartMax` × triggered by main | §2.2 |
| Wall-clock / heap / CPU budgets | §2.2 ("CPU/mem budgets") |
| Capability slots are RPC proxies via `MessagePort` | §5.2a |
## Verification approach
- `src/worker-boot/worker-boot.test.ts` — boots the worker simulator with a trivial plugin module; readiness signal observed; capability slots present.
- `src/lifecycle-receiver/lifecycle-receiver.test.ts` — every command dispatches to the right hook; throws propagate as `SuaveplanError`.
- `src/crash-detector/crash-detector.test.ts` — `uncaughtException` becomes `WORKER_CRASHED` envelope; non-zero exit.
- `src/budget-guard/budget-guard.test.ts` — heap-limit triggers `WORKER_HEAP_LIMIT`; wall-clock exceeded on `start` triggers `WORKER_WALL_CLOCK_BUDGET`.
- `src/request-dispatcher/request-dispatcher.test.ts` — request envelope decoded → handler runs → response encoded; abort signal fires on main-side cancel.
- `tests/integration/real-worker.test.ts` — spawns a real `Worker` (gated `INTEGRATION=1`); end-to-end boot + lifecycle + crash propagation (timeout 30000).
## Migration impact
None — additive only. `api-host` will depend on this for spawning workers in Stage 4 wiring.