open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/conformance/PROTOCOL.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

109 lines
5.1 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.

# JouleClaw vendor-energy measurement protocol
Honest energy accounting requires honest energy readings. The
[`EnergyCounter`] trait in this crate names three provenance tiers:
- **HwShunt** — a hardware reading: a shunt (board-level current
sense, IPMI sensor), or a vendor energy counter (Intel/AMD RAPL
MSR, NVML cumulative energy, Apple IOReport) while it is
demonstrably live for the domain the figure claims — the
measurer observed the counter covering that domain advance under
load. A counter that is present but not live (frozen, or
republished in lumps) gives no HwShunt figure. Authoritative.
- **ModelBased** — a vendor-published energy model (DCGM-X,
NVML `power.draw`, vendor whitepapers) consulted at run time.
Trustworthy if the model itself was validated by HwShunt.
- **Estimator** — anything derived from wall-clock × static
power ceiling. Useful as a floor; never as a ceiling.
The circuit breaker is only as honest as the **worst** counter in a
request's span — a single Estimator reading taints the whole walk.
That is by design.
## Why this file exists
The trait + provenance taxonomy is one half of the honesty story.
The other half is **actual measured numbers** for specific silicon
under specific workloads, so an operator can compute the energy
cost of a cascade walk before issuing it. This document specifies
*how* a third party with vendor hardware produces those numbers in
a way the rest of the JouleClaw ecosystem can consume.
Per-vendor fixture files live in `vendors/`. Each one ships
**empty** — values are `null` until a measurement run fills them.
A fixture with non-null values is a *standing claim* by its author
that the numbers were produced by the protocol below.
## Measurement protocol (vendor-agnostic)
Every workload measurement MUST capture:
1. **Hardware identification** —
`vendor`, `model`, `tdp_w` (datasheet), `host_os`.
2. **Counter** — which trait impl was used (`RaplCounter`,
`NvmlCounter`, `AppleIoReportCounter`, `AppleSiliconMeter`, or a
vendor-specific impl).
3. **Provenance** — `HwShunt` | `ModelBased` | `Estimator`. `HwShunt`
only for a counter observed live over the run (for Apple IOReport,
`ioreport_status()` returned `Live`); record in `method` how
liveness was established.
4. **Resolution** — `resolution_uj` (smallest delta the counter
meaningfully reports) and `min_window_ns` (smallest sampling
window below which the reading is noise-dominated). Honest
self-reporting; do not round below.
5. **Workload** — one of:
- `idle_1s` — power draw with no workload for one second.
- `tier_L0_lookup` — N = 10,000 keyed map lookups, no
mathematics. Floor energy.
- `tier_L1_local_index` — BM25 search over the seeded reference
corpus (`jouleclaw-local-index`'s in-memory fixture).
- `tier_L3_model_7b_token` — one decoded token of a 7B-param
model at fp16 with a 64-token prompt. The token-energy
baseline.
- `tier_L3_model_70b_token` — same, at 70B-param fp16.
6. **Method** — `cold` (counter zeroed before the workload starts)
or `warm` (counter sampled at workload boundaries).
7. **Joules per workload-unit** — `joules_uj_per_unit` for
token-shaped workloads; `joules_uj_total` otherwise.
8. **Sample count** — `n_samples` and `stddev_uj` so consumers
can reason about variance.
9. **Date** — ISO-8601 timestamp of the measurement run.
10. **Attestation** — `signed_by` (optional Ed25519 verifying key
hex of the operator), `attestation_url` (optional URL to the
public log entry / paper / blog post backing the claim).
## What a conformant fixture file looks like
See `vendors/intel-rapl-template.json` for the empty shape.
Every workload from §5 above MUST be measured (or explicitly
marked `"workload_not_run": true`); cherry-picking the favourable
ones isn't conformant. The whole point is to make the
expensive-tier-token energy visible alongside the floor.
## Why the fixtures ship empty
The workspace dev machine cannot produce HwShunt readings for
non-Intel CPU silicon, any NVIDIA GPU, any AMD GPU, any Apple
Silicon GPU, any TPU, or any Chinese accelerator. Pretending
otherwise — populating the fixtures with numbers from blog posts
or vendor whitepapers — would corrupt the provenance contract the
trait exists to enforce. Honest empty fixtures > dishonest filled
ones.
A third party with the hardware fills them, signs them, and PRs
them back. Anyone else holding the fixture-author's verifying key
can then verify the claim was actually made by that operator.
## Roadmap (not part of this conformance)
- A `jouleclaw-vendor-energy-attest` binary that takes a fixture
JSON, signs it via Ed25519, and emits a wire-form attestation
in the same shape as JCR-1 receipts (so the same Python/Go
verifiers can audit it).
- A registry of community-contributed fixtures with conflict
resolution (when two operators measure the same silicon and
disagree, both are kept; consumers decide which to trust).
- Cross-vendor parity: at least one HwShunt fixture per major
silicon lineage (Intel CPU / AMD CPU / NVIDIA GPU / AMD GPU /
Apple Silicon / Cloud TPU / Huawei Ascend / Cambricon /
Qualcomm Cloud AI).