Files
suaveplan/openspec/archive/2026-05-17-add-crypto-keys/tasks.md
2026-05-27 23:29:53 +01:00

9.5 KiB
Raw Blame History

Tasks — crypto-keys

Every checkbox MUST reflect work actually done in this change.

1. Spec authoring

  • 1.1 Read openspec/AGENTS.md §0, §0a, §0b, §0c, §6a, §7, §13, §14 end-to-end
  • 1.2 Read ../suaveplan/packages/universal/keys/src/lib/chain-signer.ts in full
  • 1.3 Read ../suaveplan/packages/universal/keys/src/lib/keys-common.ts (factory section)
  • 1.4 Read ../suaveplan/enterprise/keys.md to confirm scope boundary
  • 1.5 Read genesis packages/crypto/crypto/src/keychain/keychain.ts and keychain/index.ts (public surface)
  • 1.6 Skim every packages/crypto/crypto-curve/src/primitives/*/impl/*.noble/*.ts and confirm sign/verify signature
  • 1.7 Draft proposal.md with scope, divergence table, source mapping, affected dependents
  • 1.8 Draft design.md covering file layout, three codecs, three hashers, algorithm dispatch, error-code map, factory layer
  • 1.9 Draft specs/crypto-keys/spec.md with ### Requirement: / #### Scenario: / **Verified by:** form
  • 1.10 Run openspec validate add-crypto-keys and fix any structural failures

2. Scaffolding

  • 2.1 Create packages/crypto/crypto-keys/ mirroring packages/database/seed/ layout
  • 2.2 Write package.json v0.0.0, sideEffects: false, subpath exports for every feature module (error-codes/error-codes, types/{chain-level,hasher,codec}, hashers/{raw,domain,merkle}/, codecs/{tlv,recursive-ed25519,cbor}/, signer/signer, factory/factory)
  • 2.3 Wire deps: @suaveplan/crypto: workspace:^, @suaveplan/crypto-curve: workspace:^, @suaveplan/crypto-hash: workspace:^, @suaveplan/cbor: workspace:^, @suaveplan/error: workspace:^, @suaveplan/types: workspace:^, tslib: catalog:build
  • 2.4 Wire devDeps: @suaveplan/testing: workspace:^, @types/node: catalog:, @vitest/coverage-istanbul: catalog:testing, fast-check: catalog:testing, typescript: catalog:, vite: catalog:build, vite-plugin-dts: catalog:build, vitest: catalog:testing
  • 2.5 Write tsconfig.json, tsconfig.lib.json extending the genesis base
  • 2.6 Write vite.config.ts (multi-entry, ES-only, preserveModules)
  • 2.7 Write vitest.config.ts using a project-local config with pool: 'forks' + 100% Istanbul thresholds
  • 2.8 Write tests/setup/polyfills.ts (universal tier, no polyfills needed — same pattern as seed)
  • 2.9 Write README.md (≥ 200 words, all 6 required sections per CLAUDE.md docs gate)

3. Implementation — errors & shared types

  • 3.1 src/error-codes/error-codes.ts — defineCodes("CHAIN_KEYS", [...]) with 7 codes; ChainKeysError extends SuaveplanError<ChainKeysErrorCode> with depth? / algorithm? discriminants
  • 3.2 src/error-codes/error-codes.test.ts — every code reachable from a ChainKeysError construction; cause chain preservation; discriminants populate
  • 3.3 src/error-codes/error-codes.md — full doc with required sections
  • 3.4 src/error-codes/index.ts — explicit named re-exports
  • 3.5 src/types/chain-level.ts — ChainLevel, VerificationResult, ParsedChainLevel interfaces; chain-level.md sibling
  • 3.6 src/types/hasher.ts — ChainHasher interface (hash, name); hasher.md sibling
  • 3.7 src/types/codec.ts — ChainCodec interface (encode, decode, name); codec.md sibling

4. Implementation — hashers (3)

  • 4.1 src/hashers/raw/raw.ts — RawHasher implements ChainHasher (identity hash)
  • 4.2 src/hashers/raw/raw.test.ts — identity property; ignores parent; name is "raw"
  • 4.3 src/hashers/raw/raw.md + index.ts
  • 4.4 src/hashers/domain/domain.ts — DomainHasher implements ChainHasher with constructor-supplied domainTag (1–64 ASCII bytes); raises CHAIN_KEYS.INVALID_DOMAIN_TAG
  • 4.5 src/hashers/domain/domain.test.ts — sha256(utf8(tag) || encodedKeys); constructor validation (empty / oversize); ignores parent; name is "domain"
  • 4.6 src/hashers/domain/domain.md + index.ts
  • 4.7 src/hashers/merkle/merkle.ts — MerkleHasher implements ChainHasher; root hash without parent, parent root hash for non-root
  • 4.8 src/hashers/merkle/merkle.test.ts — root path; non-root commits to parent; tampering with parent invalidates child
  • 4.9 src/hashers/merkle/merkle.md + index.ts

5. Implementation — codecs (3)

  • 5.1 src/codecs/tlv/tlv.ts — TlvChainCodec implements ChainCodec; [version=1][hasher][per level: algorithm + keys_len:u16 + keys + sig_len:u16 + sig]; algorithm-id ↔ enum byte table; hasher-name ↔ enum byte table; raises CHAIN_KEYS.INVALID_FORMAT on bad version/truncation/length overrun
  • 5.2 src/codecs/tlv/tlv.test.ts — encode/decode round-trip for every algorithm; truncated buffer; bad version byte; length overrun; multi-level chain
  • 5.3 src/codecs/tlv/tlv.md + index.ts
  • 5.4 src/codecs/recursive-ed25519/recursive-ed25519.ts — RecursiveEd25519Codec implements ChainCodec; fixed keys_size = 8 * KEY_SLOT_PUBLIC_BYTE_SIZE; rejects non-Ed25519 at encode (CHAIN_KEYS.UNSUPPORTED_ALGORITHM)
  • 5.5 src/codecs/recursive-ed25519/recursive-ed25519.test.ts — Ed25519 round-trip; non-Ed25519 rejection; multi-level recursion; bad version
  • 5.6 src/codecs/recursive-ed25519/recursive-ed25519.md + index.ts
  • 5.7 src/codecs/cbor/cbor.ts — CborChainCodec implements ChainCodec; CBOR map { v, h, levels: [{ a, k, s }] }; deterministic encoder; rejects unknown h/a strings
  • 5.8 src/codecs/cbor/cbor.test.ts — encode/decode round-trip; deterministic byte equality; bad CBOR; missing fields
  • 5.9 src/codecs/cbor/cbor.md + index.ts

6. Implementation — ChainedKeySigner

  • 6.1 src/signer/signer.types.ts — ISigner / SignerOptions / SignerSignOptions interfaces
  • 6.2 src/signer/signer.ts — ChainedKeySigner class:
    • constructor { hasher?, codec? } defaults RawHasher + TlvChainCodec
    • sign(publicKeys, signSlot, parent?) — encodes public keychain, computes bytes-to-sign via hasher (passing parsed parent's leaf level if applicable), dispatches to @suaveplan/crypto-curve sign by signSlot.algorithm, prepends new level via codec
    • verify(chainBytes, trustedRoot) — decodes via codec, walks every level computing the same bytes-to-sign, dispatches to crypto-curve verify, checks deepest signer matches trustedRoot.public via constant-time compare, returns { valid, depth, levels, errors }
    • parse(chainBytes) — decode-only
    • stripToDepth(chainBytes, depth) — re-encode the leaf-most depth levels via codec
    • Wraps any crypto-curve exception as ChainKeysError with cause preserved
  • 6.3 src/signer/signer.test.ts — every public method × every (hasher, codec) combination × every algorithm; tampering tests at every byte boundary; abort signal future-proofing (omitted intentionally; sign is in-memory)
  • 6.4 src/signer/signer.md + index.ts

7. Implementation — hierarchical factory

  • 7.1 src/factory/factory.ts — createRootKeysWithChain, createServerKeysWithChain, rollServerKeysWithChain; all default to new ChainedKeySigner() when omitted; raise CHAIN_KEYS.INVALID_KEYCHAIN when rootKeys.master/rootKeys.sign lacks private material
  • 7.2 src/factory/factory.test.ts — 3-level chain (root → server → user); verify against root; tamper at server level → fails; missing root.master / root.sign → INVALID_KEYCHAIN; injected signer with non-default hasher works end-to-end
  • 7.3 src/factory/factory.md + index.ts

8. Root barrel & public surface

  • 8.1 src/index.ts — re-export every public symbol via explicit named exports (no export * — rule §6a)
  • 8.2 Verify bunx turbo build --filter=@suaveplan/crypto-keys succeeds and dist contains every advertised subpath

9. Pre-archive gates

  • 9.1 bunx turbo lint --filter=@suaveplan/crypto-keys green; zero biome suppressions
  • 9.2 bunx turbo typecheck --filter=@suaveplan/crypto-keys green
  • 9.3 bunx turbo test --filter=@suaveplan/crypto-keys green; zero .skip / .only / it.todo
  • 9.4 bunx turbo test:coverage --filter=@suaveplan/crypto-keys green at 100% statement/branch/function/line (Istanbul)
  • 9.5 bun run docs:check --filter=@suaveplan/crypto-keys green
  • 9.6 grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/crypto/crypto-keys/src/ → zero matches (rule §7)
  • 9.7 grep -rnE "^export \*" packages/crypto/crypto-keys/src/ → only the root barrel and exempt forms (rule §6a)
  • 9.8 grep -rnE "from ['\"](node:|bun:|fs|path|stream|net|http)['\"]" packages/crypto/crypto-keys/src/ → zero matches (universal tier)
  • 9.9 grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\\.skip\|\\.only\|\\bany\\b\|console\\.log" packages/crypto/crypto-keys/src/ --include="*.ts" → zero matches
  • 9.10 Every ### Requirement in specs/crypto-keys/spec.md checked off against the implementation
  • 9.11 bun scripts/folder-reorg/generate-manifest.ts; MANIFEST.md includes @suaveplan/crypto-keys
  • 9.12 STATUS.md row added or updated — entry under crypto plugin, status ✅

10. Fold + archive

  • 10.1 Fold specs/crypto-keys/spec.md into openspec/specs/crypto-keys/spec.md (new canonical capability; convert ## ADDED Requirements → ## Requirements and prepend ## Purpose)
  • 10.2 openspec validate crypto-keys clean
  • 10.3 Move openspec/changes/add-crypto-keys/ → openspec/archive/$(date +%Y-%m-%d)-add-crypto-keys/
  • 10.4 Commit per CLAUDE.md commit-style; do not push without user approval