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

29 KiB
Raw Permalink Blame History

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 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 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.