# Capability Spec Delta: api (extend-api-foundation-host-services) **Package**: `@suaveplan/api` **Status**: PROPOSED ## ADDED Requirements ### Requirement: SuaveApp seeds canonical host services before plugin init `SuaveApp.initialize()` SHALL call `this._registry.seedServices(...)` immediately before `this._registry.initialize()`. The seeded map SHALL include `app` (the OpenAPIHono instance), `openAPIRegistry`, `logger`, `eventBus`, `clock` always; AND optionally `migrationRunner` (when supplied via `SuaveAppOptions.migrationRunner`) AND `db` (when supplied via the new `SuaveAppOptions.db`). After init, every plugin's `ctx.getService("app")` / `ctx.getService("openAPIRegistry")` / etc. SHALL resolve to the corresponding instance with typed inference via the ServiceRegistry module augmentation. #### Scenario: plugin resolves seeded app via ctx.getService - **WHEN** a SuaveApp is built with a plugin whose `onInit(ctx) { recordApp(ctx.getService("app")) }` runs, and `app.initialize()` is awaited - **THEN** `recordApp` was called with the OpenAPIHono instance the app holds at `this._hono` (referential equality, not a wrapper) #### Scenario: missing migrationRunner option leaves the slot undefined - **WHEN** `new SuaveApp({ ...options /* no migrationRunner */ })` is built and a plugin asks `ctx.getService("migrationRunner")` - **THEN** the result is `undefined` **Verified by:** `src/app/seed-services.test.ts::"plugin resolves seeded app via ctx.getService"` AND `src/app/seed-services.test.ts::"openAPIRegistry / logger / eventBus / clock are seeded"` AND `src/app/seed-services.test.ts::"missing migrationRunner leaves slot undefined"` --- ### Requirement: ServiceRegistry module augmentation declares canonical host slots The package SHALL ship a side-effect-importable module at `./services/registry-augmentation` that declares: ```ts declare module "@suaveplan/api-plugin/service-registry" { interface ServiceRegistry { app: OpenAPIHono; openAPIRegistry: OpenAPIRegistry; migrationRunner?: MigrationRunner; db?: SurrealAdapter; logger: LoggerLike; eventBus: EventBusLike; clock: Clock; } } ``` Consumers SHALL get full type inference on `ctx.getService("app")` (returns `OpenAPIHono | undefined`), etc. The augmentation SHALL be auto-loaded by the package root barrel (`src/index.ts` side-effect imports it) so any consumer of `@suaveplan/api` picks it up without an explicit import. Subpath consumers of `@suaveplan/api/services/registry-augmentation` get it directly. #### Scenario: ctx.getService("app") type-narrows to OpenAPIHono | undefined - **WHEN** `expectTypeOf(ctx.getService("app")).toEqualTypeOf()` is asserted in the conformance test - **THEN** the type check passes **Verified by:** `src/services/registry-augmentation.spec-conformance.test.ts::"every host slot type-narrows correctly via expectTypeOf"` AND `src/services/registry-augmentation.test.ts::"side-effect import auto-loads via root barrel"` --- ### Requirement: SuaveAppOptions.db is optional `SuaveAppOptions` SHALL accept an optional `db?: SurrealAdapter` field. When supplied, it's seeded into `ServiceRegistry["db"]`; when omitted, the slot stays `undefined`. The constructor SHALL store it on `this._db` for later seeding. No change to existing options. #### Scenario: db option seeded when provided - **WHEN** `new SuaveApp({ ...config, db: surrealAdapter })` is built and a plugin queries `ctx.getService("db")` - **THEN** the result is `surrealAdapter` (referential) **Verified by:** `src/app/options.test.ts::"db option seeded when provided"` AND `src/app/options.test.ts::"omitting db leaves the slot undefined"`