Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
## Why
The `verifyChain(bundleBytes, trustAnchorBundleBytes, options)` walker in `packages/crypto/crypto/src/bundles/chain-bundle/chain-bundle.ts` accepts a trust anchor as raw `Uint8Array`. There is no shipped helper to **load** that buffer from disk or environment, so every consumer (CLI, daemons, drive sync, network plugins) hand-rolls the same `readFile + parsePublicBundle + self-verify` sequence — or worse, skips the self-verify and trusts whatever bytes happen to live at the path. The umbrella change `restore-crypto-keychain-packer-umbrella` named this surface (`loadTrustAnchor()` / `loadTrustAnchorFromEnv()` / `SUAVEPLAN_TRUST_ANCHOR_PATH`) but the implementation slice was deferred. The `crypto-spec-restore-versioning` archive (2026-05-03) explicitly excluded the requirement because the `src/trust-anchors/` directory does not exist on genesis. The chain-bundle source already references a `TRUST_ANCHOR.MISMATCH` error code (line 199 of `chain-bundle.ts`) that is never defined in `errors/codes/codes.ts` — a stale doc-only signal that the surface was planned and never built.
This change fills the gap with a small, dependency-free loader module that takes injectable filesystem and environment readers (no direct `node:fs` import in the universal package), self-verifies the bundle on load, and surfaces stable structured errors. The module is the single auditable point through which trust anchors enter a Suaveplan process — a security-relevant choke point.
## What Changes
- **MODIFIED `crypto` capability**: add 1 requirement covering trust-anchor bootstrap. New surface in `packages/crypto/crypto/src/trust-anchors/`:
- `TrustAnchor` type — `{ bytes: Uint8Array; bundle: PublicBundle<TLayoutFields> }`
- `TrustAnchorReader` interface — `read(path: string): Promise<Uint8Array>`
- `loadTrustAnchor(reader, path): Promise<TrustAnchor>` — reads bytes, parses, self-verifies
- `loadTrustAnchorFromEnv(reader, getenv?, envVar?): Promise<TrustAnchor>` — env-driven path, defaults to `SUAVEPLAN_TRUST_ANCHOR_PATH`
- 3 new error codes: `TRUST_ANCHOR.PATH_MISSING`, `TRUST_ANCHOR.READ_FAILED`, `TRUST_ANCHOR.SELF_VERIFY_FAILED`
- Subpath export `./trust-anchors`
- **MODIFIED `crypto` capability**: extend the existing `Chain bundle traversal` requirement's `Verified by:` list to include the new trust-anchor self-verify scenario (the loader is what produces the `trustAnchorBundleBytes` `verifyChain` consumes).
- This change ships **source + tests + docs**, not just spec. Every requirement carries a `Verified by:` pointer to a real test name, per the convention re-affirmed by `crypto-spec-restore-versioning`.
## Capabilities
### New Capabilities
(none)
### Modified Capabilities
- `crypto`: add 1 requirement (`Trust-anchor bootstrap`); no requirements modified or removed.
## Impact
- **Affected specs**: `openspec/specs/crypto/spec.md` (1 requirement added).
- **Affected code**:
- `packages/crypto/crypto/src/trust-anchors/` (new directory — 5 files: types, impl, tests, docs, barrel).
- `packages/crypto/crypto/src/errors/codes/codes.ts` (3 codes added).
- `packages/crypto/crypto/src/index.ts` (re-export the new surface).
- `packages/crypto/crypto/package.json` (subpath export `./trust-anchors`).
- `packages/crypto/crypto/vite.config.ts` (build entry).
- `packages/crypto/crypto/tests/no-duplicates.test.ts` (whitelist `trust-anchors/`).
- **Affected tests**: 2 new test files (round-trip, error-code conformance). Coverage gate stays at 100/100/100/100.
- **Downstream**: `crypto-cli` and any daemon that consumes a chain bundle can replace its hand-rolled loader with the shipped one. No breaking change — the existing `verifyChain` signature is unchanged; consumers opt in to the loader.
- **Risk**: low. The loader is a thin wrapper over an existing tested verify-path. Filesystem and env access are caller-injected, so the universal package picks up no platform deps.

View File

@@ -0,0 +1,67 @@
<!-- markdownlint-disable MD024 -->
# 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<TrustAnchor>` — explicit-path entry.
- `loadTrustAnchorFromEnv(reader, options): Promise<TrustAnchor>` — 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"`
<!-- markdownlint-enable MD024 -->

View File

@@ -0,0 +1,35 @@
## 1. Source
- [ ] 1.1 Create `packages/crypto/crypto/src/trust-anchors/trust-anchor.types.ts` with `TrustAnchor`, `TrustAnchorReader`, `LoadTrustAnchorOptions`, `LoadTrustAnchorFromEnvOptions`
- [ ] 1.2 Create `packages/crypto/crypto/src/trust-anchors/trust-anchor.ts` with `loadTrustAnchor(reader, path): Promise<TrustAnchor>` and `loadTrustAnchorFromEnv(reader, options?): Promise<TrustAnchor>`. Default env var: `SUAVEPLAN_TRUST_ANCHOR_PATH`. Self-verify via existing `verifyPublicBundle(bytes, null)` (root-mode).
- [ ] 1.3 Add error codes to `packages/crypto/crypto/src/errors/codes/codes.ts`: `TRUST_ANCHOR.PATH_MISSING`, `TRUST_ANCHOR.READ_FAILED`, `TRUST_ANCHOR.SELF_VERIFY_FAILED`
- [ ] 1.4 Create `packages/crypto/crypto/src/trust-anchors/index.ts` (barrel — explicit named exports per `openspec/AGENTS.md §6a`)
- [ ] 1.5 Re-export the public surface from `packages/crypto/crypto/src/index.ts`
## 2. Build wiring
- [ ] 2.1 Add subpath export `./trust-anchors` to `packages/crypto/crypto/package.json`
- [ ] 2.2 Add `trust-anchors/index` build entry to `packages/crypto/crypto/vite.config.ts`
- [ ] 2.3 Whitelist `trust-anchors` in `packages/crypto/crypto/tests/no-duplicates.test.ts` (allowed top-level src dirs)
## 3. Tests + docs
- [ ] 3.1 Create `packages/crypto/crypto/src/trust-anchors/trust-anchor.test.ts` covering: round-trip self-verify; deterministic injectable `reader`; explicit-path and env-path entry points; verified bundle is byte-identical to the input.
- [ ] 3.2 Create `packages/crypto/crypto/src/trust-anchors/trust-anchor.spec-conformance.test.ts` covering all 3 new error codes (`PATH_MISSING`, `READ_FAILED`, `SELF_VERIFY_FAILED`).
- [ ] 3.3 Create `packages/crypto/crypto/src/trust-anchors/trust-anchor.md` (≥200 words, basic + advanced examples, full API reference).
## 4. Spec delta
- [ ] 4.1 Author `openspec/changes/crypto-trust-anchor-bootstrap/specs/crypto/spec.md` with the `## ADDED Requirements` block for `Trust-anchor bootstrap`.
- [ ] 4.2 Run `openspec validate crypto-trust-anchor-bootstrap --strict` — green.
## 5. Verification gate
- [ ] 5.1 `bunx turbo lint typecheck test --filter=@suaveplan/crypto` — green.
- [ ] 5.2 `bunx turbo test:coverage --filter=@suaveplan/crypto` — 100/100/100/100.
- [ ] 5.3 `bunx turbo build --filter=...@suaveplan/crypto` — every dependent compiles green.
## 6. Archive
- [ ] 6.1 `openspec archive crypto-trust-anchor-bootstrap --yes` — fold the requirement into `openspec/specs/crypto/spec.md`.
- [ ] 6.2 Confirm post-archive: `openspec validate --specs` shows `✓ spec/crypto`.