open-standards/joule-code/CHANGELOG.md
Transaction Science 7181b8a00a
Some checks failed
WAI mbt2018_mean integer decode (regenerated vectors) / does this change touch it (pull_request) Has been cancelled
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Has been cancelled
wai-quantum WebGPU member / does this change touch it (pull_request) Has been cancelled
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Has been cancelled
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · integrated GPU, Vulkan (framework-128gb) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · DX12 and Vulkan (cbio-windows) (pull_request) Has been cancelled
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Has been cancelled
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 2m19s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 9s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m49s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m9s
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 50s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 57s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 5s
WAI energy receipts / does this change touch it (pull_request) Successful in 5s
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Successful in 1m20s
WAI neural sink gates / does this change touch it (pull_request) Successful in 4s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 6s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 5s
wai-web wasm32 / does this change touch it (pull_request) Successful in 5s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m7s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 26s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 8m15s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 2m2s
wai-pack / wai-pack · linux (pull_request) Successful in 1m21s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m12s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 18m23s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 18s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 2m49s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 11s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 28s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m31s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 3m59s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Failing after 7m21s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 15m53s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 17m23s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 3m28s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 1h3m59s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 1h5m54s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 1h7m50s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 12m11s
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Successful in 18s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 5m13s
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Successful in 12m30s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m44s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m51s
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Successful in 11m3s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 44s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 2m6s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 23m45s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 32s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m12s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 6m44s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 9m9s
wai-pack / wai-pack · macos (pull_request) Successful in 1m49s
Merge joule-moq/harden (#327, now with main and metering) into joule-moq/ingest
SPEC §5 keeps live-metering's paragraph and then live-ingest's; joule-code's CHANGELOG keeps
both entries, the gateway's first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 19:22:19 -04:00

244 lines
22 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.

# Changelog
All notable changes to the Joule Code standard are recorded here. The format follows the spirit of Keep a Changelog; the standard versions independently of its siblings (tags `joule-code-vX.Y.Z`).
## [Unreleased]
### Added
- **§5.1 Grant-gated consultation (OPTIONAL)** — the JCP binding: tier as granted action (`l0`…`l4`), energy class as chained budget debit, denial as fallthrough, every consultation sealed as a chained JCR-1 receipt. Reference binding: the `joule-code-jcp` crate (`JcpGate::wrap` over any `Stage`); no `l4` in the chain → no remote frontier agent, structurally. Conformance gains class **C5** with a published vector file (`gated_cascade_vectors.json`, fixed fixture identities, deterministic end to end) run by the harness against the real jcp runtime alongside C1–C4.
- `joule-code-cascade` ships a reference **L0 (Cache)** stage: a content-addressed cache of resolved coding operations keyed by `(prior, intent)`. A hit closes the request at L0 (picojoule class) without consulting any higher tier — the first concrete cascade stage, making stop-at-first-close demonstrable in the reference library. A third party may use it or substitute its own.
- `joule-code-cascade` ships a generic **`FnStage`**: any tier (L1 Lawful, L2 Embed, L3 Model) can be expressed by supplying a resolver closure, so a consumer binds the JouleClaw agent schema — or any other runtime/model — to the cascade without this crate depending on it. (A test wires an L3 model stage behind an empty L0 and confirms it closes at L3.)
- `examples/l3-jouleclaw/` — a consumer **binding** showing JouleClaw's L3 (Model) tier wired into the cascade: a `jouleclaw_llm_cheap::LlmBackend` behind a `FnStage(L3)` that generates a `CodingOp` + joules-class receipt. **Three** backends share the slot: `EchoBackend` (model-free, for the unit tests); `JouleclawRuntimeBackend`, which runs a GGUF model **in-process on JouleClaw's own from-scratch tensor runtime** (`jouleclaw-runtime` over `jouleclaw-loader-gguf` and the reference/Apple kernel backends — no llama.cpp, no FFI); and `LlamaCompletionBackend`, which shells out to llama.cpp. The `run` binary generates with a local model (in-process runtime by default; `JOULE_CODE_L3_ENGINE=llama` for the shell-out), content-addresses the result as a `CodingOp`, and **meters host energy over the inference** for the receipt (honest provenance: `HwShunt` on RAPL/NVML, `Estimator` on Apple). Verified live: the llama.cpp path against a local 350M GGUF, and the in-process from-scratch runtime against `ternary-bonsai-1.7b-q2_0` (qwen3, Q2_0) — real text, a real `op_id`, the cascade closing at L3 (`tests/runtime_l3.rs`, opt-in via `--ignored`). Standalone workspace, so the standard's lib stays free of a JouleClaw dependency; `jouleclaw_dispatch::ModelBank` selects the cheapest capable backend in production.
- `joule-code-collab` (Research) lands the **federated-collaboration interface** (`Collab` trait: submit / committed / membership), a **single-node reference cluster** (`LocalCluster`), and the **BFT consensus core** (`LockstepCluster`): a multi-node *lockstep deterministic simulation with BFT supermajority commit* — an op commits only when ≥`2f+1` of `n=3f+1` honest nodes independently validate it, and committed ops are `op_id`-ordered so every honest node derives a byte-identical log. Tested: commits with honest agreement, tolerates `f` Byzantine nodes (safety), loses liveness only beyond `f`, rejects non-members despite quorum, deterministic round ordering. It also adds `protected` — **FROST threshold authorization** of protected operations: a `t`-of-`n` FROST (Ed25519) aggregate signature over an op's `op_id`, verifiable against the cluster's single group key (no member holds the whole key; fewer than `t` cannot authorize). Tested: a 2-of-3 threshold authorizes and binds exactly one op_id; below threshold cannot. And **MLS group keying** (RFC 9420, via `openmls`, `tests/mls_keying.rs`): the cluster members form an MLS group and derive a shared group key with no central server — verified that members agree on the exported secret, that adding a member advances the epoch, and that a member an epoch behind derives a different (forward-secret) key. (SPEC §7, `spec/collab.md`.)
- `examples/transport-moqt/` — the **CommunicationOS (MoQT) transport binding**, the last of the four federation layers. A consumer binding that carries a collaboration's committed (consensus-ordered) coding operations over [`inv-moq`](../../joulesperbit/crates/inv-moq) — CommunicationOS's MoQT (Media over QUIC Transport) protocol with its JWP receipt extension. The end-to-end test wires the whole stack in-process: `LockstepCluster` commits ops → each is published as a MoQT object → every object carries a real Ed25519/BLAKE3 **signed receipt** → a subscriber receives all ops, verifies the receipts against the publisher key, and recovers the original `SignedOp` intact; a second test shows a receipt-requiring subscriber rejecting an untrusted publisher's group (ownerless authenticity). Driven through `inv-moq`'s `MockFabric` (real signatures + BLAKE3 merkle receipts, no QUIC; the `quic` feature is the real-wire path behind the same trait surface). Standalone workspace, so the reference library stays free of a joulesperbit dependency. **The four federation layers — consensus, threshold authorization, group keying, transport — are now all implemented and tested.** (SPEC §7, `spec/collab.md`.)
### Changed
- **§6 / `spec/receipt.md` §2 — a vendor counter is a hardware reading only while it is demonstrably live.** A reading from a vendor energy counter (RAPL, NVML, Apple SMC/IOReport) MAY be tagged `HwShunt`, and supply `measured_uj`, only while the counter has been observed advancing under load for the domain the figure claims (for IOReport: the CPU energy rail itself, in at least 5 sample intervals, at least 4 of every 5, over at least 300 ms of CPU burn). A counter that is present but not live — frozen, or republished in lumps — MUST NOT produce either; the reading falls back to the estimator. A figure MUST NOT claim coverage of a domain its counter does not read. No wire or conformance-vector change.
### Changed — `joule-moq`, `joule-moq-relay` and `joule-moq-web`: L5 hardening
- **`ReceiptChains`: a join's fill from group 0.** A group fetched into the gap below a
join's anchor, with a `null` parent, is held as history and admitted when the group
above it links back to it (WAI jwp-receipts §3 step 3), where it was refused
`chain_break`. A `null` parent outside that gap, and a fork, are still `chain_break`.
- **Receipts in WAI's binary form (jwp-receipts §2.7).** `GroupReceipt::to_binary` /
`from_binary` (the canonical bytes, and a legacy group's parent), `ObjectReceipt::
to_binary` / `from_binary` / `body_binary`, `read_receipts`, `link_hash_of`,
`Malformed`, `RECEIPTS_MAGIC`. `ReceiptBundle::to_payload` and `from_payload` keep their
signatures and now write and read `WJR1`; `ReceiptBundle::read` also gives the link
hash over the bytes received. The `JWP_OBJECT_RECEIPT` property carries an object
receipt standing alone in that form. The JSON payload is not read any more. Serde
derives on the receipt types stay, for callers' own use; nothing on the wire uses them.
- **Proof of possession of a room grant (WAI live-rooms §10.5).** `joule-moq::grant_proof`:
the `WRP1` record (`GrantProof::sign`, `read`, `to_bytes`), `check_proof` (§10.5.4, the
grant's digest and principal from the caller's own reader), `grant_digest_and_principal`
(for a verifier with none), the Setup Options `SETUP_OPTION_GRANT_CHALLENGE` (`0x3A47`)
and `SETUP_OPTION_GRANT_PROOF` (`0x3A49`) with `add_challenge`, `challenge_of`,
`add_grant`, `proofs_of`, `proof_for`, the HTTP header names, `context` and base64url.
New dependency: `sha2` 0.10 (already in the lockfile).
- **`joule-moq-relay`.** The relay sends its SETUP first, with a fresh challenge and the
receipt extension declared whatever the client says. A grant rule with `proof = true`
names a room grant's record: a SETUP that presents it without a proof that passes is
closed `UNAUTHORIZED` (§12.2), and a request resting on it is refused unless its
session proved it. The transports give the session the target the client addressed
(WebTransport `:authority` and `:path`, the WebSocket upgrade's `Host` and path; native
QUIC's AUTHORITY and PATH). `Authorizer::allows` takes the proven set;
`Authorizer::requires_proof`; `ClientOptions` gains `grants`, `target` and
`sign_challenge`. `receipts_track_suffix` defaults to `_receipts`, live-rooms §6.1's.
- **`joule-moq-web`.** A participant presents its grant with a proof signed in wasm
(`clientSetupWithGrant`), the per-participant secret gone; the receipts tracks are
`0_receipts`; the verifier measures each receipts object, and the same receipts as JSON.
### Changed — `joule-moq` 0.3.0: `draft-ietf-moq-transport-22`
`joule-moq` speaks `draft-ietf-moq-transport-22` (1 October 2026), negotiated by
the ALPN `moqt-22`, in place of its draft-08-style codec. No byte of the old wire
form survives: -22 changed the varint, the control streams, every message and the
object framing. The crate's version moves to 0.3.0 because its public API changes:
- `draft22`: the codec of -22, a module of its own selected through `draft::Draft`,
so another draft can be added beside it. Every control message and parameter,
Key-Value-Pairs, Properties, Setup Options, the Authorization Token, Request IDs
and request-stream rules, subgroup streams, datagrams, fetch streams and padding,
and the status and error codes. Every refusal is a typed `WireError` carrying the
draft's consequence; `MoqError::Wire` wraps it.
- `MOQT_DRAFT_VERSION` is `0xff000016`. `transport_quic::ALPN_MOQT` is `moqt-22`.
- `messages` re-exports -22's control messages. `SetupMessage`, `AnnounceMessage`,
`SubscribeMessage`, `SubscribeFilter` and the draft-08 `ControlMessage` are gone.
SUBSCRIBE's `max_mrl_layer` is an OBJECT_PROPERTY_FILTER on the `MRL_LAYER`
property (`messages::mrl_cap_filter`); `require_receipts` is the subscriber's own
policy, met by subscribing to a track's receipts track.
- `carriage` (new): the crate's objects ride -22 subgroup objects, with the object
receipt, the `WAI_RECEIPT` property of WAI `extensions/moq-streaming-format.md`
§5 and the layer fields as properties in an application range; a group's
receipts ride its receipts track (§4.1). `ReceiptBundle` moved here from `mock`,
which re-exports it.
- `session`: the draft by ALPN (`MoqSession::select_alpn`); SETUP carries options
and no version or role, and `build_setup` takes no label; Request IDs by end.
- `transport_quic`: a pair of unidirectional control streams opened by SETUP, a
request stream per request, subgroup, fetch and padding streams, and datagrams.
`ControlStream`, `write_object_to_uni`, `read_object_from_uni`,
`write_group_receipt_to_uni` and `read_group_receipt_from_uni` are replaced by
`QuicSession::{open_control, accept_control, open_request, accept_request,
open_subgroup, open_fetch, accept_data}`, `send_object`, `send_receipt_bundle`
and `DataReceiver`.
- `wire::encode_varint` and `decode_varint` are -22's integer; every `u64` encodes.
- `mock`: objects, receipt bundles and requests cross the -22 codec; the relay
reads an object's delivery timeout from its OBJECT_DELIVERY_TIMEOUT property.
- `tests/vectors/draft-22/`: a corpus of -22 vectors, good and negative, generated
by `tests/draft22_vectors.rs`, and an independent decoder that checks it in CI.
### Added — `joule-moq-ingest` 0.1.0: WHIP into a room's MoQ tracks, WHEP out of them
A new crate, library and binary, between WebRTC and the MoQ tracks of a WAI room, on
`joule-moq`'s codec and `joule-moq-relay`'s client. The mapping is WAI's
`extensions/live-ingest.md`.
- **WHIP (RFC 9725).** POST an offer for the answer, `Location` and `ETag`; PATCH for
trickle ICE and ICE restarts, with `If-Match` (`428`, `412`); DELETE. The bearer token is
a WAI room grant, checked against the room's signed descriptor; a grant without the
publish power for a media kind of the offer is `403` with `insufficient_scope` (RFC 6750
§3.1). Every request that carries a grant also proves possession of its key
(`WAI-Room-Proof` over a single-use `WAI-Room-Challenge`, WAI live-rooms §10.5.3), on the
session's PATCH, DELETE, GET and HEAD as on the POST.
- **Into MoQ.** str0m depacketises H.264, VP8, VP9, AV1 and Opus into frames; each frame is
one `WAI1` envelope in its capability's framing, video grouped at keyframes and audio in
fixed windows, with the frame's room-clock time (anchored at the first frame's arrival,
re-anchored past `max_skew_ms`) and, on each group's first object, the grant reference.
The gateway publishes the tracks and a signed catalog at the relay as their publisher and
signs every group's receipts as the first hop, chained per session.
- **WHEP (`draft-ietf-wish-whep-05`).** A player POSTs an offer for an owner's tracks; the
gateway reads the owner's catalog once its receipt verifies (`409` with `Retry-After`
until then), joins each track at its latest group, verifies every object against its
receipt before it packetises the frame back to RTP, and drops and counts what fails.
- **Bounds.** Sessions, tracks per session, bitrate (stated as `b=AS`), frame size and the
queue to the relay, each a configuration field; `gateway.example.toml` holds no secret.
- **A signed record per session**, in the shape of live metering's per-hop record: the
seam a `WMH1` writer plugs into.
- **The WebRTC stack** is `str0m` 0.24.1 (MIT OR Apache-2.0), pinned, with its pure-Rust
crypto backend; the HTTP resources are `hyper` 1.11.1 (MIT). Every crate new to the
lockfile is MIT, Apache-2.0 or ISC.
- **Tests**, on loopback: WHIP to a relay subscriber with every receipt verified and the
bytes equal, DELETE ending the tracks; WHEP playback from the joined group; VP8, VP9 and
AV1 end to end; the refusals; trickle ICE and an ICE restart with media across it; the
room reader against WAI's live-rooms corpus. A CI job runs every target on linux-arm64.
### Added — `joule-moq-relay`: the per-hop session record of a WAI room
The relay signs WAI's per-hop session record, `WMH1` (`wai/extensions/live-metering.md`
§3), of a room from its session summaries (`metering::hop_record`, `Relay::hop_record`):
for each track of the room its sessions carried, the objects and bytes in and out, the
objects dropped for their delivery timeout or still queued at the end with those whose
receipts it forwarded, the objects refused at the door and deduplicated; its subscriber
sessions' time by layer class, from the subscriptions each held; the events it delivered
(objects of Object ID above 0 on the room's events tracks); and a ledger of the room grants
its sessions ran under, by the token each presented in SETUP (`[[metering.grants]]` in the
configuration). Its own energy is the unmetered marker. The record holds nothing per object
or per viewer: media tracks are counted one by one, events tracks and every other track of
the room together, subscribers by layer class.
- The session summary carries what the record is made from, and its format is
`joule-moq-relay/session-summary/2`: per track, as a publisher, objects and bytes
received, refused at the door and deduplicated; per track, as a subscriber, fetched
bytes, objects still queued at the end and objects of Object ID above 0; the session's
layer spans per room; and the room grant its SETUP token maps to. A datagram's bytes
are now counted among a publisher's bytes received.
- `tests/room_metering.rs`: two publishers and three subscribers of one room, one
subscriber slow, over QUIC and WebTransport; the record verifies under the relay's key
and every count in it is the sum of the summaries' counts. Run with
`WAI_METERING_FIXTURE_DIR`, the test writes the record and its ledger, which WAI's
metering corpus holds as recorded.
### Added — `joule-moq-relay` 0.1.0: a `draft-ietf-moq-transport-22` relay
A new crate, library and binary, on `joule-moq`'s codec. Publishers send it tracks,
subscribers ask it for them, and it fans each object out to every subscription in the
order -22's priorities give (§5.1.2), dropping what passes a subscriber's delivery timeout
at this hop (§5.2). Whatever it drops, the object's receipts still reach the subscriber:
a track's receipts track is never given a delivery timeout.
- **Three transports, one draft:** native QUIC (ALPN `moqt-22`) and WebTransport over
HTTP/3 (`WT-Available-Protocols` / `WT-Protocol`, `draft-ietf-webtrans-http3-16`) on one
UDP endpoint, and a WebSocket fallback that carries `draft-ietf-webtrans-http2-15`
capsules, one per binary message, under the subprotocol `moqt-22`. The fallback is a
non-normative binding, written out in the crate's README with example bytes.
- **The relay protocol:** session setup; namespace publish and subscribe routing;
PUBLISH; SUBSCRIBE served from an upstream the relay holds or opens; FETCH and fills
from its cache or forwarded upstream; TRACK_STATUS; REQUEST_UPDATE; GOAWAY on shutdown;
Request-ID parity and duplicate checks; the authorization-token alias cache; several
publishers of one track, deduplicated by (group, subgroup).
- **What the cache covers** is worked out from the groups it holds whole, each time it is
asked, so a group whose streams finish after a later group's is still served from the
cache once it is whole; a group never seen is a gap it does not answer across.
- **A signed session summary** per session. The relay's own energy is the unmetered
marker, since no meter backs it.
- **Not yet:** SUBSCRIBE_TRACKS and Range Filters, WebTransport over HTTP/2, 0-RTT, the
relay moving its own upstream subscriptions, and finding a receipts track from the
catalog rather than by its name; the README lists each.
### Changed — `joule-moq` 0.2.0: WAI's labelled JWP forms, and a strict verifier
`joule-moq` reads and writes the forms of WAI's `extensions/jwp-receipts.md`
that sign an object's energy figure, its class and its origin, and a group
total's class. Every receipt in the legacy forms keeps its bytes and verifies as
before; a verifier that predates the labelled forms refuses them. The crate's
version moves to 0.2.0 because its public API changes:
- `ObjectReceipt` gains `energy_class: Option<Vec<u8>>` (the class in WAI
energy-measurement §2.7's signed encoding). Present, the object's leaf is
`BLAKE3("moq-jwp:merkle-leaf\x02" ‖ content_hash ‖ u64_be(joules_micro) ‖
class ‖ u32_be(len(origin)) ‖ origin)`; absent, the legacy leaf. New:
`ObjectReceipt::leaf`, `ObjectReceipt::check_label`,
`ReceiptBuilder::add_labelled_object`, `ReceiptBuilder::class_for`.
- `GroupReceipt` gains `energy_class`, `energy_coverage` and
`parent_receipt_hash`. With a class, the group is signed under
`moq-jwp:group-sign\x02`, which covers the class, the coverage and the
parent link. `energy_coverage` is written only when it is non-zero, and a
legacy group carrying a non-zero coverage is refused: its signature does not
cover one. New: `ReceiptBuilder::finalize_labelled`, `GroupReceipt::link_hash`
and `compose_group_class`, the rule a labelled group's class is composed by.
- `ReceiptVerifier::verify_group` returns a `VerifiedGroup` (the root with the
group's leaf count and coordinates) instead of the bare root, and
`verify_object` and `verify_payload` take `&VerifiedGroup`. An object is
accepted only at the position its proof walks: its coordinates are the
group's, `object_id` is its leaf index, the proof has the group's depth and
its direction bits are the index's.
- `MerkleProof::verify` is replaced by `MerkleProof::verify_leaf`, which takes
the leaf in its form and the group's leaf count, and
`MerkleProof::walks_its_position`.
- `verify_group` holds the key and the signature to WAI jwp-receipts §3.1
step 3's rule: the publisher key must be the canonical encoding of a point
not of small order (a `y` coordinate ≥ p is refused), `S` must be below L
(checked by the crate as well as by `verify_strict`), `R` must not be of
small order, and the cofactorless equation must hold.
- `JWP_MOQ_EXTENSION_VERSION` is 2.
- `tests/wai_jwp_corpus.rs` runs every leaf, group and object vector of
`wai/jwp-conformance/vectors.json` through the crate.
External JWP implementations need the same update to verify labelled leaves and
groups, and to refuse what this verifier refuses.
## [0.2.0] — draft
### Changed
- **Receipts now seal as JCR-1 envelopes.** The receipt's signed form is the family-shared JCR-1 wire format — canonical det-CBOR (RFC 8949 §4.2.1, length-first key order) inside a COSE_Sign1 envelope (RFC 9052, EdDSA `alg = -8`), as the `joule-code/1` profile. This replaces v0.1's bespoke `estimated_uj ‖ sequence ‖ nonce` preimage. A Joule Code receipt now verifies under the family's existing JCR-1 tooling and its cross-language (Python/Go) reference verifiers. (SPEC §6.1.)
- `joule-code-receipt` gains a `jcr1` module (canonical CBOR + COSE_Sign1, vendored from the JCR-1 reference, no JouleClaw dependency) and `Receipt::to_jcr1_wire` / `seal` / `verify_envelope`; the bespoke `sign`/`verify` are removed.
- Conformance C3 vectors regenerated as JCR-1 COSE_Sign1 envelopes; the harness verifies the envelope and that its payload is the receipt's canonical core. A byte-equality test pins the canonical encoder against a published JCR-1 vector.
## [0.1.0] — draft
Initial draft standard.
### Added
- `SPEC.md` — normative specification: the cascade requirement (L0–L4, stop-at-first-close), the receipt requirement (`measured + estimated`, `Provenance`), and the no-capture requirement.
- The coding-operation cargo: content-addressed (BLAKE3-256), signed (Ed25519, prior-state-bound) transitions over a source tree; deterministic CBOR canonical form.
- The receipt schema in Joule Wire Protocol form, energy-signed.
- The cascade interface and the agent-schema binding to JouleClaw; the L4 binding to remote frontier agents over ACP.
- The federated-collaboration binding, specified at interface level and classified Research.
- `joule-code-rs/` reference library: `joule-code-schema`, `joule-code-cargo`, `joule-code-receipt`, `joule-code-cascade`, `joule-code-conformance`.
- `conformance/v1/` reference vector pack (C1–C4), internally consistent pending regeneration against the reference library.