vr-jcs 0.4.0

RFC 8785 JSON Canonicalization Scheme (JCS) for deterministic serialization in Rust
Documentation
# vr-jcs v0.4 Release Notes

RFC 8785 JSON Canonicalization Scheme implementation for the VertRule
ecosystem. v0.4 adds first-class digest and canonical-bytes APIs so
callers no longer have to hand-thread `blake3::hash` over canonical
bytes.

## What changed since v0.3

- **Canonical digest API**`to_canon_digest_with(value, strategy)`
  bundles canonicalization with a named [`DigestAlgorithm`]
  (`Blake3Untagged`, `Blake3Keyed`, `Blake3DomainSeparated`, `Sha256`),
  returning a typed `CanonicalDigest` that records which algorithm
  produced the bytes
- **Fixed-policy BLAKE3 helpers**`to_canon_blake3_digest(value)` and
  `to_canon_blake3_digest_from_slice(json)` are convenience wrappers
  for the common `blake3::hash(canonical_bytes)` pattern; the strict
  sibling validates untrusted input before digesting
- **`CanonicalBytes` newtype**`canonical_bytes_from_slice(json)`
  returns a wrapper whose construction is crate-private. Digest,
  signature, and receipt APIs can now statically require "bytes that
  came out of JCS" rather than accepting any `&[u8]`. There is no
  `AsRef<[u8]>` or `Deref` impl; coercion goes through explicit
  `as_slice` / `into_vec` so escapes are greppable. The `Debug` impl
  shows length only — never the bytes — to avoid accidental receipt
  leaks in logs
- **`JcsErrorInfo` stable projection**`JcsError::into_info()`
  collapses to a `Json | Validation` enum so downstream crates can map
  errors without matching on the `#[non_exhaustive]` `JcsError`.
  Future variants flow into `Validation` via `Display`
- **New `JcsError::UnsupportedAlgorithm(String)` variant** — surfaces
  declared-but-unwired algorithms (today: `Sha256`) so policy code
  can name them without a build-time dependency. Additive under
  `#[non_exhaustive]`

## What ships

Strict / typed canonicalization (unchanged from v0.3):

- `to_canon_bytes_from_slice`, `to_canon_string_from_str` — strict
  parse + canonical emit for untrusted JSON
- `to_canon_bytes`, `to_canon_string` — deprecated typed `Serialize`
  path, retained for migration
- `canonicalize` — in-place UTF-16 code-unit key sorting

New in v0.4:

- `canonical_bytes_from_slice``CanonicalBytes`
- `CanonicalBytes` (with `as_slice`, `len`, `is_empty`, `into_vec`,
  length-only `Debug`)
- `DigestAlgorithm` (`#[non_exhaustive]`) + `DigestAlgorithm::name()`
- `DigestStrategy` + `blake3_untagged` / `blake3_keyed` /
  `blake3_domain_separated` / `sha256` constructors
- `CanonicalDigest { algorithm, bytes }`
- `to_canon_digest_with`
- `to_canon_blake3_digest`, `to_canon_blake3_digest_from_slice`
- `JcsErrorInfo`, `JcsError::into_info`, `JcsError::UnsupportedAlgorithm`

## Key decisions

- `DigestAlgorithm` is `#[non_exhaustive]` and constructors live on
  `DigestStrategy`. Callers select algorithms by name rather than
  raw enum construction, so adding future algorithms (e.g. SHA-256
  once wired) does not break match arms downstream.
- `CanonicalBytes::from_jcs` is `pub(crate)`. The only way to obtain
  one externally is `canonical_bytes_from_slice` — the strict parse
  path. This is what makes the type a useful boundary marker.
- BLAKE3 domain separation uses `blake3::derive_key(context, bytes)`,
  matching the BLAKE3 spec for domain-separated digests.
- SHA-256 is declared in the API but returns
  `JcsError::UnsupportedAlgorithm` at call time so receipt schemas
  and policy packs can reference it before the implementation lands.

## Migration

No breaking changes. v0.3 callers continue to compile against v0.4.
The deprecated typed path (`to_canon_bytes<T>`, `to_canon_string<T>`)
remains available with the same warnings.

Recommended adoption pattern for new code:

- Use `canonical_bytes_from_slice` over `to_canon_bytes_from_slice`
  when the bytes will feed a digest, signature, or receipt — the
  newtype keeps "JCS-derived" as a type-level fact.
- Use `to_canon_blake3_digest_from_slice` for the common case of
  `blake3::hash(canonical_bytes)` over untrusted input.
- Use `to_canon_digest_with` + `DigestStrategy` when the algorithm
  choice is governance-bearing (keyed or domain-separated digests).

---

# vr-jcs v0.3 Release Notes

RFC 8785 JSON Canonicalization Scheme implementation for the VertRule
ecosystem. Single authoritative source for all receipt canonicalization.

## What changed since v0.2

- **Strict path promoted to primary API**`to_canon_bytes_from_slice`
  and `to_canon_string_from_str` are now the recommended entry points
  for all trust-bearing code paths
- **Typed path deprecated**`to_canon_bytes<T>` and `to_canon_string<T>`
  remain available but emit deprecation warnings; migrate to the strict
  path for untrusted input
- **Nesting depth limits** — all paths enforce `MAX_NESTING_DEPTH = 128`,
  preventing stack exhaustion on adversarial input
- **Pretty-printed input accepted** — strict path now accepts any valid
  JSON formatting (including whitespace) and canonicalizes it

## What ships

- `to_canon_bytes_from_slice`, `to_canon_string_from_str` — strict
  parse + canonical emit for untrusted JSON
- `to_canon_bytes`, `to_canon_string` — deprecated typed `Serialize`
  path for caller-controlled construction
- `canonicalize` — in-place UTF-16 code-unit key sorting
- `JcsError` error type with `NestingDepthExceeded` variant
- ECMAScript-compatible number rendering via `zmij`
- I-JSON string validation (noncharacter rejection)
- Duplicate property name rejection on strict parse paths

## Key decisions

- `serde_json` uses `preserve_order` + `arbitrary_precision` features.
  `preserve_order` makes `canonicalize()` behave correctly: insertion
  order is retained after sorting.
- Duplicate-key detection uses `BTreeSet` (not `HashSet`) for
  deterministic iteration — no nondeterminism in error paths.
- RFC 8785 conformance vectors added as committed test fixtures.
- Three helpers (`deserialize_json_value_no_duplicates`,
  `validate_string_contents`, `is_safe_integer`) are `pub` but marked
  `#[doc(hidden)]`. They exist for sibling-crate access and are not
  part of the stable v0.3 contract. See `PUBLIC_SURFACE.md`.

## Consumers

- `vertrule-schemas` depends on `vr-jcs` for internal canonicalization
- `vertrule-verifier` depends on `vr-jcs` directly (not via schemas)
- All JCS consumers in the ecosystem use this crate directly