29 KiB
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:
- 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. - A root → server signing hierarchy (
createRootKeys/createServerKeys/rollServerKeys), matching real PKI patterns. - A bit-packed wire format (
packVault/unpackVault/packPublicKeys/viewPublicKeys) where vaults are encrypt-then-sign and public bundles self-verify. - 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 topackages/crypto/crypto-core/. Every existing consumer's import path is updated in the same change.
New packages
@suaveplan/crypto(new opinionated layer atpackages/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 windowVault— encrypted-and-signed serialisation of aKeychainwith BIP39 restorePacker/Unpacker— schema-driven encrypt-and-sign envelope overbitpackerChunkedPacker/ChunkedUnpacker— per-chunk-signed P2P fragmentation with reassemblyAuthority— signing abstraction for root and server identities (in-memory v1, HSM-ready interface)RevocationList— root-signed CRL with issue / append / verify / checkAuditLog— append-only Merkle log of issue / roll / revoke events, root-signedRootIdentity/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 atpackages/crypto/crypto-cli/) —keychainbinary 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,Keychainadapter,validate,generatemove 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-namedChainSignermove into@suaveplan/crypto.ChainSigneris replaced byAuthority(which earns the name by actually signing under a hierarchy).
Modified
@suaveplan/slsa— builder identity is no longer constructed from rawcrypto.ed25519calls. Provenance signing goes throughAuthority.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 exposesBitWriter.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-generatedkeys-common.tsto cramuserIdinto 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'ssubkey_id(default:masterVersion u16 ‖ userId u32 ‖ depth u8 ‖ slotVersion u8); custom layouts registered viadefineDerivationLayout(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 namespaceKEY_SLOT_PRIVATE_SCHEMA/KEY_SLOT_PUBLIC_SCHEMA— per-slot record with coordinate + algorithm enum + validity window + key materialVAULT_PAYLOAD_SCHEMA— full keychain +seedCount+ arbitrary applicationdata: Uint8Array, parent-signed underDOMAIN_VAULT_V1, then password-AEAD-encrypted (private-key blob is a separate password-encrypted regime sochangePassworddoesn'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;verifyChainwalks leaf → root checking each link's parent signature, validity window, and CRL status)CRL_ENTRY_SCHEMAandAUDIT_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/cryptoand@suaveplan/crypto-clithat imports@suaveplan/crypto-core(orcrypto-utils/crypto-hash/crypto-curve/crypto-cipherfor cryptographic operations — encoding helpers stay open) fails conformance witherrorseverity. The allowlist is committed toscripts/conformance/crypto-core-allowlist.ymlso additions are reviewable. - Wire-format KAT vectors at
packages/crypto/crypto/test-fixtures/wire-format/*.binwith 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
Uint8Arrays, 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 injectableClockat every verify call. NoDate.now()anywhere insrc/.
Out of scope
- Threshold / Shamir-split root. The
Authorityinterface 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.
Authorityis 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.
RevocationListis read from a local file or in-memory blob in v1;--fetch-crland 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
slsaSigstore Rekor integration to share theAuditLog. Flagged as a reuse opportunity but kept separate for v1 to keep this change reviewable. - Re-pointing every existing consumer of
crypto-coreprimitives 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 signaturetests/import-allowlist.test.ts— pins that the only packages allowed to import@suaveplan/crypto-coreare@suaveplan/cryptoand@suaveplan/crypto-cli
@suaveplan/crypto
src/keychain/keychain.test.ts— slot generation, algorithm tag enforcement, validity-window enforcement, public/private extraction, hierarchical derivationsrc/keychain/keychain.spec-conformance.test.ts— type-level slot map; OTel attribute-redaction; strict-mode behaviour;expectTypeOfon every literal claimsrc/vault/vault.test.ts— pack → sign → encrypt round-trip; tamper detection (swap byte, expect verify failure); BIP39 restore;Vault.lock()zeroes secretssrc/vault/vault.spec-conformance.test.ts— KAT vectors against committedtest-fixtures/wire-format/vault-v1.bin; Argon2id parameter floor enforcedsrc/packer/packer.test.ts— encrypt-and-sign envelope; recipient-verification scenario fromnx/libs/crypto-packer/README.md; replay rejection viaissuedAt+noncesrc/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 reassemblysrc/chunked-packer/chunked-packer.spec-conformance.test.ts— streaming via Web Streams; event emission order;AbortSignalcancellationsrc/authority/authority.test.ts— sign-under-domain-tag; revoked-key signing rejection;dispose()zeroes; Clock-driven validity-window enforcementsrc/authority/authority.spec-conformance.test.ts— interface conformance harness reusable by future HSM backendssrc/revocation/revocation.test.ts— append signed entry; verify entry against trust anchor; reject mutation of past entries; check-by-versionsrc/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.tsandsrc/identity/server-identity.test.ts—createRoot/createServer/rollServer/revokeServerhappy 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/bip39tests/wire-format-kat.test.ts— golden-bytes assertion against every fixture undertest-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.tsthroughsrc/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 classtests/no-password-flag.test.ts— pins that--passwordis rejected with a security error and a pointer to TTY/env-var inputtests/tty-mock.test.ts— driven via theclipackage'sInputReaderinterface; verifies password-prompt UX without a real TTY
@suaveplan/slsa (modified)
src/builder/builder-identity.test.ts— modified: builder constructed viaAuthority, signing viaAuthority.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 thatslsano longer imports@suaveplan/crypto(nowcrypto-core) directly for signing operations
Conformance gate
scripts/conformance/crypto-core-allowlist.test.ts— runsscripts/openspec-conformance.tsacross 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-keysis currently imported only by@suaveplan/crypto-vault(folding both eliminates the edge entirely).@suaveplan/crypto-vaultis currently not imported by anything else in genesis (cross-checked againstMANIFEST.md).
Manifest / CLAUDE.md
CLAUDE.mdpackage list: removecrypto-keys,crypto-vault; renamecrypto→crypto-core; addcrypto,crypto-cli. The single## Packageslisting in CLAUDE.md is the canonical edit;MANIFEST.mdis regenerated viabun scripts/folder-reorg/generate-manifest.ts.scripts/folder-reorg/assignment.yml— update plugin assignments for the renamed/new packages (all stay under thecryptoplugin).
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.