22 KiB
22 KiB
Tasks — @suaveplan/crypto-rsa (RSA primitives — RFC 8017 + RFC 5208 + RFC 7518)
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.
1. Spec authoring
- 1.1 Read
../suaveplan/enterprise/crypto-rsa.mdend-to-end - 1.2 Read RFC 8017 §4 (data conversion primitives), §5 (cryptographic primitives, incl. §5.1.2 CRT), §7 (RSAES encryption schemes), §8 (RSASSA signature schemes), §9 (encoding methods EMSA-PSS + EMSA-PKCS1-v1_5), §B.2.1 (MGF1), Appendix A (ASN.1 modules), Appendix C (test vectors)
- 1.3 Read RFC 5208 §5 (
PrivateKeyInfo) and RFC 5958 §2 (OneAsymmetricKeysuccessor) - 1.4 Read RFC 5280 §1.2 + §4.1 + Appendix A (RSA OIDs
1.2.840.113549.1.1.{1,5,10,11,12,13}+SubjectPublicKeyInfo) - 1.5 Read RFC 7517 §4.2 + §4.3 + §4.4 + §6.3, RFC 7518 §3.3 + §3.5 + §6.3, RFC 7638 §3.2 (JWK / JWA / Thumbprint)
- 1.6 Read RFC 7468 §3, §5, §10, §11 (PEM textual encoding for
PUBLIC KEY,RSA PRIVATE KEY,PRIVATE KEY) - 1.7 Read NIST FIPS 186-5 §5 + Appendix B.3.1 + Appendix A.1 (RSA DSA, probable-prime generation, key-pair parameters)
- 1.8 Read NIST SP 800-56B Rev. 2 §6.3 (key-pair generation guidance)
- 1.9 Read Google Wycheproof
rsa_signature_*.json,rsa_signature_pss_*.json,rsa_oaep_*.jsonschemas + a sample of theacceptabletest cases - 1.10 Read NIST CAVP RSA test-vector format (RSAVS / RSA2VS)
- 1.11 Read RFC 5990 (RSA-KEM) — confirm out-of-scope rationale
- 1.12 Read W3C Web Cryptography API §RSA-OAEP / RSASSA-PKCS1-v1_5 / RSA-PSS sections for subtle engine adapter
- 1.13 Read Bleichenbacher 1998 (CRYPTO '98), Manger 2001 (CRYPTO 2001), Bleichenbacher 2006 (signature forgery), Kocher 1996 (timing attacks) — confirm hardening approach
- 1.14 Read
openspec/AGENTS.mdSections 13, 14, 14a - 1.15 Read
CLAUDE.md(root + crypto family entries —crypto-cipher,crypto-curve,crypto-hash,crypto-utils,crypto-vault) - 1.16 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.17 Fill in
## Source mappingtable — every requirement cites an RFC / FIPS / NIST / Wycheproof /enterprise/crypto-rsa.mdline range - 1.18 Draft
specs/crypto-rsa/spec.mdfromopenspec/templates/spec-delta.md - 1.19 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles
2. Package scaffold
- 2.1
packages/crypto/crypto-rsa/directory +src/{asn1,keys,keygen,policy,math,encoding,sign,verify,encrypt,decrypt,jwk,fingerprint,pem,blinding,engine,handle,profile,errors,schemas,observability,__benchmarks__,__fixtures__}/skeleton - 2.2
package.json—@suaveplan/crypto-rsa@0.0.0,type: "module",sideEffects: false, deps pinned via workspace + catalog refs - 2.3
tsconfig.lib.jsonextends@suaveplan/typescript-config/library - 2.4
vite.config.ts+vitest.config.ts(usecreateTestConfig()from@suaveplan/testing/config;provider: 'istanbul'; thresholds 100/100/100/100) - 2.5
README.mdwith package description, install command, sign + verify quick-start, encrypt + decrypt quick-start, keygen quick-start, FIPS profile quick-start, Modules table linking every co-located.md - 2.6 Subpath exports map covers
.,./keys,./keygen,./sign,./verify,./encrypt,./decrypt,./encoding,./jwk,./fingerprint,./pem,./blinding,./engine,./handle,./profile,./policy,./schemas,./observability,./errors
3. Implementation — ASN.1 / DER
- 3.1
src/asn1/reader.ts—readInteger,readSequence,readObjectIdentifier,readBitString,readOctetString,readNull,readContextSpecific - 3.2
src/asn1/writer.ts— matching DER writer - 3.3 Sibling
.mdforreader.ts+writer.ts
4. Implementation — canonical key shapes
- 4.1
src/keys/keys.types.ts—RsaPublicKey { n, e, bitLength },RsaPrivateKey { n, e, d, p, q, dp, dq, qInv, bitLength } - 4.2
src/schemas/schemas.ts—RsaPublicKeySchema,RsaPrivateKeySchema,RsaParseOptionsSchema,RsaSerializeOptionsSchema,RsaSignOptionsSchema,RsaVerifyOptionsSchema,RsaPssOptionsSchema,RsaOaepOptionsSchema,HashAlgorithmSchema
5. Implementation — key parsing
- 5.1
src/keys/parse-pkcs1-public.ts— RFC 8017 Appendix A.1.1 - 5.2
src/keys/parse-pkcs1-private.ts— RFC 8017 Appendix A.1.2 - 5.3
src/keys/parse-spki.ts— RFC 5280 + RFC 8017 Appendix A.1.1; rejects non-rsaEncryptionOID - 5.4
src/keys/parse-pkcs8.ts— RFC 5208 / RFC 5958 - 5.5
src/keys/parse-jwk.ts— RFC 7517 §6.3; rejectskty !== "RSA" - 5.6
src/keys/parse.ts— façade dispatching on{ format }
6. Implementation — key serialisation
- 6.1
src/keys/serialize-pkcs1-public.ts - 6.2
src/keys/serialize-pkcs1-private.ts - 6.3
src/keys/serialize-spki.ts - 6.4
src/keys/serialize-pkcs8.ts - 6.5
src/keys/serialize-jwk.ts— base64url per RFC 7515 §2 (no padding) - 6.6
src/keys/serialize.ts— façade - 6.7 Round-trip invariant: parse → serialise → parse equals original
7. Implementation — modulus-length policy
- 7.1
src/policy/modulus.ts—enforceMinBitLength(key, { allowLegacy? }); default floor 2048;allowLegacy: truepermits 1024; below 1024 always rejects withCRYPTO_RSA.MODULUS_TOO_SHORT
8. Implementation — modular arithmetic
- 8.1
src/math/modpow.ts— wraps@noble/curves/abstract/modular.pow; NEVERBigInt ** BigInt % N - 8.2
src/math/i2osp.ts— Integer-to-Octet-String per RFC 8017 §4.1 - 8.3
src/math/os2ip.ts— Octet-String-to-Integer per RFC 8017 §4.2 - 8.4
src/math/crt.ts— RFC 8017 §5.1.2 second-form (m = m2 + h*q)
9. Implementation — MGF1 + encoding methods
- 9.1
src/encoding/mgf1.ts— RFC 8017 §B.2.1; throwsMGF_FAILUREonmaskLen > 2^32 * hLen - 9.2
src/encoding/emsa-pkcs1-v1_5.ts— RFC 8017 §9.2 with the four DigestInfo prefix constants (SHA-1 / 256 / 384 / 512) literal-byte verified - 9.3
src/encoding/emsa-pss-encode.ts— RFC 8017 §9.1.1 - 9.4
src/encoding/emsa-pss-verify.ts— RFC 8017 §9.1.2; constant-time compare via@suaveplan/crypto-utils
10. Implementation — signatures
- 10.1
src/sign/pkcs1v15.ts— RFC 8017 §8.2.1 (CRT-aware) - 10.2
src/verify/pkcs1v15.ts— RFC 8017 §8.2.2; constant-time encoded-message compare - 10.3
src/sign/pss.ts— RFC 8017 §8.1.1 with injectableRngfrom@suaveplan/types - 10.4
src/verify/pss.ts— RFC 8017 §8.1.2
11. Implementation — encryption
- 11.1
src/encrypt/pkcs1v15.ts— RFC 8017 §7.2.1 - 11.2
src/decrypt/pkcs1v15.ts— RFC 8017 §7.2.2; combinedCRYPTO_RSA.DECRYPT_FAILED(Bleichenbacher defence) - 11.3
src/encrypt/oaep.ts— RFC 8017 §7.1.1 - 11.4
src/decrypt/oaep.ts— RFC 8017 §7.1.2; combined error
12. Implementation — JWK thumbprint
- 12.1
src/jwk/thumbprint.ts— RFC 7638 §3.2 with RSA member orderinge, kty, n - 12.2 Worked-example match against RFC 7638 §3.1 reference
"NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
12a. Implementation — SPKI fingerprint
- 12a.1
src/fingerprint/spki.ts—computeSpkiFingerprint(publicKey, { hash? = "SHA-256" }): Uint8Array - 12a.2
src/fingerprint/format.ts—formatFingerprint(bytes, format)forhex/base64/base64url/colon-hex - 12a.3 Failure to derive SPKI bytes throws
CRYPTO_RSA.FINGERPRINT_FAILED
12b. Implementation — PEM framing (RFC 7468)
- 12b.1
src/pem/parse-public.ts— accepts-----BEGIN PUBLIC KEY-----(SPKI) +-----BEGIN RSA PUBLIC KEY-----(PKCS#1) - 12b.2
src/pem/parse-private.ts— accepts-----BEGIN PRIVATE KEY-----(PKCS#8) +-----BEGIN RSA PRIVATE KEY-----(PKCS#1) - 12b.3
src/pem/serialize-public.ts+src/pem/serialize-private.ts— emit 64-char-line base64 body per RFC 7468 §3 - 12b.4 Mismatched BEGIN / END, unknown label, or non-base64 body throws
CRYPTO_RSA.PEM_MALFORMED
12c. Implementation — key generation
- 12c.1
src/keygen/miller-rabin.ts—isProbablePrime(n, rng, rounds? = 64): booleanper NIST FIPS 186-5 Appendix B.3.1 - 12c.2
src/keygen/exponent.ts—validatePublicExponent(e)(odd,1 < e < 2^256, coprime withλ(n)) - 12c.3
src/keygen/generate.ts—generateRsaKeyPair({ modulusBits?, publicExponent?, rng, primes?, signal?, profile? }): Promise<RsaPrivateKey>; defaultsmodulusBits: 3072,publicExponent: 65537n,primes: 2,profile: "permissive" - 12c.4
src/keygen/multi-prime.ts— multi-prime support per RFC 8017 §3 (primes ∈ {3, 4, 5}); serialises to PKCS#1RSAPrivateKeyversion 1with non-emptyotherPrimeInfos - 12c.5 AbortSignal threaded into prime-search loop; abort interrupts within ≤ 100 ms wall-clock and throws
CRYPTO_RSA.ABORTED - 12c.6 Miller-Rabin exhaustion throws
CRYPTO_RSA.KEYGEN_FAILED - 12c.7 Deterministic mode produces byte-identical keys under identical seeded
rng
12d. Implementation — blinding (RFC 8017 §5.1.2)
- 12d.1
src/blinding/blinding.ts—blindMessage(m, key, rng): { mPrime, rInverse }+unblindResult(s, rInverse, key): bigint - 12d.2 Wired into every
rsaSign*/rsaDecrypt*call by default - 12d.3 Opt-out via
{ blinding: false }emitscrypto_rsa.blinding_disabled_total{operation}counter increment - 12d.4 Mitata bench
blinded_vs_unblinded_2048confirms < 5% overhead
12e. Implementation — RsaCryptoEngine adapter
- 12e.1
src/engine/engine.types.ts—RsaCryptoEngineinterface (sign,verify,encrypt,decrypt,modPow) - 12e.2
src/engine/noble.ts— default engine over@noble/curves/abstract/modular.pow - 12e.3
src/engine/subtle.ts—globalThis.crypto.subtleadapter; non-extractable keys by default;{ extractable: true }opt-in emits warning counter - 12e.4
src/engine/select.ts—defaultEngine(operation, key)auto-selects subtle when available else noble; emitscrypto_rsa.engine_selected_total{engine} - 12e.5
subtlerequested but unavailable throwsCRYPTO_RSA.ENGINE_UNAVAILABLE
12f. Implementation — HSM / PKCS#11 bridge
- 12f.1
src/handle/handle.types.ts—RsaKeyHandle+RsaKeyHandleProviderinterfaces - 12f.2
src/handle/registry.ts—registerKeyHandleProvider(provider)+resolveKeyHandle(key) - 12f.3
src/handle/software.ts—SoftwareRsaKeyHandlereference implementation
12g. Implementation — FIPS 186-5 profile
- 12g.1
src/profile/profile.ts—enforceProfile(input, { profile })withpermissiveandfips-186-5modes - 12g.2
fips-186-5rejectsmodulusBits ∉ {2048, 3072, 4096},publicExponent ∉ [2^16+1, 2^256-1]or even, PSSsaltLength ≠ hLen,hash === "SHA-1"in sign / verify — each throwsCRYPTO_RSA.PROFILE_VIOLATIONwithruleattribute
12h. Implementation — JWK key_ops / use / alg enforcement
- 12h.1
src/keys/jwk-policy.ts—enforceJwkPolicy(jwk, operation) - 12h.2 Wired into every public surface (
rsaSign*,rsaVerify*,rsaEncrypt*,rsaDecrypt*) - 12h.3 Cross-policy violation throws
CRYPTO_RSA.KEY_OP_NOT_PERMITTED
12i. Implementation — modulus-size DoS guard
- 12i.1
src/policy/modulus.tsextended withenforceMaxBitLength(key, { allowOversize? })rejectingbitLength > 16384withCRYPTO_RSA.MODULUS_TOO_LARGE
13. Implementation — errors + observability
- 13.1
src/errors/errors.ts—defineCodes("CRYPTO_RSA", ["MODULUS_TOO_SHORT", "MODULUS_TOO_LARGE", "INVALID_KEY", "INVALID_SIGNATURE", "SIGNATURE_INVALID", "UNSUPPORTED_HASH", "UNSUPPORTED_FORMAT", "DECRYPT_FAILED", "MGF_FAILURE", "KEYGEN_FAILED", "PRIME_INVALID", "KEY_OP_NOT_PERMITTED", "PROFILE_VIOLATION", "ENGINE_UNAVAILABLE", "PEM_MALFORMED", "FINGERPRINT_FAILED", "ABORTED"]);CryptoRsaError extends SuaveplanError<CryptoRsaErrorCode> - 13.2 Every code (17) thrown by ≥1 production path in
src/ - 13.3
src/observability/metrics.tsemits:- histograms
crypto_rsa.verify_duration_ms{scheme,hash,engine}/sign_duration_ms{scheme,hash,engine}/encrypt_duration_ms{scheme,hash,engine}/decrypt_duration_ms{scheme,hash,engine}/keygen_duration_ms{modulus_bits,primes}/modpow_duration_ms{operation,blinded} - counters
crypto_rsa.outcome_total{operation,outcome}/crypto_rsa.errors_total{code}/crypto_rsa.engine_selected_total{engine}/crypto_rsa.fingerprint_total{format}/crypto_rsa.profile_violation_total{rule}/crypto_rsa.blinding_disabled_total{operation}
- histograms
14. Root barrel + subpath exports
- 14.1
src/index.ts— explicit named re-exports (noexport *) - 14.2
package.jsonexports map matches subpath plan in §2.6 - 14.3 Verify subpath count matches feature module count (§13 step 8)
15. Test-vector generator + fixtures
- 15.1
scripts/generate-rsa-vectors.ts— deterministic generator producing PKCS1v15 signature / PSS signature / OAEP encrypt vectors against a fixed test key - 15.2 RFC 8017 Appendix C vectors committed under
src/__fixtures__/rfc8017-appendix-c/ - 15.3 NIST CAVP RSA vectors committed under
src/__fixtures__/cavp/(sign / verify / encrypt) sourced from https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/digital-signatures - 15.4 Google Wycheproof vectors committed under
src/__fixtures__/wycheproof/(rsa_signature_*.json,rsa_signature_pss_*.json,rsa_oaep_*.json) sourced from https://github.com/google/wycheproof/tree/master/testvectors - 15.5 Generator idempotent — re-running produces byte-identical output
16. Tests (runner-neutral imports — @suaveplan/testing/runner)
- 16.1
keys/parse-pkcs1-public.test.ts— RFC 8017 Appendix A.1.1 round-trip; malformed →INVALID_KEY - 16.2
keys/parse-pkcs1-private.test.ts— Appendix A.1.2 round-trip - 16.3
keys/parse-spki.test.ts—rsaEncryptionOID accept; wrong OID reject - 16.4
keys/parse-pkcs8.test.ts— RFC 5208 + RFC 5958 shapes accept - 16.5
keys/parse-jwk.test.ts—kty: "RSA"accept;kty: "EC"reject (UNSUPPORTED_FORMAT) - 16.6
keys/serialize-*.test.ts— every parse path's inverse, byte-exact - 16.7
keys/roundtrip.spec-conformance.test.ts— property: random valid key → serialise → parse → equal - 16.8
policy/modulus.test.ts— 2048-bit accept; 1024-bit reject under defaults; 1024-bit accept underallowLegacy: true; 512-bit always reject - 16.9
math/modpow.test.ts— matches naiveBigIntreference on small inputs; source-grep gateBigInt \*\*returns zero - 16.10
math/crt.test.ts— CRT output equals naivec^d mod non Appendix C vectors - 16.11
encoding/mgf1.test.ts— Appendix CMGF1outputs match; oversizemaskLenthrowsMGF_FAILURE - 16.12
encoding/emsa-pkcs1-v1_5.test.ts— SHA-1 / 256 / 384 / 512 DigestInfo prefixes match RFC 8017 §9.2 note 1 byte-exactly - 16.13
encoding/emsa-pss-encode.test.ts+encoding/emsa-pss-verify.test.ts— Appendix C round-trip; bit-flipped encoding rejects - 16.14
verify/pkcs1v15.test.ts— Appendix C accept; SHA-384 / 512 / 1 accept; bit-flipped reject - 16.15
verify/pss.test.ts— Appendix C accept; customsaltLength; custommgfHash; bit-flipped reject - 16.16
sign/pkcs1v15.test.ts— Appendix C reference signature matches (CRT path) - 16.17
sign/pss.test.ts— round-trip across SHA-256 / 384 / 512; deterministic with fixed RNG - 16.18
encrypt/pkcs1v15.test.ts+decrypt/pkcs1v15.test.ts— Appendix C round-trip; bad padding →DECRYPT_FAILEDindistinguishable from value-error - 16.19
encrypt/oaep.test.ts+decrypt/oaep.test.ts— Appendix C round-trip; non-emptylabelround-trip - 16.20
jwk/thumbprint.test.ts— RFC 7638 §3.1 worked example matches - 16.21
fingerprint/spki.test.ts+fingerprint/format.test.ts— SHA-256 over RFC 8017 Appendix C SPKI; four output formats produce expected strings - 16.22
pem/parse-public.test.ts+pem/parse-private.test.ts+pem/serialize-public.test.ts+pem/serialize-private.test.ts— all four armor labels round-trip; malformed armor →PEM_MALFORMED - 16.23
keygen/miller-rabin.test.ts— known primes accepted, composites rejected;rounds: 64honoured - 16.24
keygen/generate.test.ts— sign-then-verify round-trip onmodulusBits ∈ {2048, 3072}; deterministic mode byte-identical under seeded RNG; AbortSignal interrupts within 100 ms →ABORTED; Miller-Rabin exhaustion →KEYGEN_FAILED - 16.25
keygen/multi-prime.test.ts—primes: 3produces valid 3-prime key with non-emptyotherPrimeInfos; sign-then-verify round-trip - 16.26
keygen/exponent.test.ts— evenerejects;e === 2nrejects;e === 65537naccepts - 16.27
blinding/blinding.test.ts—blindMessage→ private-op →unblindResultequals naive private-op on Appendix C ciphertexts; opt-out emitsblinding_disabled_totalincrement - 16.28
engine/select.test.ts— subtle selected when present; noble fallback when absent; explicitsubtleagainst unavailable →ENGINE_UNAVAILABLE - 16.29
engine/noble.test.ts+engine/subtle.test.ts— both engines produce verify-equivalent outputs on Appendix C inputs - 16.30
handle/registry.test.ts+handle/software.test.ts— provider register / resolve round-trip;SoftwareRsaKeyHandlematches direct call - 16.31
profile/profile.test.ts—fips-186-5rejects 1024-bit,e = 3,hash: "SHA-1"in sign, PSSsaltLength: 0;permissiveaccepts all - 16.32
keys/jwk-policy.test.ts—key_ops/use/algcross-policy rejects withKEY_OP_NOT_PERMITTED - 16.33
policy/modulus-max.test.ts— 16384-bit accepts; 32768-bit rejects withMODULUS_TOO_LARGE; 32768-bit accepts underallowOversize: true - 16.34
wycheproof/replay.test.ts— everyvalidaccepts; everyinvalidrejects; everyacceptabledocumented - 16.35
cavp/replay.test.ts— every NIST CAVP RSA vector replayed - 16.36
errors/errors.test.ts— every code (17) in the registry thrown by ≥1 production path (audit table) - 16.37
observability/metrics.test.ts— every histogram + counter asserted with real in-memoryMeterProvider, includingengine_selected_total,fingerprint_total,profile_violation_total,blinding_disabled_total - 16.38
schemas/schemas.test.ts— Zod boundary rejects malformed inputs with the right error code - 16.39
crypto-rsa.spec-conformance.test.ts— Appendix C end-to-end; property test (sign / verify, encrypt / decrypt, parse / serialise round-trips across every format); verifiable-randomness determinism property;BigInt \*\*grep gate; every "MUST reject" path - 16.40
index.test.ts— every documented subpath resolves - 16.41 Every
it(...)carries{ timeout: 5000 }(or 30000 for 4096-bit modpow / keygen)
17. Documentation
- 17.1 Sibling
.mdfor every implementation.ts(≥200 words, basic + advanced example, full API reference) - 17.2 README links every co-located
.mdunder## Modules - 17.3 Update root
CLAUDE.mdpackage listing under "Crypto family"
18. Benchmarks
- 18.1
src/__benchmarks__/crypto-rsa.bench.ts— mitata covering:verify_pkcs1_sha256_{2048,3072,4096} × engine ∈ {noble, subtle}verify_pss_sha256_{2048,3072,4096} × enginesign_pkcs1_sha256_{2048,3072,4096} × enginesign_pss_sha256_{2048,3072,4096} × engineencrypt_oaep_sha256_{2048,3072,4096} × enginedecrypt_oaep_sha256_{2048,3072,4096} × enginegenerate_{2048,3072,4096}(noble only)parse_{pkcs1,pkcs8,spki,jwk,pem}_2048crt_vs_naive_2048ratioblinded_vs_unblinded_2048ratio
- 18.2 Baseline numbers published in README
- 18.3 CRT path measurably ≥ 2× faster than non-CRT path on the 2048-bit sign benchmark
- 18.4 Blinding overhead < 5% of unblinded private-op latency on the 2048-bit bench
19. Pre-archive verification (the 5-gate triple)
- 19.1
bun run check— Biome 0 errors / 0 warnings / 0 info - 19.2
bunx turbo lint typecheck test --filter=@suaveplan/crypto-rsa— green - 19.3
bunx turbo test:coverage --filter=@suaveplan/crypto-rsa— 100/100/100/100 - 19.4
bun scripts/openspec-conformance.ts @suaveplan/crypto-rsa— exit 0, noerrorfindings - 19.5
bunx turbo build --filter=@suaveplan/crypto-rsa— green
20. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 20.1 Generics threaded;
expectTypeOfassertions onRsaPublicKey/RsaPrivateKeydiscriminants and on options-object literal types - 20.2 Zod schemas wired at every declared boundary (
RsaPublicKeySchema,RsaPrivateKeySchema, every options schema) - 20.3 Every error code in
errors/errors.tsthrown by at least one production path - 20.4 Strict-mode behaviors tested both on and off (
allowLegacymodulus-policy on + off; CRT vs naive private-op equivalence) - 20.5 OTel instruments emitted from production paths and asserted with a real in-memory
MeterProvider - 20.6 Lifecycle dedup — N/A (sign / verify / encrypt / decrypt are stateless; document explicitly)
- 20.7 No retroactive tick — every box above reflects work done in this change
21. Root-doc refresh + markdownlint (§13b)
Deferred — root docs (
MANIFEST.md,MISSING.yml,STATUS.md,SUMMARY.md,CLAUDE.md) are refreshed by the orchestrator after parallel crypto-family migrations land.
- 21.1
bun scripts/folder-reorg/generate-manifest.ts— refreshMANIFEST.md(orchestrator) - 21.2
bun scripts/generate-missing.ts— refreshMISSING.yml(orchestrator) - 21.3
STATUS.md— movecrypto-rsafrom Pending → Completed (orchestrator) - 21.4
SUMMARY.md— add per-package paragraph (orchestrator) - 21.5
bunx markdownlint-cli2 --fix— apply auto-fixes (orchestrator) - 21.6
bunx markdownlint-cli2(no --fix) — exit 0 (orchestrator)
22. Archive
- 22.1 Fold spec delta into
openspec/specs/crypto-rsa/spec.md - 22.2 Move
openspec/changes/enterprise-crypto-rsa/→openspec/archive/<yyyy-mm-dd>-enterprise-crypto-rsa/ - 22.3 Refresh
MANIFEST.mdif package set or deps changed (orchestrator)