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

222 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `@suaveplan/cbor` — RFC 8949 CBOR encoder/decoder + RFC 9052 COSE bridge
**Package**: `@suaveplan/cbor` (NEW)
**Status**: PROPOSED
**Type**: new-package (genesis-original; greenfield — no upstream port)
**Date**: 2026-05-10
**Path**: `packages/vault/cbor/`
## Why
`@suaveplan/webauthn` parses CTAP2 authenticator data, attestation statements, and COSE_Key public keys (RFC 9052), every one of which is a CBOR-encoded payload. The package gates on a universal-tier CBOR codec. External alternatives (`cbor-x`, `cbor`) drag in CommonJS interop hazards, oversize transitive deps, and decode behaviors that diverge from RFC 8949 §5.1 strict well-formedness. An in-house, RFC-cited, zero-dependency codec keeps `webauthn` universal-tier and lets every future consumer (CWT per RFC 8392, dCBOR, COSE-keyed vault attachments) share one canonical implementation with one error registry, one OTel surface, and one deterministic-encoding profile.
## What changes
### New package: `@suaveplan/cbor`
- ADDED: `packages/vault/cbor/` package scaffold (`package.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md`).
- ADDED: `src/encoder/encoder.ts` — `Encoder` class and `encode(value, options?): Uint8Array` for RFC 8949 major types 0–7 with preferred-serialization shortest-form integers (§4.2.1).
- ADDED: `src/encoder/encoder.types.ts` — `EncoderOptions`, `IndefiniteLengthMarker`, `PreferFloat16` flag, `MaxItems`, `MaxDepth`.
- ADDED: `src/encoder/indefinite.ts` — opt-in indefinite-length encoder for arrays / maps / byte strings / text strings (§3.2). Disabled when `deterministic: true`.
- ADDED: `src/encoder/half-float.ts` — IEEE-754 binary16 encode helper (major type 7 / value 25). Opt-in via `EncoderOptions.preferFloat16`.
- ADDED: `src/encoder/deterministic.ts` — RFC 8949 §4.2.1 + §4.2.2 deterministic profile: shortest-form integers, sorted map keys (lexicographic over canonical CBOR-encoded key bytes), no indefinite-length encodings, half-float disabled.
- ADDED: `src/encoder/dcbor.ts` — dCBOR profile (draft-mcnally-deterministic-cbor) exported under the `./deterministic` subpath, layering reduction rules for numeric values and prohibition of `undefined` / `simple-value` over the deterministic baseline.
- ADDED: `src/decoder/decoder.ts` — `Decoder` class and `decode(bytes, options?): unknown` for RFC 8949 major types 0–7. Strict mode (default) enforces every §5.1 well-formedness rule.
- ADDED: `src/decoder/decoder.types.ts` — `DecoderOptions`, `Tagged<T>`, `DecoderMode = "strict" | "lax"`.
- ADDED: `src/decoder/well-formed.ts` — well-formedness checker per §5.1 (`isWellFormed(bytes): boolean`, plus internal helpers used during decode).
- ADDED: `src/decoder/half-float.ts` — IEEE-754 binary16 decode (parity with reference implementation; never gated, always available).
- ADDED: `src/streaming/pull-decoder.ts` — `PullDecoder` over `Uint8Array` and `AsyncIterable<Uint8Array>` / `ReadableStream<Uint8Array>` exposing `next(): Promise<IteratorResult<unknown>>` so callers materialize one item at a time. Bounded-memory; never buffers the whole stream.
- ADDED: `src/sequences/sequences.ts` — RFC 8742 CBOR Sequences encode (`encodeSequence(items, options?): Uint8Array`) and decode (`decodeSequence(bytes, options?): unknown[]`, plus `decodeSequenceStream` async iterator).
- ADDED: `src/tags/registry.ts` — `TagRegistry` with `register(tag, handler)` / `unregister(tag)` / `lookup(tag)`. Built-in tags: 0 (RFC 3339 date/time), 1 (epoch), 2 (positive bignum), 3 (negative bignum), 21 (base64url), 22 (base64), 23 (base16), 24 (encoded CBOR), 32 (URI), 35 (regex), 55799 (self-describe).
- ADDED: `src/tags/tagged.ts` — `Tagged<T>` value object (`{ tag: number, value: T }`) returned by the decoder for unknown tags in strict mode-with-fallthrough and in lax mode.
- ADDED: `src/cose/cose-key.ts` — `decodeCoseKey(bytes): CoseKey` returning the discriminated union `OkpKey | Ec2Key | RsaKey | SymmetricKey` per RFC 9052 §7. Exported under the `./cose` subpath.
- ADDED: `src/cose/jwk.ts` — `coseKeyToJwk(key): JsonWebKey` and `jwkToCoseKey(jwk): CoseKey` round-trip helpers.
- ADDED: `src/cose/alg-map.ts` — `coseAlgToCryptoAlg(alg)` mapping COSE algorithm labels (-7 ES256, -8 EdDSA, -35 ES384, -36 ES512, -257 RS256, etc.) to genesis-internal `@suaveplan/crypto-curve` algorithm IDs.
- ADDED: `src/cose/cose.types.ts` — `CoseKey`, `OkpKey`, `Ec2Key`, `RsaKey`, `SymmetricKey`, `CoseAlg`, `CoseKty`, `CoseCurve`, `CoseKeyOp`. Discriminated by `kty`.
- ADDED: `src/diagnostic/diagnostic.ts` — `toDiagnostic(value): string` emitting RFC 8949 §8 diagnostic notation. Marked debug-only in docs; never invoked by encoder / decoder hot paths.
- ADDED: `src/errors/error-codes.ts` — `defineCodes("CBOR", [...])` registry with `MALFORMED`, `UNEXPECTED_TYPE`, `UNKNOWN_TAG_STRICT`, `NESTED_TOO_DEEP`, `LENGTH_OVERFLOW`, `INDEFINITE_NOT_ALLOWED`, `NON_DETERMINISTIC` plus `CborError extends SuaveplanError<CborErrorCode>`.
- ADDED: `src/observability/metrics.ts` — `cbor.encode_duration_ms` + `cbor.decode_duration_ms` histograms; `cbor.tag_total{tag}` + `cbor.error_total{code}` counters. Wired into encode / decode hot paths.
- ADDED: `src/schemas/schemas.ts` — Zod boundary schemas for `EncoderOptions`, `DecoderOptions`, `TagHandlerSchema`, `CoseKeySchema` discriminated union, `JwkSchema`. Validated at every public entry point; failures surface as `CBOR.MALFORMED` (decoder input) or as a Zod-wrapped boundary error (option misuse).
- ADDED: `src/__benchmarks__/encode.bench.ts`, `decode.bench.ts`, `cose-key.bench.ts` — mitata benchmarks for 1 KiB COSE_Key, 1 MiB nested map, 100 KiB array of small items.
- ADDED: `src/index.ts` — explicit named re-exports of the public surface (no `export *`).
### Subpath exports
- `.` — root barrel: encode, decode, `Tagged`, `TagRegistry`, error codes, OTel.
- `./encoder` — `Encoder`, `encode`, `EncoderOptions`, half-float helper.
- `./decoder` — `Decoder`, `decode`, `DecoderOptions`, well-formedness checker.
- `./streaming` — `PullDecoder` (whole-item) + `ChunkDecoder` (partial-item) over `AsyncIterable<Uint8Array>` / `ReadableStream<Uint8Array>`.
- `./sequences` — RFC 8742 `encodeSequence` / `decodeSequence` / `decodeSequenceStream`.
- `./tags` — `TagRegistry`, `Tagged`, built-in tag handlers.
- `./deterministic` — RFC 8949 §4.2 deterministic profile + dCBOR profile.
- `./cose` — `COSE_Key` parsing, `JWK` round-trip, alg-map, plus `COSE_Sign` / `COSE_Sign1` / `COSE_Encrypt` / `COSE_Encrypt0` / `COSE_Mac` / `COSE_Mac0` encode / decode / verify / decrypt (RFC 9052 §4–§6).
- `./cwt` — RFC 8392 CBOR Web Token claim encode / decode / verify with time-window validation.
- `./cddl` — RFC 8610 CDDL lexer / parser / validator.
- `./diagnostic` — `toDiagnostic` debug emitter.
- `./errors` — `CBOR_CODES`, `CborError`, `CborErrorCode`.
### Dependencies
- `@suaveplan/error workspace:^` — `defineCodes`, `SuaveplanError` base.
- `@suaveplan/types workspace:^` — shared interface contracts (`Clock` for CWT time validation, base interfaces).
- `@suaveplan/crypto-curve workspace:^` — only consumed by `./cose` (`coseAlgToCryptoAlg`, `verifyCoseSign1`) and `./cwt` (`verifyCwt`). Root + `./encoder` + `./decoder` + `./cddl` + `./streaming` + `./sequences` + `./tags` + `./diagnostic` subpaths do NOT depend on `crypto-curve` at runtime.
- `@suaveplan/crypto-cipher workspace:^` — only consumed by `./cose` (`decryptCoseEncrypt0`) for AES-GCM / ChaCha20-Poly1305 AEAD primitives.
- `@suaveplan/crypto-hash workspace:^` — only consumed by `./cose` (`verifyCoseMac0`) for HMAC-SHA-{256,384,512}.
- `@opentelemetry/api catalog:observability` — encode/decode histograms, tag/error counters, COSE / CWT / CDDL operation spans.
- `tslib catalog:build`.
- `zod catalog:forms` — option boundary validation.
- **No** `@noble/*`, `cbor-x`, `cbor`, `@stablelib/*`, browser globals, or Node built-ins. Pure TypeScript over `Uint8Array` + `DataView`.
## Out of scope (residual)
Per maintainer instruction, every previously-deferred capability has been folded back IN-scope (CWT, full COSE Sign/Sign1/Encrypt/Encrypt0/Mac/Mac0, CDDL parser+validator, partial-item streaming). The only remaining residual exclusion:
- **CBOR-LD / SD-CBOR-LD** — distinct semantic-web layer (RDF + JSON-LD context compression) above CBOR; addressed in a separate `@suaveplan/cbor-ld` package when a consumer requests it. **Justification**: CBOR-LD requires a JSON-LD context resolver and an RDF graph model neither of which are CBOR-codec concerns; folding it in would couple this package to a semantic-web stack none of the current or pending consumers need. Not a silent gap.
## In-scope additions (Wave 2 — folded back per maintainer)
### CWT (RFC 8392) — `./cwt` subpath
- ADDED: `src/cwt/claims.ts` — `CwtClaims` discriminated record with the seven IANA-registered claim labels (1 `iss`, 2 `sub`, 3 `aud`, 4 `exp`, 5 `nbf`, 6 `iat`, 7 `cti`) plus a `private[label]` slot for unregistered labels; encoder accepts `Date | number` for time-typed claims and round-trips through `Tagged(1, epoch)` per RFC 8392 §3.
- ADDED: `src/cwt/encode.ts` — `encodeCwt(claims, options): Uint8Array` producing a deterministic-encoded claim set ready to be wrapped in `COSE_Sign1` or `COSE_Mac0`.
- ADDED: `src/cwt/decode.ts` — `decodeCwt(bytes): CwtClaims` returning the typed claim set; rejects malformed labels with `CBOR.UNEXPECTED_TYPE`.
- ADDED: `src/cwt/verify.ts` — `verifyCwt(token, key, options): Promise<CwtVerifyResult>` orchestrating `COSE_Sign1` / `COSE_Mac0` verification via `@suaveplan/crypto-curve` (delegated through the COSE algorithm map) and time-window validation against an injectable `Clock`.
- ADDED: `src/cwt/cwt.types.ts` — `CwtClaims`, `CwtVerifyOptions`, `CwtVerifyResult`, `CwtPrivateClaim`.
- Errors used: `CBOR.UNEXPECTED_TYPE`, new `CBOR.CWT_EXPIRED`, `CBOR.CWT_NOT_YET_VALID`, `CBOR.CWT_AUDIENCE_MISMATCH`, `CBOR.CWT_ISSUER_MISMATCH`, `CBOR.CWT_SIGNATURE_INVALID`.
### COSE structures (RFC 9052 §4–§6) — extend `./cose` subpath
- ADDED: `src/cose/sig-structure.ts` — `buildSigStructure(context, protected, externalAad, payload): Uint8Array` per RFC 9052 §4.4. Five contexts: `"Signature"`, `"Signature1"`, `"CounterSignature"`, `"MAC"`, `"MAC0"`.
- ADDED: `src/cose/sign.ts` — `encodeCoseSign({ protected, unprotected, payload, signers }): Uint8Array` and `decodeCoseSign(bytes): CoseSign` covering RFC 9052 §4.1 multi-signer messages (CBOR tag 98).
- ADDED: `src/cose/sign1.ts` — `encodeCoseSign1({ protected, unprotected, payload, signature }): Uint8Array` / `decodeCoseSign1(bytes): CoseSign1` / `verifyCoseSign1(msg, key, options): Promise<boolean>` covering RFC 9052 §4.2 single-signer messages (CBOR tag 18). Wires the algorithm-map → `@suaveplan/crypto-curve` ECDSA / EdDSA verifiers.
- ADDED: `src/cose/encrypt.ts` — `encodeCoseEncrypt({ protected, unprotected, ciphertext, recipients }): Uint8Array` / `decodeCoseEncrypt(bytes): CoseEncrypt` covering RFC 9052 §5.1 multi-recipient encrypted messages (CBOR tag 96).
- ADDED: `src/cose/encrypt0.ts` — `encodeCoseEncrypt0({ protected, unprotected, ciphertext }): Uint8Array` / `decodeCoseEncrypt0(bytes): CoseEncrypt0` / `decryptCoseEncrypt0(msg, key, options): Promise<Uint8Array>` covering RFC 9052 §5.2 single-recipient encrypted messages (CBOR tag 16). Wires AES-GCM / ChaCha20-Poly1305 via `@suaveplan/crypto-cipher`.
- ADDED: `src/cose/mac.ts` — `encodeCoseMac({ protected, unprotected, payload, tag, recipients }): Uint8Array` / `decodeCoseMac(bytes): CoseMac` covering RFC 9052 §6.1 multi-recipient MAC messages (CBOR tag 97).
- ADDED: `src/cose/mac0.ts` — `encodeCoseMac0({ protected, unprotected, payload, tag }): Uint8Array` / `decodeCoseMac0(bytes): CoseMac0` / `verifyCoseMac0(msg, key, options): Promise<boolean>` covering RFC 9052 §6.2 single-recipient MAC messages (CBOR tag 17). Wires HMAC-SHA-{256,384,512} via `@suaveplan/crypto-hash`.
- ADDED: `src/cose/headers.ts` — `CoseHeader` map type, header-label registry covering RFC 9052 §3.1 (`alg`, `crit`, `content type`, `kid`, `IV`, `Partial IV`, `counter signature`).
- New errors: `CBOR.COSE_SIG_INVALID`, `CBOR.COSE_AEAD_FAILED`, `CBOR.COSE_HEADER_INVALID`, `CBOR.COSE_RECIPIENT_NOT_FOUND`.
### CDDL (RFC 8610) parser + validator — `./cddl` subpath
- ADDED: `src/cddl/lexer.ts` — RFC 8610 ABNF token stream (`identifier`, `=`, `/`, `//`, `*`, `+`, `?`, `..`, `&`, `~`, control operators `.size` `.bits` `.regexp` `.cbor` `.within` `.and` `.lt` `.le` `.gt` `.ge` `.eq` `.ne` `.default`, integer / text-string / byte-string / float literals, comments).
- ADDED: `src/cddl/parser.ts` — `parseCddl(source): CddlAst` producing the typed AST per RFC 8610 §3 grammar (rules, types, groups, occurrence indicators, ranges, choices, control operators, generic parameters).
- ADDED: `src/cddl/ast.ts` — `CddlAst`, `CddlRule`, `CddlType`, `CddlGroup`, `CddlOccurrence`, `CddlChoice`, `CddlControl`, `CddlGenericParam` discriminated unions.
- ADDED: `src/cddl/validate.ts` — `validateCbor(value, schema, options): CddlValidationResult` walking a decoded CBOR value against a CDDL rule by name; emits a structured error path on mismatch (`{ path: (string | number)[], expected, got }`).
- ADDED: `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`).
- ADDED: `src/cddl/control-ops.ts` — control-operator implementations (`.size` length bound, `.bits` set membership, `.regexp` pattern match, `.cbor` nested-CBOR bytes, `.within`, `.and`, comparison ops, `.default`).
- ADDED: `src/cddl/cddl.types.ts` — option / result types, error path discriminator.
- New errors: `CBOR.CDDL_PARSE_FAILED`, `CBOR.CDDL_VALIDATION_FAILED`, `CBOR.CDDL_RULE_NOT_FOUND`, `CBOR.CDDL_UNDEFINED_REFERENCE`.
### Partial-item streaming — extend `./streaming` subpath
- ADDED: `src/streaming/chunk-decoder.ts` — `ChunkDecoder` for incremental decode of one CBOR item *larger than memory* over `AsyncIterable<Uint8Array>` / `ReadableStream<Uint8Array>`. Emits `{ kind: "string-chunk" | "bytes-chunk" | "array-start" | "array-end" | "map-start" | "map-end" | "tag" | "value" | "break" }` events as the underlying bytes flow in. Bounded memory regardless of item size.
- ADDED: `src/streaming/chunk-decoder.types.ts` — `ChunkDecoderEvent` discriminated union, `ChunkDecoderOptions`.
- New error: `CBOR.STREAM_INTERRUPTED` for mid-item disconnect.
## Source mapping
This package is genesis-original; no `../suaveplan/enterprise/cbor.md` line range exists for any feature. Per maintainer instruction the source-of-record is the RFC itself.
| Requirement | RFC source | Notes |
|---|---|---|
| Encoder for major types 0–7 with preferred-serialization shortest-form integers | RFC 8949 §3.1 + §4.2.1 | `unsigned`, `negative`, `byte-string`, `text-string`, `array`, `map`, `tag`, `simple/float` |
| Indefinite-length arrays / maps / strings (opt-in) | RFC 8949 §3.2 | Disallowed when `deterministic: true`; throws `INDEFINITE_NOT_ALLOWED` |
| Tagged items + built-in tag registry (0, 1, 2, 3, 21, 22, 23, 24, 32, 35, 55799) | RFC 8949 §3.4 + IANA CBOR Tags Registry | Each tag's semantics cited in sibling `.md`; tag 24 (encoded CBOR) re-enters the decoder |
| Half-precision float encode (opt-in) + decode | RFC 8949 §3.3 (Major 7 / value 25) + §D | Binary16 IEEE-754; reference test vectors in §A |
| Preferred serialization (shortest-form ints, no indefinite) | RFC 8949 §4.1 + §4.2.1 | Always-on; deterministic profile is a strict superset |
| Deterministic encoding profile | RFC 8949 §4.2.1 + §4.2.2 | Sorted map keys via lexicographic comparison over canonical encoded key bytes |
| dCBOR profile | draft-mcnally-deterministic-cbor (latest) §3 | Reduction rules for numeric values; prohibits `undefined` / generic simple values |
| RFC 8742 CBOR Sequences encode + decode | RFC 8742 §2 + §3 | Concatenation of zero-or-more well-formed CBOR items; no enclosing array |
| Decoder strict-mode well-formedness | RFC 8949 §5.1 | Includes "no extraneous bytes after item" (§5.1.2) and "all break stops have a matching indefinite start" |
| Decoder lax mode | RFC 8949 §5.2 (basic validity) | Lax mode applies §5.2 only; strict mode adds §5.1 |
| Streaming pull-decoder | RFC 8949 §3.2 + §5.1 | Bounded memory; consumer drives via `next()` |
| Bounded recursion + bounded item count | RFC 8949 §1.2 (security considerations) + §10 | `maxDepth: 64` default → `NESTED_TOO_DEEP`; `maxItems: 1_000_000` default → `LENGTH_OVERFLOW` |
| `decodeCoseKey` returning typed key union | RFC 9052 §7 + IANA COSE Key Common Parameters | Discriminator on `kty` (label 1) |
| `coseKeyToJwk` / `jwkToCoseKey` round-trip | RFC 9052 §7.1 + RFC 8037 §2 + RFC 7518 §6 | EC2/OKP/RSA/symmetric round-trip |
| COSE alg label → genesis algorithm ID | RFC 9053 §2 + IANA COSE Algorithms Registry | -7 ES256, -8 EdDSA, -35 ES384, -36 ES512, -257 RS256, …; rejects unknown labels |
| Sig_structure / Enc_structure / MAC_structure builders | RFC 9052 §4.4 + §5.3 + §6.3 | Five contexts: `Signature`, `Signature1`, `CounterSignature`, `MAC`, `MAC0`; deterministic-encoded |
| `COSE_Sign` / `COSE_Sign1` encode + decode + verify | RFC 9052 §4.1 + §4.2 + IANA CBOR Tags 98 / 18 | Wires ECDSA / EdDSA via `@suaveplan/crypto-curve` |
| `COSE_Encrypt` / `COSE_Encrypt0` encode + decode + decrypt | RFC 9052 §5.1 + §5.2 + IANA CBOR Tags 96 / 16 | Wires AES-GCM / ChaCha20-Poly1305 via `@suaveplan/crypto-cipher` |
| `COSE_Mac` / `COSE_Mac0` encode + decode + verify | RFC 9052 §6.1 + §6.2 + IANA CBOR Tags 97 / 17 | Wires HMAC-SHA-{256,384,512} via `@suaveplan/crypto-hash` |
| COSE header registry (`alg`, `crit`, `content type`, `kid`, `IV`, `Partial IV`, `counter signature`) | RFC 9052 §3.1 + IANA COSE Header Parameters | Typed `CoseHeader` map; rejects malformed labels |
| CWT claim labels (`iss`/`sub`/`aud`/`exp`/`nbf`/`iat`/`cti`) | RFC 8392 §3 + IANA CWT Claims | Typed `CwtClaims` discriminator; epoch tag round-trip |
| CWT verify (signature + time window) | RFC 8392 §7 + RFC 9052 §4.2 / §6.2 | Delegates to `verifyCoseSign1` / `verifyCoseMac0`; `Clock` injected |
| CDDL lexer + parser | RFC 8610 §3 ABNF grammar | Full prelude; control operators; generic params; comments |
| CDDL validator with structured error path | RFC 8610 §3 + §3.8 prelude | `{ path, expected, got }` discriminator on mismatch |
| CDDL control operators | RFC 8610 §3.8.1–3.8.16 | `.size`, `.bits`, `.regexp`, `.cbor`, `.within`, `.and`, comparisons, `.default` |
| Partial-item streaming (`ChunkDecoder`) | RFC 8949 §3.2 (indefinite-length forms) | Event stream of `string-chunk` / `bytes-chunk` / container-start/end / break; bounded memory regardless of item size |
| Diagnostic notation | RFC 8949 §8 | Debug-only emitter; never on hot path |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| `defineCodes("CBOR", [...])` 7-code registry | Genesis convention — every package surfaces typed errors via `@suaveplan/error`. The seven codes (`MALFORMED`, `UNEXPECTED_TYPE`, `UNKNOWN_TAG_STRICT`, `NESTED_TOO_DEEP`, `LENGTH_OVERFLOW`, `INDEFINITE_NOT_ALLOWED`, `NON_DETERMINISTIC`) each map to a distinct §5.1 / §10 failure class so callers can branch on root cause. |
| Zod boundary schemas at every public option type | Genesis convention (CLAUDE.md §2 / §11). Catches caller misuse at the boundary; option misuse never silently degrades into malformed bytes. |
| OTel surface (`cbor.encode_duration_ms`, `cbor.decode_duration_ms`, `cbor.tag_total`, `cbor.error_total`) | Genesis convention — every hot-path package emits histograms + counters. Tag attribute is bounded by IANA registry size; error code attribute is bounded by the 7-entry code registry. |
| 300-line file budget + 100/100/100/100 coverage + sibling `.md` per impl file | CLAUDE.md global rules (Quality + Documentation sections). |
| Property-based round-trip tests (fast-check) + deterministic-mode commutativity | CLAUDE.md "no mocks" rule; round-trip is the only test that proves encoder + decoder agree on the wire format under arbitrary structures. |
| Mitata benchmarks under `src/__benchmarks__/` | CLAUDE.md global benchmark requirement. Baseline numbers feed the README perf table and a future CI regression gate. |
| Tag-handler registry pluggability | Future consumer extensibility — `webauthn` may register WebAuthn-specific tags (e.g. AAGUID handling) without modifying this package. |
## Verification approach
Each requirement carries a `**Verified by:**` line in the spec delta naming concrete `it(...)` titles inside the test files below.
- `packages/vault/cbor/src/encoder/encoder.test.ts` — major types 0–7 encode, preferred-serialization shortest-form integers, simple-value encoding, float32 / float64 encode.
- `packages/vault/cbor/src/encoder/encoder.spec-conformance.test.ts` — RFC 8949 Appendix A test vectors (every published vector produces the byte sequence in the appendix).
- `packages/vault/cbor/src/encoder/indefinite.test.ts` — opt-in indefinite-length emission for arrays / maps / strings; rejected under `deterministic: true`.
- `packages/vault/cbor/src/encoder/half-float.test.ts` — binary16 encode for normal, subnormal, ±0, ±Inf, NaN, smallest-normal, largest-normal.
- `packages/vault/cbor/src/encoder/deterministic.test.ts` — sorted map keys; shortest-form ints; no indefinite-length output; throws `NON_DETERMINISTIC` when caller forces an indefinite item.
- `packages/vault/cbor/src/encoder/dcbor.test.ts` — dCBOR reduction rules; rejects `undefined`; property-based commutativity.
- `packages/vault/cbor/src/decoder/decoder.test.ts` — major types 0–7 decode; tagged-value materialization; `Tagged<T>` for unknown tags; strict-mode rejects extraneous trailing bytes.
- `packages/vault/cbor/src/decoder/decoder.spec-conformance.test.ts` — RFC 8949 Appendix A round-trip; §5.1 well-formedness rules; lax vs strict.
- `packages/vault/cbor/src/decoder/well-formed.test.ts` — `isWellFormed` returns false for every §5.1 failure class.
- `packages/vault/cbor/src/decoder/half-float.test.ts` — binary16 decode parity with reference encoder; subnormals; payload-preserving NaN.
- `packages/vault/cbor/src/streaming/pull-decoder.test.ts` — pulls one item per `next()`; bounded memory under a 1 GiB sequence (test asserts heap delta upper bound); cancels on `signal.aborted`.
- `packages/vault/cbor/src/sequences/sequences.test.ts` — RFC 8742 encode + decode; empty sequence; mixed-major-type sequence; partial-trailing-byte rejection in strict mode.
- `packages/vault/cbor/src/tags/registry.test.ts` — register / lookup / unregister; double-register rejects; unknown tag in strict-fallthrough returns `Tagged`.
- `packages/vault/cbor/src/tags/builtin.test.ts` — every built-in tag (0, 1, 2, 3, 21, 22, 23, 24, 32, 35, 55799) round-trips through the registry.
- `packages/vault/cbor/src/cose/cose-key.test.ts` — RFC 9052 §7 sample EC2, OKP, RSA, symmetric keys decode to the typed union; `kty` discriminator works.
- `packages/vault/cbor/src/cose/jwk.test.ts` — ES256 EC2 ↔ JWK round-trip; Ed25519 OKP ↔ JWK round-trip; RS256 RSA ↔ JWK round-trip (with `n`/`e` base64url encoding); symmetric key round-trip.
- `packages/vault/cbor/src/cose/alg-map.test.ts` — every IANA-registered alg label this package supports maps to the right `@suaveplan/crypto-curve` ID; unknown label throws `UNEXPECTED_TYPE`.
- `packages/vault/cbor/src/cose/sig-structure.test.ts` — RFC 9052 §4.4 vectors for all five contexts produce byte-identical canonical output.
- `packages/vault/cbor/src/cose/sign1.test.ts` — RFC 9052 Appendix C.2 ES256 / EdDSA test vectors round-trip; tampered signature rejected with `COSE_SIG_INVALID`; tag-prefix and tagless inputs both supported.
- `packages/vault/cbor/src/cose/sign.test.ts` — multi-signer encode + decode; per-signer header preserved; verify-any returns the matching signer index.
- `packages/vault/cbor/src/cose/encrypt0.test.ts` — RFC 9052 Appendix C.3 AES-GCM-256 vector decrypts; tampered ciphertext rejected with `COSE_AEAD_FAILED`; ChaCha20-Poly1305 round-trip.
- `packages/vault/cbor/src/cose/encrypt.test.ts` — multi-recipient encode + decode; per-recipient KEK lookup.
- `packages/vault/cbor/src/cose/mac0.test.ts` — HMAC-SHA-256 / 384 / 512 round-trip; tampered MAC rejected.
- `packages/vault/cbor/src/cose/mac.test.ts` — multi-recipient MAC round-trip.
- `packages/vault/cbor/src/cose/headers.test.ts` — header label registry round-trip; malformed label rejected with `COSE_HEADER_INVALID`.
- `packages/vault/cbor/src/cwt/claims.test.ts` — every IANA claim label round-trips; `Date` ↔ epoch via tag 1; private claims preserved.
- `packages/vault/cbor/src/cwt/encode.test.ts` — deterministic claim-set encoding; canonical bytes stable across two runs.
- `packages/vault/cbor/src/cwt/decode.test.ts` — RFC 8392 Appendix A vectors decode to typed claims.
- `packages/vault/cbor/src/cwt/verify.test.ts` — `COSE_Sign1`-wrapped CWT verifies; expired token throws `CWT_EXPIRED`; `nbf` future throws `CWT_NOT_YET_VALID`; audience-mismatch throws `CWT_AUDIENCE_MISMATCH`.
- `packages/vault/cbor/src/cddl/lexer.test.ts` — RFC 8610 §3 token-stream coverage including comments, control operators, ranges.
- `packages/vault/cbor/src/cddl/parser.test.ts` — every grammar production from RFC 8610 §3; rejects malformed source with `CDDL_PARSE_FAILED` carrying line / column.
- `packages/vault/cbor/src/cddl/validate.test.ts` — RFC 8610 Appendix examples (CWT schema, COSE_Sign schema) validate against real RFC vectors; mismatch surfaces `{ path, expected, got }`.
- `packages/vault/cbor/src/cddl/built-ins.test.ts` — every prelude type accepts its conforming values and rejects non-conforming.
- `packages/vault/cbor/src/cddl/control-ops.test.ts` — every control operator (`.size`, `.bits`, `.regexp`, `.cbor`, `.within`, `.and`, comparisons, `.default`) tested with conforming + non-conforming input.
- `packages/vault/cbor/src/streaming/chunk-decoder.test.ts` — emits expected event sequence for an indefinite-length text-string in 3 chunks; bounded heap delta over a 10 GiB synthetic indefinite byte-string; aborts mid-item with `STREAM_INTERRUPTED`.
- `packages/vault/cbor/src/diagnostic/diagnostic.test.ts` — RFC 8949 §8 sample diagnostic strings emit byte-identical output.
- `packages/vault/cbor/src/errors/error-codes.test.ts` — every code in the registry is thrown by ≥1 production code path; `expectTypeOf<CborErrorCode>().toEqualTypeOf<…>()` pins the literal union.
- `packages/vault/cbor/src/observability/metrics.test.ts` — encode/decode histograms record under a real in-memory `MeterProvider`; `cbor.tag_total{tag}` increments per decoded tag; `cbor.error_total{code}` increments on every thrown `CborError`.
- `packages/vault/cbor/src/schemas/schemas.test.ts` — every Zod schema rejects malformed options with the genesis boundary error.
- `packages/vault/cbor/src/encoder/property.test.ts` — fast-check round-trip: arbitrary `JsonValue`-like structures encode → decode → byte-identical.
- `packages/vault/cbor/src/encoder/deterministic-property.test.ts` — fast-check: deterministic encoder emits byte-identical output across two runs on shuffled-key maps.
- `packages/vault/cbor/src/index.test.ts` — every documented subpath export resolves.
## Migration impact
None — additive only; this is the first delta of a new package. No existing code in genesis imports from `@suaveplan/cbor`. `@suaveplan/webauthn` is the gating consumer and lands in a follow-up change once this package archives; that change will add the dep edge and migrate `webauthn`'s CTAP2 / attestation parsers off any external CBOR placeholder.
## Impact
### Affected dependents
`bunx turbo ls --filter=...@suaveplan/cbor` returns no current dependents (the package does not yet exist on `genesis`). The pending consumer is:
- `@suaveplan/webauthn` — [ ] tests run / [ ] docs reviewed / [x] no action needed in THIS change (reason: webauthn is itself pending migration; consumer wiring lands in the webauthn migration change once that proposal is opened, per `STATUS.md`).
No foundation-package fan-out applies (this is a new universal-tier leaf package; foundation deps `@suaveplan/error` and `@suaveplan/types` are unchanged).