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

79 lines
2.9 KiB
Markdown

<!-- markdownlint-disable MD024 -->
# Capability: api-secrets-vault
**Package**: `@suaveplan/api-secrets-vault`
**Status**: ACTIVE
> Every requirement below carries a `**Verified by:**` line.
## Requirements
### Requirement: Vault-backed adapter implements `secrets`
The package SHALL implement the abstract `secrets` capability over a `@suaveplan/vault` instance. The vault is passed in already unlocked; the adapter MUST NOT see the master key.
#### Scenario: get returns stored value
- WHEN a secret is set on the underlying vault AND `get(name)` is called
- THEN the adapter returns the value AND `api.secrets.reads_total{backend="vault"}` increments
**Verified by:** `src/adapter/adapter.test.ts::"get returns stored value AND increments reads_total"`
---
### Requirement: Locked vault surfaces explicitly
The package SHALL distinguish a locked vault (`SECRETS_VAULT_LOCKED`) from a missing entry (`SECRETS_VAULT_NOT_FOUND`).
#### Scenario: locked vault raises specific code
- WHEN the vault is locked at read time
- THEN reads raise `SECRETS_VAULT_LOCKED` AND no plaintext fallback occurs
**Verified by:** `src/adapter/adapter.test.ts::"locked vault raises SECRETS_VAULT_LOCKED"`
---
### Requirement: Per-plugin allowlist enforcement
The package SHALL refuse reads of secret names not in the plugin manifest's `secrets[]`. Refusals raise `SECRETS_VAULT_NOT_ALLOWED`.
#### Scenario: un-manifested read refused
- WHEN a plugin's manifest declares `secrets = [{ name: "STRIPE" }]` AND code calls `get("AWS")`
- THEN the call raises `SECRETS_VAULT_NOT_ALLOWED`
**Verified by:** `src/allowlist/allowlist.test.ts::"un-manifested read raises SECRETS_VAULT_NOT_ALLOWED"`
---
### Requirement: Vault updates surface as `secret.changed` events
The package SHALL subscribe to the vault's `secret.updated` event AND, when the updated secret is in the plugin's allowlist, emit `secret.changed` on the host event bus carrying `{ name }` only (no value).
#### Scenario: allowlisted update emits secret.changed
- WHEN the vault publishes `secret.updated` for a name in the allowlist
- THEN the host bus receives exactly one `secret.changed` event AND the payload contains only `{ name }`
#### Scenario: non-allowlisted update does not emit
- WHEN the vault publishes `secret.updated` for a name NOT in the allowlist
- THEN no `secret.changed` event is emitted on the host bus
**Verified by:** `src/watcher/watcher.test.ts::"allowlisted update emits secret.changed"` AND `src/watcher/watcher.test.ts::"non-allowlisted update does not emit"`
---
### Requirement: Passes `runSecretsConformance`
The package SHALL pass `runSecretsConformance` from `@suaveplan/api-secrets/testing`.
#### Scenario: conformance passes
- WHEN `runSecretsConformance("vault", makeVaultAdapter)` runs
- THEN every harness `it(...)` passes
**Verified by:** `src/adapter/adapter.spec-conformance.test.ts::"vault adapter passes runSecretsConformance"`