Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# @suaveplan/api-secrets-env — SecretsLike adapter backed by process.env / Bun.env with per-plugin allowed-keys enforcement. Reads from an injectable source function so tests never mutate the real environment; an optional prefix scopes lookups for multi-tenant single-host deployments.
|
||||
|
||||
Stage-4 `SecretsLike` adapter backed by `process.env` / `Bun.env` with
|
||||
per-plugin allowed-keys enforcement.
|
||||
|
||||
The simplest secrets source: read from the host's environment. Suitable
|
||||
for local development, single-host deployments, container orchestrators
|
||||
that inject secrets as env vars, and anywhere the operator already has
|
||||
tooling around env-var injection.
|
||||
|
||||
Per-plugin allow-list keeps each plugin scoped to its manifest-declared
|
||||
secrets — a worker or untrusted plugin cannot enumerate or read any env
|
||||
var beyond what its `manifest.secrets[]` declared. Values never appear
|
||||
in logs or errors — only names.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [adapter](./src/adapter/adapter.md) — Stage-4 concrete `SecretsLike` implementation that resolves secret names against the host process's environment variabl…
|
||||
- [adapter](./src/adapter/adapter.md) — Stage-4 concrete `SecretsLike` implementation that resolves secret names against the host process's environment variabl…
|
||||
- [descriptor-builder](./src/descriptor-builder/descriptor-builder.md) — Convenience builder that turns a compact allow-list declaration into a fully-typed `SecretDescriptor[]` suitable for em…
|
||||
- [descriptor-builder](./src/descriptor-builder/descriptor-builder.md) — Convenience builder that turns a compact allow-list declaration into a fully-typed `SecretDescriptor[]` suitable for em…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry and dedicated `SuaveplanError` subclass for the env-backed secrets adapter. Every failure mo…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry and dedicated `SuaveplanError` subclass for the env-backed secrets adapter. Every failure mo…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/api-secrets-env
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { createEnvSecrets } from "@suaveplan/api-secrets-env";
|
||||
|
||||
const secrets = createEnvSecrets({
|
||||
allowedKeys: ["STRIPE_SECRET_KEY", "JWT_SIGNING_KEY"],
|
||||
});
|
||||
|
||||
const key = await secrets.require("STRIPE_SECRET_KEY");
|
||||
```
|
||||
|
||||
### Prefix for multi-tenant single-host deployments
|
||||
|
||||
```ts
|
||||
const secrets = createEnvSecrets({
|
||||
allowedKeys: ["API_KEY", "WEBHOOK_SECRET"],
|
||||
prefix: "BILLING_",
|
||||
// Reads BILLING_API_KEY, BILLING_WEBHOOK_SECRET from env.
|
||||
});
|
||||
```
|
||||
|
||||
### Manifest-driven allow-list
|
||||
|
||||
The companion `buildEnvSecretDescriptors` builder lets a plugin derive
|
||||
its `manifest.secrets[]` and the adapter's `allowedKeys` from a single
|
||||
declaration so they cannot drift:
|
||||
|
||||
```ts
|
||||
import {
|
||||
buildEnvSecretDescriptors,
|
||||
descriptorAllowedKeys,
|
||||
createEnvSecrets,
|
||||
} from "@suaveplan/api-secrets-env";
|
||||
|
||||
const descriptors = buildEnvSecretDescriptors([
|
||||
"STRIPE_SECRET_KEY",
|
||||
{ name: "OPTIONAL_WEBHOOK_KEY", required: false },
|
||||
]);
|
||||
|
||||
const secrets = createEnvSecrets({
|
||||
allowedKeys: descriptorAllowedKeys(descriptors),
|
||||
});
|
||||
|
||||
// `descriptors` goes on the plugin manifest's `secrets[]`.
|
||||
```
|
||||
|
||||
## Fail-fast contract
|
||||
|
||||
The plugin manifest declares each required secret with
|
||||
`{ name, required: true }`. The host's installer reads the manifest's
|
||||
`secrets[]` and, at install or start time, calls
|
||||
`enforceRequiredSecrets` (from `@suaveplan/api-secrets`) against the
|
||||
adapter's `list()`. Any `required: true` secret missing from the host's
|
||||
env triggers `SECRETS.SECRET_REQUIRED_MISSING_AT_START`, and the
|
||||
installer refuses to install / refuses to start.
|
||||
|
||||
This adapter contributes to that contract by:
|
||||
|
||||
1. enumerating the plugin's permitted names via `list()` — the same
|
||||
names the manifest declares;
|
||||
2. returning `undefined` from `get()` for a permitted-but-absent name
|
||||
so `enforceRequiredSecrets` can detect it;
|
||||
3. throwing `SECRETS_ENV.KEY_NOT_FOUND` from `require()` so a plugin
|
||||
that chooses to defer the check past install also fails loudly.
|
||||
|
||||
## Allowed-keys enforcement
|
||||
|
||||
Every `get` / `require` checks the allow-list BEFORE consulting the env
|
||||
source. A non-permitted name throws
|
||||
`SECRETS_ENV.KEY_NOT_PERMITTED` and never reads from the snapshot —
|
||||
the env source can be expensive or have side effects without affecting
|
||||
this ordering. `list()` returns the frozen `allowedKeys` array, so the
|
||||
adapter never surfaces any name outside the plugin's manifest.
|
||||
|
||||
## Secret-value redaction
|
||||
|
||||
Secret VALUES never appear in error messages, log lines, or telemetry
|
||||
emitted by this package — only NAMES. Errors carry the name on
|
||||
`error.context.name` for diagnostic surfaces; the value (when one was
|
||||
resolved) never leaves the call site.
|
||||
|
||||
## API
|
||||
|
||||
| Export | Subpath | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `createEnvSecrets` | `./adapter` | Build a `SecretsLike` over env. |
|
||||
| `CreateEnvSecretsOptions` | `./adapter` | Option-bag type. |
|
||||
| `buildEnvSecretDescriptors` | `./descriptor-builder` | Compact → `SecretDescriptor[]`. |
|
||||
| `descriptorAllowedKeys` | `./descriptor-builder` | Extract names for `allowedKeys`. |
|
||||
| `SECRETS_ENV_CODES` | `./error-codes` | Frozen `SECRETS_ENV.*` code registry. |
|
||||
| `SecretsEnvError` | `./error-codes` | Thrown by every failure mode. |
|
||||
|
||||
## Conformance
|
||||
|
||||
Passes `runSecretsConformance` from `@suaveplan/api-secrets/testing`
|
||||
unchanged — see `src/adapter/adapter.spec-conformance.test.ts`.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
Reference in New Issue
Block a user