# `secrets-like` — `SecretsLike` re-export and required-secret enforcement _(no submodules)_ ## Purpose This module is the runtime entry point for the abstract Secrets contract. The canonical `SecretsLike` interface — `get(name)`, `require(name)`, `list()` — lives in `@suaveplan/api-types/secrets` so type-only consumers (the manifest validator, the plugin host signature) can depend on it without pulling api-secrets's runtime. We re-export it here so plugin authors importing from `@suaveplan/api-secrets` get one canonical entry. The runtime in this file is the host-side helper pair `checkRequiredSecrets` and `enforceRequiredSecrets` — both walk a manifest's `secrets[]` to assert every `required: true` descriptor resolves before the plugin transitions from `installing → installed`. The helpers are split into a returning form and a throwing form so callers can integrate the check into whatever lifecycle plumbing they prefer — discriminated-result for hosts that aggregate failures, exception for hosts that bail fast. ## Features - Re-export of the canonical `SecretsLike` from `@suaveplan/api-types`. - `SecretsListLike` — minimal structural shape (`list(): readonly string[]`) accepted by the helpers. - `checkRequiredSecrets` — pure, returns `{ ok: true } | { ok: false; missing }`. - `enforceRequiredSecrets` — throws `SecretsError(SECRETS.SECRET_REQUIRED_MISSING_AT_START)` listing every missing name on `context.missing`. ## Usage: Basic ```ts import { enforceRequiredSecrets } from "@suaveplan/api-secrets/secrets-like"; await enforceRequiredSecrets(ctx.secrets, manifest.secrets); // throws SECRETS.SECRET_REQUIRED_MISSING_AT_START if any required name absent ``` ## Usage: Advanced Aggregate missing secrets across multiple plugins during a batch install: ```ts import { checkRequiredSecrets } from "@suaveplan/api-secrets/secrets-like"; interface InstallPlan { plugin: string; missing: readonly string[] } const plans: InstallPlan[] = []; for (const { name, secretsView, descriptors } of installCandidates) { const result = checkRequiredSecrets(secretsView, descriptors); if (!result.ok) plans.push({ plugin: name, missing: result.missing }); } if (plans.length > 0) { // present an operator-facing report rather than failing the first plugin reportToAdminConsole(plans); } ``` ## API Reference - `type SecretsLike` — re-exported from `@suaveplan/api-types/secrets`. - `interface SecretsListLike` — `{ list(): readonly string[] }`. - `function checkRequiredSecrets(secrets, descriptors): RequiredSecretsCheck`. - `function enforceRequiredSecrets(secrets, descriptors): void` — throws on failure. ## Implementation Notes The helpers accept `SecretsListLike` rather than the full `SecretsLike` so tests can pass a `{ list: () => [...] }` literal without implementing every method. Required-secret enforcement deliberately uses `list()` rather than calling `require()` for every entry — `list()` is documented as cheap (often O(1) over an in-memory map), while `require` may hit a network or a secret-manager daemon. The throwing form attaches the full missing-names array to `error.context.missing`, which the `@suaveplan/error` wire-format adapter renders into the JSON envelope so admin UIs can show every missing name in one shot rather than playing whack-a-mole.