# `@suaveplan/api-host/empty-secrets` _(no submodules)_ ## Purpose `EmptySecrets` is the no-op default `SecretsLike` implementation the host injects into every `PluginContext` / `RequestContext` when an operator constructs a `HostAdapters` bag without a `secrets` slot. It preserves the foundation-fix-gap-3 design intent that plugin authors can always destructure `ctx.secrets` without a defensive null guard — the failure mode is an explicit, debuggable `SECRETS.SECRET_NOT_FOUND` throw rather than a `TypeError` from a missing field. In production a real adapter (e.g. `createEnvSecrets` from `@suaveplan/api-secrets-env`, a Vault-backed adapter, or an HSM bridge) replaces this default. `EmptySecrets` is exclusively the "nothing configured" fall-through. ## Features - `get(name)` resolves to `undefined` for every key. - `require(name)` throws `SECRETS.SECRET_NOT_FOUND` carrying the requested key on `context.name` and a sentinel `context.source = "host.default.empty-secrets"` for correlation. - `list()` returns the frozen empty `readonly string[]`. - Zero state — `EmptySecrets` instances are interchangeable; the host constructs one per `createHost` call so OTel attribute attachment remains traceable per host instance. - Stable error identity — every throw is a `SecretsError` so callers can `instanceof`-narrow without importing the abstract base. ## Usage: Basic ```ts import { EmptySecrets } from "@suaveplan/api-host/empty-secrets"; const secrets = new EmptySecrets(); const maybe = await secrets.get("STRIPE_SECRET_KEY"); // maybe === undefined — `EmptySecrets` never resolves a value. ``` ## Usage: Advanced A custom host that injects `EmptySecrets` only in `development` mode so production deploys fail fast when the real adapter wiring is missing: ```ts import { createHost } from "@suaveplan/api-host/host"; import type { HostAdapters } from "@suaveplan/api-host/host"; import { EmptySecrets } from "@suaveplan/api-host/empty-secrets"; import { createEnvSecrets } from "@suaveplan/api-secrets-env"; function buildSecrets(mode: "production" | "development") { if (mode === "production") { return createEnvSecrets({ allowedKeys: ["STRIPE_SECRET_KEY"] }); } return new EmptySecrets(); } const adapters: HostAdapters = { /* ...other slots... */ secrets: buildSecrets(process.env["NODE_ENV"] as never), } as never; const host = await createHost({ adapters, plugins: [], mode: "development" }); ``` ## API Reference `class EmptySecrets implements SecretsLike` - `get(name: string): Promise` — always `undefined`. - `require(name: string): Promise` — always throws `SecretsError` with code `SECRETS.SECRET_NOT_FOUND` and `context: { name, source: "host.default.empty-secrets" }`. - `list(): readonly string[]` — frozen empty array, stable across calls. ## Implementation Notes `EmptySecrets` is deliberately the simplest possible `SecretsLike` implementation — every method is constant-time and allocation-free (the frozen empty array is module-scoped and shared). The class lives inside `@suaveplan/api-host` rather than `@suaveplan/api-secrets` because the policy decision "what does the host inject when no adapter is provided" is a host-orchestration concern, not a secrets-domain concern. Sibling adapters (`api-secrets-env`, hypothetical `api-secrets-vault`) stay focused on concrete sources. Error codes are sourced from `@suaveplan/api-secrets`'s `SECRETS_CODES` registry so the throw uses the canonical namespace string (`SECRETS.SECRET_NOT_FOUND`) every other secrets source uses. This keeps audit / OTel log dashboards consistent regardless of which source surfaced the failure.