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,82 @@
# `@suaveplan/api-host` — host orchestration core for the API plugin foundation
**Package**: `@suaveplan/api-host`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 2 (Host orchestration core) — see umbrella `openspec/changes/api-plugin-foundation/`
## Why
Stage 1 ships the contracts (`api-types`, `api-manifest`, abstract router/auth/db/etc.). Nothing in the foundation is exercised until a host process actually loads a plugin, drives it through `install → start → stop`, injects the declared capabilities into its `PluginContext`, mounts its routes under the configured router adapter, runs its requests through the per-request pipeline (§6), and emits the OTel surface (§12). `api-host` is the package that does all of that.
This is the keystone of the foundation. Every Stage 3+ change either consumes `api-host` directly (worker proxies talk to it) or extends it (adapters register with its `CapabilityRegistry`). Specs in this change set bind the contract that every later stage rests on — getting the surface right here is what lets us add isolation tiers, conformance suites, sagas, and engines without rewriting the core.
Per design doc §4, §5, §6, §8, §12: lifecycle FSM, capability injection, per-request pipeline, hot reload, and observability all live in this package.
## What changes
Net-new package. Public surface is large but cleanly partitioned. Subpath exports per concern so consumers tree-shake to what they need; `./testing` subpath is **reserved** here and populated in Stage 5.
**ADDED exports:**
- `./define-plugin` — `definePlugin<Caps>(spec)` author-facing API; generic over manifest's `requiredCapabilities`; returns a `PluginModule<Caps>` discriminated by `isolation` tier.
- `./capability-registry` — `CapabilityRegistry`: register, lookup, version-compat check against `apiHost` + `requiredCapabilities`; emits `CAPABILITY_VERSION_MISMATCH` / `CAPABILITY_NOT_REGISTERED` / `CAPABILITY_DUPLICATE_REGISTRATION`.
- `./lifecycle` — `LifecycleDriver`: per-plugin FSM mutex; transitions named in §4 ledger; `start_failed` auto-rollback hand-off to `RollbackJournal`; `crashed`/auto-restart for worker-tier plugins; emits `plugin.lifecycle_transitions_total`.
- `./hot-reload` — `ReloadDriver`: blue-green primitive (§8.1), `RollbackJournal` impl (§8.1a), drain timer, force-cancel, file-watch + admin-API triggers, native-binding guard.
- `./plugin-ledger` — host-owned metadata DB. Migrations create tables `suaveplan_plugin_ledger`, `suaveplan_rollback_journal`, `suaveplan_saga_instances`, `suaveplan_saga_steps`, `suaveplan_2pc_transactions`. (Saga/2PC tables are reserved — populated by `api-saga`/`api-2pc` in Stage 6; their schema ships here because the host owns its metadata schema lifecycle.) Migration runner is forward-only and idempotent.
- `./admin-api` — Hono sub-router mounted under `adminPrefix` (default `/admin`, `null` to disable). Endpoints from §8.4. RBAC scope `host:admin`; per-principal rate-limit (60/min, 1/min for destructive); audit envelope per call.
- `./error-boundary` — wraps every pipeline step (§6 steps 1–11); maps `SuaveplanError → manifest statusMap`, `AbortError → 499`, anything else → 500 + cause-chain log; serializes the `ErrorEnvelope` from `api-types/errors`.
- `./request-pipeline` — orders steps 1–11 from §6 exactly; cookie-auth → CSRF; streaming-route bypass; response-validation strictness `enforce` (prod) / `warn` (dev); emits the OTel span tree from §12.3.
- `./otel` — `HostInstruments` registry: every counter/histogram in §12.3; OTel resource builder pre-tagged with `host.version`/`host.id`; per-plugin meter created at install, disposed at uninstall.
- `./host` — top-level `createHost(config)` entry point composing every internal driver; `host.start()` / `host.stop()` / `host.fetch(...)` (test handle).
- `./error-codes` — every `HOST_*` / `PLUGIN_*` / `RELOAD_*` / `CAPABILITY_*` / `ADMIN_*` code defined via `@suaveplan/error`'s `defineCodes`. Status mapping per code.
- `./testing` — **reserved subpath**. Populated in Stage 5 with `createTestHost` + in-memory adapter wiring. Stage 2 ships an empty placeholder typed export so consumers can declare a peer-dep on the path.
- `.` (root barrel) — namespace re-exports per CLAUDE.md rule 10.
## Out of scope
- **Concrete capability adapters** — `api-router-hono`, `api-auth-jwt`, `api-db-adapter`, etc. all live in Stage 4. The host accepts adapter instances via `CapabilityRegistry.register(...)` — it does not bundle any.
- **Worker isolation runtime** — `api-worker-host`, `api-worker-rpc`, and the worker-proxy packages all live in Stage 3. The host's lifecycle driver KNOWS about the `isolated`/`sandboxed` tiers (it computes the right capability surface per plugin) but the proxy plumbing is a separate package.
- **Saga / 2PC orchestrators** — `api-saga` and `api-2pc` are Stage 6. Their ledger tables ship here only because the host owns the metadata schema.
- **In-memory test adapters** — every `*-in-memory` package lives in Stage 5. The `./testing` subpath is reserved but empty in Stage 2.
- **OTel SDK initialisation** — the host emits via `@suaveplan/opentelemetry`'s contract types; SDK wiring (exporter URL, sampler) is operator config, not this package's responsibility.
## Source mapping
Genesis-original. Single source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
| Requirement | Design doc section |
|---|---|
| Lifecycle FSM states + transitions + dedup mutex | §4 |
| Capability registry + version-compat check | §5.2 |
| Two-context separation (`PluginContext` admin DB vs `RequestContext` tenant DB) | §5.1 |
| Per-request pipeline ordering (steps 1–11) | §6 |
| Error envelope + boundary placement | §6 end, §12.1, §12.7 |
| Hot-reload blue-green primitive | §8.1 |
| `RollbackJournal` + auto-rollback to prior known-good | §8.1a, §8.7 |
| Reinstall criteria (schemaVersion, scopes, secrets, dependsOn) | §8.6 |
| Admin API surface + RBAC + rate-limit + audit | §8.4 |
| Plugin ledger host-owned metadata DB | §4 (closing paragraph), §8.1a |
| OTel span tree + metric surface | §12.3 |
| Audit envelope | §12.5 |
| Health/readiness endpoints | §12.6 |
| `definePlugin` capability-map typing (`as const` literal arrays) | §5.3 |
## Verification approach
Per requirement cluster:
- `src/lifecycle/lifecycle.test.ts` + `*.spec-conformance.test.ts` — every transition exercised; FSM mutex dedup tested; `crashed` reachable only from worker tiers; `rolled_back` distinct admin display.
- `src/hot-reload/blue-green.test.ts` + `rollback-journal.test.ts` — shadow slot lifecycle, version pointer atomic flip, drain timer, force-cancel, native-binding refusal, ring-buffer depth 3, in-memory `WeakRef` + persisted-cursor recovery after restart.
- `src/capability-registry/capability-registry.test.ts` — register / lookup / version compat / duplicate / missing-required.
- `src/plugin-ledger/migrations.test.ts` — forward-only migrations run idempotently against testcontainer SQLite + Postgres + (optional) SurrealDB; table schemas match §8.1a / §9.
- `src/admin-api/*.test.ts` — every endpoint from §8.4; RBAC scope enforcement; per-principal rate-limit; audit emission; `adminPrefix: null` disables HTTP entirely.
- `src/error-boundary/*.test.ts` — `SuaveplanError → statusMap`, `AbortError → 499`, unknown → 500; envelope shape matches `api-types/errors`.
- `src/request-pipeline/*.test.ts` — steps 1–11 ordered, CSRF gating on cookie auth, streaming bypass, response-validation strictness.
- `src/otel/*.test.ts` — every metric in §12.3 emitted from at least one production path, asserted with `@opentelemetry/sdk-metrics`'s `InMemoryMetricExporter`.
- `src/define-plugin/define-plugin.spec-conformance.test.ts` — `expectTypeOf` proves `ctx.db` available iff `db` declared; undeclared capability is a type error.
- `tests/integration/host-boot.test.ts` — `bun examples/api-host-empty.ts`-style boot: empty host returns 200 on `/internal/livez`; this is the umbrella's Stage-2-complete gate.
## Migration impact
None — additive only. Nothing in the monorepo depends on `@suaveplan/api-host` yet. Per umbrella §Stage-2, this package is the keystone; every later stage depends on it.