# Capability Spec Delta: crypto (crypto-trust-anchor-bootstrap) **Package**: `@suaveplan/crypto` **Status**: PROPOSED > Every requirement below carries a `**Verified by:**` line at authoring time, pointing to a real test in `packages/crypto/crypto/src/trust-anchors/`. ## ADDED Requirements ### Requirement: Trust-anchor bootstrap `@suaveplan/crypto` SHALL ship a trust-anchor loader at `src/trust-anchors/` that reads a self-verifying root public bundle from a caller-supplied filesystem reader and returns both the raw bytes (for direct use with `verifyChain`) and the parsed `PublicBundle` (for diagnostics). The loader SHALL self-verify the bundle in root-mode (`verifyPublicBundle(bytes, null)`) on every load — bytes that do not self-verify SHALL be rejected with `CRYPTO.TRUST_ANCHOR.SELF_VERIFY_FAILED`. The package SHALL expose: - `loadTrustAnchor(reader, path): Promise` — explicit-path entry. - `loadTrustAnchorFromEnv(reader, options): Promise` — env-driven entry. `options.getenv` is required (the universal package never reads `process.env` directly); production wires `(name) => process.env[name]`, tests inject deterministic stubs. `options.envVar` defaults to `SUAVEPLAN_TRUST_ANCHOR_PATH`. - `DEFAULT_TRUST_ANCHOR_ENV_VAR` — the canonical env-var name as a `const`. - Types `TrustAnchor`, `TrustAnchorReader`, `TrustAnchorEnvGetter`, `LoadTrustAnchorFromEnvOptions`. The universal-tier package MUST NOT import `node:fs` or read `process.env` at the module-import level; all platform access is caller-injected via the `TrustAnchorReader` interface and the optional `getenv` callback. Production wires `(path) => fs.readFile(path)`; tests inject deterministic stubs. #### Scenario: round-trip self-verify - **WHEN** a self-signed root public bundle is supplied via the reader and `loadTrustAnchor(reader, path)` is called - **THEN** the call returns `{ bytes, bundle }` where `bytes` is byte-identical to the input and `bundle` has a 32-byte `sign` slot public key #### Scenario: env-var default name - **WHEN** `loadTrustAnchorFromEnv(reader, { getenv })` is called without `options.envVar` - **THEN** `getenv` is invoked with `"SUAVEPLAN_TRUST_ANCHOR_PATH"` and the returned path is read #### Scenario: env-var name override - **WHEN** `loadTrustAnchorFromEnv(reader, { envVar: "VAULT_ANCHOR", getenv })` is called - **THEN** `getenv` is invoked with `"VAULT_ANCHOR"` and the returned path is read #### Scenario: missing env-var rejected - **WHEN** `loadTrustAnchorFromEnv(reader, { envVar, getenv: () => undefined })` is called - **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.PATH_MISSING` and `context.envVar` reflects the requested name #### Scenario: empty env-var string rejected - **WHEN** `loadTrustAnchorFromEnv(reader, { envVar, getenv: () => "" })` is called - **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.PATH_MISSING` #### Scenario: reader failure surfaces structured error - **WHEN** the reader's `read(path)` rejects (e.g. ENOENT) - **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.READ_FAILED`, with the reader's error attached via `cause` and `context.path` equal to the requested path #### Scenario: tampered bytes rejected - **WHEN** the loader receives bytes that parse but fail `verifyPublicBundle(bytes, null)` - **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.SELF_VERIFY_FAILED` and `context.byteLength` reflects the actual length #### Scenario: garbage bytes rejected - **WHEN** the loader receives bytes that fail to parse as a public bundle - **THEN** the call throws `CryptoError` with code `CRYPTO.TRUST_ANCHOR.SELF_VERIFY_FAILED` **Verified by:** `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"loads, self-verifies, and returns byte-identical bytes"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"the returned bundle parses identically to a fresh verify on the bytes"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"uses the default env var name SUAVEPLAN_TRUST_ANCHOR_PATH"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"honors a caller-supplied envVar override"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts::"getenv is invoked exactly once with the resolved env-var name"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.PATH_MISSING when env var is unset"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.PATH_MISSING when env var is empty string"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.READ_FAILED when reader rejects"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.SELF_VERIFY_FAILED on tampered bundle bytes"` AND `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts::"throws TRUST_ANCHOR.SELF_VERIFY_FAILED on garbage bytes"`