open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcr1/README.md
Transaction Science 33f0b89124 jouleclaw-energy: a live IOReport reading is a hardware reading, and only a live one
The owner's decision, applied: a vendor energy counter (RAPL, NVML, IOReport)
is a real reading, and what separates one such reading from another is
provenance, not accuracy. This crate gave RAPL and NVML `HwShunt` and gave the
IOReport counter `ModelBased`, on the reasoning that Apple's Energy Model is a
vendor model. That reasoning grades accuracy. What the counter could not
establish was provenance: on macOS 27.0.1 its CPU rails are frozen for an
unprivileged reader, and that is now caught by the liveness probe. A counter
that has passed it is as much a reading as RAPL is.

The grade. `AppleIoReportCounter` declares `HwShunt`, and stamps it on every
reading through one constant, `READING_GRADE`, shared by `provenance()` and
`read()`. It earns the grade by being live: the counter exists to read only
where `admits()` let it through (CPU rail `Live`), and a declined counter
produces no reading to carry any grade, so a frozen host still meters through
the load-average estimator at `Estimator`.

The rule, written once and shared (jouleclaw SPEC §1.3, §9.4; JCR-1 §2.3;
the crate docs; vendor-energy PROTOCOL/README): a vendor counter's reading MAY
be graded as a hardware reading only while the counter is demonstrably live for
the domain the figure claims; the implementation MUST have observed the counter
covering that domain advance under load (for IOReport: the CPU rail itself, in
at least 5 intervals, 4 of every 5, over at least 300 ms while the probing
thread burns CPU). A counter present but not live MUST NOT produce a
hardware-graded figure; the implementation falls back to its estimator or
reports the work unmetered, and SHOULD record why. A figure MUST NOT claim
coverage of a domain its counter does not read; where the format has a coverage
field, such a figure is Partial.

No partial reading from the GPU rail. Where the CPU rail is frozen but `GPU
Energy` still advances (the M5 Max), the counter stays declined. The GPU rail
is machine-wide: every GPU client on the host moves it, display compositing
included, with nothing of the metered work running. It does not cover part of
the work; it covers other work, so no coverage field makes it honest. The
rationale is in the module docs and SPEC §1.3.

Docs and data that said otherwise:
- SPEC §1.3 table and floors, §9.4; JCR-1 §2.3 and the crate README (toy
  vectors become measurements only off counters that were live).
- JCR-1 vector 6's description said "measured on Apple IOReport". Its joules
  are toy values like every vector's. Only the `description` changed; it sits
  outside the signed core, which `verify.py` builds from `receipt` alone. Both
  verifiers pass 10/10 after the edit: `verify.py` (cbor2 6.1.4, cryptography
  50.0.2, exit 0) and the independent `verify.go` (exit 0). `git diff` of the
  file is that one line; a field-by-field JSON diff finds only (6,
  description). The conformance README's slot 6 line follows.
- vendor-energy conformance: README row 27 names `AppleIoReportCounter` and the
  `HwShunt` grade, with the liveness condition a filled fixture must meet;
  `apple-silicon-ioreport.json` is an unsigned, empty stub (`signed_by` null,
  every workload not run, no hash of it anywhere), so its `impl` and
  `provenance` are corrected in place.
- MEASURED_ENERGY.md: a dated note that the 74.1 J / 25.5 W row was committed
  2026-06-12, predates macOS 27, and recorded neither chip nor OS. The "reads
  CPU/GPU/ANE/DRAM with no sudo" claim is replaced by what macOS 27.0.1 does:
  the counter opens without sudo, but the CPU rails are frozen (all 645 rails
  on the M3 Ultra), so those hosts are estimated.
- local-index / rerank tier SHOULDs, the CLI's `meter` help, meter.rs,
  apple.rs, energy_probe, Cargo feature comments, harness-econ docs.
- l3-jouleclaw README: it claimed `HwShunt` on RAPL/NVML hosts, but the
  binding builds jouleclaw-energy with `ioreport` only, so off Apple Silicon
  the receipt is the token estimate. It now says what happens on each host.
- mesh-schema's `HwShunt` definition includes vendor counters subject to
  liveness; mesh-meter, the mesh README and mesh-engine-gguf no longer say
  real silicon alone yields `HwShunt`.
- The Live-branch assertions in mesh-meter, l3-jouleclaw and jouledesk-github
  expected `ModelBased` for IOReport; they expect `HwShunt`. jouledesk-github's
  fixtures that paired `apple-ioreport` with `ModelBased` now pair it with
  `HwShunt`, and keep a `ModelBased` case under a neutral meter name.

Tests. `a_live_counters_readings_are_graded_as_hardware_readings` builds a
counter on `Live` directly. That opens the real all-channel subscription, which
opens here even though the gate declines this host. It reads it twice and
asserts each reading is `HwShunt` over `SocTotal`. On this M5 Max (macOS 27.0.1):
"an admitted IOReport counter: opens=true, 2 readings, each graded HwShunt",
so the read path is covered on a frozen host. With `--features "ioreport
rapl"`, `a_live_ioreport_reading_has_the_grade_of_a_rapl_reading` compares the
grade with `RaplCounter`'s own. The used/declined tests assert `HwShunt` in
their live branch. This host still takes the declined branch: "ioreport
declined (CpuRailFrozen): 154 ms of CPU work metered by `apple-silicon` as
6200000 µJ, provenance estimator".

Run on this M5 Max: jouleclaw-energy 24+1+1 (default), 76 lib + 1 + 1
(`ioreport`), 79 lib (`ioreport rapl`); jouleclaw-jcr1 50; harness-econ 14;
local-index 41; rerank 37; cli 10; mesh-meter (`hw-meter-apple`) 10;
mesh-engine-gguf 1; l3-jouleclaw 2 + 4 (2 ignored, need a model);
jouledesk-github 12. All pass.

Falsified, each mutant restored with `git checkout` and the tree confirmed clean:
- `READING_GRADE` set back to `ModelBased`: the new test fails at the
  declared grade (left: ModelBased, right: HwShunt), 75 passed / 1 failed.
- Only `read()`'s stamp set back to `ModelBased` (the declared grade left at
  `HwShunt`): the new test fails on the reading itself, "a live counter's
  reading is not graded as a hardware reading" (ModelBased, SocTotal) vs
  (HwShunt, SocTotal), 75 / 1. The read path is what the test pins.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 20:17:13 -04:00

101 lines
4.3 KiB
Markdown

# jouleclaw-jcr1 — JCR-1 reference
The Rust-side reference for the **JouleClaw Receipt Specification
(JCR-1)** — a canonical, signed, per-resolution thermodynamic receipt
that any third party can verify with the spec, the public key, and
standard CBOR + COSE tooling. No JouleClaw dependency required to
*check* a receipt; this crate is needed only to *mint* one.
The wedge: a system that claims joule efficiency is worth nothing if a
third party can't audit the numbers. JCR-1 makes the receipt the
audit primitive.
## What this ships
```
crates/jouleclaw-jcr1/
├── README.md
├── run.sh ← Rust mints, Python + Go each verify
├── docs/
│ ├── JCR-1-spec.md ← the receipt standard (CC-BY-4.0)
│ └── JCR-1-v1.1-intent.md ← additive v1.1 proposal: intent in signed core
├── conformance/
│ ├── README.md ← suite guide + negative-test results
│ ├── vectors.json ← 10 signed conformance vectors
│ ├── test-keys.json ← published test keys (TEST ONLY)
│ ├── verify.py ← Python reference verifier (cbor2 + cryptography)
│ └── verify.go ← independent Go verifier (stdlib only)
├── examples/
│ └── jcr1_mint.rs ← Rust producer: read energy → sign → emit
└── src/lib.rs ← det-CBOR encoder, COSE_Sign1, HMAC, projection
```
## Three implementations, one standard
- **Rust** (this crate's `lib.rs` + `examples/jcr1_mint.rs`) — mints
receipts from a live energy reading, projects a [`Receipt`] to the
JCR-1 wire form, canonicalises with RFC 8949 §4.2.1 (length-first
then bytewise key order), seals in COSE_Sign1 (RFC 9052, EdDSA /
Ed25519). 25 lib tests including byte-equal re-encode of all 10
conformance vectors.
- **Python** (`conformance/verify.py`) — reference verifier using
`cbor2` and `cryptography`.
- **Go** (`conformance/verify.go`) — independent verifier using only
the Go standard library, with a from-scratch canonical CBOR
encoder. Agreement byte-for-byte with the Python output is what
makes the spec — not the reference code — the source of truth.
## Quickstart
```
./crates/jouleclaw-jcr1/run.sh
```
You'll see, in order:
1. The Rust producer mints a fresh receipt from a measured energy
reading (HwShunt on Linux/x86 with RAPL; honest Estimator + 1 mJ
floor everywhere else).
2. The Python reference verifier accepts the 10 canonical vectors.
3. The independent Go verifier accepts the same 10 vectors.
4. Both Python and Go accept the freshly Rust-minted receipt.
## Public Rust API
```rust
use jouleclaw_jcr1::{
canonical_cbor, // any JSON Value → det-CBOR bytes
canonical_signed_core, // &Receipt → signed-core bytes
to_jcr1_wire, // &Receipt → JCR-1 wire JSON Value
signed_core_bytes, // wire Value → signed-core bytes
cose_sign1_envelope, // build COSE_Sign1 over a core
verify_cose_sign1, // verify + extract payload
hmac_tool_payload, // §5 NUL-delimited bytes
hmac_tool_hex, // HMAC-SHA-256 lowercase hex
check_honesty, // §6 step 5 consistency rules
VECTORS_JSON, TEST_KEYS_JSON, // embedded conformance suite
};
```
## Honest scope (v0.1)
- JCR-1 v1 wire form only. The wave-8 `Receipt.model_id` and
`Receipt.intent` fields ride along on the Rust `Receipt` type but
are NOT in the signed core — see [`docs/JCR-1-v1.1-intent.md`](docs/JCR-1-v1.1-intent.md)
for the proposed additive bump.
- Ed25519 (`alg = -8`) only. ES256 (`alg = -7`) is permitted by the
spec but not implemented here; the envelope verifier rejects other
algorithms.
- Key transparency / rotation is out of scope. KERI AID rotation is
recommended by JCR-1 §4 but `kid → public key` resolution is the
consumer's job.
- The 10 vectors are TOY joule values exercising each spec path. They
become measurements only when minted off real RAPL / NVML / IOReport
counters that were live for the domain they claim (JCR-1 §2.3,
Liveness) and cross-validated against Zeus. Vector 6's `ModelBased`
span is a toy grade on a toy figure, not an IOReport reading.
## License
Code: Apache-2.0 (workspace default). Specification text + conformance
vectors: CC-BY-4.0.