7.1 KiB
Capability Spec Delta: api-secrets (api-secrets)
Package: @suaveplan/api-secrets
Status: SHIPPED 2026-05-25
Every requirement carries a
**Verified by:**line.
ADDED Requirements
Requirement: SecretsLike re-export from api-types
The package SHALL re-export the canonical SecretsLike interface from @suaveplan/api-types/secrets. The canonical shape exposes get(name): Promise<string | undefined>, require(name): Promise<string> (throws SECRETS.SECRET_NOT_FOUND on miss), and list(): readonly string[] (enumerates declared names).
Scenario: get returns the value when present
- WHEN a stub source seeded with
{ A: "alpha" }callsget("A") - THEN the promise resolves to
"alpha"
Scenario: get returns undefined when absent
- WHEN the same source calls
get("MISSING") - THEN the promise resolves to
undefined
Scenario: require throws SECRET_NOT_FOUND on miss
- WHEN the source calls
require("MISSING") - THEN a
SecretsErroris thrown carryingcode === "SECRETS.SECRET_NOT_FOUND"
Verified by: src/secrets-like/secrets-like.test.ts::"SecretsLike contract — stub implementation > get returns the value when present" AND src/secrets-like/secrets-like.test.ts::"SecretsLike contract — stub implementation > get returns undefined when absent" AND src/secrets-like/secrets-like.test.ts::"SecretsLike contract — stub implementation > require throws SECRET_NOT_FOUND when absent"
Requirement: import z from "zod" everywhere
Every Zod import MUST use the default form.
Scenario: no named import of z
- WHEN the test greps
import { z } from "zod"insrc/** - THEN zero matches
Verified by: tests/dep-graph.test.ts::"zod-import-form > every src file uses default-import z"
Requirement: SecretDescriptor schema
The package SHALL export secretDescriptorSchema describing { name: string, required: boolean, description?: string, format?: "string" | "base64" | "json" }. Unknown keys MUST be rejected. The list variant secretDescriptorListSchema MUST accept the empty array.
Scenario: minimal descriptor parses
- WHEN
{ name: "STRIPE_KEY", required: true }is parsed - THEN it succeeds AND
formatis undefined
Scenario: unknown format rejected
- WHEN
{ name: "X", required: true, format: "weird" }is parsed - THEN Zod reports an issue at
format
Scenario: unknown keys rejected
- WHEN
{ name: "X", required: true, requried: true }(typo) is parsed - THEN the parse fails
Verified by: src/descriptor/descriptor.test.ts::"secretDescriptorSchema > minimal descriptor parses" AND src/descriptor/descriptor.test.ts::"secretDescriptorSchema > unknown format rejected" AND src/descriptor/descriptor.test.ts::"secretDescriptorSchema > unknown keys rejected"
Requirement: redactSecret returns the canonical marker
The package SHALL export redactSecret(value: string): "<redacted>" returning the literal string "<redacted>" for any input — including the empty string — and REDACTED_MARKER exposing the same literal.
Scenario: any input maps to canonical marker
- WHEN any string is passed to
redactSecret - THEN the result equals
"<redacted>"
Verified by: src/redaction/redaction.test.ts::"redactSecret > any input maps to canonical marker"
Requirement: CapabilityId — secrets and secrets.bun
The package SHALL export SECRETS_CAPABILITY_ID (literal "secrets") and SECRETS_BUN_CAPABILITY_ID (literal "secrets.bun"), both typed as CapabilityId via as const satisfies CapabilityId. If api-types ever drops either id, this package's typecheck MUST fail.
Scenario: both ids assignable to CapabilityId
- WHEN the test assigns each constant to a
CapabilityId-typed variable - THEN the assignment compiles
Verified by: src/capability-id/capability-id.test.ts::"capability-id constants > both ids are valid CapabilityId values"
Requirement: Required-secret enforcement at start
The package SHALL export checkRequiredSecrets (returns a discriminated { ok } result) and enforceRequiredSecrets (throws SecretsError(SECRETS.SECRET_REQUIRED_MISSING_AT_START) listing missing names on context.missing). Hosts use these to gate the installing → installed transition.
Scenario: missing required secret throws SECRET_REQUIRED_MISSING_AT_START
- WHEN a stub source has no entry for
"X"AND descriptors include{ name: "X", required: true } - THEN
enforceRequiredSecretsthrowsSecretsErrorwithcode === "SECRETS.SECRET_REQUIRED_MISSING_AT_START"ANDcontext.missinglists["X"]
Scenario: optional descriptors ignored
- WHEN descriptors include
{ name: "OPT", required: false }AND the source has no entry for"OPT" - THEN
checkRequiredSecretsreturns{ ok: true }
Verified by: src/secrets-like/secrets-like.test.ts::"enforceRequiredSecrets > throws SECRET_REQUIRED_MISSING_AT_START with missing list" AND src/secrets-like/secrets-like.test.ts::"checkRequiredSecrets > ignores optional descriptors"
Requirement: Error codes registry frozen
The package SHALL export SECRETS_CODES containing exactly: SECRET_NOT_FOUND, SECRET_REQUIRED_MISSING_AT_START, SECRET_INVALID_FORMAT, SOURCE_UNAVAILABLE. The registry MUST be Object.freezed. The package SHALL also export SecretsError — a concrete SuaveplanError<SecretsCode> subclass with a public constructor.
Scenario: every documented code present
- WHEN the test enumerates
Object.keys(SECRETS_CODES) - THEN the sorted keys equal
["SECRET_INVALID_FORMAT", "SECRET_NOT_FOUND", "SECRET_REQUIRED_MISSING_AT_START", "SOURCE_UNAVAILABLE"]
Scenario: registry is frozen
- WHEN
Object.isFrozen(SECRETS_CODES)is evaluated - THEN it equals
true
Verified by: src/error-codes/error-codes.test.ts::"SECRETS_CODES > contains exactly four keys" AND src/error-codes/error-codes.test.ts::"SECRETS_CODES > is frozen"
Requirement: Conformance harness for sources
The package SHALL export runSecretsConformance({ label, factory, isNotFound? }). The harness MUST register five scenarios per call: get resolves seeded value, get resolves undefined for missing, require resolves seeded value, require throws (per isNotFound) on miss, and list() enumerates seeded names. Every concrete source in Stage 4/8 MUST pass it.
Scenario: stub source passes default-predicate suite
- WHEN a stub
SecretsLikeis run throughrunSecretsConformancewith the default predicate - THEN all five scenarios pass
Scenario: custom isNotFound predicate honoured
- WHEN a source represents missing names as
throw new Error("CUSTOM_MISSING")ANDisNotFoundchecks for that message - THEN the harness still passes
Verified by: src/testing/conformance.test.ts::"stub source — default predicate — SecretsLike conformance > require throws SECRET_NOT_FOUND for missing names" AND src/testing/conformance.test.ts::"stub source — custom predicate — SecretsLike conformance > require throws SECRET_NOT_FOUND for missing names"