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

296 lines
29 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Restore the keychain + packer top-layer; rename `crypto` → `crypto-core`
**Package**: `@suaveplan/crypto-core`, `@suaveplan/crypto`, `@suaveplan/crypto-cli`, `@suaveplan/slsa` (modified), `@suaveplan/bitpacker` (modified)
**Status**: **UMBRELLA — superseded 2026-05-03 by four sequential child changes**
**Type**: refactor + new-package + feature
## Sliced execution (2026-05-03)
The 101-task scope is too large to ship atomically. After audit, ~41 of the new opinionated layer's files are already scaffolded (keychain, derivation, bundles, domain-tags, authority, errors), bitpacker `bytes` is done, both `crypto-core` and `crypto` packages exist and pass tests. The remaining work is sliced into four sequential change folders, each independently shippable:
| # | Change | Scope | Blocks |
|---|---|---|---|
| 1 | `restore-crypto-cleanup-duplicates` | Remove 46 duplicate primitive files from `crypto/src/{aead,agree,auth-box,sealed-box,sign,primitives,engines}`; re-export same surface from `crypto-core` | unblocks the import-allowlist gate (slice 4) |
| 2 | `restore-crypto-fold-keys-vault` | Fold `@suaveplan/crypto-keys` + `@suaveplan/crypto-vault` content into `crypto/src/{keychain,vault}`; migrate `auth/auth` consumer; delete the two old packages | unblocks the opinionated-layer Vault work (slice 3) |
| 3 | `restore-crypto-opinionated-layer` | Build the missing `crypto/src/{packer,chunked-packer,vault,revocation,audit-log,identity,wire-format}` modules with KAT fixtures + property tests | unblocks the CLI commands (slice 4) |
| 4 | `restore-crypto-cli-and-allowlist` | Build `@suaveplan/crypto-cli` (8 commands); migrate `slsa` to `Authority`; install the import-allowlist conformance gate | terminal slice — finishes the program |
This umbrella is retained for archival traceability. **Do not start work here.** Pick up from `restore-crypto-cleanup-duplicates`.
---
> Original proposal text below — preserved for traceability of intent and the source mapping that drove the slice plan.
## (Original) Why
## Why
The genesis rebuild correctly separated cryptographic primitives into focused leaf packages (`crypto-utils`, `crypto-hash`, `crypto-curve`, `crypto-cipher`, `crypto`, `crypto-keys`, `crypto-vault`). That substrate is a real upgrade over main's coupled `@suaveplan/crypto` blob.
What was lost in the rebuild is the *opinionated* layer that gave those primitives a reason to exist together. The original design — captured across `nx/libs/keys/src/lib/keys-common.ts`, `nx/libs/crypto-packer/`, `nx/libs/file-packer/`, `suaveplan/packages/universal/packer/`, and `suaveplan/packages/universal/crypto/src/keys/keychain/` — encoded a coherent product story:
1. A multi-purpose, **versioned 8-slot keychain** (`master / encrypt / sign / kx / salt / hash / stream / device`) with `{v, m, u, d, o}` metadata baked into every key.
2. A **root → server signing hierarchy** (`createRootKeys` / `createServerKeys` / `rollServerKeys`), matching real PKI patterns.
3. A **bit-packed wire format** (`packVault` / `unpackVault` / `packPublicKeys` / `viewPublicKeys`) where vaults are encrypt-then-sign and public bundles self-verify.
4. A **schema-driven encrypt-and-sign envelope** (`Packer` / `Unpacker`), generalised to chunked P2P (`ChunkedPacker` / `ChunkedUnpacker`) with per-chunk signatures, streaming, and compression.
Genesis has the primitives. Genesis has `bitpacker`. Genesis has `cli`, `readline`, `events`, `mime`. Genesis does not have any of the four opinionated artifacts above. `ChainSigner` in `crypto-vault` is named after the missing concept but is functionally a flat `Map<string, Keypair>`. The downstream consequence is silent drift: `slsa` already calls `crypto.ed25519.sign(secretKey, …)` directly with raw bytes, the path of least resistance every consumer will follow if nothing constrains them.
This change restores the top-layer as a deliberate, enforced artifact rather than letting it re-form as sediment in each new package.
## What changes
### Rename
- **`@suaveplan/crypto` → `@suaveplan/crypto-core`** — pure rename of the existing primitive façade. Public surface unchanged. Re-export shape preserved, package directory moves to `packages/crypto/crypto-core/`. Every existing consumer's import path is updated in the same change.
### New packages
- **`@suaveplan/crypto`** (new opinionated layer at `packages/crypto/crypto/`) — keychain + vault + packer + authority + revocation + audit log. The only crypto package the rest of the monorepo is allowed to import. Exposes:
- `Keychain` — 8-slot versioned record with per-slot algorithm tag + validity window
- `Vault` — encrypted-and-signed serialisation of a `Keychain` with BIP39 restore
- `Packer` / `Unpacker` — schema-driven encrypt-and-sign envelope over `bitpacker`
- `ChunkedPacker` / `ChunkedUnpacker` — per-chunk-signed P2P fragmentation with reassembly
- `Authority` — signing abstraction for root and server identities (in-memory v1, HSM-ready interface)
- `RevocationList` — root-signed CRL with issue / append / verify / check
- `AuditLog` — append-only Merkle log of issue / roll / revoke events, root-signed
- `RootIdentity` / `ServerIdentity` — typed wrappers around the keychain pair (private vault + public bundle)
- `createRoot` / `createServer` / `rollServer` / `revokeServer` — hierarchy operations
- Domain-separation tag constants (`DOMAIN_VAULT_V1`, `DOMAIN_PUBLIC_KEYCHAIN_V1`, `DOMAIN_PACKER_V1`, `DOMAIN_CHUNK_V1`, `DOMAIN_CRL_V1`, `DOMAIN_AUDIT_V1`)
- **`@suaveplan/crypto-cli`** (new at `packages/crypto/crypto-cli/`) — `keychain` binary built on `@suaveplan/cli` + `@suaveplan/readline` + `@suaveplan/crypto`. Eight commands: `root init`, `server init`, `server roll`, `server revoke`, `inspect`, `verify`, `export-public`, `restore`. TTY-only password input, stdin/stdout streamable, distinct exit codes.
### Removed (folded into new `crypto`)
- **`@suaveplan/crypto-keys`** — REMOVED. `Keypair`, `Keychain` adapter, `validate`, `generate` move into `@suaveplan/crypto/keychain`. Folding eliminates the artificial split between "key shape" and "keychain" — they were always one concept.
- **`@suaveplan/crypto-vault`** — REMOVED. `Vault`, `BIP39 mnemonic`, and the misleadingly-named `ChainSigner` move into `@suaveplan/crypto`. `ChainSigner` is replaced by `Authority` (which earns the name by actually signing under a hierarchy).
### Modified
- **`@suaveplan/slsa`** — builder identity is no longer constructed from raw `crypto.ed25519` calls. Provenance signing goes through `Authority.sign(DOMAIN_SLSA_BUILDER_V1, payload)`; builder keys are issued as server identities under a SLSA-builder root. The DSSE envelope format is unchanged; only the construction path changes.
- **`@suaveplan/bitpacker`** — adds a `"bytes"` field type (fixed-length byte arrays, 1–65535 bytes per field, byte-aligned) and exposes `BitWriter.byteAlign()` / `BitReader.byteAlign()`. Required by `@suaveplan/crypto`'s wire-format schemas which carry 4-byte ASCII tags, 32-byte keypairs, 16-byte nonces, and 64-byte signatures. The original main BitPacker had this concept (`"32u8"` field syntax); the genesis rewrite dropped it and the rewrite was the constraint that drove the AI-generated `keys-common.ts` to cram `userId` into 4 bits. Restoring it lets the keychain use full-width derivation coordinates as designed.
### Wire-format schemas (introduced as part of this change)
The new `@suaveplan/crypto` package commits these schemas as canonical wire formats; every one has a golden-bytes KAT fixture:
- `DERIVATION_LAYOUT_V1` — pluggable u64 layout for libsodium's `subkey_id` (default: `masterVersion u16 ‖ userId u32 ‖ depth u8 ‖ slotVersion u8`); custom layouts registered via `defineDerivationLayout(id, schema)`
- `DERIVATION_COORDINATE_SCHEMA` — `purpose ASCII[4] ‖ application ASCII[4] ‖ layoutId u8 ‖ <layout fields>` — fully replaces the AI-rewritten 28-bit `{v,m,u,d,o}` ceiling with the libsodium-native 128-bit derivation namespace
- `KEY_SLOT_PRIVATE_SCHEMA` / `KEY_SLOT_PUBLIC_SCHEMA` — per-slot record with coordinate + algorithm enum + validity window + key material
- `VAULT_PAYLOAD_SCHEMA` — full keychain + `seedCount` + arbitrary application `data: Uint8Array`, parent-signed under `DOMAIN_VAULT_V1`, then password-AEAD-encrypted (private-key blob is a separate password-encrypted regime so `changePassword` doesn't re-derive)
- `PUBLIC_BUNDLE_SCHEMA` — `magic("SVPB") ‖ formatVersion ‖ issuerRef ‖ issuedAt ‖ nonce ‖ subjectRef ‖ slots[] ‖ parentSignature[64]`
- `CHAIN_BUNDLE_SCHEMA` — `magic("SVCB") ‖ formatVersion ‖ linkCount ‖ links[]` for X.509-style chain traversal (root-of-trust at the tail; `verifyChain` walks leaf → root checking each link's parent signature, validity window, and CRL status)
- `CRL_ENTRY_SCHEMA` and `AUDIT_ENTRY_SCHEMA` — revocation list and audit log entries (each root-signed)
### Enforcement (the anti-sediment gate)
- **Import allowlist in `scripts/openspec-conformance.ts`**: any package other than `@suaveplan/crypto` and `@suaveplan/crypto-cli` that imports `@suaveplan/crypto-core` (or `crypto-utils` / `crypto-hash` / `crypto-curve` / `crypto-cipher` for cryptographic operations — encoding helpers stay open) fails conformance with `error` severity. The allowlist is committed to `scripts/conformance/crypto-core-allowlist.yml` so additions are reviewable.
- **Wire-format KAT vectors** at `packages/crypto/crypto/test-fixtures/wire-format/*.bin` with a golden-bytes test asserting byte-for-byte stability. Any future refactor that changes the format fails loudly.
- **Domain-separation tag** prefixed before every signed payload. Tags are typed as branded strings, not raw `Uint8Array`s, so a caller cannot omit one or pass the wrong one.
- **Algorithm tag** required on every keychain slot. Verifier checks the tag against an allowlist before dispatching to the engine. Keys without a tag are rejected at parse time.
- **Validity windows** (`notBefore`, `notAfter`) baked into every keychain slot, checked against an injectable `Clock` at every verify call. No `Date.now()` anywhere in `src/`.
## Out of scope
- **Threshold / Shamir-split root.** The `Authority` interface is designed so a threshold backend can be dropped in later, but no threshold implementation ships in this change. Single-key root only, v1.
- **HSM / YubiKey / TPM driver implementations.** `Authority` is the abstraction; concrete hardware-backed drivers are deferred to dedicated changes per backend (`crypto-authority-yubikey`, `crypto-authority-tpm`).
- **Post-quantum algorithms.** The algorithm tag exists so a PQ algorithm can be added without a wire-format break, but no PQ engine is wired in v1.
- **Network-fetched CRL distribution.** `RevocationList` is read from a local file or in-memory blob in v1; `--fetch-crl` and any HTTP/OCSP-style retrieval is a follow-up.
- **Key escrow / recovery service.** BIP39-mnemonic-based local recovery only.
- **Trust-anchor distribution mechanism.** v1 documents two patterns (compiled-in pinned constant; env-var path to a trusted public bundle); no opinionated registry / TUF-style updater ships.
- **Migration of `slsa` Sigstore Rekor integration to share the `AuditLog`.** Flagged as a reuse opportunity but kept separate for v1 to keep this change reviewable.
- **Re-pointing every existing consumer of `crypto-core` primitives that uses encoding helpers** (`toHex`, `fromHex`, `toBase64`) — those stay open. Only the *cryptographic* primitives (sign / verify / aead-encrypt / aead-decrypt / agree / sealed-box / auth-box / hash-when-used-as-MAC) trigger the conformance gate.
## Source mapping
This change derives from five legacy locations across `../nx/` and `../suaveplan/`. Every requirement in the spec deltas cites at least one source range below.
### `../suaveplan/enterprise/keys.md` (118 lines)
| Requirement (in `specs/crypto/spec.md`) | Source lines | Notes |
|---|---|---|
| Keychain — 8 named slots with `{v, m, u, d, o}` metadata | L42-L49, L95-L117 (cross-ref `keys-common.ts`) | Reworded; algorithm tag and validity window added (above-baseline, see "no source line" table) |
| Keychain — JWK / PEM / raw lossless serialisation | L40-L41 | Bit-packed binary kept as primary; JWK/PEM added as alternative encoders for ecosystem compatibility |
| Keychain — rotation lifecycle (active / next / retired) | L41 | Implemented as version-bump (`rollServer`) + CRL (`revokeServer`); KID stable across rotations |
| Keychain — Zod schemas, schema-registry integration | L41 | All public types validated; `crypto.wireFormatVersion` registered with `@suaveplan/schema-registry` |
| Keychain — property-based round-trips | L42 | fast-check round-trip suite over BitPacker schemas |
| Keychain — injectable Clock and RNG | L43 | `Clock` and `Rng` consumed via constructor; module scope deterministic |
| Keychain — OTel instrumentation, no key bytes in attributes | L44 | Verified via OTel attribute-redaction test |
| Keychain — structured errors, never leak key bytes | L45 | `CryptoError` codes; redaction test asserts no `Uint8Array` attribute over 16 bytes |
| Keychain — secure memory hygiene (zeroise on release) | L46 | `Vault.lock()` / `Authority.dispose()` zeroise; tested via post-zeroise inspection |
| Keychain — `AbortSignal` everywhere async | L47 | All async surfaces accept `AbortSignal` |
| Keychain — i18n-ready, no hard-coded user-visible strings | L49 | All errors keyed via `@suaveplan/i18n` resolver |
### `../suaveplan/enterprise/server-keys.md` (118 lines)
| Requirement (in `specs/crypto-cli/spec.md`) | Source lines | Notes |
|---|---|---|
| CLI — server key management commands | L11-L12 | Reworded into eight concrete commands |
| CLI — JWT identity & RBAC integration | L40 | Out-of-scope for v1 (CLI ships standalone); flagged as integration target |
| CLI — Idempotency-Key handling | L42 | Adapted: CLI mutations (`server roll`, `revoke`) accept `--idempotency-key` and dedupe via local file ledger |
| CLI — audit logging on writes | L43 | `AuditLog` append on every state-changing command |
| CLI — observability (spans, logs, metrics) | L44 | OTel spans + structured logger output |
| CLI — health / readiness | L46 | Out-of-scope (CLI is not a long-lived service) |
### `../suaveplan/enterprise/crypto.md` (117 lines)
| Requirement (in `specs/crypto/spec.md`) | Source lines | Notes |
|---|---|---|
| Engine-pluggable cryptographic backends | (entire file context) | Already satisfied by genesis `crypto-core` (formerly `crypto`); preserved by rename |
| Domain-separation tags on every signed payload | (above-baseline) | Added in genesis; not in source — see "no source line" table |
### `../suaveplan/enterprise/packer.md` (118 lines) and `packer-browser.md` (114 lines)
| Requirement (in `specs/crypto/spec.md`) | Source lines | Notes |
|---|---|---|
| Packer — encrypt + sign envelope | packer.md L11-L40 | Implemented via `Packer.pack()` / `Unpacker.unpack()` |
| Packer — chunked P2P fragmentation | packer.md L41-L60 | `ChunkedPacker.packFileToChunks()` / `ChunkedUnpacker.reassembleFile()` |
| Packer — per-chunk signature | packer.md L41-L60 | Each chunk signed independently; tampering with any chunk fails on reassembly |
| Packer — streaming for large files | packer.md L61-L80 | Web Streams; injectable highWaterMark |
| Packer — compression (configurable level) | packer.md L41-L60 | Zstd preferred; level 0–9 |
| Packer — event emission for progress | packer.md L61-L80 | `events` integration; deterministic FIFO |
| Packer — schema-driven structured payloads | packer-browser.md L20-L60 | BitPacker schemas drive both packer and packer-public-keychain wire formats |
### `nx/libs/keys/src/lib/keys-common.ts` (296 lines, full file)
| Requirement (in `specs/crypto/spec.md`) | Source lines | Notes |
|---|---|---|
| Keychain — 8-slot wire schema (`fullKeychainSchema`) | L60-L69 | Verbatim slot names; field types preserved |
| Keychain — public-only schema (`publicKeychainSchema`) | L33-L38 | Verbatim; used by `packPublicKeys` |
| Vault — `packVault` (pack → sign → encrypt-with-password) | L111-L127 | Algorithm preserved; AES-GCM replaced with XChaCha20-Poly1305 (above-baseline rationale below) |
| Vault — `unpackVault` (decrypt → verify → unpack) | L129-L173 | Domain-separation tag added (above-baseline) |
| Public bundle — `packPublicKeys` with self-embedded signature | L91-L109 | Verbatim semantics; tag-prefixed |
| Public bundle — `viewPublicKeys` self-verifies via embedded sign key | L175-L213 | Verbatim semantics |
| Hierarchy — `createRootKeys` | L215-L226 | Renamed `createRoot` |
| Hierarchy — `createServerKeys(root, password)` | L228-L247 | Renamed `createServer`; takes `Authority` instead of raw root keychain |
| Hierarchy — `rollServerKeys(root, password, currentVersion)` | L249-L272 | Renamed `rollServer`; emits CRL entry for prior version (above-baseline) |
| Vault file I/O (`openVault`) | L274-L280 | Folded into CLI; not in library surface |
### `nx/libs/crypto-packer/README.md` and `src/`
| Requirement (in `specs/crypto/spec.md`) | Source lines | Notes |
|---|---|---|
| `BitPackerCrypto` (schema + encrypt + sign in one) | README L52-L168 | Reborn as `Packer` with the same triple, generalised |
| `BitPackerCryptoConfig` shape | README L249-L260 | Mapped to `PackerOptions` |
| Recipient-verification example (sender encrypts to recipient pubkey, signs with own privkey) | README L282-L366 | Preserved as `Packer` use case; tested as `it("seals to recipient and verifies sender", ...)` |
| Event-driven error handling | README L262-L279 | Mapped to `events` integration on `Packer`/`ChunkedPacker` |
### `nx/libs/file-packer/README.md` and `src/`
| Requirement (in `specs/crypto/spec.md`) | Source lines | Notes |
|---|---|---|
| File-level packing with metadata (filename, type, owner, expiry) | README L22-L91 | Metadata wrapper preserved; `verifyOwner` / `checkExpiry` mapped to `Authority`-checked claims |
| Pluggable validator (size limit, type allowlist) | README L57-L62 | Out of scope here — kept as a future `@suaveplan/file-validator` package consuming `crypto`'s `Packer` |
| Pluggable storage backend | README L52-L56 | Out of scope here — same rationale |
### `suaveplan/packages/universal/packer/src/index.md` (703 lines)
| Requirement (in `specs/crypto/spec.md`) | Source lines | Notes |
|---|---|---|
| `Packer` / `Unpacker` / `ChunkedPacker` / `ChunkedUnpacker` surface | L20-L109 | Verbatim API shape preserved |
| AEAD options (AES-256-GCM / XChaCha20-Poly1305 / ChaCha20-Poly1305) | L11-L18 | Verbatim; default switched to XChaCha20-Poly1305 |
| Streaming via Web Streams + async iterables | L296-L348 | Verbatim |
| P2P chunk verification (per-chunk signed; tampered chunks reject) | L356-L448 | Verbatim semantics; reassembly tested with tampered-chunk scenario |
| BaseKey integration (every signing/encrypt op consumes a Keychain entry, not raw bytes) | L8-L18, L181-L246 | This is the central rule; enforced via `Authority` requiring `Keychain` slot reference |
| Event system (pack/unpack/chunk/reassemble events) | L550-L604 | Mapped to `@suaveplan/events` |
### `suaveplan/packages/universal/crypto/src/keys/keychain/keychain.md`
| Requirement (in `specs/crypto/spec.md`) | Source lines | Notes |
|---|---|---|
| `generateKeychain` / `filterKeychain` / `extractPublicKeys` / `extractPrivateKeys` | L16-L67 | Verbatim API; method names preserved |
| Keychain structure (8 slots: master / encrypt / sign / kx / salt / hash / stream / device) | L95-L117 | Verbatim slot list and semantics |
| Per-key fields (`s`, `p`, `v`, `m`, `d`, `u`, `o`) | L108-L117 | Verbatim — these are the wire format |
| Hierarchical derivation for multiple purposes | L9-L13 | Each slot derived independently from master + slot offset |
| Security: master never shared, `extractPublicKeys` before transmit, namespace separation by user ID | L118-L124 | Tested as separate scenarios |
### Requirements with no source line (above-baseline additions)
| Requirement | Justification |
|---|---|
| Algorithm tag required on every keychain slot | Original is hard-wired ed25519/x25519. Without a tag, post-quantum migration or even ECDSA-P256 fallback requires a wire-format break. The tag is reserved space at zero cost in v1. |
| Validity windows (`notBefore`, `notAfter`) | Original treats all keys as valid forever. SSL has these for a reason. Without them, a stolen key keeps verifying. |
| Domain-separation tags before every signature | A signature over message X in protocol A must not be valid in protocol B. The original signs raw bytes; this is a real cross-protocol replay risk. Cheap to add now, expensive after deployment. |
| `RevocationList` (signed CRL) | Without revocation, key rotation is a lie — old server keys verify forever. The original `rollServerKeys` increments version but never marks the prior version as revoked. |
| `AuditLog` (signed Merkle log of issue/roll/revoke) | Required to prove "this server key was valid at time T" after the fact. Reuses `slsa`'s Rekor pattern conceptually. |
| `Authority` interface (not raw secret-key references) | The root is the crown jewel. Even if v1 ships an in-memory backend, the *interface* must allow HSM/threshold backends without changing every caller. Passing `Uint8Array` secret keys around precludes this. |
| Replay protection: `issuedAt` + `nonce` in signed envelopes | Original `packPublicKeys` signs only the bit-packed bytes; an old signed envelope replays indefinitely. Verifier rejects envelopes whose `issuedAt` is older than a configurable window or whose `nonce` was already seen. |
| Default AEAD switched from AES-GCM to XChaCha20-Poly1305 | XChaCha20-Poly1305 has a 192-bit nonce, eliminating the nonce-reuse class of bugs that plague AES-GCM in long-lived vault scenarios. AES-GCM remains available as an explicit option. |
| `crypto-core` import allowlist (anti-sediment gate) | The single most important guarantee in this proposal. Documentation alone has demonstrably failed to prevent direct primitive use (see `slsa` calling `crypto.ed25519.sign` today). Mechanically enforced. |
| Wire-format KAT vectors with golden-bytes test | Pins the wire format against silent regression in any future refactor. Without this, v2 of the keychain could change a field width and old vaults break with no test failure. |
| TTY-only password input in CLI | `--password` flag leaks via shell history. Non-negotiable for a credential-handling CLI. |
| Distinct CLI exit codes per failure class | Operators need to distinguish "wrong password" from "tampered vault" from "expired key" from "revoked key" — each requires a different operational response. |
## Verification approach
Every requirement is pinned by named `it(...)` titles in tests committed in this change. Coverage is asserted by the existing pre-publish gate (`bunx turbo test:coverage --filter=@suaveplan/<pkg>` at 100/100/100/100).
### `@suaveplan/crypto-core` (rename only)
- `tests/rename.test.ts` — pins that every previously-public symbol is still exported under the new package name with the same signature
- `tests/import-allowlist.test.ts` — pins that the only packages allowed to import `@suaveplan/crypto-core` are `@suaveplan/crypto` and `@suaveplan/crypto-cli`
### `@suaveplan/crypto`
- `src/keychain/keychain.test.ts` — slot generation, algorithm tag enforcement, validity-window enforcement, public/private extraction, hierarchical derivation
- `src/keychain/keychain.spec-conformance.test.ts` — type-level slot map; OTel attribute-redaction; strict-mode behaviour; `expectTypeOf` on every literal claim
- `src/vault/vault.test.ts` — pack → sign → encrypt round-trip; tamper detection (swap byte, expect verify failure); BIP39 restore; `Vault.lock()` zeroes secrets
- `src/vault/vault.spec-conformance.test.ts` — KAT vectors against committed `test-fixtures/wire-format/vault-v1.bin`; Argon2id parameter floor enforced
- `src/packer/packer.test.ts` — encrypt-and-sign envelope; recipient-verification scenario from `nx/libs/crypto-packer/README.md`; replay rejection via `issuedAt`+`nonce`
- `src/packer/packer.spec-conformance.test.ts` — KAT vectors; domain-separation tag enforcement (mismatched tag must fail verify)
- `src/chunked-packer/chunked-packer.test.ts` — fragment / per-chunk-sign / reassemble; tampered-chunk rejection at reassembly
- `src/chunked-packer/chunked-packer.spec-conformance.test.ts` — streaming via Web Streams; event emission order; `AbortSignal` cancellation
- `src/authority/authority.test.ts` — sign-under-domain-tag; revoked-key signing rejection; `dispose()` zeroes; Clock-driven validity-window enforcement
- `src/authority/authority.spec-conformance.test.ts` — interface conformance harness reusable by future HSM backends
- `src/revocation/revocation.test.ts` — append signed entry; verify entry against trust anchor; reject mutation of past entries; check-by-version
- `src/audit-log/audit-log.test.ts` — append → Merkle root → root-signature; tamper detection (modify any past entry, expect root mismatch)
- `src/identity/root-identity.test.ts` and `src/identity/server-identity.test.ts` — `createRoot` / `createServer` / `rollServer` / `revokeServer` happy paths and edge cases (re-roll same version, revoke non-existent server)
- `tests/dep-graph.test.ts` — pins that `@suaveplan/crypto`'s own dependencies are exactly: `crypto-core`, `crypto-utils`, `crypto-hash`, `crypto-curve`, `crypto-cipher`, `bitpacker`, `error`, `events`, `types`, `i18n`, `opentelemetry`, plus external `@scure/bip39`
- `tests/wire-format-kat.test.ts` — golden-bytes assertion against every fixture under `test-fixtures/wire-format/`
- `tests/property-roundtrips.test.ts` — fast-check property tests for all pack/unpack pairs
### `@suaveplan/crypto-cli`
- `src/commands/root-init.test.ts` through `src/commands/restore.test.ts` — one test file per command; happy path + every failure mode (bad password, tampered vault, expired key, revoked key, missing trust anchor)
- `tests/exit-codes.test.ts` — pins distinct exit codes per failure class
- `tests/no-password-flag.test.ts` — pins that `--password` is rejected with a security error and a pointer to TTY/env-var input
- `tests/tty-mock.test.ts` — driven via the `cli` package's `InputReader` interface; verifies password-prompt UX without a real TTY
### `@suaveplan/slsa` (modified)
- `src/builder/builder-identity.test.ts` — modified: builder constructed via `Authority`, signing via `Authority.sign(DOMAIN_SLSA_BUILDER_V1, …)`. The DSSE envelope output is unchanged — covered by the existing round-trip test.
- `tests/no-direct-crypto-core.test.ts` — pins that `slsa` no longer imports `@suaveplan/crypto` (now `crypto-core`) directly for signing operations
### Conformance gate
- `scripts/conformance/crypto-core-allowlist.test.ts` — runs `scripts/openspec-conformance.ts` across the monorepo and asserts the import allowlist is honoured
## Migration impact
### `@suaveplan/crypto` (old name, now `crypto-core`) consumers in the monorepo
`grep -rl "@suaveplan/crypto\"" packages/` lists every consumer that needs an import-path update. Most consumers will be moved to `@suaveplan/crypto` (the new opinionated layer); only signed-construction-internal cases stay on `@suaveplan/crypto-core`.
| Consumer | Today's import | Migrates to | Reason |
|---|---|---|---|
| `@suaveplan/crypto-keys` | `@suaveplan/crypto/sign`, `@suaveplan/crypto-utils` | (deleted; merged into `@suaveplan/crypto`) | Folded |
| `@suaveplan/crypto-vault` | `@suaveplan/crypto/sign`, `@suaveplan/crypto-keys` | (deleted; merged into `@suaveplan/crypto`) | Folded |
| `@suaveplan/crypto-sodium` | `@suaveplan/crypto` | `@suaveplan/crypto-core` | Engine adapter — primitive-layer consumer |
| `@suaveplan/crypto-browser` | `@suaveplan/crypto` | `@suaveplan/crypto-core` | Engine adapter — primitive-layer consumer |
| `@suaveplan/slsa` | `@suaveplan/crypto`, `@suaveplan/crypto-hash`, `@suaveplan/crypto-utils` | `@suaveplan/crypto` (for signing); `@suaveplan/crypto-hash` and `@suaveplan/crypto-utils` stay (hashing + encoding helpers) | Builder identity moved to `Authority` |
| Any package that today writes `import { ed25519 } from "@suaveplan/crypto"` | (drop) | `@suaveplan/crypto` `Authority.sign(...)` | Anti-sediment rule |
| Any package that uses `toHex`/`fromHex`/`toBase64` from `@suaveplan/crypto-utils` | unchanged | unchanged | Encoding helpers stay open |
### Removed package consumers
- `@suaveplan/crypto-keys` is currently imported only by `@suaveplan/crypto-vault` (folding both eliminates the edge entirely).
- `@suaveplan/crypto-vault` is currently not imported by anything else in genesis (cross-checked against `MANIFEST.md`).
### Manifest / CLAUDE.md
- `CLAUDE.md` package list: remove `crypto-keys`, `crypto-vault`; rename `crypto` → `crypto-core`; add `crypto`, `crypto-cli`. The single `## Packages` listing in CLAUDE.md is the canonical edit; `MANIFEST.md` is regenerated via `bun scripts/folder-reorg/generate-manifest.ts`.
- `scripts/folder-reorg/assignment.yml` — update plugin assignments for the renamed/new packages (all stay under the `crypto` plugin).
### Backwards compatibility
None. Genesis is at `0.1.0` for every package; no downstream contracts exist yet. The rename and the fold both happen in a single commit set; no shim is shipped.