21 KiB
21 KiB
Tasks — @suaveplan/cbor (RFC 8949 codec + RFC 9052 COSE bridge)
Every checkbox below MUST reflect work actually done in this change. Do not retroactively tick boxes. 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/cbor.mdend-to-end - 1.2 Read RFC 8949 (CBOR), RFC 9052 (COSE), RFC 8742 (CBOR Sequences), draft-mcnally-deterministic-cbor (dCBOR) end-to-end
- 1.3 Read
openspec/AGENTS.mdSections 13, 14, 14a - 1.4 Confirm
proposal.mdcovers every public-surface item, every subpath, and every dependency - 1.5 Confirm
specs/cbor/spec.mdcarries one requirement per logical capability with ≥2 scenarios each - 1.6 Confirm every requirement has a
**Verified by:**line naming concreteit(...)titles in concrete test file paths - 1.7 Source-mapping table cites every RFC section consumed
2. Package scaffold
- 2.1 Create
packages/vault/cbor/withsrc/{encoder,decoder,streaming,sequences,tags,cose,cwt,cddl,diagnostic,errors,observability,schemas,__benchmarks__}/ - 2.2
package.json—@suaveplan/cbor@0.0.0,type: module,sideEffects: false, deps include@suaveplan/error,@suaveplan/types,@suaveplan/crypto-curve,@suaveplan/crypto-cipher,@suaveplan/crypto-hash,@opentelemetry/api,tslib,zodvia catalog refs only (no bare versions) - 2.3
package.jsonexportsmap declares.,./encoder,./decoder,./streaming,./sequences,./tags,./deterministic,./cose,./cwt,./cddl,./diagnostic,./errors(12 subpaths) — each mapped to its builtdist/<feature>/<feature>.js+.d.tspair - 2.4
tsconfig.lib.jsonextends@suaveplan/typescript-config/library.json - 2.5
vite.config.tswith multi-entry build (one entry per subpath) +vitest.config.tscallingcreateTestConfig()from@suaveplan/testing/config - 2.6
README.mdwith package name, description,bun addinstall, quick-start example, Modules table linking every co-located.md
3. Implementation — encoder
- 3.1
src/encoder/encoder.ts—Encoderclass +encode(value, options?): Uint8Arrayfor major types 0–7 (RFC 8949 §3.1) - 3.2 Preferred-serialization shortest-form integer rule (RFC 8949 §4.2.1) — always-on
- 3.3
src/encoder/indefinite.ts— opt-in indefinite-length arrays / maps / byte strings / text strings (RFC 8949 §3.2) - 3.4
src/encoder/half-float.ts— IEEE-754 binary16 encode (Major 7 / value 25); opt-in viaEncoderOptions.preferFloat16 - 3.5
src/encoder/deterministic.ts— RFC 8949 §4.2 deterministic profile (sorted map keys via lexicographic comparison over canonical encoded key bytes; shortest-form ints; no indefinite-length; no half-float) - 3.6
src/encoder/dcbor.ts— dCBOR profile under./deterministicsubpath; reduction rules; rejectsundefined - 3.7
src/encoder/encoder.types.ts—EncoderOptions,IndefiniteLengthMarker, related types
4. Implementation — decoder
- 4.1
src/decoder/decoder.ts—Decoderclass +decode(bytes, options?): unknownfor every major type - 4.2
src/decoder/well-formed.ts—isWellFormed(bytes)per RFC 8949 §5.1; trailing-byte rejection in strict mode - 4.3
src/decoder/half-float.ts— binary16 decode (always available) - 4.4
src/decoder/decoder.types.ts—DecoderOptions,Tagged<T>,DecoderMode - 4.5 Lax-mode opt-in (
mode: "lax") per RFC 8949 §5.2 basic validity only
5. Implementation — streaming pull-decoder
- 5.1
src/streaming/pull-decoder.ts—PullDecoderoverUint8ArrayandAsyncIterable<Uint8Array>/ReadableStream<Uint8Array>exposingnext(): Promise<IteratorResult<unknown>> - 5.2 Bounded memory — heap delta under fixed cap regardless of stream size; verified by test
- 5.3
AbortSignalhonored —next()rejects withCBOR.MALFORMEDwhen signal aborts mid-item
6. Implementation — sequences (RFC 8742)
- 6.1
src/sequences/sequences.ts—encodeSequence(items, options?): Uint8Array,decodeSequence(bytes, options?): unknown[],decodeSequenceStream(stream, options?): AsyncIterable<unknown> - 6.2 Empty-sequence handling (returns empty
Uint8Array/ empty array) - 6.3 Strict-mode rejects partial trailing bytes; lax-mode tolerates and stops
7. Implementation — tag registry
- 7.1
src/tags/registry.ts—TagRegistrywithregister/unregister/lookup; double-register throws - 7.2
src/tags/tagged.ts—Tagged<T>value object - 7.3 Built-in tag handlers for 0 (date/time), 1 (epoch), 2 (positive bignum), 3 (negative bignum), 21 (base64url), 22 (base64), 23 (base16), 24 (encoded CBOR — re-enters decoder), 32 (URI), 35 (regex), 55799 (self-describe)
- 7.4 Unknown tag in strict mode returns
Tagged{ tag, value }rather than throwing (RFC 8949 §3.4 — unknown tags MUST NOT cause decode failure)
8. Implementation — COSE bridge (./cose subpath)
- 8.1
src/cose/cose-key.ts—decodeCoseKey(bytes): CoseKeyreturningOkpKey | Ec2Key | RsaKey | SymmetricKeyper RFC 9052 §7 - 8.2
src/cose/jwk.ts—coseKeyToJwk(key): JsonWebKeyandjwkToCoseKey(jwk): CoseKeyround-trip - 8.3
src/cose/alg-map.ts—coseAlgToCryptoAlg(alg)mapping IANA COSE alg labels to@suaveplan/crypto-curvealgorithm IDs (-7 ES256, -8 EdDSA, -35 ES384, -36 ES512, -257 RS256, …) - 8.4
src/cose/cose.types.ts—CoseKeydiscriminated union bykty;CoseAlg,CoseKty,CoseCurve,CoseKeyOpliteral unions - 8.5
src/cose/headers.ts— typedCoseHeadermap + label registry (RFC 9052 §3.1:alg1,crit2,content type3,kid4,IV5,Partial IV6,counter signature7); rejects malformed withCOSE_HEADER_INVALID - 8.6
src/cose/sig-structure.ts—buildSigStructure(context, protectedHeader, externalAad, payload)per RFC 9052 §4.4 covering all five contexts (Signature,Signature1,CounterSignature,MAC,MAC0); deterministic-encoded - 8.7
src/cose/sign1.ts—encodeCoseSign1/decodeCoseSign1/verifyCoseSign1(RFC 9052 §4.2, CBOR tag 18); wires@suaveplan/crypto-curveECDSA / EdDSA verifiers - 8.8
src/cose/sign.ts—encodeCoseSign/decodeCoseSign(RFC 9052 §4.1, CBOR tag 98); per-signer header carriage;verifyCoseSign(msg, key)returns matching signer index - 8.9
src/cose/encrypt0.ts—encodeCoseEncrypt0/decodeCoseEncrypt0/decryptCoseEncrypt0(RFC 9052 §5.2, CBOR tag 16); wires AES-GCM / ChaCha20-Poly1305 via@suaveplan/crypto-cipher - 8.10
src/cose/encrypt.ts—encodeCoseEncrypt/decodeCoseEncrypt(RFC 9052 §5.1, CBOR tag 96); per-recipient KEK lookup - 8.11
src/cose/mac0.ts—encodeCoseMac0/decodeCoseMac0/verifyCoseMac0(RFC 9052 §6.2, CBOR tag 17); wires HMAC-SHA-{256,384,512} via@suaveplan/crypto-hash - 8.12
src/cose/mac.ts—encodeCoseMac/decodeCoseMac(RFC 9052 §6.1, CBOR tag 97); multi-recipient MAC
8a. Implementation — CWT (./cwt subpath, RFC 8392)
- 8a.1
src/cwt/claims.ts— typedCwtClaimsdiscriminator; IANA labels 1–7 (isssubaudexpnbfiatcti) +private[label]slot - 8a.2
src/cwt/encode.ts—encodeCwt(claims, options): Uint8Arraywith deterministic profile;Date↔ epoch via tag 1 - 8a.3
src/cwt/decode.ts—decodeCwt(bytes): CwtClaims; rejects malformed withUNEXPECTED_TYPE - 8a.4
src/cwt/verify.ts—verifyCwt(token, key, options): Promise<CwtVerifyResult>orchestratingverifyCoseSign1/verifyCoseMac0plus time-window check via injectableClock; throwsCWT_EXPIRED/CWT_NOT_YET_VALID/CWT_AUDIENCE_MISMATCH/CWT_ISSUER_MISMATCH/CWT_SIGNATURE_INVALID - 8a.5
src/cwt/cwt.types.ts—CwtClaims,CwtVerifyOptions,CwtVerifyResult,CwtPrivateClaim
8b. Implementation — CDDL (./cddl subpath, RFC 8610)
- 8b.1
src/cddl/lexer.ts— RFC 8610 ABNF token stream (identifiers, literals, control operators, comments, ranges) - 8b.2
src/cddl/parser.ts—parseCddl(source): CddlAstcovering every RFC 8610 §3 production; rejects malformed source withCDDL_PARSE_FAILEDcarrying{ line, column } - 8b.3
src/cddl/ast.ts— typed AST:CddlAst,CddlRule,CddlType,CddlGroup,CddlOccurrence,CddlChoice,CddlControl,CddlGenericParam - 8b.4
src/cddl/built-ins.ts— RFC 8610 §3.8 prelude (uint,nint,int,bstr,tstr,bool,nil,null,undefined,tdate,time,number,text,bytes,float,float16,float32,float64,any) - 8b.5
src/cddl/control-ops.ts—.size,.bits,.regexp,.cbor,.within,.and, comparison ops,.default - 8b.6
src/cddl/validate.ts—validateCbor(value, schema, options): CddlValidationResultwalking decoded value against rule by name; emits{ path, expected, got }on mismatch; throwsCDDL_VALIDATION_FAILED/CDDL_RULE_NOT_FOUND/CDDL_UNDEFINED_REFERENCE - 8b.7
src/cddl/cddl.types.ts— option / result types, error path discriminator
8c. Implementation — partial-item streaming (extends ./streaming)
- 8c.1
src/streaming/chunk-decoder.ts—ChunkDecoderoverAsyncIterable<Uint8Array>/ReadableStream<Uint8Array>emittingChunkDecoderEvent(string-chunk/bytes-chunk/array-start/array-end/map-start/map-end/tag/value/break) - 8c.2 Bounded memory regardless of item size — verified by 10 GiB indefinite-byte-string test asserting heap-delta cap
- 8c.3 AbortSignal honored — emits final
STREAM_INTERRUPTEDerror event mid-item - 8c.4
src/streaming/chunk-decoder.types.ts—ChunkDecoderEventdiscriminated union;ChunkDecoderOptions
9. Implementation — diagnostic notation
- 9.1
src/diagnostic/diagnostic.ts—toDiagnostic(value): stringper RFC 8949 §8; debug-only - 9.2 Sibling
.mdexplicitly states "never invoke from hot paths"
10. Implementation — errors, schemas, observability
- 10.1
src/errors/error-codes.ts—defineCodes("CBOR", [...])registry:MALFORMED,UNEXPECTED_TYPE,UNKNOWN_TAG_STRICT,NESTED_TOO_DEEP,LENGTH_OVERFLOW,INDEFINITE_NOT_ALLOWED,NON_DETERMINISTIC,STREAM_INTERRUPTED,COSE_SIG_INVALID,COSE_AEAD_FAILED,COSE_HEADER_INVALID,COSE_RECIPIENT_NOT_FOUND,CWT_EXPIRED,CWT_NOT_YET_VALID,CWT_AUDIENCE_MISMATCH,CWT_ISSUER_MISMATCH,CWT_SIGNATURE_INVALID,CDDL_PARSE_FAILED,CDDL_VALIDATION_FAILED,CDDL_RULE_NOT_FOUND,CDDL_UNDEFINED_REFERENCE;CborError extends SuaveplanError<CborErrorCode> - 10.2
src/observability/metrics.ts—cbor.encode_duration_ms+cbor.decode_duration_mshistograms;cbor.tag_total{tag}+cbor.error_total{code}counters - 10.3
src/schemas/schemas.ts— Zod schemas forEncoderOptions,DecoderOptions,TagHandler,CoseKeydiscriminated union,Jwk - 10.4 Every code in the registry is thrown by ≥1 production code path; verified by
error-codes.test.ts
11. Implementation — bounded recursion / item count
- 11.1 Encoder + decoder honor
maxDepth(default 64); throwCBOR.NESTED_TOO_DEEPon overflow - 11.2 Decoder honors
maxItems(default 1_000_000); throwsCBOR.LENGTH_OVERFLOWwhen an indefinite-length container or a CBOR Sequence exceeds the cap - 11.3 Both caps configurable via
EncoderOptions/DecoderOptions
12. Root barrel + subpath barrels
- 12.1
src/index.ts— explicit named re-exports (noexport *); namespace re-exports allowed for sub-namespaces (e.g.export * as cose from "./cose/index.js"is permitted per CLAUDE.md §10) - 12.2 Every
<feature>/index.tsuses explicit named re-exports per AGENTS.md §6a - 12.3
package.jsonexportsmap matches the 10 subpaths declared in §2.3
13. Tests (runner-neutral imports — @suaveplan/testing/runner only)
- 13.1
src/encoder/encoder.test.ts— major types 0–7; preferred-serialization shortest-form - 13.2
src/encoder/encoder.spec-conformance.test.ts— RFC 8949 Appendix A vectors round-trip - 13.3
src/encoder/indefinite.test.ts— opt-in emission; rejected underdeterministic: true - 13.4
src/encoder/half-float.test.ts— binary16 encode for normal, subnormal, ±0, ±Inf, NaN, boundary values - 13.5
src/encoder/deterministic.test.ts— sorted keys; shortest-form; no indefinite; throwsNON_DETERMINISTICon forced indefinite - 13.6
src/encoder/dcbor.test.ts— dCBOR reduction rules; rejectsundefined - 13.7
src/encoder/property.test.ts— fast-check round-trip on arbitrary structures - 13.8
src/encoder/deterministic-property.test.ts— fast-check byte-identical output across two runs on shuffled-key maps - 13.9
src/decoder/decoder.test.ts— every major type decode;Tagged<T>for unknown tags - 13.10
src/decoder/decoder.spec-conformance.test.ts— RFC 8949 §5.1 well-formedness rules; lax vs strict - 13.11
src/decoder/well-formed.test.ts—isWellFormedreturns false for every §5.1 failure class - 13.12
src/decoder/half-float.test.ts— decode parity; subnormals; payload-preserving NaN - 13.13
src/streaming/pull-decoder.test.ts— one-item-per-next(); bounded memory under 1 GiB sequence; AbortSignal honored - 13.14
src/sequences/sequences.test.ts— RFC 8742 encode + decode; empty sequence; mixed types; partial-trailing-byte handling - 13.15
src/tags/registry.test.ts— register / lookup / unregister; double-register throws; unknown tag yieldsTagged - 13.16
src/tags/builtin.test.ts— every built-in tag (0, 1, 2, 3, 21, 22, 23, 24, 32, 35, 55799) round-trips - 13.17
src/cose/cose-key.test.ts— RFC 9052 §7 EC2/OKP/RSA/symmetric sample keys decode to typed union - 13.18
src/cose/jwk.test.ts— ES256/Ed25519/RS256/symmetric round-trip - 13.19
src/cose/alg-map.test.ts— every supported alg label maps; unknown label throwsUNEXPECTED_TYPE - 13.20
src/diagnostic/diagnostic.test.ts— RFC 8949 §8 sample strings produce byte-identical output - 13.21
src/errors/error-codes.test.ts— every registry code thrown by ≥1 production path;expectTypeOfpins the literal union - 13.22
src/observability/metrics.test.ts— histograms + counters recorded under in-memoryMeterProvider - 13.23
src/schemas/schemas.test.ts— every Zod schema rejects malformed options - 13.24
src/index.test.ts— every documented subpath resolves - 13.25
src/cose/sig-structure.test.ts— RFC 9052 §4.4 vectors for all five contexts byte-identical - 13.26
src/cose/sign1.test.ts— RFC 9052 Appendix C.2 ES256 / EdDSA round-trip; tampered sig throwsCOSE_SIG_INVALID; tagged + tagless inputs - 13.27
src/cose/sign.test.ts— multi-signer encode + decode; verify-any returns matching index - 13.28
src/cose/encrypt0.test.ts— RFC 9052 Appendix C.3 AES-GCM-256 vector decrypts; tampered →COSE_AEAD_FAILED; ChaCha20-Poly1305 round-trip - 13.29
src/cose/encrypt.test.ts— multi-recipient round-trip; per-recipient KEK lookup - 13.30
src/cose/mac0.test.ts— HMAC-SHA-{256,384,512} round-trip; tampered MAC →COSE_SIG_INVALID - 13.31
src/cose/mac.test.ts— multi-recipient MAC round-trip - 13.32
src/cose/headers.test.ts— header label registry round-trip; malformed →COSE_HEADER_INVALID - 13.33
src/cwt/claims.test.ts— every IANA claim label round-trips;Date↔ epoch tag 1 - 13.34
src/cwt/encode.test.ts— deterministic claim-set encoding stable across runs - 13.35
src/cwt/decode.test.ts— RFC 8392 Appendix A vectors decode to typed claims - 13.36
src/cwt/verify.test.ts—COSE_Sign1-wrapped CWT verify;CWT_EXPIRED/CWT_NOT_YET_VALID/CWT_AUDIENCE_MISMATCHpaths - 13.37
src/cddl/lexer.test.ts— token-stream coverage incl. comments, control ops, ranges - 13.38
src/cddl/parser.test.ts— every RFC 8610 §3 production; malformed source →CDDL_PARSE_FAILEDwith{line,column} - 13.39
src/cddl/validate.test.ts— RFC 8610 Appendix CWT + COSE_Sign schemas validate against real RFC vectors; mismatch surfaces{path,expected,got} - 13.40
src/cddl/built-ins.test.ts— every prelude type accepts conforming + rejects non-conforming - 13.41
src/cddl/control-ops.test.ts— every control operator tested both directions - 13.42
src/streaming/chunk-decoder.test.ts— event sequence for indefinite-length text-string in 3 chunks; bounded heap over 10 GiB synthetic byte-string; AbortSignal →STREAM_INTERRUPTED - 13.43 Every
it(...)carries{ timeout: 5000 }(or{ timeout: 30000 }for the streaming + property tests) - 13.44 Runner-neutral imports —
grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/vault/cbor/src/returns zero output
14. Documentation
- 14.1 Sibling
.mdfor every implementation.ts(≥200 words, basic + advanced examples, full API reference) —encoder.md,indefinite.md,half-float.md,deterministic.md,dcbor.md,decoder.md,well-formed.md,pull-decoder.md,chunk-decoder.md,sequences.md,registry.md,tagged.md,builtin.md,cose-key.md,jwk.md,alg-map.md,headers.md,sig-structure.md,sign.md,sign1.md,encrypt.md,encrypt0.md,mac.md,mac0.md,claims.md(cwt),encode.md(cwt),decode.md(cwt),verify.md(cwt),lexer.md(cddl),parser.md(cddl),validate.md(cddl),built-ins.md(cddl),control-ops.md(cddl),diagnostic.md,error-codes.md,metrics.md,schemas.md - 14.2 README.md links every co-located
.mdunder## Modules - 14.3 Update
CLAUDE.mdpackage listing — add thecborparagraph under the existing Vault grouping (aftervault-sync)
15. Benchmarks
- 15.1
src/__benchmarks__/encode.bench.ts— encode 1 KiB COSE_Key, 100 KiB array of small items, 1 MiB nested map - 15.2
src/__benchmarks__/decode.bench.ts— decode the same payloads - 15.3
src/__benchmarks__/cose-key.bench.ts—decodeCoseKeyon EC2/OKP/RSA samples - 15.4 README.md publishes baseline mitata numbers
16. Pre-archive verification (the 5-gate triple)
- 16.1
bun run check— Biome 0 errors / 0 warnings / 0 info across the monorepo - 16.2
bunx turbo lint typecheck test --filter=@suaveplan/cbor— green - 16.3
bunx turbo test:coverage --filter=@suaveplan/cbor— 100/100/100/100 - 16.4
bun scripts/openspec-conformance.ts @suaveplan/cbor— exit 0, noerrorfindings - 16.5
bunx turbo build --filter=@suaveplan/cbor— green
17. Pre-archive 7-row checklist (AGENTS.md §13)
- 17.1 Generics threaded —
Tagged<T>,TagHandler<T>,PullDecoderitem type;expectTypeOfassertions on every literal-type claim (CborErrorCode,CoseKey,CoseAlg) - 17.2 Zod schemas wired at every declared boundary (
encode,decode,decodeCoseKey,jwkToCoseKey, tag registration) - 17.3 Every error code in
error-codes.tsthrown by ≥1 production path - 17.4 Strict-mode behaviors tested both on (
mode: "strict") and off (mode: "lax") - 17.5 OTel instruments emitted from production paths and asserted with a real in-memory
MeterProvider - 17.6 Lifecycle dedup — N/A (codec is stateless beyond the optional
TagRegistryinstance; registry double-register throws and is tested) - 17.7 No retroactive tick — every box above reflects work done in this change
18. Banned-pattern + barrel-shape + runner-import scans
- 18.1
grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/vault/cbor/src/ --include="*.ts"— zero output - 18.2 Barrel-shape scan per AGENTS.md §13 step 4a — zero output for any
<feature>/index.ts - 18.3 Runner-import scan per AGENTS.md §13 step 4b — zero output
- 18.4 Missing-
.mdscan — zero output - 18.5 Bare-version scan — zero output
- 18.6 Subpath count matches feature module count (12 subpaths:
.,./encoder,./decoder,./streaming,./sequences,./tags,./deterministic,./cose,./cwt,./cddl,./diagnostic,./errors) - 18.7 Security scan — no hardcoded secrets
19. Root-doc refresh
- 19.1
bun scripts/folder-reorg/generate-manifest.ts— refreshMANIFEST.md - 19.2
bun scripts/generate-missing.ts— refreshMISSING.yml - 19.3
STATUS.md— movecborrow toCompleted - 19.4
SUMMARY.md— add per-package paragraph - 19.5
bunx markdownlint-cli2 --fixthen a secondbunx markdownlint-cli2pass with exit 0 - 19.6
git statusshows only intentional changes
20. Archive
- 20.1 Fold spec delta into
openspec/specs/cbor/spec.md(canonical, status ACTIVE) - 20.2 Move
openspec/changes/enterprise-cbor/→openspec/archive/<yyyy-mm-dd>-enterprise-cbor/ - 20.3 Confirm
MANIFEST.mdlists@suaveplan/cborafter refresh