first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
# `secrets-like` — `SecretsLike` re-export and required-secret enforcement
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.