boundary-compiler 0.1.1

RFC 8785 JSON Canonicalization (JCS) with boundary profiles and duplicate-key rejection
Documentation
# boundary-compiler

RFC 8785 JSON Canonicalization Scheme (JCS) for Rust, with strict
duplicate-key rejection and a content-hash primitive. Extracted from
[`semantic-memory`](../semantic-memory) as part of the V30 hostile
audit so that JCS is a reusable, audited, dependency-free crate.

This crate is the canonicalization layer for the entire
RecursiveIntell memory stack: every receipt, every projection import,
and every export envelope flows through `boundary-compiler`'s
`Canonicalizer` and is identified by its `ContentDigest`.

## Why a separate JCS crate?

RFC 8785 has subtle gotchas — number formatting (no `+` prefix, no
leading zeros, no `.0` for integers), string escaping (the surrogate
range is forbidden), and the mandate that duplicate object keys are
an error (not "last wins"). Most JSON canonicalization in the wild
either (a) does it wrong, or (b) is buried inside a larger crate that
you can't reuse.

`boundary-compiler` is the small, correct, reusable version. It
depends on `blake3`, `serde`, `serde_json`, `sha2`, and `thiserror`.
No async, no platform-specific code, no FFI. Builds in 2 seconds.

## Quick Start

```rust
use boundary_compiler::{Canonicalizer, ContentDigest, BoundaryProfile};
use serde_json::json;

fn main() {
    // RFC 8785 canonicalization — sorted keys, strict number formatting.
    let c = Canonicalizer::new();
    let val = json!({"b": 2, "a": 1});
    let canonical = c.canonicalize(&val).unwrap();
    assert_eq!(canonical, r#"{"a":1,"b":2}"#);

    // blake3 content digest of the JCS bytes — what you hash for receipts.
    let digest = ContentDigest::compute(&val).unwrap();
    println!("JCS digest: {}", digest);
}
```

Run it: `cargo run --example quick_start` (see `examples/`).

## What you get

### Core API

- `Canonicalizer::new()` — construct a canonicalizer with the default
  RFC 8785 settings.
- `canonicalize(&Value) -> Result<String, JcsError>` — full RFC 8785
  canonicalization. Sorted object keys, RFC 8785 number formatting
  (no `+`, no leading zeros, integers as integers, floats with full
  precision), RFC 8789 string escaping (the surrogate range is
  forbidden), no insignificant whitespace.
- `parse_with_dup_check(&str) -> Result<Value, JcsError>` — strict
  JSON parser that rejects duplicate object keys (RFC 8785 §3.2.2.2).
  Returns `JcsError::DuplicateKey { key, line, column }` on conflict.
- `parse_and_validate(&str) -> Result<Value, JcsError>` — parse +
  duplicate detection in one call.
- `canonicalize_flexible(&Value) -> Result<String, JcsError>`  convenience for "value is already in memory, just canonicalize."
- `ContentDigest::compute(&Value) -> Result<ContentDigest, JcsError>`  blake3 hash of the JCS bytes. The single handle a downstream
  receipt needs to verify content integrity.

### Boundary profiles

A `BoundaryProfile` contains only resource rules that the crate
actually enforces. `parse` checks the input-byte budget before
allocation, rejects decoded duplicate keys, enforces aggregate node
and depth budgets plus per-container/string budgets, and emits RFC
8785 bytes with a receipt listing every applied rule.

```rust
use boundary_compiler::{BoundaryProfile, ResourceCeilings};

let profile = BoundaryProfile::new(ResourceCeilings {
    max_input_bytes: 1_048_576,
    max_nodes: 100_000,
    max_depth: 32,
    max_object_keys: 128,
    max_string_bytes: 1_048_576,
    max_array_len: 1024,
});
let admitted = profile.parse(r#"{"b":2,"a":1}"#)?;
assert_eq!(admitted.canonical_bytes, br#"{"a":1,"b":2}"#);
# Ok::<(), boundary_compiler::JcsError>(())
```

The old dialect, schema ID/version, canonicalization-profile,
unknown-field-policy, and float-digit fields were removed because
they were metadata without behavior. This crate supports one dialect:
RFC 8785. Schema identity and unknown-field policy belong to a real
schema validator. A decimal digit cap would conflict with RFC 8785's
required IEEE-754/ECMAScript number serialization.

### Schema validation

`SchemaValidator` fails closed because no schema engine is configured
in this base crate. Applications requiring schema identity,
unknown-field admission, or schema versioning must use a configured
schema-validation layer before treating a value as admitted.

## Error handling

All fallible operations return `Result<_, JcsError>`. There is
**no `unwrap()` or `expect()` in production code** (verified by
clippy with `-D warnings`). Errors are:

- `JcsError::DuplicateKey { key, line, column }` — the input JSON
  has two object members with the same key.
- `JcsError::InvalidJson(String)` — the input JSON is malformed
  (after the strict parser sees it).
- `JcsError::NumberOutOfRange(String)` — the value contains a
  number outside the JSON spec (NaN, Infinity, etc.).
- `JcsError::SchemaValidation(String)` — a `BoundaryProfile`
  check failed.
- `JcsError::ResourceExceeded(String)` — a `ResourceCeilings`
  limit was hit.

## Test coverage

- **27 integration tests** in `tests/`:
  - RFC 8785 key sorting (single object, nested, mixed with arrays)
  - Duplicate-key rejection (single, nested, with arrays)
  - Number formatting (integers, negative, scientific, large floats,
    `0.0`, `-0.0`, `1e1000`)
  - String escaping (control chars, surrogate range, basic unicode)
  - Round-trip determinism (canonicalize 1000 random values, verify
    bit-exact output)
  - Boundary profile behavior (Reject vs Strip vs Accept)
  - Resource ceiling enforcement (depth, key count, value size)
- **5 doctests** in the lib.rs doc-comment.
- `cargo test` clean, `cargo clippy --all-targets -- -D warnings` clean.

## Performance

The canonicalizer is O(n) in the JSON value size, with the constant
dominated by the sorted-key pass. For typical payloads (≤10 KB):

- `canonicalize` of a 1 KB object: ~10 µs
- `ContentDigest::compute` of a 1 KB object: ~15 µs
- Round-trip 1000 canonicalizations of 1 KB objects: ~25 ms

Numbers measured on a Fedora 43 dev box with `cargo bench`
(included as `benches/canonicalize.rs`).

## MSRV

Rust 1.75 (2021 edition). The crate uses only stable features.

## Dependencies

- `blake3` — for `ContentDigest`. The single hash function used.
- `serde` / `serde_json` — for the strict JSON parser and value
  traversal.
- `sha2` — kept in the dep tree for cross-crate compatibility
  with the wider Libraries stack (semantic-memory, bitemporal-runtime
  both use sha2 for digest).
- `thiserror` — for the `JcsError` enum.

Zero platform-specific code. Zero FFI. Zero async. Builds in ~2s.

## License

Apache-2.0 (single-licensed). See `LICENSE-APACHE` for the full text.
The MIT license (`LICENSE-MIT`) is also provided for downstream
projects that need a permissive license, but the canonical license
of this crate is Apache-2.0.

## Changelog

See `CHANGELOG.md` for the release history.

## Where it's used

`boundary-compiler` is a foundational layer of:

- [`semantic-memory`]../semantic-memory — every receipt, every
  projection import, and every export envelope is JCS-canonicalized
  and content-hashed by this crate.
- [`bitemporal-runtime`]../bitemporal-runtime — the
  `SupersessionReceipt` digest uses `boundary_compiler::ContentDigest`
  for the value-binding hash (added in the V31 hostile audit fix).
- [`forge-memory-bridge`]../forge-memory-bridge — the bridge
  from Forge export envelopes to projection import batches
  canonicalizes every envelope via this crate.

Any system that needs deterministic JSON canonicalization (signed
manifests, content-addressed dedup, cross-language interop) can
adopt `boundary-compiler` directly without pulling in the rest of
the stack.