Files
suaveplan/openspec/archive/2026-05-03-restore-crypto-keychain-packer-umbrella/tasks.md
2026-05-27 23:29:53 +01:00

14 KiB

Tasks — Restore the keychain + packer top-layer; rename crypto → crypto-core

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify. Retroactive ticking is the failure mode this template guards against.

1. Spec authoring

  • 1.1 Read ../suaveplan/enterprise/keys.md, server-keys.md, crypto.md, packer.md, packer-browser.md end-to-end
  • 1.2 Read existing openspec/specs/crypto/spec.md (current primitive-layer spec; will be moved under crypto-core)
  • 1.3 Read openspec/AGENTS.md Sections 13 and 14
  • 1.4 Draft proposal.md from openspec/templates/proposal.md — DONE in this change folder
  • 1.5 Fill in ## Source mapping table — every requirement cites a line range OR appears in the "no source line" table with justification — DONE
  • 1.6 Draft spec deltas:
    • 1.6.a specs/crypto-core/spec.md — RENAMED + import-allowlist requirement
    • 1.6.b specs/crypto/spec.md — full new spec (Keychain, Vault, Packer, ChunkedPacker, Authority, RevocationList, AuditLog, Identity hierarchy, domain tags, KAT vectors)
    • 1.6.c specs/crypto-cli/spec.md — eight commands + UX rules + exit codes
    • 1.6.d specs/crypto-keys/spec.md — REMOVED with migration pointer
    • 1.6.e specs/crypto-vault/spec.md — REMOVED with migration pointer
    • 1.6.f specs/slsa/spec.md — MODIFIED: builder identity via Authority
  • 1.7 Every requirement in every spec delta carries a **Verified by:** line naming concrete it(...) titles

2. Implementation — @suaveplan/bitpacker (bytes field)

  • 2.0.1 Extend FieldType with "bytes" and add byteLength to FieldDef Zod schema (packages/primitives/bitpacker/src/types/common.ts)
  • 2.0.2 Implement bytes encoding in BitWriter: pad to byte boundary, write byteLength bytes, pad after; matching path in BitReader
  • 2.0.3 Expose BitWriter.byteAlign() and BitReader.byteAlign() as public methods
  • 2.0.4 Update BitSchema size computation for variable-length payloads; document fixed-vs-runtime size semantics
  • 2.0.5 Add error codes: SCHEMA.INVALID_BYTE_LENGTH, PACK.BYTES_LENGTH_MISMATCH, UNPACK.INSUFFICIENT_BYTES
  • 2.0.6 KAT-assert that pre-change schemas (uint/int/bool/fixed only) produce byte-identical output (tests/legacy-schema-kat.test.ts)
  • 2.0.7 Sibling .md for the new field type plus updated bit-schema.md
  • 2.0.8 Pre-publish gate green for @suaveplan/bitpacker

2. Implementation — @suaveplan/crypto-core (rename)

  • 2.1 Create packages/crypto/crypto-core/ and move every file from packages/crypto/crypto/ (preserve git history via git mv)
  • 2.2 Update package.json name → @suaveplan/crypto-core
  • 2.3 Update every workspace:^ reference in the monorepo from @suaveplan/crypto to @suaveplan/crypto-core except for consumers that should now use the new opinionated @suaveplan/crypto
  • 2.4 Update every import statement accordingly (codemod: bun scripts/codemods/rename-crypto-to-core.ts)
  • 2.5 Update CLAUDE.md package list entry for crypto → crypto-core
  • 2.6 Verify primitive surface is byte-identical: bunx turbo build --filter=@suaveplan/crypto-core and diff dist/index.d.ts against pre-rename snapshot

3. Implementation — @suaveplan/crypto (new opinionated layer)

  • 3.1 Scaffold packages/crypto/crypto/ from the package template (package.json, tsconfig.lib.json, vite.config.ts, vitest.config.ts, README.md, src/index.ts)
  • 3.2 Set package.json deps: crypto-core, crypto-utils, crypto-hash, crypto-curve, crypto-cipher, bitpacker, error, events, types, i18n, opentelemetry, @scure/bip39, zod
  • 3.3 Move crypto-keys/src/keypair/* → crypto/src/keychain/keypair/*
  • 3.4 Move crypto-keys/src/keychain/* → crypto/src/keychain/* (memory + serialized adapters)
  • 3.5 Move crypto-vault/src/mnemonic/* → crypto/src/vault/mnemonic/*
  • 3.6 Move crypto-vault/src/vault/* → crypto/src/vault/*
  • 3.7 Implement DerivationCoordinate (purpose ASCII[4] ‖ application ASCII[4] ‖ layoutId u8 ‖ <layout fields>) and DerivationLayout registry; ship DERIVATION_LAYOUT_V1 (u16 + u32 + u8 + u8 = 64 bits exactly)
  • 3.8 Implement open purpose registry seeded with the eight defaults (mast/encr/sign/kx/salt/hash/strm/dev); registerPurpose(tag) adds without wire-format break
  • 3.9 Implement 8-slot Keychain type using DerivationCoordinate per slot, plus {algorithm, notBefore, notAfter, secretKey?, publicKey?}
  • 3.10 Implement and commit wire-format schemas (DERIVATION_COORDINATE_SCHEMA, KEY_SLOT_PRIVATE_SCHEMA, KEY_SLOT_PUBLIC_SCHEMA, VAULT_PAYLOAD_SCHEMA, PUBLIC_BUNDLE_SCHEMA, CHAIN_BUNDLE_SCHEMA, CRL_ENTRY_SCHEMA, AUDIT_ENTRY_SCHEMA); export each from src/wire-format/; commit golden-bytes fixtures under test-fixtures/wire-format/
  • 3.11 Implement packPublicBundle / unpackPublicBundle (parent-signed under DOMAIN_PUBLIC_KEYCHAIN_V1; embedded issuerRef part of signed bytes)
  • 3.12 Implement packChainBundle / parseChainBundle / verifyChain(bundle, trustAnchor, clock, crl) walker (leaf-first concatenation; per-link validity-window, CRL, issuer-match, signature checks; max 16 links)
  • 3.13 Implement Authority interface and in-memory backend (InMemoryAuthority)
  • 3.14 Implement Packer / Unpacker (encrypt-then-sign envelope; domain-tag prefix; replay-window check)
  • 3.15 Implement ChunkedPacker / ChunkedUnpacker (per-chunk signature; reassembly with tamper detection)
  • 3.16 Implement Vault with three encryption regimes: (a) public bundle (plaintext, parent-signed); (b) private-key blob (password-AEAD, rotatable via changePassword without re-derivation); (c) vault payload (auth-box-sealed under keypair, parent-signed under DOMAIN_VAULT_V1, carries master/seedCount/keychain/data: Uint8Array); BIP39 + Argon2id; lock() zeroises; rotateApplicationData() re-seals payload only
  • 3.17 Implement RevocationList (root-signed CRL; append-only; verify-against-anchor)
  • 3.18 Implement AuditLog (append-only Merkle log; root-signed root; tamper-detection on past entries)
  • 3.19 Implement RootIdentity, ServerIdentity, createRoot, createServer, rollServer, revokeServer (replacing ChainSigner)
  • 3.20 Implement domain-separation tag constants as branded string types; signature paths require a tag
  • 3.21 Wire @suaveplan/error codes (one per failure class — see spec for full list)
  • 3.22 Wire OTel spans + counters + histograms via @suaveplan/opentelemetry; redaction-test asserts no key bytes in attributes
  • 3.23 Wire @suaveplan/events for pack/unpack/chunk/reassemble/issue/roll/revoke events
  • 3.24 Inject Clock and Rng (from @suaveplan/types) at every entry point; no Date.now() / Math.random() anywhere in src/
  • 3.25 Wire @suaveplan/i18n for all user-visible error messages
  • 3.26 Co-located .md per implementation file (≥ 200 words, basic + advanced examples)

4. Implementation — @suaveplan/crypto-cli (new)

  • 4.1 Scaffold packages/crypto/crypto-cli/ with package.json bin entry → dist/keychain.js
  • 4.2 Deps: @suaveplan/cli, @suaveplan/readline, @suaveplan/crypto, @suaveplan/error, @suaveplan/types, @suaveplan/i18n, @suaveplan/opentelemetry
  • 4.3 Implement keychain root init — generate root identity, write encrypted vault, emit public bundle
  • 4.4 Implement keychain server init — generate server identity under a root, emit signed public bundle, append to audit log
  • 4.5 Implement keychain server roll — version-bump rotation, signed by root, append CRL entry for prior version, append audit-log entry
  • 4.6 Implement keychain server revoke — append CRL entry, append audit-log entry, do not re-issue
  • 4.7 Implement keychain inspect — view a packed public keychain (verifies self-signature)
  • 4.8 Implement keychain verify — verify any signed envelope against a trust anchor + CRL
  • 4.9 Implement keychain export-public — strip secrets, output portable public bundle
  • 4.10 Implement keychain restore — re-derive a vault from BIP39 mnemonic + version
  • 4.11 TTY-only password input via cli's password prompt; --password flag rejected with a security-pointer error
  • 4.12 Stdin/stdout streamable for every command; --output json|binary|text
  • 4.13 Distinct exit codes: 0 success, 2 usage error, 10 bad password, 11 tampered vault, 12 expired key, 13 revoked key, 14 missing trust anchor, 15 audit-log mismatch
  • 4.14 --idempotency-key on server roll and server revoke; local-file ledger
  • 4.15 OTel spans + structured logger output for every command

5. Implementation — @suaveplan/slsa (modified)

  • 5.1 Replace direct crypto.ed25519.sign(secretKey, …) calls with Authority.sign(DOMAIN_SLSA_BUILDER_V1, …)
  • 5.2 Builder identity now constructed from a ServerIdentity issued under a SLSA-builder root
  • 5.3 Provenance test fixtures regenerated with new construction path; DSSE envelope output unchanged (asserted by round-trip equality test)
  • 5.4 @suaveplan/crypto added as workspace dep; @suaveplan/crypto (now crypto-core) for signing removed
  • 5.5 @suaveplan/crypto-hash and @suaveplan/crypto-utils retained — hashing + encoding helpers stay open
  • 5.6 New test tests/no-direct-crypto-core.test.ts pins import discipline

6. Anti-sediment enforcement

  • 6.1 Add scripts/conformance/crypto-core-allowlist.yml with two entries: @suaveplan/crypto, @suaveplan/crypto-cli
  • 6.2 Extend scripts/openspec-conformance.ts to fail with error severity when any package outside the allowlist imports cryptographic primitives from crypto-core / crypto-hash-as-MAC / crypto-curve / crypto-cipher (encoding helpers from crypto-utils and pure-hash uses from crypto-hash remain open)
  • 6.3 Test scripts/conformance/crypto-core-allowlist.test.ts exercises positive (allowed) and negative (forbidden) cases against fixture packages
  • 6.4 Wire-format KAT test at packages/crypto/crypto/tests/wire-format-kat.test.ts asserts golden bytes for: vault-v1, public-keychain-v1, packer-envelope-v1, chunk-v1, crl-v1, audit-entry-v1
  • 6.5 Property-based round-trip test at packages/crypto/crypto/tests/property-roundtrips.test.ts (fast-check)

7. Tests

  • 7.1 Unit tests at src/<feature>/<feature>.test.ts covering every scenario in the spec deltas
  • 7.2 Spec-conformance tests at src/<feature>/<feature>.spec-conformance.test.ts pinning type-level / observability / strict-mode / lifecycle scenarios
  • 7.3 Integration / contract / property tests where the spec demands them
  • 7.4 Every it(...) carries { timeout: 5000 } (or 30000 for genuinely async)
  • 7.5 Tampered-vault, tampered-chunk, expired-key, revoked-key scenarios each pinned by a named it(...)
  • 7.6 Replay-rejection test (old envelope, fresh verify) pinned

8. Documentation

  • 8.1 Sibling .md for every implementation .ts (≥200 words, basic + advanced examples, full API reference)
  • 8.2 README at packages/crypto/crypto/README.md links every co-located .md
  • 8.3 README at packages/crypto/crypto-cli/README.md lists every command with examples
  • 8.4 Update CLAUDE.md package listing: remove crypto-keys + crypto-vault, rename crypto → crypto-core, add crypto + crypto-cli
  • 8.5 Refresh MANIFEST.md via bun scripts/folder-reorg/generate-manifest.ts

9. Pre-archive verification (the 5-gate triple, per package touched)

For each of @suaveplan/crypto-core, @suaveplan/crypto, @suaveplan/crypto-cli, @suaveplan/slsa:

  • 9.1 bun run check — Biome 0 errors / 0 warnings / 0 info across the whole monorepo
  • 9.2 bunx turbo lint typecheck test --filter=@suaveplan/<pkg> — green
  • 9.3 bunx turbo test:coverage --filter=@suaveplan/<pkg> — 100/100/100/100
  • 9.4 bun scripts/openspec-conformance.ts @suaveplan/<pkg> — exit 0, no error findings
  • 9.5 bunx turbo build --filter=@suaveplan/<pkg> — green

10. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 10.1 Generics threaded; expectTypeOf assertions on every literal-type claim (slot map, algorithm tag, domain-separation tag)
  • 10.2 Zod schemas wired at every declared boundary (Keychain, Vault, packed envelopes, CRL entries, audit entries, CLI command args)
  • 10.3 Every error code in error-codes.ts thrown by at least one production path
  • 10.4 Strict-mode behaviours tested both on and off (algorithm allowlist, validity-window enforcement, replay-window enforcement)
  • 10.5 OTel instruments emitted from production paths and asserted with a real in-memory MeterProvider
  • 10.6 Lifecycle dedup tested where claimed (replay-window dedup of nonces; idempotency-key dedup in CLI)
  • 10.7 No retroactive tick — every box above reflects work done in this change

11. Archive

  • 11.1 Fold spec deltas into openspec/specs/crypto-core/spec.md, openspec/specs/crypto/spec.md, openspec/specs/crypto-cli/spec.md, openspec/specs/slsa/spec.md
  • 11.2 Delete openspec/specs/crypto-keys/ and openspec/specs/crypto-vault/ (folded packages)
  • 11.3 Move openspec/changes/restore-crypto-keychain-packer/ → openspec/archive/<yyyy-mm-dd>-restore-crypto-keychain-packer/
  • 11.4 MANIFEST.md refreshed and committed
  • 11.5 Root README.md updated to reflect new crypto package layout

Audit Note (2026-05-02)

DEFERRED in the genesis spec-cleanup pass on 2026-05-02. Rationale:

  • 101 tasks across multiple packages: rename @suaveplan/crypto → @suaveplan/crypto-core, introduce new @suaveplan/crypto top-layer (Packer/Unpacker/Authority/AuditLog/RevocationList/Keychain/Vault), modify @suaveplan/slsa, modify @suaveplan/bitpacker, and add new @suaveplan/crypto-cli package. Genuinely large architectural change requiring sustained focused work — not safe to bulk-approve under an audit pass. 0/101 done.