8.1 KiB
@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'srequiredCapabilities; returns aPluginModule<Caps>discriminated byisolationtier../capability-registry—CapabilityRegistry: register, lookup, version-compat check againstapiHost+requiredCapabilities; emitsCAPABILITY_VERSION_MISMATCH/CAPABILITY_NOT_REGISTERED/CAPABILITY_DUPLICATE_REGISTRATION../lifecycle—LifecycleDriver: per-plugin FSM mutex; transitions named in §4 ledger;start_failedauto-rollback hand-off toRollbackJournal;crashed/auto-restart for worker-tier plugins; emitsplugin.lifecycle_transitions_total../hot-reload—ReloadDriver: blue-green primitive (§8.1),RollbackJournalimpl (§8.1a), drain timer, force-cancel, file-watch + admin-API triggers, native-binding guard../plugin-ledger— host-owned metadata DB. Migrations create tablessuaveplan_plugin_ledger,suaveplan_rollback_journal,suaveplan_saga_instances,suaveplan_saga_steps,suaveplan_2pc_transactions. (Saga/2PC tables are reserved — populated byapi-saga/api-2pcin 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 underadminPrefix(default/admin,nullto disable). Endpoints from §8.4. RBAC scopehost: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); mapsSuaveplanError → manifest statusMap,AbortError → 499, anything else → 500 + cause-chain log; serializes theErrorEnvelopefromapi-types/errors../request-pipeline— orders steps 1–11 from §6 exactly; cookie-auth → CSRF; streaming-route bypass; response-validation strictnessenforce(prod) /warn(dev); emits the OTel span tree from §12.3../otel—HostInstrumentsregistry: every counter/histogram in §12.3; OTel resource builder pre-tagged withhost.version/host.id; per-plugin meter created at install, disposed at uninstall../host— top-levelcreateHost(config)entry point composing every internal driver;host.start()/host.stop()/host.fetch(...)(test handle)../error-codes— everyHOST_*/PLUGIN_*/RELOAD_*/CAPABILITY_*/ADMIN_*code defined via@suaveplan/error'sdefineCodes. Status mapping per code../testing— reserved subpath. Populated in Stage 5 withcreateTestHost+ 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 viaCapabilityRegistry.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 theisolated/sandboxedtiers (it computes the right capability surface per plugin) but the proxy plumbing is a separate package. - Saga / 2PC orchestrators —
api-sagaandapi-2pcare Stage 6. Their ledger tables ship here only because the host owns the metadata schema. - In-memory test adapters — every
*-in-memorypackage lives in Stage 5. The./testingsubpath 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;crashedreachable only from worker tiers;rolled_backdistinct 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-memoryWeakRef+ 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: nulldisables HTTP entirely.src/error-boundary/*.test.ts—SuaveplanError → statusMap,AbortError → 499, unknown → 500; envelope shape matchesapi-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'sInMemoryMetricExporter.src/define-plugin/define-plugin.spec-conformance.test.ts—expectTypeOfprovesctx.dbavailable iffdbdeclared; 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.