# `capability-registry` — runtime registry of host-injected capabilities _(no submodules)_ ## Purpose The `CapabilityRegistry` is the live, single-instance store of every capability adapter wired into a host process. Stage-4 adapter packages (`api-router-hono`, `api-auth-jwt`, `api-db-adapter`, etc.) register against this registry at host boot. The host's lifecycle driver consults it on every plugin install to (a) verify every entry in `manifest.requiredCapabilities` resolves to a registered adapter and (b) verify the adapter's version satisfies the plugin's `apiHost` semver range. The registry is also the disposal coordinator — `stop()` on the host invokes `dispose()` here, and disposers run in **reverse-registration** order so deeper consumers (e.g. `db`) shut down before their dependencies (e.g. `logger`). ## Features - `register(...)` with semver-shape validation (`INVALID_VERSION`), scope-enum validation (`INVALID_SCOPE`), and duplicate detection (`DUPLICATE_REGISTRATION`). - `lookup(id)` / `require(id)` (strict, throws `NOT_REGISTERED`). - `assertSatisfies(ranges)` — per-plugin compat check used by the lifecycle driver; throws `NOT_REGISTERED` or `VERSION_MISMATCH` with the failing `(id, range, actual)` context. - Reverse-registration-order `dispose()` — collects every disposer failure and surfaces them via one aggregated `DISPOSER_FAILED` error so a single bad disposer does not block teardown. - `defineCapability(...)` — author-facing helper (identity at runtime) used by adapter packages. ## Usage: Basic ```ts import { CapabilityRegistry, defineCapability, } from "@suaveplan/api-host/capability-registry"; const registry = new CapabilityRegistry(); registry.register( defineCapability({ id: "logger", version: "1.0.0", scope: "host", provider: logger, }) ); const entry = registry.require("logger"); // throws CAPABILITY.NOT_REGISTERED if missing. ``` ## Usage: Advanced Plug into a manifest's `requiredCapabilities` resolution: ```ts import { CapabilityRegistry } from "@suaveplan/api-host/capability-registry"; const registry = new CapabilityRegistry(); // ... register every adapter ... function installPlugin(manifest: PluginManifest): void { // The manifest pins ranges via `requiredCapabilities` + per-id range table. registry.assertSatisfies({ db: "^1.0.0", "auth.session": "~1.4", }); // ... proceed with lifecycle install ... } // Host teardown — disposers run in REVERSE-registration order. await registry.dispose(); ``` ## API Reference - `class CapabilityRegistry` - `register(reg)`: void - `lookup(id): CapabilityRegistration | undefined` - `require(id): CapabilityRegistration` - `has(id): boolean` - `ids(): readonly CapabilityId[]` - `size(): number` - `assertSatisfies(ranges: Record): void` - `dispose(): Promise` - `defineCapability(reg): CapabilityRegistration` - `CapabilityScope`: `"host" | "plugin" | "request"` - `CapabilityRegistration`: shape consumed by `register`. ## Implementation Notes Version compatibility uses `@suaveplan/api-manifest/dep-graph`'s `satisfies(version, range)` — the same narrow semver dialect the manifest validator accepts (pinned, caret, tilde, major-wildcard, `*`). Unparseable ranges throw at `register` time inside `satisfies`; the registry catches and re-raises as `CAPABILITY.VERSION_MISMATCH` so the lifecycle driver only ever has to match on one code. Disposers are awaited sequentially in reverse-registration order — concurrent dispose would risk teardown races between adapters that share resources (e.g. `events` dispatching to `logger`). Idempotency: a second `dispose()` call is a no-op, so the host's stop path can call it freely even after a partial failure.