Files
2026-05-27 23:29:53 +01:00

4.2 KiB

@suaveplan/api-secrets — abstract Secrets contract

Package: @suaveplan/api-secrets Status: PROPOSED Type: new-package Stage: 1 (Contracts) — see umbrella openspec/changes/api-plugin-foundation/

Why

Every plugin needs to read secrets — Stripe keys, JWT signing keys, OAuth client secrets, database passwords. Different deployments want different sources: process.env / Bun.env (dev + simple prod), Bun.secrets (OS keystore), @suaveplan/vault (the genesis vault), HashiCorp Vault, AWS Secrets Manager. This package owns the abstract contract: SecretsLike (read-only, typed against the manifest's secrets[] declarations) plus SecretDescriptor (the manifest-side declaration). Concrete sources plug behind it (api-secrets-env, api-secrets-bun, api-secrets-vault).

Per design §5.2: "every adapter exports defineCapability({ id, version, scope, provider, disposer })." Per §5.1: ctx.secrets: SecretsLike; // typed against manifest.secrets[].

What changes

Net-new package. Contract + Zod schema for SecretDescriptor + conformance harness.

  • ADDED: ./secrets-like — abstract SecretsLike: get(name: string): Promise<string> (returns the secret value); has(name: string): Promise<boolean>; dispose(): Promise<void>. The type MUST be generic over a Names extends string so ctx.secrets.get(name) autocompletes against the manifest's declared secret names.
  • ADDED: ./descriptor — SecretDescriptor shape: { name: string, required: boolean, description?: string, format?: "string" | "base64" | "json" }. Zod schema for parsing manifest entries.
  • ADDED: ./redaction — redactSecret(value: string): string returning "<redacted>" for log/audit use. Helper, not enforced — adapters / loggers may use it.
  • ADDED: ./capability-id — adds "secrets" (and ensures "secrets.bun" is reserved for the Stage-8 adapter) to CapabilityId.
  • ADDED: ./error-codes — frozen SECRETS_CODES: SECRET_NOT_FOUND, SECRET_REQUIRED_MISSING_AT_START, SECRET_INVALID_FORMAT, SOURCE_UNAVAILABLE.
  • ADDED: ./testing — runSecretsConformance(factory) reusable harness.

CapabilityId additions

This package contributes "secrets" to the CapabilityId union. The Bun-specific "secrets.bun" capability id is reserved here (declared in the union) for the Stage-8 api-secrets-bun adapter, so plugins can declare requiredCapabilities: ["secrets.bun"] at type-check time before the adapter ships.

Out of scope

  • Concrete sources (api-secrets-env, api-secrets-bun, api-secrets-vault) — Stages 4 and 8.
  • Secret rotation — out of scope for v1 (separate change).
  • Encryption-at-rest — source-specific.

Source mapping

Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.

Requirement Design doc section
ctx.secrets: SecretsLike typed against manifest §5.1, §5.2
Required-secret enforcement at start §3 manifest example ("required": true) + §4 install hook order
Per-secret format declaration §3 manifest example
"secrets.bun" capability §3 manifest example ("optionalCapabilities": ["events.bounded-queue", "secrets.bun"])

Requirements with no source line

Requirement Justification
redactSecret helper Logging plugins / OTel exporters need a canonical redaction marker; pinning the string "<redacted>" here means every downstream tool agrees.
runSecretsConformance harness Stage-5 deliverable (§13.4).

Verification approach

  • src/secrets-like/secrets-like.spec-conformance.test.ts — get/has/dispose shape; Names extends string generic narrows return type.
  • src/descriptor/descriptor.test.ts — Zod accept/reject suite over SecretDescriptor.
  • src/redaction/redaction.test.ts — input → "<redacted>"; empty string handled.
  • src/capability-id/capability-id.spec-conformance.test.ts — "secrets" AND "secrets.bun" in CapabilityId.
  • tests/dep-graph.test.ts — deps: @suaveplan/api-types, @suaveplan/error, @suaveplan/types, zod.

Migration impact

None — additive only.

Impact

Affected dependents

  • (none) — [x] no action needed (reason: net-new; concrete sources land Stage 4/8)