Initial Commit
This commit is contained in:
82
openspec/archive/2026-05-25-api-host/proposal.md
Normal file
82
openspec/archive/2026-05-25-api-host/proposal.md
Normal 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.
|
||||
Reference in New Issue
Block a user