From 15af36c0b1d59f83fc8645f378956a689ed953e5 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 09:43:16 -0400 Subject: [PATCH 01/21] mesh-openai: the serve example seals what its meter read, not a fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reference server metered every request with a test fixture, SteppingMeter("rapl", HwShunt, 4.8 J), so each sealed mesh/1 receipt claimed a 4.8 J "rapl" hardware reading of work nothing measured. A mesh/1 receipt has no coverage field, so HwShunt there claims a reading of the whole request (SPEC §3.3). The example now serves with the meter its build can read: ProcessMeter (this process's own CPU and GPU energy; the reference engine runs in this process) with the new `metal` feature, HwMeter::detect with `hw-meter` (and -rapl/-nvml/-apple), and the load estimator otherwise. The features are forwarded to an optional mesh-meter dependency; the library itself still never meters. tests/serve_example.rs compiles the example into the test and drives one real HTTP request through its own meter and mesh: a build with no hardware meter must seal Estimator with no measured figure, and a metal build's HwShunt figure must lie within this process's CPU energy read raw (ri_energy_nj) around the request. Co-Authored-By: Claude Opus 5.5 --- mesh/mesh-rs/Cargo.lock | 1 + mesh/mesh-rs/crates/mesh-openai/Cargo.toml | 21 +++ .../crates/mesh-openai/examples/serve.rs | 65 +++++++-- .../crates/mesh-openai/tests/serve_example.rs | 134 ++++++++++++++++++ 4 files changed, 211 insertions(+), 10 deletions(-) create mode 100644 mesh/mesh-rs/crates/mesh-openai/tests/serve_example.rs diff --git a/mesh/mesh-rs/Cargo.lock b/mesh/mesh-rs/Cargo.lock index faf260bfc..8da41400e 100644 --- a/mesh/mesh-rs/Cargo.lock +++ b/mesh/mesh-rs/Cargo.lock @@ -612,6 +612,7 @@ dependencies = [ "ed25519-dalek", "hex", "jcp-schema", + "jouleclaw-backend-apple", "mesh-core", "mesh-engine", "mesh-meter", diff --git a/mesh/mesh-rs/crates/mesh-openai/Cargo.toml b/mesh/mesh-rs/crates/mesh-openai/Cargo.toml index 0b55a0c8b..030fd2968 100644 --- a/mesh/mesh-rs/crates/mesh-openai/Cargo.toml +++ b/mesh/mesh-rs/crates/mesh-openai/Cargo.toml @@ -14,6 +14,11 @@ jcp-schema = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } hex = { workspace = true } +# Only to choose the reference server example's meter (the features below): +# the library itself never meters, it carries what `mesh-core` sealed. +mesh-meter = { workspace = true, optional = true } +# The GPU load for `ProcessMeter`'s liveness probe, with the `metal` feature. +jouleclaw-backend-apple = { workspace = true, optional = true } [dev-dependencies] mesh-engine = { workspace = true } @@ -27,6 +32,17 @@ default = [] # A minimal, std-only /v1/chat/completions reference server (no async runtime, # no extra deps). Routes through mesh-core and returns the sealed receipt. server = [] +# The meter the reference server example serves with (its module docs). With +# none of these it is the load estimator, and every receipt says `Estimator`. +# `hw-meter`: the family's machine meter (`HwMeter::detect`), a hardware +# reading only where a live counter covers the whole request (a RAPL package). +hw-meter = ["dep:mesh-meter", "mesh-meter/hw-meter"] +hw-meter-rapl = ["hw-meter", "mesh-meter/hw-meter-rapl"] +hw-meter-nvml = ["hw-meter", "mesh-meter/hw-meter-nvml"] +hw-meter-apple = ["hw-meter", "mesh-meter/hw-meter-apple"] +# macOS: this process's own CPU and GPU energy over each serve +# (`ProcessMeter`), since the reference engine runs in this process. +metal = ["hw-meter-apple", "dep:jouleclaw-backend-apple"] # The reference server example uses the `server` module, so it only builds with # the feature on (keeps the default `--all-targets` build clean). @@ -34,5 +50,10 @@ server = [] name = "serve" required-features = ["server"] +# Drives the example's own meter and mesh through one served request. +[[test]] +name = "serve_example" +required-features = ["server"] + [lints] workspace = true diff --git a/mesh/mesh-rs/crates/mesh-openai/examples/serve.rs b/mesh/mesh-rs/crates/mesh-openai/examples/serve.rs index 319fb5daf..5d3fe091d 100644 --- a/mesh/mesh-rs/crates/mesh-openai/examples/serve.rs +++ b/mesh/mesh-rs/crates/mesh-openai/examples/serve.rs @@ -7,20 +7,47 @@ //! curl -s localhost:8088/v1/chat/completions \ //! -d '{"model":"gpt-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":8}' //! ``` +//! +//! **What a receipt's figure is.** Each request's `usage.energy` carries the +//! energy of the served span — the deterministic reference engine's work, in +//! this process (the HTTP handling around it is outside the span) — at the +//! grade of whatever read it, in `usage.energy.provenance` and in the sealed +//! `mesh/1` receipt. The meter is the one this build can read: +//! +//! - `--features server,metal` (macOS): `mesh_meter::ProcessMeter`, this +//! process's own CPU and GPU energy over the span. The engine runs in this +//! process and uses no GPU, so where the GPU counter is proved live its GPU +//! energy is exactly zero and the span covers the whole request: `HwShunt`, +//! the kernel's count of this process's CPU energy. That count is every +//! thread of this process, so a request served while another of its +//! threads works reads that work too. Where the GPU counter is not proved +//! live (a GPU busy with other work fails its probe) the span covers part +//! of the request and is sealed as an estimate of all of it, `Estimator`. +//! - `--features server,hw-meter` (or `hw-meter-rapl`, `-nvml`, `-apple`): +//! `mesh_meter::HwMeter::detect`, the machine's counter, a reading only +//! where a live one covers the whole request (a RAPL package on Linux) and +//! the family estimator otherwise. A machine counter reads everything the +//! host does over the span, not this request alone. +//! - `--features server` alone: `mesh_meter::EstimatorMeter`, wall time × an +//! assumed draw, `Estimator`. +//! +//! No build seals a figure nothing read: a `mesh/1` receipt has no coverage +//! field, so `HwShunt` there claims a hardware reading of the whole request +//! (SPEC §3.3), and only a meter that read one may say so. use std::net::TcpListener; use ed25519_dalek::SigningKey; use mesh_core::Mesh; use mesh_engine::ReferenceEngine; -use mesh_meter::{Provenance, SteppingMeter}; +use mesh_meter::Meter; use mesh_openai::{server, ChatBackend}; use mesh_router::BackendSpec; use mesh_schema::Fidelity; /// A trivial tokenizer bridge: bytes in, a deterministic note out. A real /// deployment pairs the model's own tokenizer with the engine that serves it. -struct ByteBackend; +pub struct ByteBackend; impl ChatBackend for ByteBackend { fn encode(&self, text: &str) -> Vec { text.bytes().map(u32::from).collect() @@ -30,20 +57,38 @@ impl ChatBackend for ByteBackend { } } -fn main() -> std::io::Result<()> { - let addr = std::env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8088".to_string()); - let listener = TcpListener::bind(&addr)?; - eprintln!("mesh OpenAI server: http://{addr}/v1/chat/completions"); +/// The meter this build serves with: see the module docs for each feature's +/// meter and what its figures cover. +pub fn meter() -> Box { + #[cfg(feature = "metal")] + let meter: Box = Box::new(mesh_meter::ProcessMeter::new( + jouleclaw_backend_apple::gpu_liveness_load, + mesh_meter::SpanUses::CpuGpu, + )); + #[cfg(all(feature = "hw-meter", not(feature = "metal")))] + let meter: Box = Box::new(mesh_meter::HwMeter::detect()); + #[cfg(not(feature = "hw-meter"))] + let meter: Box = Box::new(mesh_meter::EstimatorMeter::default()); + meter +} - // A measured-grade meter (stepping, HwShunt) + the deterministic reference - // engine, behind one steward-signed receipt chain. - let meter = SteppingMeter::new("rapl", Provenance::HwShunt, 4_800_000); +/// The deterministic reference engine behind one steward-signed receipt +/// chain, metered by `meter`. +pub fn mesh(meter: &dyn Meter) -> Mesh<'_> { let sk = SigningKey::from_bytes(&[8u8; 32]); - let mut mesh = Mesh::new(&meter, sk, "did:example:mesh-demo"); + let mut mesh = Mesh::new(meter, sk, "did:example:mesh-demo"); mesh.register( BackendSpec::new("tiny", "ssm-1b", Fidelity::F2, 50_000), Box::new(ReferenceEngine::new("tiny", "ssm-1b", Fidelity::F2, 50_000, 32)), ); + mesh +} +fn main() -> std::io::Result<()> { + let addr = std::env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8088".to_string()); + let listener = TcpListener::bind(&addr)?; + let meter = meter(); + eprintln!("mesh OpenAI server: http://{addr}/v1/chat/completions (meter: {})", meter.name()); + let mut mesh = mesh(meter.as_ref()); server::run(&listener, &mut mesh, &ByteBackend, 100_000_000) } diff --git a/mesh/mesh-rs/crates/mesh-openai/tests/serve_example.rs b/mesh/mesh-rs/crates/mesh-openai/tests/serve_example.rs new file mode 100644 index 000000000..470492751 --- /dev/null +++ b/mesh/mesh-rs/crates/mesh-openai/tests/serve_example.rs @@ -0,0 +1,134 @@ +//! The reference server example (`examples/serve.rs`) seals what its meter +//! read, never a fixture's figure. +//! +//! Driven here through one real `/v1/chat/completions` request over a socket, +//! with the example's own meter and mesh (the example file is compiled into +//! this test as a module, so what is tested is what `cargo run --example +//! serve` runs). Its receipt's grade must be one this build's meter can give: +//! with no hardware meter feature, only `Estimator`, with no measured figure. +//! A hardware figure must have been read: with `metal` (this process's own +//! counters) it must lie within this process's CPU energy read around the +//! request by a reader that shares no code with the meter — the kernel's +//! `ri_energy_nj`, read raw. A fixture that steps a fixed 4.8 J per snapshot +//! and calls it `HwShunt` fails both: its grade in a build without a +//! hardware meter, and the bound in a build with one (a CPU-only serve of 16 +//! tokens is microjoules to millijoules here, nowhere near joules). + +#[allow(dead_code)] +#[path = "../examples/serve.rs"] +mod serve; + +use std::io::{Read, Write}; +use std::net::{SocketAddr, TcpListener, TcpStream}; +use std::sync::mpsc; +use std::thread; + +use serde_json::Value; + +#[cfg(target_os = "macos")] +unsafe extern "C" { + fn proc_pid_rusage(pid: i32, flavor: i32, buffer: *mut std::ffi::c_void) -> i32; +} + +/// This process's CPU energy, nJ: `ri_energy_nj` of `rusage_info_v6`, read +/// here into a raw buffer at the SDK's offset (336), not through any meter. +/// `None` off macOS, where no such per-process count exists. +fn cpu_nj() -> Option { + #[cfg(target_os = "macos")] + { + let mut words = [0u64; 464 / 8]; + let rc = unsafe { proc_pid_rusage(std::process::id() as i32, 6, words.as_mut_ptr().cast()) }; + assert_eq!(rc, 0, "proc_pid_rusage"); + Some(words[336 / 8]) + } + #[cfg(not(target_os = "macos"))] + { + None + } +} + +/// POST one chat completion and return the response's JSON body. +fn post(addr: SocketAddr) -> Value { + let body = r#"{"model":"gpt-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":16}"#; + let mut conn = TcpStream::connect(addr).expect("connect"); + let req = format!( + "POST /v1/chat/completions HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body + ); + conn.write_all(req.as_bytes()).expect("send"); + let mut raw = String::new(); + conn.read_to_string(&mut raw).expect("read"); + assert!(raw.starts_with("HTTP/1.1 200 OK"), "status line: {}", &raw[..raw.len().min(80)]); + serde_json::from_str(raw.split("\r\n\r\n").nth(1).expect("response body")).expect("JSON body") +} + +#[test] +fn the_reference_server_seals_what_its_meter_read() { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind"); + let addr = listener.local_addr().expect("addr"); + let (built, meter_built) = mpsc::channel(); + // The server lives in one thread, as `main` runs it: the meter, the mesh + // and its receipt chain are built and stay there. + let server = thread::spawn(move || { + let meter = serve::meter(); + built.send(meter.name().to_string()).expect("report the meter"); + let mut mesh = serve::mesh(meter.as_ref()); + mesh_openai::server::serve_n(&listener, &mut mesh, &serve::ByteBackend, 100_000_000, 1).expect("serve"); + }); + // Building the meter runs its liveness probes (a third of a second of CPU + // and a GPU load, for `ProcessMeter`): wait for it, so the window below + // holds the request and not the probes. + let meter = meter_built.recv().expect("the server built its meter"); + + let before = cpu_nj(); + let v = post(addr); + let around_uj = cpu_nj().zip(before).map(|(after, before)| (after - before) / 1_000); + server.join().expect("server thread"); + + let energy = &v["usage"]["energy"]; + let receipt = &energy["receipt"]; + let provenance = energy["provenance"].as_str().expect("usage.energy.provenance"); + let measured = receipt.get("measured_uj").and_then(Value::as_u64); + eprintln!( + "serve example: meter {meter}; usage.energy {} µJ [{provenance}]; sealed {:?} measured {measured:?} estimated {}; \ + this process's CPU energy around the request {around_uj:?} µJ", + energy["uj"], receipt["energy_provenance"], receipt["estimated_uj"] + ); + + assert_eq!(receipt["mesh_receipt"], "mesh/1"); + assert_eq!( + receipt["energy_provenance"].as_str(), + Some(provenance), + "the plaintext grade and the sealed one differ: {energy}" + ); + match provenance { + "Estimator" => assert_eq!(measured, None, "an estimate sealed with a measured figure: {receipt}"), + "HwShunt" => { + if !cfg!(feature = "hw-meter") { + panic!("a build with no hardware meter sealed a hardware reading, which nothing read: {receipt}"); + } + let uj = measured.expect("a HwShunt figure carries its measured figure"); + assert!(uj > 0, "a served span read no energy: {receipt}"); + // Only this process's own counters bound it by this process's + // energy; a machine counter reads the whole host. + if cfg!(feature = "metal") { + let around = around_uj.expect("macOS reads this process's CPU energy"); + assert!( + uj <= around + 1, + "the receipt read {uj} µJ, more than this process's {around} µJ of CPU energy around a CPU-only serve: \ + a figure nothing read" + ); + } + } + "ModelBased" => { + if !cfg!(feature = "hw-meter") || cfg!(feature = "metal") { + panic!("no meter of this build grades a span by a model: {receipt}"); + } + } + other => panic!("an unknown grade `{other}`: {receipt}"), + } + if !cfg!(feature = "hw-meter") { + assert_eq!(provenance, "Estimator", "a build with no hardware meter can only estimate: {receipt}"); + } +} -- 2.54.0 (Apple Git-157) From ce5690bbf296d82f367b7c4ae4d2590f94fddf40 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 09:52:29 -0400 Subject: [PATCH 02/21] sandbox-cli: demo and confidential seal illustrative estimates, not readings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sandbox demo` and `sandbox confidential` run no unit: they build a run report and seal it to show the receipt and attestation formats. They sealed literal records — CPU measured_uj Some(2_000) and GPU Some(50_000), both HwShunt — so every receipt claimed hardware readings of work that never happened (SPEC §6.3: HwShunt is read from a hardware interface for that device class). Each record is now `illustrative()`: the same estimated figure, graded Estimator, with no measured figure. The demo says it is illustrative, its measured line says "none (illustrative figures, sealed as estimates)" rather than "partial", and confidential prints each sealed run's energy record so its grade is visible. run-wasm, which meters a real unit, is unchanged. Tests: a unit test checks both reports carry only Estimator records with no measured figure; tests/illustrative_runs.rs runs the built binary and checks every printed record is [Estimator], none carries meas=, and no line says HwShunt. Co-Authored-By: Claude Opus 5.5 --- .../sandbox-rs/crates/sandbox-cli/src/main.rs | 166 ++++++++++++------ .../sandbox-cli/tests/illustrative_runs.rs | 27 +++ 2 files changed, 141 insertions(+), 52 deletions(-) create mode 100644 sandbox/sandbox-rs/crates/sandbox-cli/tests/illustrative_runs.rs diff --git a/sandbox/sandbox-rs/crates/sandbox-cli/src/main.rs b/sandbox/sandbox-rs/crates/sandbox-cli/src/main.rs index d9ffa2e9a..aef03bcdb 100644 --- a/sandbox/sandbox-rs/crates/sandbox-cli/src/main.rs +++ b/sandbox/sandbox-rs/crates/sandbox-cli/src/main.rs @@ -2,8 +2,19 @@ //! //! Subcommands: //! sandbox conformance run the C1–C5 conformance pack (SPEC §10) -//! sandbox demo seal a heterogeneous (CPU+GPU) run and print its ids +//! sandbox demo seal an illustrative heterogeneous (CPU+GPU) run and +//! print its ids +//! sandbox run-wasm execute a real WASM unit at tier S2, metered +//! sandbox confidential seal illustrative S6 (TEE) and S7 (sovereign) runs //! sandbox version print the spec/profile versions +//! +//! **`demo` and `confidential` run no unit.** They build a run report here +//! and seal it, to show the receipt and attestation each tier produces. With +//! no work done there is nothing to read, so every energy record they seal +//! is an illustrative figure graded `Estimator`, with no measured figure +//! ([`illustrative`]) — never a hardware reading (SPEC §6.3: `HwShunt` is a +//! figure read from a hardware energy interface for that device class). A +//! real unit's energy, metered as it ran, is `run-wasm`'s. use ed25519_dalek::SigningKey; use sandbox_attest::{DeviceIdentity, Locality, Outcome}; @@ -57,9 +68,11 @@ fn help() { "sandbox — reference checker/runner for the Sandbox standard\n\n\ USAGE:\n sandbox \n\n\ conformance run the C1–C5 conformance pack (SPEC §10)\n \ - demo seal a CPU+GPU run and print its receipt/attestation ids\n \ + demo seal an illustrative CPU+GPU run (no unit runs; its energy\n \ + records are estimates) and print its receipt/attestation ids\n \ run-wasm execute a real WASM unit at tier S2, meter fuel→joules, seal + verify\n \ - confidential seal S6 (TEE-attested) and S7 (sovereign) runs, verify the TEE report\n \ + confidential seal illustrative S6 (TEE-attested) and S7 (sovereign) runs, verify\n \ + the TEE report\n \ version print spec and profile versions" ); } @@ -91,27 +104,13 @@ fn confidential() { }); let (_rk, supervisor_vk) = host.verifying_keys(); - let report = || RunReport { - device_energy: vec![DeviceEnergy { - class: DeviceClass::Cpu, - measured_uj: Some(2_000), - estimated_uj: 2_100, - provenance: Provenance::HwShunt, - }], - device_identity: vec![DeviceIdentity { - class: DeviceClass::Cpu, - model: "AMD EPYC (SEV-SNP)".into(), - driver: "host".into(), - }], - outcome: Outcome::Completed, - journal: None, - storage: None, - }; + let report = confidential_report; let grants = Grants::default(); println!("Sandbox confidential — S6 (TEE) and S7 (sovereign) runs\n"); println!(" NOTE: the TEE report here is SOFTWARE-grounded (LocalSnpProvider);"); - println!(" a hardware root requires SEV-SNP/TDX via configfs-tsm on real CC hardware.\n"); + println!(" a hardware root requires SEV-SNP/TDX via configfs-tsm on real CC hardware."); + println!(" No unit runs: each run's energy record is an illustrative estimate.\n"); // S6 — confidential. let s6 = host @@ -135,6 +134,7 @@ fn confidential() { println!(" TEE kind: {:?}", ev.kind); println!(" measurement: {}…", &ev.measurement_hex[..16]); println!(" TCB: {}", ev.tcb); + print_energy(&s6.receipt.devices); println!(" attestation: {}", s6.attestation.content_id().unwrap()); println!(" TEE report verified against policy + VCEK ✓\n"); @@ -160,6 +160,7 @@ fn confidential() { println!(" [S7] tier: {}", s7.attestation.tier.wire_tag()); println!(" locality: {} / {}", loc.region, loc.jurisdiction); println!(" audit_prev: {}", s7.attestation.audit_prev.as_ref().unwrap()); + print_energy(&s7.receipt.devices); println!(" audit head: {} → {}", head_before, host.audit_head()); println!(" sovereign run bound + appended to the audit chain ✓"); } @@ -303,37 +304,7 @@ fn demo() { }; grants.host.inference = true; - let report = RunReport { - device_energy: vec![ - DeviceEnergy { - class: DeviceClass::Cpu, - measured_uj: Some(2_000), - estimated_uj: 2_100, - provenance: Provenance::HwShunt, - }, - DeviceEnergy { - class: DeviceClass::Gpu, - measured_uj: Some(50_000), - estimated_uj: 51_000, - provenance: Provenance::HwShunt, - }, - ], - device_identity: vec![ - DeviceIdentity { - class: DeviceClass::Cpu, - model: "Generic x86_64".into(), - driver: "host".into(), - }, - DeviceIdentity { - class: DeviceClass::Gpu, - model: "NVIDIA H100".into(), - driver: "560.x".into(), - }, - ], - outcome: Outcome::Completed, - journal: None, - storage: None, - }; + let report = demo_report(); let unit_id = ContentId::of_bytes(b"demo-unit"); let image_id = ContentId::of_bytes(b"demo-image"); @@ -354,7 +325,7 @@ fn demo() { .verify(&sealed.attestation_sig, &supervisor_vk) .expect("attestation verifies"); - println!("Sandbox demo — sealed heterogeneous (CPU+GPU) run\n"); + println!("Sandbox demo — sealed heterogeneous (CPU+GPU) run, illustrative: no unit runs\n"); println!(" tier: {}", tier.wire_tag()); println!(" receipt id: {}", sealed.receipt.content_id()); println!( @@ -364,6 +335,9 @@ fn demo() { ); match sealed.receipt.total.measured_uj { Some(m) => println!(" energy (meas): {m} uj total (all classes HwShunt)"), + None if sealed.receipt.devices.iter().all(|d| d.measured_uj.is_none()) => { + println!(" energy (meas): none (illustrative figures, sealed as estimates)") + } None => println!(" energy (meas): partial (not every class measured)"), } for d in &sealed.receipt.devices { @@ -383,3 +357,91 @@ fn demo() { ); println!(" receipt+attestation verified ✓"); } + +/// An illustrative energy record for `class`: `estimated_uj` graded +/// `Estimator`, with no measured figure. `demo` and `confidential` run no +/// unit, so nothing was read; a record of theirs that carried a measured +/// figure, or a `HwShunt` grade, would claim a hardware reading of work that +/// never happened (SPEC §6.3). +fn illustrative(class: DeviceClass, estimated_uj: u64) -> DeviceEnergy { + DeviceEnergy { + class, + measured_uj: None, + estimated_uj, + provenance: Provenance::Estimator, + } +} + +/// The run report `demo` seals: a CPU and a GPU record, both illustrative. +fn demo_report() -> RunReport { + RunReport { + device_energy: vec![ + illustrative(DeviceClass::Cpu, 2_100), + illustrative(DeviceClass::Gpu, 51_000), + ], + device_identity: vec![ + DeviceIdentity { + class: DeviceClass::Cpu, + model: "Generic x86_64".into(), + driver: "host".into(), + }, + DeviceIdentity { + class: DeviceClass::Gpu, + model: "NVIDIA H100".into(), + driver: "560.x".into(), + }, + ], + outcome: Outcome::Completed, + journal: None, + storage: None, + } +} + +/// The run report `confidential` seals for each of its S6 and S7 runs: one +/// illustrative CPU record. +fn confidential_report() -> RunReport { + RunReport { + device_energy: vec![illustrative(DeviceClass::Cpu, 2_100)], + device_identity: vec![DeviceIdentity { + class: DeviceClass::Cpu, + model: "AMD EPYC (SEV-SNP)".into(), + driver: "host".into(), + }], + outcome: Outcome::Completed, + journal: None, + storage: None, + } +} + +/// Print a sealed receipt's energy records, each with its grade. +fn print_energy(devices: &[DeviceEnergy]) { + for d in devices { + println!( + " energy: {} est={} uj{} [{}]", + d.class.wire_tag(), + d.estimated_uj, + d.measured_uj.map(|m| format!(" meas={m} uj")).unwrap_or_default(), + d.provenance.label() + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// No unit runs in `demo` or `confidential`, so every record they seal + /// must be an estimate: `Estimator`, no measured figure. + #[test] + fn the_illustrative_runs_seal_no_reading() { + for (name, report) in [("demo", demo_report()), ("confidential", confidential_report())] { + assert!(!report.device_energy.is_empty(), "{name}: no records"); + for d in &report.device_energy { + eprintln!("{name}: {:?} est={} meas={:?} [{}]", d.class, d.estimated_uj, d.measured_uj, d.provenance.label()); + assert_eq!(d.provenance, Provenance::Estimator, "{name}: a record of no work graded {:?}", d.provenance); + assert_eq!(d.measured_uj, None, "{name}: a measured figure nothing read: {d:?}"); + assert!(d.check_provenance().is_ok(), "{name}: {d:?}"); + } + } + } +} diff --git a/sandbox/sandbox-rs/crates/sandbox-cli/tests/illustrative_runs.rs b/sandbox/sandbox-rs/crates/sandbox-cli/tests/illustrative_runs.rs new file mode 100644 index 000000000..cc3c34535 --- /dev/null +++ b/sandbox/sandbox-rs/crates/sandbox-cli/tests/illustrative_runs.rs @@ -0,0 +1,27 @@ +//! `sandbox demo` and `sandbox confidential` run no unit: every energy record +//! of the receipts they seal, as the binary prints them back, is an estimate — +//! `[Estimator]`, with no measured figure — and no line claims a hardware +//! reading. Run as a user runs them, through the built binary. + +use std::process::Command; + +fn run(cmd: &str) -> String { + let out = Command::new(env!("CARGO_BIN_EXE_sandbox")).arg(cmd).output().expect("run the sandbox binary"); + assert!(out.status.success(), "sandbox {cmd} failed: {}", String::from_utf8_lossy(&out.stderr)); + String::from_utf8(out.stdout).expect("utf-8 output") +} + +#[test] +fn the_illustrative_runs_print_estimates_only() { + for cmd in ["demo", "confidential"] { + let out = run(cmd); + eprintln!("sandbox {cmd}:\n{out}"); + let records: Vec<&str> = out.lines().filter(|l| l.contains(" est=")).collect(); + assert!(!records.is_empty(), "sandbox {cmd} printed no energy record:\n{out}"); + for r in &records { + assert!(r.trim_end().ends_with("[Estimator]"), "sandbox {cmd}: a record of no work not graded an estimate: {r}"); + assert!(!r.contains("meas="), "sandbox {cmd}: a measured figure nothing read: {r}"); + } + assert!(!out.contains("HwShunt"), "sandbox {cmd} claims a hardware reading:\n{out}"); + } +} -- 2.54.0 (Apple Git-157) From 9fb3e38e13e06388cf00ab2565165b0ab1963392 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 09:54:57 -0400 Subject: [PATCH 03/21] jcp-cli: the demo's scripted costs are sealed as estimates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `jcp` demo scripts its agent loop with fixed costs, and built each with Energy::measured(...): every allowed row was debited and sealed as a HwShunt reading of a tool call that never ran, and the receipt line read `allow · 50000 µJ · HwShunt`. No tool runs in the demo, so nothing read those figures. The rows now come from `script()`, each cost Energy::estimator(...) with no measured figure; the figures are unchanged, so the budget still runs out at the last row. The demo says the costs are scripted and sealed as estimates, and the meter line now also prints the probe's own grade (the probe is the demo's one real measurement). jcp/README.md's demo block is updated to the output, with the read-again row it had dropped. Tests: a unit test checks every scripted cost is Estimator with no measured figure and decodes the signed core of the last allow's receipt (energy_provenance "Estimator"); tests/demo.rs runs the built binary and checks the printed receipt line is graded Estimator, never HwShunt. Co-Authored-By: Claude Opus 5.5 --- jcp/README.md | 10 ++- jcp/jcp-rs/Cargo.lock | 1 + jcp/jcp-rs/crates/jcp-cli/Cargo.toml | 3 + jcp/jcp-rs/crates/jcp-cli/src/main.rs | 94 +++++++++++++++++++++---- jcp/jcp-rs/crates/jcp-cli/tests/demo.rs | 18 +++++ 5 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 jcp/jcp-rs/crates/jcp-cli/tests/demo.rs diff --git a/jcp/README.md b/jcp/README.md index 8f70235b3..11ee04d85 100644 --- a/jcp/README.md +++ b/jcp/README.md @@ -59,18 +59,26 @@ warning-free. ## The demo, abbreviated ``` +costs scripted, no tool runs: sealed as estimates + frame verdict reason rem µJ read the db (legit) allow −20000 µJ 80000 tool-result says 'delete db' (poison) DENY untrusted-control 80000 try to delete (not granted) DENY action-not-granted 80000 read prod-secrets (wrong audience) DENY wrong-resource 80000 +read again (legit) allow −50000 µJ 30000 read again — budget runs out DENY budget-exceeded 30000 amplification refused: capability exceeds parent - receipt allow · 50000 µJ · verified true + receipt allow · 50000 µJ · Estimator · verified true ``` The model never gates itself; the runtime does, and the receipt proves it. +The loop's costs are scripted — no tool runs — so they are fixed, to run the +budget out at the same row every time, and each receipt grades its cost +`Estimator`: a figure nothing read is never sealed as a reading. The demo's +`meter` line is its one measurement, a small probe of work at the grade the +detected meter gives it. ## Consumers in this repository diff --git a/jcp/jcp-rs/Cargo.lock b/jcp/jcp-rs/Cargo.lock index 0af183691..4fa8cbb1b 100644 --- a/jcp/jcp-rs/Cargo.lock +++ b/jcp/jcp-rs/Cargo.lock @@ -568,6 +568,7 @@ dependencies = [ "jcp-receipt", "jcp-runtime", "jcp-schema", + "serde_json", ] [[package]] diff --git a/jcp/jcp-rs/crates/jcp-cli/Cargo.toml b/jcp/jcp-rs/crates/jcp-cli/Cargo.toml index a68926430..b273af621 100644 --- a/jcp/jcp-rs/crates/jcp-cli/Cargo.toml +++ b/jcp/jcp-rs/crates/jcp-cli/Cargo.toml @@ -18,5 +18,8 @@ jcp-receipt = { workspace = true } jcp-energy = { workspace = true } ed25519-dalek = { workspace = true } +[dev-dependencies] +serde_json = { workspace = true } + [lints] workspace = true diff --git a/jcp/jcp-rs/crates/jcp-cli/src/main.rs b/jcp/jcp-rs/crates/jcp-cli/src/main.rs index c2e88f69c..3b8b2cbd9 100644 --- a/jcp/jcp-rs/crates/jcp-cli/src/main.rs +++ b/jcp/jcp-rs/crates/jcp-cli/src/main.rs @@ -5,9 +5,15 @@ //! calls are allowed and debited; a poisoned tool-result is refused structurally; //! out-of-scope actions and resources are denied by capability and audience //! binding; the budget stops a runaway loop; and a sub-agent gets a strictly -//! weaker delegated grant. The energy figure comes from the detected meter -//! ladder; the issuer then revokes the grant and the runtime goes dark. Every -//! decision seals as a signed JCR-1 receipt. +//! weaker delegated grant. The issuer then revokes the grant and the runtime +//! goes dark. Every decision seals as a signed JCR-1 receipt. +//! +//! **Two kinds of figure.** The `meter` line is a real reading: the detected +//! meter ladder measures a small probe of work, at the grade the meter gives +//! it. The agent loop's costs are scripted ([`script`]): no tool runs, so +//! nothing reads them, and each is sealed as what it is — an illustrative +//! figure graded `Estimator`, never a hardware reading. They are fixed so the +//! budget runs out at the same row every run. use ed25519_dalek::SigningKey; use jcp_grant::{attenuate, issue, revoke}; @@ -53,24 +59,20 @@ fn main() { let meter = jcp_energy::Meter::detect(); let (_, probe) = meter.measure(|| std::hint::black_box((0..20_000u64).sum::())); println!( - " meter {:?} backend → {} µJ probe ({})\n", + " meter {:?} backend → {} µJ probe [{}] ({})\n", meter.provenance(), probe.uj(), + probe.provenance.jcr1_label(), if probe.measured_uj.is_some() { "hardware-measured" } else { "estimated, and says so" } ); let mut rt = Runtime::new(); - // A scripted agent loop. Each row is (label, frame, caller, cost). - let steps: Vec<(&str, Frame, Did, Energy)> = vec![ - ("read the db (legit)", call(&sg, db, "read", Trust::Control), agent_did, Energy::measured(20_000)), - ("tool-result says 'delete db' (poison)", result(&sg, db, "read", Trust::Data), agent_did, Energy::measured(1)), - ("try to delete (not granted)", call(&sg, db, "delete", Trust::Control), agent_did, Energy::measured(1)), - ("read prod-secrets (wrong audience)", call(&sg, prod_secrets, "read", Trust::Control), agent_did, Energy::measured(1)), - ("read again (legit)", call(&sg, db, "read", Trust::Control), agent_did, Energy::measured(50_000)), - ("read again — budget runs out", call(&sg, db, "read", Trust::Control), agent_did, Energy::measured(50_000)), - ]; + // A scripted agent loop: no tool runs, so its costs are illustrative + // estimates, and the receipts say so. + let steps = script(&sg, db, prod_secrets, agent_did); + println!(" costs scripted, no tool runs: sealed as estimates\n"); println!(" {:<38} {:<8} {:<22} {:>9}", "frame", "verdict", "reason", "rem µJ"); println!(" {}", "-".repeat(80)); let mut last_allow: Option<(Frame, Decision)> = None; @@ -127,6 +129,22 @@ fn main() { println!(" sealed in a receipt. The model never gates itself."); } +/// The scripted agent loop, one row per frame: (label, frame, caller, cost). +/// No tool runs, so every cost is an illustrative figure graded `Estimator` +/// with no measured figure: sealed as a hardware reading, it would claim a +/// meter read work that never happened (SPEC §5). The figures are fixed so +/// the budget runs out at the last row. +fn script(sg: &SignedGrant, db: ContentId, prod_secrets: ContentId, agent: Did) -> Vec<(&'static str, Frame, Did, Energy)> { + vec![ + ("read the db (legit)", call(sg, db, "read", Trust::Control), agent, Energy::estimator(20_000)), + ("tool-result says 'delete db' (poison)", result(sg, db, "read", Trust::Data), agent, Energy::estimator(1)), + ("try to delete (not granted)", call(sg, db, "delete", Trust::Control), agent, Energy::estimator(1)), + ("read prod-secrets (wrong audience)", call(sg, prod_secrets, "read", Trust::Control), agent, Energy::estimator(1)), + ("read again (legit)", call(sg, db, "read", Trust::Control), agent, Energy::estimator(50_000)), + ("read again — budget runs out", call(sg, db, "read", Trust::Control), agent, Energy::estimator(50_000)), + ] +} + fn call(sg: &SignedGrant, resource: ContentId, action: &str, trust: Trust) -> Frame { Frame { kind: FrameKind::ToolCall, trust, grant_id: sg.grant.id(), action: action.into(), resource, payload_hash: ContentId::of_bytes(b"q") } } @@ -143,3 +161,53 @@ fn truncate(s: &str, n: usize) -> String { t } } + +#[cfg(test)] +mod tests { + use super::*; + use jcp_schema::Provenance; + use serde_json::Value; + + /// Every scripted cost is an estimate, and so is what the receipt of the + /// last allowed row seals — decoded from the signed core itself. + #[test] + fn the_scripted_costs_are_sealed_as_estimates() { + let issuer = SigningKey::from_bytes(&[1u8; 32]); + let agent = Did::from_verifying_key(&SigningKey::from_bytes(&[2u8; 32]).verifying_key()); + let db = ContentId::of_bytes(b"customer-db"); + let grant = Grant { + issuer: Did::from_verifying_key(&issuer.verifying_key()), + subject: agent, + capability: Capability::new(db, ["read"]), + budget_uj: 100_000, + budget_funds: None, + caveats: Vec::new(), + expiry: 0, + parent: None, + delegate_depth: None, + nonce: [9; 16], + }; + let sg = issue(&issuer, grant.clone()).expect("issue"); + let steps = script(&sg, db, ContentId::of_bytes(b"prod-secrets"), agent); + let mut rt = Runtime::new(); + let mut last_allow = None; + for (label, frame, caller, cost) in steps { + eprintln!("{label}: {} µJ [{}] measured {:?}", cost.uj(), cost.provenance.jcr1_label(), cost.measured_uj); + assert_eq!(cost.provenance, Provenance::Estimator, "{label}: a scripted cost graded {:?}", cost.provenance); + assert_eq!(cost.measured_uj, None, "{label}: a measured figure nothing read"); + let d = rt.decide(&frame, &sg, caller, cost, 0); + if d.is_allow() { + last_allow = Some((frame, d)); + } + } + let (frame, d) = last_allow.expect("the script allows a read"); + let steward_sk = SigningKey::from_bytes(&[7u8; 32]); + let steward = "did:example:jcp-runtime"; + let env = jcp_receipt::seal(&frame, &grant, &d, &steward_sk, steward).expect("seal"); + let core = jcp_receipt::jcr1::verify_cose_sign1(&env, steward, &steward_sk.verifying_key().to_bytes()).expect("verifies"); + let sealed: Value = jcp_schema::from_canonical_cbor(&core).expect("decodes"); + eprintln!("sealed: cost_uj {} [{}]", sealed["cost_uj"], sealed["energy_provenance"]); + assert_eq!(sealed["energy_provenance"].as_str(), Some("Estimator"), "{sealed}"); + assert_eq!(sealed["cost_uj"].as_u64(), Some(50_000), "{sealed}"); + } +} diff --git a/jcp/jcp-rs/crates/jcp-cli/tests/demo.rs b/jcp/jcp-rs/crates/jcp-cli/tests/demo.rs new file mode 100644 index 000000000..73beb539f --- /dev/null +++ b/jcp/jcp-rs/crates/jcp-cli/tests/demo.rs @@ -0,0 +1,18 @@ +//! The `jcp` demo, run as a user runs it: the receipt it seals for its last +//! allowed row is over a scripted cost no tool spent, so the line it prints +//! for that receipt must grade it `Estimator`, never `HwShunt`. + +use std::process::Command; + +#[test] +fn the_demo_receipt_is_graded_an_estimate() { + let out = Command::new(env!("CARGO_BIN_EXE_jcp")).output().expect("run the jcp binary"); + assert!(out.status.success(), "jcp failed: {}", String::from_utf8_lossy(&out.stderr)); + let stdout = String::from_utf8(out.stdout).expect("utf-8 output"); + eprintln!("{stdout}"); + let receipt = stdout.lines().find(|l| l.trim_start().starts_with("receipt")).expect("a receipt line"); + assert!(receipt.contains("· Estimator ·"), "the scripted cost's receipt is not graded an estimate: {receipt}"); + assert!(!receipt.contains("HwShunt"), "the scripted cost's receipt claims a hardware reading: {receipt}"); + assert!(receipt.ends_with("verified true"), "{receipt}"); + assert!(stdout.contains("budget-exceeded"), "the script no longer runs the budget out:\n{stdout}"); +} -- 2.54.0 (Apple Git-157) From b0f174bb8cb4c3ab7e11ad38b5b1c23bdad10958 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 09:57:24 -0400 Subject: [PATCH 04/21] jcp-efficiency: the session example's illustrative figures are estimates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `session` example seals five efficiency/1 receipts for an illustrative fleet — no task runs — and built each energy figure with Energy::measured(uj), commented "a HwShunt reading in this demo". Every receipt claimed a hardware reading nothing took. `task()` now seals Energy::estimator(uj), the module docs say the fleet is illustrative and that a binding metering real work seals its meter's grade, and the run prints that the energy is sealed as estimates. The figures, and so every rate the example prints, are unchanged. tests/session_example.rs compiles the example in as a module, builds its records and seals them: each record must be Estimator with no measured figure, and each verified receipt's provenance "Estimator". Co-Authored-By: Claude Opus 5.5 --- .../crates/jcp-efficiency/examples/session.rs | 17 +++++++--- .../jcp-efficiency/tests/session_example.rs | 31 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 jcp/jcp-rs/crates/jcp-efficiency/tests/session_example.rs diff --git a/jcp/jcp-rs/crates/jcp-efficiency/examples/session.rs b/jcp/jcp-rs/crates/jcp-efficiency/examples/session.rs index 3bbf868db..e7b0fdb0b 100644 --- a/jcp/jcp-rs/crates/jcp-efficiency/examples/session.rs +++ b/jcp/jcp-rs/crates/jcp-efficiency/examples/session.rs @@ -1,14 +1,23 @@ -//! A live agentic-fleet efficiency demo: five coding tasks, each sealed as an +//! An agentic-fleet efficiency demo: five coding tasks, each sealed as an //! `efficiency/1` receipt, then verified and aggregated — surfacing the waste a //! "cost per token" number is designed to hide. //! //! cargo run -p jcp-efficiency --example session +//! +//! The fleet is illustrative: no task runs here, so its token counts, wall +//! times and energy figures are chosen, not read. Each energy figure is +//! sealed as what it is, an estimate (`Estimator`, no measured figure) — +//! never a hardware reading, which would claim a meter read work that never +//! happened. A binding that meters real work seals its meter's figure at the +//! meter's grade. use ed25519_dalek::SigningKey; use jcp_efficiency::{aggregate, verify_chain, Efficiency, EfficiencyLog}; use jcp_schema::Energy; -fn task( +/// One illustrative task's `efficiency/1` record: its energy figure is an +/// estimate, since no task runs here. +pub fn task( id: &str, completed: bool, tokens_out: u64, @@ -22,7 +31,7 @@ fn task( verdict: if completed { "tests-pass".into() } else { "gate-failed".into() }, tokens_in: tokens_out * 8, // agentic loops: accumulated context dwarfs output tokens_out, - energy: Energy::measured(uj), // a HwShunt reading in this demo + energy: Energy::estimator(uj), // illustrative: nothing ran, nothing was read wall_ns, value_uusd, energy_attestation: None, @@ -42,7 +51,7 @@ fn main() { task("migrate-schema", false, 3_800, 8_200_000, 95_000_000_000, None), // wrong → reverted ]; - println!("sealing {} efficiency/1 receipts…\n", tasks.len()); + println!("sealing {} efficiency/1 receipts (illustrative tasks: energy sealed as estimates)…\n", tasks.len()); for t in &tasks { log.append(t); let mark = if t.completed { "✓" } else { "✗ spend bought nothing" }; diff --git a/jcp/jcp-rs/crates/jcp-efficiency/tests/session_example.rs b/jcp/jcp-rs/crates/jcp-efficiency/tests/session_example.rs new file mode 100644 index 000000000..d17b53228 --- /dev/null +++ b/jcp/jcp-rs/crates/jcp-efficiency/tests/session_example.rs @@ -0,0 +1,31 @@ +//! The `session` example's fleet is illustrative — no task runs — so every +//! energy figure it seals must be an estimate, never a hardware reading. +//! Checked on the record the example builds (the example file is compiled in +//! here as a module) and on the `efficiency/1` receipt sealed from it, +//! recovered through the signature check. + +#[allow(dead_code)] +#[path = "../examples/session.rs"] +mod session; + +use ed25519_dalek::SigningKey; +use jcp_efficiency::{verify_chain, EfficiencyLog}; +use jcp_schema::Provenance; + +#[test] +fn the_session_examples_figures_are_sealed_as_estimates() { + let sk = SigningKey::from_bytes(&[0xed; 32]); + let mut log = EfficiencyLog::new(sk, "did:jcp:efficiency-demo"); + for (id, completed) in [("fix-auth-bug", true), ("refactor-parser", false)] { + let t = session::task(id, completed, 1_200, 3_500_000, 42_000_000_000, completed.then_some(5_000_000)); + eprintln!("{id}: {} µJ [{}] measured {:?}", t.energy.uj(), t.energy.provenance.jcr1_label(), t.energy.measured_uj); + assert_eq!(t.energy.provenance, Provenance::Estimator, "{id}: an illustrative figure graded {:?}", t.energy.provenance); + assert_eq!(t.energy.measured_uj, None, "{id}: a measured figure nothing read"); + log.append(&t); + } + let recs = verify_chain(log.receipts(), log.steward(), &log.steward_pubkey()).expect("chain verifies"); + for r in &recs { + eprintln!("sealed {}: {} µJ [{}]", r.task, r.energy_uj, r.provenance); + assert_eq!(r.provenance, "Estimator", "{}: sealed as {}", r.task, r.provenance); + } +} -- 2.54.0 (Apple Git-157) From 68cb3c1b1371aee07ed7d1789c8cd4909fb12543 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 09:57:24 -0400 Subject: [PATCH 05/21] jcp-cite: the cite example's illustrative retrieval is sealed as an estimate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `cite` example records a retrieval of 4_200 µJ that no fetch spent (no document is read) and decided and sealed it as Energy::measured, so its jcp receipt claimed a hardware reading of work no meter saw. The cost now comes from `retrieval_cost()`, the citation's figure graded Estimator with no measured figure; the docs and the first output line say the retrieval is illustrative. The figure, and so the receipt's cost_uj and the citation commitment, are unchanged. tests/cite_example.rs compiles the example in as a module: its cost must be Estimator with no measured figure, and the receipt sealed over it must carry energy_provenance "Estimator", decoded from the signed core. Co-Authored-By: Claude Opus 5.5 --- jcp/jcp-rs/Cargo.lock | 1 + jcp/jcp-rs/crates/jcp-cite/Cargo.toml | 3 + jcp/jcp-rs/crates/jcp-cite/examples/cite.rs | 22 ++++++- .../crates/jcp-cite/tests/cite_example.rs | 57 +++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 jcp/jcp-rs/crates/jcp-cite/tests/cite_example.rs diff --git a/jcp/jcp-rs/Cargo.lock b/jcp/jcp-rs/Cargo.lock index 4fa8cbb1b..bdb2c0aaf 100644 --- a/jcp/jcp-rs/Cargo.lock +++ b/jcp/jcp-rs/Cargo.lock @@ -555,6 +555,7 @@ dependencies = [ "jcp-runtime", "jcp-schema", "serde", + "serde_json", "thiserror 2.0.20", ] diff --git a/jcp/jcp-rs/crates/jcp-cite/Cargo.toml b/jcp/jcp-rs/crates/jcp-cite/Cargo.toml index 16832ec1b..0772ecca5 100644 --- a/jcp/jcp-rs/crates/jcp-cite/Cargo.toml +++ b/jcp/jcp-rs/crates/jcp-cite/Cargo.toml @@ -16,5 +16,8 @@ ed25519-dalek = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } +[dev-dependencies] +serde_json = { workspace = true } + [lints] workspace = true diff --git a/jcp/jcp-rs/crates/jcp-cite/examples/cite.rs b/jcp/jcp-rs/crates/jcp-cite/examples/cite.rs index f6d9ceb88..5db4ede92 100644 --- a/jcp/jcp-rs/crates/jcp-cite/examples/cite.rs +++ b/jcp/jcp-rs/crates/jcp-cite/examples/cite.rs @@ -6,6 +6,11 @@ //! ```text //! cargo run -p jcp-cite --example cite //! ``` +//! +//! The retrieval is illustrative: no document is fetched, so the energy the +//! citation records is a chosen figure, and the decision it is debited under +//! seals it as an estimate ([`retrieval_cost`]) — never as a hardware reading +//! of work no meter saw. use ed25519_dalek::SigningKey; use jcp_cite::{verify, Citation, Source}; @@ -37,10 +42,14 @@ fn main() { Source::new(ContentId::of_bytes(doc_a), g_finance).at("/finance/q3-report"), Source::new(ContentId::of_bytes(doc_b), g_people).at("/people/q4-plan"), ], - 4_200, // µJ the retrieval consumed + 4_200, // µJ the retrieval is put at: illustrative, nothing read it [7; 16], ); - println!("agent read {} sources → answer, {} µJ", citation.sources.len(), citation.energy_uj); + println!( + "agent read {} sources → answer, {} µJ (illustrative: sealed as an estimate)", + citation.sources.len(), + citation.energy_uj + ); // The retrieval is decided and metered under a real grant, and the receipt // commits the citation by content id. @@ -58,7 +67,7 @@ fn main() { }; let sg = issue(&issuer, grant.clone()).unwrap(); let frame = citation.frame(grant.id(), corpus).unwrap(); - let decision = Runtime::new().decide(&frame, &sg, agent_did, Energy::measured(citation.energy_uj), 1); + let decision = Runtime::new().decide(&frame, &sg, agent_did, retrieval_cost(&citation), 1); let steward = SigningKey::from_bytes(&[9u8; 32]); let receipt = seal(&frame, &grant, &decision, &steward, "did:example:steward").unwrap(); let core = extract(&receipt, "did:example:steward", &steward.verifying_key().to_bytes()).unwrap(); @@ -93,3 +102,10 @@ fn main() { } } } + +/// The cost the retrieval is debited and sealed at: the citation's recorded +/// figure, graded `Estimator` with no measured figure, because nothing in +/// this example read it. +pub fn retrieval_cost(citation: &Citation) -> Energy { + Energy::estimator(citation.energy_uj) +} diff --git a/jcp/jcp-rs/crates/jcp-cite/tests/cite_example.rs b/jcp/jcp-rs/crates/jcp-cite/tests/cite_example.rs new file mode 100644 index 000000000..6780b74f7 --- /dev/null +++ b/jcp/jcp-rs/crates/jcp-cite/tests/cite_example.rs @@ -0,0 +1,57 @@ +//! The `cite` example's retrieval is illustrative — no document is fetched — +//! so the cost it is decided and sealed at must be an estimate, never a +//! hardware reading. Checked on the cost the example builds (the example +//! file is compiled in here as a module) and on the `jcp` receipt sealed +//! over it, decoded from the signed core. + +#[allow(dead_code)] +#[path = "../examples/cite.rs"] +mod cite; + +use ed25519_dalek::SigningKey; +use jcp_cite::{Citation, Source}; +use jcp_grant::issue; +use jcp_runtime::Runtime; +use jcp_schema::{Capability, ContentId, Did, Grant, Provenance}; + +#[test] +fn the_cite_examples_retrieval_is_sealed_as_an_estimate() { + let issuer = SigningKey::from_bytes(&[1u8; 32]); + let agent = Did::from_verifying_key(&SigningKey::from_bytes(&[2u8; 32]).verifying_key()); + let corpus = ContentId::of_bytes(b"acme-workspace"); + let citation = Citation::new( + agent, + ContentId::of_bytes(b"answer"), + vec![Source::new(ContentId::of_bytes(b"doc"), ContentId::of_bytes(b"grant:finance"))], + 4_200, + [7; 16], + ); + let cost = cite::retrieval_cost(&citation); + eprintln!("retrieval cost: {} µJ [{}] measured {:?}", cost.uj(), cost.provenance.jcr1_label(), cost.measured_uj); + assert_eq!(cost.provenance, Provenance::Estimator, "an illustrative retrieval graded {:?}", cost.provenance); + assert_eq!(cost.measured_uj, None, "a measured figure nothing read"); + assert_eq!(cost.uj(), citation.energy_uj, "the receipt's cost is the citation's figure"); + + let grant = Grant { + issuer: Did::from_verifying_key(&issuer.verifying_key()), + subject: agent, + capability: Capability::new(corpus, ["retrieve"]), + budget_uj: 1_000_000, + budget_funds: None, + caveats: Vec::new(), + expiry: 0, + parent: None, + delegate_depth: None, + nonce: [0; 16], + }; + let sg = issue(&issuer, grant.clone()).expect("issue"); + let frame = citation.frame(grant.id(), corpus).expect("frame"); + let decision = Runtime::new().decide(&frame, &sg, agent, cost, 1); + assert!(decision.is_allow(), "{:?}", decision.verdict); + let steward = SigningKey::from_bytes(&[9u8; 32]); + let env = jcp_receipt::seal(&frame, &grant, &decision, &steward, "did:example:steward").expect("seal"); + let core = jcp_receipt::jcr1::verify_cose_sign1(&env, "did:example:steward", &steward.verifying_key().to_bytes()).expect("verifies"); + let sealed: serde_json::Value = jcp_schema::from_canonical_cbor(&core).expect("decodes"); + eprintln!("sealed: cost_uj {} [{}]", sealed["cost_uj"], sealed["energy_provenance"]); + assert_eq!(sealed["energy_provenance"].as_str(), Some("Estimator"), "{sealed}"); +} -- 2.54.0 (Apple Git-157) From 7f41c9a807c617b36333aa6a8d6284de682982b5 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 09:57:24 -0400 Subject: [PATCH 06/21] jcp-action-receipt: end_to_end authorises at an estimate, not a reading The end_to_end example decides its action before the work runs and debited Energy::measured(250_000), so the authority receipt it seals and binds into the AR-1 receipt claimed a 0.25 J hardware reading of work that had not happened. Its own docs promise HwShunt "only when every record the meter returned is a hardware reading". The decision now debits `authorised_cost()`, the action's 0.25 J estimate graded Estimator with no measured figure, and the docs say the work's reading is step 3's. The work receipt and the AR-1 receipt are unchanged: they already carry the meter's own grade. tests/end_to_end_example.rs compiles the example in as a module and checks the authorised cost is Estimator with no measured figure. Co-Authored-By: Claude Opus 5.5 --- .../jcp-action-receipt/examples/end_to_end.rs | 15 +++++++++++-- .../tests/end_to_end_example.rs | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 jcp/jcp-rs/crates/jcp-action-receipt/tests/end_to_end_example.rs diff --git a/jcp/jcp-rs/crates/jcp-action-receipt/examples/end_to_end.rs b/jcp/jcp-rs/crates/jcp-action-receipt/examples/end_to_end.rs index e42177598..42a06b98a 100644 --- a/jcp/jcp-rs/crates/jcp-action-receipt/examples/end_to_end.rs +++ b/jcp/jcp-rs/crates/jcp-action-receipt/examples/end_to_end.rs @@ -14,7 +14,9 @@ //! - the grant is **really issued** and **really attenuated**, and amplification //! is really refused; //! - the authorisation is a **real `decide` call** against a real budget, and -//! the receipt records the verdict that call returned; +//! the receipt records the verdict that call returned. It is decided before +//! the work runs, so the cost it debits is the action's estimate +//! ([`authorised_cost`], `Estimator`) — the work's reading is step 3's; //! - the joules are **metered**, and the span's provenance is carried through //! verbatim: `HwShunt` only when every record the meter returned is a //! hardware reading. Where the host has no live counter — or the span's CPU @@ -96,7 +98,7 @@ fn main() -> Result<(), Box> { resource, payload_hash: ContentId::of_bytes(b"input/quarterly-report"), }; - let decision = rt.decide(&frame, &delegated, sub_did, Energy::measured(250_000), 0); + let decision = rt.decide(&frame, &delegated, sub_did, authorised_cost(), 0); let verdict = match decision.verdict { Verdict::Allow => "allow", Verdict::Deny(_) => "deny", @@ -250,3 +252,12 @@ fn main() -> Result<(), Box> { ); Ok(()) } + +/// The cost the authorisation debits: decided before the work runs, it is +/// the action's estimate, 0.25 J, graded `Estimator` with no measured figure. +/// Sealed as a reading, the authority receipt would claim a meter read work +/// that had not yet happened; the joules a meter did read are the work +/// receipt's, and the action receipt's. +pub fn authorised_cost() -> Energy { + Energy::estimator(250_000) +} diff --git a/jcp/jcp-rs/crates/jcp-action-receipt/tests/end_to_end_example.rs b/jcp/jcp-rs/crates/jcp-action-receipt/tests/end_to_end_example.rs new file mode 100644 index 000000000..497ecaf45 --- /dev/null +++ b/jcp/jcp-rs/crates/jcp-action-receipt/tests/end_to_end_example.rs @@ -0,0 +1,21 @@ +//! The `end_to_end` example authorises its action before the work runs, so +//! the cost that decision debits and the authority receipt seals is an +//! estimate — never a hardware reading of work that had not happened. (The +//! work's own energy is metered, at the meter's grade, in step 3.) Checked on +//! the cost the example uses (the example file is compiled in here as a +//! module). + +#[allow(dead_code)] +#[path = "../examples/end_to_end.rs"] +mod end_to_end; + +use jcp_schema::Provenance; + +#[test] +fn the_authorisation_is_debited_an_estimate() { + let cost = end_to_end::authorised_cost(); + eprintln!("authorised cost: {} µJ [{}] measured {:?}", cost.uj(), cost.provenance.jcr1_label(), cost.measured_uj); + assert_eq!(cost.provenance, Provenance::Estimator, "a pre-run cost graded {:?}", cost.provenance); + assert_eq!(cost.measured_uj, None, "a measured figure for work that had not run"); + assert!(cost.check_provenance().is_ok(), "{cost:?}"); +} -- 2.54.0 (Apple Git-157) From 11f3ba9d350c83204bb6494737e889e9096de9ca Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 10:00:58 -0400 Subject: [PATCH 07/21] jouledesk: tag() seals the skill's cost as an estimate, not a reading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Teammate::tag (and tag_mention, which calls it) sealed each task's efficiency/1 energy as a HwShunt hardware reading. The figure is the cost the dispatch's last decision receipt sealed — the spend the skill reported, else the cost it committed before it ran; the reference Skill reports a constant — and that decision receipt already grades it Estimator. So the same figure was an estimate in one receipt and a hardware reading nothing took in the other, against SPEC §6's "honest provenance". tag() now seals Estimator; a binding whose skill meters its work passes its meter's grade through tag_with_provenance, unchanged. The decision receipts, and so the published jouledesk/1 conformance vector (summarize-allow.hex, checked by tests/pack.rs), are untouched. The unit test that pinned the old default ("plain tag seals the committed cost as a hardware reading") asserted the fabrication; it now asserts that tag and tag_mention both seal "Estimator", read back through the verified efficiency chain. Co-Authored-By: Claude Opus 5.5 --- .../jouledesk-rs/crates/jouledesk/src/lib.rs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/jouledesk/jouledesk-rs/crates/jouledesk/src/lib.rs b/jouledesk/jouledesk-rs/crates/jouledesk/src/lib.rs index 154581e89..d41bbd503 100644 --- a/jouledesk/jouledesk-rs/crates/jouledesk/src/lib.rs +++ b/jouledesk/jouledesk-rs/crates/jouledesk/src/lib.rs @@ -238,11 +238,17 @@ impl Teammate { /// teammate authorises it against its grants **outside the model**, runs it, and /// seals the decision. Returns the outcome. /// - /// The task's `efficiency/1` receipt carries its energy as a hardware reading - /// ([`EnergyProvenance::HwShunt`]). A binding whose figure came from anything - /// else — an energy model, an estimator — uses [`Self::tag_with_provenance`]. + /// The task's `efficiency/1` receipt carries the cost the dispatch's last + /// decision receipt sealed: the spend the skill reported, else the cost it + /// committed before it ran. Nothing here says how the skill obtained that + /// figure — the reference [`Skill`] reports a constant — so it is sealed as + /// an estimate ([`EnergyProvenance::Estimator`]), the grade the decision + /// receipt already gives it. Sealed as a hardware reading it would claim a + /// meter read work no meter saw (SPEC §6: energy "at honest provenance"). + /// A binding whose skill meters its work passes its meter's grade with + /// [`Self::tag_with_provenance`]. pub fn tag(&mut self, kind: &str, intent: &str) -> TagOutcome { - self.tag_with_provenance(kind, intent, EnergyProvenance::HwShunt) + self.tag_with_provenance(kind, intent, EnergyProvenance::Estimator) } /// [`Self::tag`], sealing the task's `efficiency/1` energy at `provenance`: how @@ -483,11 +489,20 @@ mod tests { assert_eq!(t.tag_with_provenance("summarize", "the Q3 thread", provenance), TagOutcome::Done); assert_eq!(sealed_provenance(&t), label, "an energy figure obtained as {provenance:?}"); } - // Plain `tag` is unchanged: it seals the committed cost as a hardware reading. + // Plain `tag` (and `tag_mention`, which calls it) knows nothing of how + // the skill obtained its cost — the reference skill reports a constant — + // so it seals an estimate, never a hardware reading nothing took. let mut t = Teammate::new(); t.grant_skill(summarizer(), 1_000_000).unwrap(); assert_eq!(t.tag("summarize", "the Q3 thread"), TagOutcome::Done); - assert_eq!(sealed_provenance(&t), "HwShunt"); + let plain = sealed_provenance(&t); + let mut m = Teammate::new(); + m.grant_skill(summarizer(), 1_000_000).unwrap(); + assert_eq!(m.tag_mention("@joule summarize the Q3 thread", "@joule"), Some(TagOutcome::Done)); + let mention = sealed_provenance(&m); + eprintln!("plain tag sealed [{plain}], tag_mention sealed [{mention}]"); + assert_eq!(plain, "Estimator", "a skill's reported constant sealed as {plain}"); + assert_eq!(mention, "Estimator", "a skill's reported constant sealed as {mention}"); } #[test] -- 2.54.0 (Apple Git-157) From 9bb42037379b67396df7d3a64455202867d62998 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 10:05:35 -0400 Subject: [PATCH 08/21] jouleclaw-harness-econ: cache and skill joules are estimates, not readings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit provenance_of graded the cache (L0) and compiled-skill (L0.5) tiers' joules HwShunt, "exact by construction". They are the figures the tiers report: a skill's constant (SKILL_JOULES, 20 nJ a resolution) and a cache's estimate of a lookup. Nothing read them, and a cascade answer carries no grade, so a run that only hit compiled skills reported its joules-per-verified-fix floor as a hardware reading of constants — the figure the swebench/local-model examples print beside the headline. Every tier's figure is now graded Estimator: the harness cannot see a grade for any of them. Runs whose model dispatches are metered already print each dispatch's own grade beside the report (window_joules, span_joules); the report's floor stays an estimate's. The EconReport field's docs say so. Test: a_skill_only_run_is_reported_as_an_estimate compiles the class before round one, so all three rounds resolve through the skill and are billed exactly 3 × SKILL_JOULES; the report's floor must be Estimator (it was HwShunt). Co-Authored-By: Claude Opus 5.5 --- .../crates/jouleclaw-harness-econ/src/lib.rs | 70 ++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-harness-econ/src/lib.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-harness-econ/src/lib.rs index 6cb27ca4a..2c9dcee94 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-harness-econ/src/lib.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-harness-econ/src/lib.rs @@ -202,7 +202,9 @@ pub struct EconReport { pub joules_per_verified_fix: f64, /// **Headline: dollars per verified fix** (`f64::INFINITY` if none verified). pub usd_per_verified_fix: f64, - /// Worst energy provenance observed — the honesty floor of the joule figures. + /// Worst energy provenance observed — the honesty floor of the joule + /// figures. `Estimator` while cascade answers carry no grade of their own: + /// every tier's joules are the figure the tier reported, not a reading. pub energy_provenance: Provenance, } @@ -494,14 +496,18 @@ impl Tier for FixedModelTier { } } -/// Honest provenance of a tier's accounted joules. Local deterministic tiers -/// (cache / skill) are exact by construction; model tiers are estimates unless -/// the tier itself carries a hardware figure (which this v0 cannot see). -fn provenance_of(tier: TierId) -> Provenance { - match tier { - TierId::L0 | TierId::L0_5ToolCompute => Provenance::HwShunt, - _ => Provenance::Estimator, - } +/// The grade of a tier's accounted joules, as far as this harness can know +/// it. A cascade answer carries the joules its tier reported and no grade: a +/// compiled skill's constant (`jouleclaw_skill::SKILL_JOULES`, 20 nJ a +/// resolution), a cache's estimate of a lookup, a model tier's own figure. No +/// tier's figure is a reading this harness took or can see the grade of, so +/// every one is graded `Estimator` — a cache or skill hit included: its figure +/// is exact only as a constant is, and sealed `HwShunt` it would claim a +/// hardware reading nothing took. A run whose model dispatches are metered +/// reports each dispatch's own grade beside the report ([`window_joules`], +/// [`span_joules`]); the report's floor stays an estimate's. +fn provenance_of(_tier: TierId) -> Provenance { + Provenance::Estimator } /// Degrade to the worse of two provenances (same floor rule as `jouleclaw-prov`). @@ -642,6 +648,52 @@ mod tests { assert_eq!(joules_obtained(Provenance::ModelBased), "measured"); } + /// A run that only ever hits a compiled skill is billed the skill's + /// constant (`SKILL_JOULES` a resolution): a figure nothing read, so the + /// report's floor must say `Estimator`, never `HwShunt`. + #[test] + fn a_skill_only_run_is_reported_as_an_estimate() { + let prompt = "generate getter for field user_id"; + let answer = "fn user_id(&self) -> &str { &self.user_id }"; + let tasks = vec![Task::text( + "getter-user_id", + "getter", + q(prompt), + move |o| matches!(o, AnswerOutput::Text(t) if t == answer), + )]; + let harness = EconHarness::new(Box::new(ConstantSkillInducer), 3); + let report = harness.run( + move |store| { + // The class is compiled before the first round, so no round + // reaches a model: every answer is the skill's. + let skill = ConstantSkillInducer + .induce(prompt, answer, TierId::L0_5ToolCompute) + .expect("a constant skill"); + store.lock().unwrap().register(skill); + let mut casc = Cascade::new(); + casc.register(Box::new(SkillTier::new(store))); + Runtime::new_without_l0(casc) + }, + &tasks, + 3, + ); + eprintln!( + "skill-only run: {} rows on tiers {:?}, {} J, floor {:?}", + report.rows.len(), + report.rows.iter().map(|r| r.tier_tag.as_str()).collect::>(), + report.total_joules, + report.energy_provenance + ); + assert_eq!(report.verified, 3, "every round resolves through the skill"); + assert!(report.rows.iter().all(|r| r.hit_skill), "a row missed the skill: {:?}", report.rows); + assert!( + (report.total_joules - 3.0 * SKILL_JOULES).abs() < 1e-15, + "billed {} J, not the skill's constant ×3", + report.total_joules + ); + assert_eq!(report.energy_provenance, Provenance::Estimator, "a skill's constant reported as a reading"); + } + #[test] fn an_estimate_is_never_reported_as_measured() { assert_eq!(joules_obtained(Provenance::Estimator), "estimated"); -- 2.54.0 (Apple Git-157) From f296c762b8a9c5dd04495b2339eb3c942f62afde Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 10:14:20 -0400 Subject: [PATCH 09/21] jouleclaw-corpus: the example ledgers' stand-in receipts are estimates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit emit_corpus and emit_corpus_semantic stand in a synthetic ledger for the receipts a deployment would read off disk, and graded every stand-in HwShunt. The corpus encodes the grade as a feature (the provenance one-hot, dims 7–9), so the emitted rows told a router that chosen figures were hardware readings. Both now grade their stand-ins Estimator, and say why. The semantic example (behind `candle`) is compile-checked with the feature. tests/emit_corpus_example.rs compiles emit_corpus in as a module, builds stand-ins across the tiers with its own `receipt` (now pub) and the corpus from them: every receipt must be Estimator and every row's one-hot must be Estimator's slot, never HwShunt's. Co-Authored-By: Claude Opus 5.5 --- .../jouleclaw-corpus/examples/emit_corpus.rs | 13 +++++-- .../examples/emit_corpus_semantic.rs | 9 +++-- .../tests/emit_corpus_example.rs | 36 +++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/tests/emit_corpus_example.rs diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus.rs index 37fbb0213..c0a664f35 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus.rs @@ -6,14 +6,17 @@ //! //! The output file is directly loadable by an EOC router trainer's JSONL //! loader. This example stands in a synthetic ledger for the receipts a -//! real deployment would read off disk. +//! real deployment would read off disk. Its figures are chosen, not read, +//! so every stand-in receipt is graded `Estimator`: the corpus carries the +//! grade as a feature, and a real deployment's receipts carry their own. use jouleclaw_corpus::{CorpusBuilder, write_jsonl}; use jouleclaw_energy::Provenance; use jouleclaw_intent::{IntentVerdict, QueryIntent}; use jouleclaw_prov::{CascadeTier, Receipt}; -fn receipt(tier: CascadeTier, uj: u64, intent: QueryIntent, confidence: f64) -> Receipt { +/// One stand-in receipt of the synthetic ledger, its figure an estimate. +pub fn receipt(tier: CascadeTier, uj: u64, intent: QueryIntent, confidence: f64) -> Receipt { Receipt { context: None, jc_receipt: "1".into(), @@ -22,7 +25,11 @@ fn receipt(tier: CascadeTier, uj: u64, intent: QueryIntent, confidence: f64) -> input_hash: "a".repeat(64), tier, joules_uj: uj, - energy_provenance: Provenance::HwShunt, + // A stand-in: nothing ran and no meter read `uj`, so it is graded + // what it is, an estimate. The corpus encodes the grade as a feature + // (its provenance one-hot); a stand-in graded `HwShunt` would teach a + // router that these figures were hardware readings. + energy_provenance: Provenance::Estimator, tools_touched: Vec::new(), claims: Vec::new(), eoc_stage: None, diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus_semantic.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus_semantic.rs index c3fd0ea4e..29ee24275 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus_semantic.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/examples/emit_corpus_semantic.rs @@ -12,7 +12,8 @@ //! is why this example is behind the `candle` feature and asks for the network //! once. The `build_with_text` join — pairing each receipt with the query that //! produced it — is exactly what a real deployment does from its own logs; here -//! we stand in a small labelled ledger. +//! we stand in a small labelled ledger, its figures chosen, not read, and so +//! graded `Estimator`. use jouleclaw_corpus::{CandleTextEmbedder, CorpusBuilder, TextEmbedder, write_jsonl}; use jouleclaw_embed_candle::{CandleEmbedder, EmbedderConfig}; @@ -30,7 +31,11 @@ fn receipt(tier: CascadeTier, uj: u64, intent: QueryIntent) -> Receipt { input_hash: "a".repeat(64), tier, joules_uj: uj, - energy_provenance: Provenance::HwShunt, + // A stand-in: nothing ran and no meter read `uj`, so it is graded + // what it is, an estimate. The corpus encodes the grade as a feature + // (its provenance one-hot); a stand-in graded `HwShunt` would teach a + // router that these figures were hardware readings. + energy_provenance: Provenance::Estimator, tools_touched: Vec::new(), claims: Vec::new(), eoc_stage: None, diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/tests/emit_corpus_example.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/tests/emit_corpus_example.rs new file mode 100644 index 000000000..009d17273 --- /dev/null +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-corpus/tests/emit_corpus_example.rs @@ -0,0 +1,36 @@ +//! The `emit_corpus` example stands in a synthetic ledger: its figures are +//! chosen, not read, so every stand-in receipt must be graded `Estimator`, +//! and the corpus rows built from them must carry that grade in their +//! provenance one-hot (dims 7–9: HwShunt / ModelBased / Estimator) — never +//! the hardware-reading slot. The example file is compiled in here as a +//! module, so its own `receipt` builds what is checked. + +#[allow(dead_code)] +#[path = "../examples/emit_corpus.rs"] +mod emit_corpus; + +use jouleclaw_corpus::CorpusBuilder; +use jouleclaw_energy::Provenance; +use jouleclaw_intent::QueryIntent; +use jouleclaw_prov::CascadeTier; + +#[test] +fn the_synthetic_ledger_is_graded_an_estimate() { + let ledger = vec![ + emit_corpus::receipt(CascadeTier::L0Cache, 12, QueryIntent::Comprehension, 0.91), + emit_corpus::receipt(CascadeTier::L2Embed, 4_100, QueryIntent::Comprehension, 0.72), + emit_corpus::receipt(CascadeTier::L3Model, 2_400_000, QueryIntent::Generation, 0.94), + emit_corpus::receipt(CascadeTier::L4Wire, 61_000_000, QueryIntent::Generation, 0.97), + ]; + for r in &ledger { + eprintln!("{}: {} µJ [{:?}]", r.id, r.joules_uj, r.energy_provenance); + assert_eq!(r.energy_provenance, Provenance::Estimator, "{}: a stand-in figure graded {:?}", r.id, r.energy_provenance); + } + let (rows, stats) = CorpusBuilder::default().with_monotone_negatives(true).build(&ledger); + eprintln!("{} rows from {} receipts", rows.len(), stats.receipts_seen); + assert!(!rows.is_empty(), "the corpus built no rows"); + for row in &rows { + let one_hot = &row.embedding[7..10]; + assert_eq!(one_hot, &[0.0, 0.0, 1.0], "a row's provenance one-hot is not Estimator's: {one_hot:?}"); + } +} -- 2.54.0 (Apple Git-157) From 12b9f4c08058f3dc315a7bb6492b04763709d26b Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 10:14:20 -0400 Subject: [PATCH 10/21] jouleclaw-stack: ledger_breakdown's simulated workload is sealed as estimates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ledger_breakdown example simulates a workload — no tier runs, no meter reads — and sealed its tool touches HwShunt at the cheap tiers and ModelBased at the model tier, so the printed summary's rows claimed hardware readings and an energy model's figures for work that never ran. Every touch now comes from `simulated_touch`, graded Estimator, and the module docs say the workload is simulated. The printed floor and rows read [Estimator]. tests/ledger_breakdown_example.rs compiles the example in as a module, seals touches at three tiers through its `simulated_touch`, and checks each touch, the summary's floor, and every by-tier and by-tool row are Estimator. Co-Authored-By: Claude Opus 5.5 --- .../examples/ledger_breakdown.rs | 23 +++++++----- .../tests/ledger_breakdown_example.rs | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/tests/ledger_breakdown_example.rs diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/examples/ledger_breakdown.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/examples/ledger_breakdown.rs index 7a041d02f..5bb0fb864 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/examples/ledger_breakdown.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/examples/ledger_breakdown.rs @@ -8,6 +8,11 @@ //! //! Run with: //! cargo run -p jouleclaw-stack --example ledger_breakdown +//! +//! The workload is simulated: no tier runs and no meter reads a figure, so +//! every tool touch is graded `Estimator` ([`simulated_touch`]), and the +//! summary's provenance floor says so. A deployment's receipts carry the +//! grade of whatever metered each touch. use jouleclaw_energy::Provenance; use jouleclaw_intent::{HeuristicClassifier, IntentClassifier}; @@ -37,15 +42,7 @@ fn main() { let mut b = ReceiptBuilder::new() .input_hash(input_hash(q)) .tier(*tier) - .account_tool(ToolTouch { - tool_id: tool_id.to_string(), - joules_uj: *joules_uj, - energy_provenance: if matches!(tier, CascadeTier::L3Model) { - Provenance::ModelBased - } else { - Provenance::HwShunt - }, - }) + .account_tool(simulated_touch(tool_id, *joules_uj)) .intent(intent); if let Some(m) = model_id { b = b.model_id(m); @@ -71,6 +68,14 @@ fn main() { print_rows("by intent", &summary.by_intent); } +/// A simulated tool touch: its figure is chosen, not read, so it is graded +/// `Estimator` at every tier. Graded `HwShunt` (as the cheap tiers were) it +/// would claim a hardware reading, and `ModelBased` (as the model tier was) an +/// energy model's figure, of work that never ran. +pub fn simulated_touch(tool_id: &str, joules_uj: u64) -> ToolTouch { + ToolTouch { tool_id: tool_id.to_string(), joules_uj, energy_provenance: Provenance::Estimator } +} + fn print_rows(label: &str, rows: &[jouleclaw_ledger::CostRow]) { if rows.is_empty() { return; diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/tests/ledger_breakdown_example.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/tests/ledger_breakdown_example.rs new file mode 100644 index 000000000..b29d7d4bb --- /dev/null +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/tests/ledger_breakdown_example.rs @@ -0,0 +1,36 @@ +//! The `ledger_breakdown` example simulates its workload: no tier runs and +//! no meter reads a figure, so every tool touch it seals must be graded +//! `Estimator`, and so must the ledger summary's provenance floor and every +//! row of it — at the model tier as at the cheap ones. The example file is +//! compiled in here as a module, so its own `simulated_touch` builds what is +//! sealed. + +#[allow(dead_code)] +#[path = "../examples/ledger_breakdown.rs"] +mod ledger_breakdown; + +use jouleclaw_energy::Provenance; +use jouleclaw_ledger::{InMemoryLedger, Ledger, LedgerStore, ProvenanceFloor, Window}; +use jouleclaw_prov::{input_hash, CascadeTier, ReceiptBuilder}; + +#[test] +fn the_simulated_workload_is_sealed_as_estimates() { + let mut ledger = InMemoryLedger::new(); + for (q, tier, uj, tool) in [ + ("explain the cascade", CascadeTier::L0Cache, 5, "cache.lookup"), + ("what does jouleclaw-prov do?", CascadeTier::L1Lawful, 12, "iac:lawful.lookup"), + ("write a hello-world fn", CascadeTier::L3Model, 1_200_000, "model:any"), + ] { + let touch = ledger_breakdown::simulated_touch(tool, uj); + eprintln!("{tool}: {} µJ [{:?}]", touch.joules_uj, touch.energy_provenance); + assert_eq!(touch.energy_provenance, Provenance::Estimator, "{tool}: a simulated figure graded {:?}", touch.energy_provenance); + let receipt = ReceiptBuilder::new().input_hash(input_hash(q)).tier(tier).account_tool(touch).seal().expect("seal"); + ledger.append(receipt).expect("append"); + } + let summary = Ledger::new(&ledger).summary(&Window::any()); + eprintln!("summary floor {:?} over {} receipts", summary.provenance_floor, summary.receipt_count); + assert_eq!(summary.provenance_floor, ProvenanceFloor::Estimator, "the simulated ledger's floor"); + for r in summary.by_tier.iter().chain(&summary.by_tool) { + assert_eq!(r.provenance_floor, ProvenanceFloor::Estimator, "row {}: {:?}", r.key, r.provenance_floor); + } +} -- 2.54.0 (Apple Git-157) From 51341edae9dad295828feacde97db522f76c3902 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 10:26:38 -0400 Subject: [PATCH 11/21] jcp-runtime: an authorization's zero is graded Estimator, not HwShunt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runtime::authorize and authorize_flow put Energy::measured(0) on every decision they return. Nothing has run at authorization, and a refusal never runs, so the zero is exact — but nothing read it. Sealed (the gateway seals every refusal authorize returns), each refusal's receipt claimed a 0 µJ hardware reading of a call that never happened, and a ledger's "all readings" floor counted it as one (SPEC §5: a figure never claims better provenance than how it was obtained). Every zero authorize returns now comes from `nothing_ran()`, Energy::estimator(0). decide/charge keep the caller's cost and grade. No pinned vector seals an authorize decision: the jcp workspace suite (440 tests, conformance vectors included) and the downstream crates that use the runtime (sandbox-jcp, joule-code-jcp, jouleclaw-jcp, joule-ui-interaction, jouledesk) pass unchanged. Test: an_authorizations_zero_is_never_a_reading drives authorize and authorize_flow through allow, untrusted control, an ungranted action and a spent budget: every cost is 0, Estimator, with no measured figure. Co-Authored-By: Claude Opus 5.5 --- jcp/jcp-rs/crates/jcp-runtime/src/lib.rs | 51 ++++++++++++++++++++---- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/jcp/jcp-rs/crates/jcp-runtime/src/lib.rs b/jcp/jcp-rs/crates/jcp-runtime/src/lib.rs index 924f9a54b..b4d3439fe 100644 --- a/jcp/jcp-rs/crates/jcp-runtime/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-runtime/src/lib.rs @@ -31,6 +31,17 @@ impl Decision { } } +/// The cost [`Runtime::authorize`] puts on its decisions: zero, because no +/// call has run yet — a refusal never runs, and an authorization only lets +/// it. The zero is exact, but nothing read it, so it is graded `Estimator` +/// with no measured figure: sealed as `HwShunt`, every refusal's receipt +/// would claim a hardware reading of a call that never happened, and a +/// ledger's "all readings" floor would count it as one (SPEC §5: a figure +/// never claims better provenance than how it was obtained). +fn nothing_ran() -> Energy { + Energy::estimator(0) +} + /// A price for one action, in a currency's minor units (SPEC §4.8). /// /// The money dual of [`Energy`] — with one deliberate difference: energy carries @@ -496,7 +507,8 @@ impl Runtime { /// because for a metered gateway the true cost is unknown until the call /// runs. Gate with this, run the tool, measure the joules, then /// [`charge`](Self::charge) the measured figure. The returned `Decision` - /// carries the budget remaining and zero cost (nothing was debited). + /// carries the budget remaining and zero cost (nothing was debited, and + /// nothing has run), graded `Estimator`: no meter read that zero. pub fn authorize(&mut self, frame: &Frame, sg: &SignedGrant, caller: Did, now: u64) -> Decision { let g = &sg.grant; let gid = g.id(); @@ -506,20 +518,20 @@ impl Runtime { { return Decision { verdict: Verdict::Deny(r), - cost: Energy::measured(0), + cost: nothing_ran(), budget_remaining_uj: remaining, }; } if remaining == 0 { return Decision { verdict: Verdict::Deny(DenyReason::BudgetExceeded), - cost: Energy::measured(0), + cost: nothing_ran(), budget_remaining_uj: 0, }; } Decision { verdict: Verdict::Allow, - cost: Energy::measured(0), + cost: nothing_ran(), budget_remaining_uj: remaining, } } @@ -571,7 +583,7 @@ impl Runtime { let remaining = g.budget_uj.saturating_sub(self.spent(&gid)); let deny = |r: DenyReason| Decision { verdict: Verdict::Deny(r), - cost: Energy::measured(0), + cost: nothing_ran(), budget_remaining_uj: remaining, }; if let Some(r) = self @@ -583,7 +595,7 @@ impl Runtime { if remaining == 0 { return Decision { verdict: Verdict::Deny(DenyReason::BudgetExceeded), - cost: Energy::measured(0), + cost: nothing_ran(), budget_remaining_uj: 0, }; } @@ -597,7 +609,7 @@ impl Runtime { } } } - Decision { verdict: Verdict::Allow, cost: Energy::measured(0), budget_remaining_uj: remaining } + Decision { verdict: Verdict::Allow, cost: nothing_ran(), budget_remaining_uj: remaining } } /// The flow-aware [`charge`](Self::charge): debit the measured tool joules as @@ -1493,4 +1505,29 @@ mod tests { .decide_chained_flow(&f, &[root_sg], did(&holder), Energy::measured(10), 10, &FlowFacts::bottom()) .is_allow()); } + + /// `authorize` puts a zero on every decision it returns — allow and each + /// refusal — because nothing has run. Nothing read that zero either, so + /// it must be graded `Estimator`, never a hardware reading: a refusal's + /// receipt seals this cost. + #[test] + fn an_authorizations_zero_is_never_a_reading() { + let (sg, holder, r) = setup(); + let mut rt = Runtime::new(); + let cases = [ + ("allow", rt.authorize(&frame(&sg, r, "read", Trust::Control), &sg, holder, 10)), + ("untrusted control", rt.authorize(&frame(&sg, r, "read", Trust::Data), &sg, holder, 10)), + ("action not granted", rt.authorize(&frame(&sg, r, "delete", Trust::Control), &sg, holder, 10)), + ("flow allow", rt.authorize_flow(&frame(&sg, r, "read", Trust::Control), &sg, holder, 10, &FlowFacts::bottom())), + ]; + rt.charge(&sg.grant, Energy::measured(100)); + let exhausted = rt.authorize(&frame(&sg, r, "read", Trust::Control), &sg, holder, 10); + let exhausted_flow = rt.authorize_flow(&frame(&sg, r, "read", Trust::Control), &sg, holder, 10, &FlowFacts::bottom()); + for (what, d) in cases.into_iter().chain([("budget spent", exhausted), ("flow, budget spent", exhausted_flow)]) { + eprintln!("{what}: {:?} cost {} µJ [{}] measured {:?}", d.verdict, d.cost.uj(), d.cost.provenance.jcr1_label(), d.cost.measured_uj); + assert_eq!(d.cost.uj(), 0, "{what}: authorize debits nothing"); + assert_eq!(d.cost.provenance, jcp_schema::Provenance::Estimator, "{what}: a zero nothing read graded {:?}", d.cost.provenance); + assert_eq!(d.cost.measured_uj, None, "{what}: a measured zero nothing read"); + } + } } -- 2.54.0 (Apple Git-157) From ee9f8cf8c796bdf28a05eb5bc678cc24303bb2f0 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 10:26:38 -0400 Subject: [PATCH 12/21] jcp-x402: the settlement receipt's zero is an estimate; pay's figure illustrative MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit seal_settlement seals the receipt that binds an on-chain settlement to the joules the paid compute consumed. It debits nothing, and sealed its own cost as Energy::measured(0): a 0 µJ hardware reading that nothing took, beside a record whose joules are the host's. It is now Energy::estimator(0), and the docs say the host's figure is committed as the host gives it, in a record with no grade, so a host must not pass an estimate there as a reading. The pay example passed a literal 5_812 µJ commented "what the meter read", with no meter and no compute. It is now labelled illustrative, in the comment and in the printed coupling line. Test: settlement_receipt_binds_tx_to_measured_joules now also decodes the settlement receipt's signed core: cost_uj 0, energy_provenance "Estimator". Co-Authored-By: Claude Opus 5.5 --- jcp/jcp-rs/crates/jcp-x402/examples/pay.rs | 11 +++++++---- jcp/jcp-rs/crates/jcp-x402/src/lib.rs | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/jcp/jcp-rs/crates/jcp-x402/examples/pay.rs b/jcp/jcp-rs/crates/jcp-x402/examples/pay.rs index 04329c8a1..9b5552aeb 100644 --- a/jcp/jcp-rs/crates/jcp-x402/examples/pay.rs +++ b/jcp/jcp-rs/crates/jcp-x402/examples/pay.rs @@ -65,16 +65,19 @@ fn main() { println!("charge in EURC (grant is USDC) → {}", verdict(&profile.authorize(&eurc, 1))); // 6. THE COUPLING. The host settles the authorized charge on chain and gets - // back a transaction; the meter reports what the paid compute actually + // back a transaction; its meter reports what the paid compute actually // consumed. Sealing them together produces the artifact neither side has: // the payment rail knows the transaction but not the energy, an energy - // meter knows the joules but not the transaction. + // meter knows the joules but not the transaction. Here both are + // illustrative: no transaction settles and no compute runs, so the + // figure below stands in for the host meter's reading — a host passes + // what its meter read, and never an estimate as one. let settled = X402Settlement { transaction: Some("0xdeadbeefcafe".into()), network: "base".into(), payer: Some("0xAgentWallet".into()), }; - let measured_uj = 5_812; // what the meter read, not the 6000 µJ estimate + let measured_uj = 5_812; // illustrative: stands in for the host meter's reading let g = profile.seal_settlement(&req, &settled, measured_uj); println!("\nsettled {} → sealed {}", settled.transaction.as_deref().unwrap_or("-"), verdict(&g)); @@ -85,7 +88,7 @@ fn main() { .expect("settlement receipt verifies"); let expected = settled.coupling_id(&req.resource, measured_uj).to_hex(); println!( - " coupling verified: {} (tx + {} µJ bound into payload_hash {}…)", + " coupling verified: {} (tx + {} µJ, illustrative, bound into payload_hash {}…)", core.payload_hash.as_deref() == Some(expected.as_str()), measured_uj, &expected[..16] diff --git a/jcp/jcp-rs/crates/jcp-x402/src/lib.rs b/jcp/jcp-rs/crates/jcp-x402/src/lib.rs index ce81d2a53..8979e49ac 100644 --- a/jcp/jcp-rs/crates/jcp-x402/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-x402/src/lib.rs @@ -350,6 +350,14 @@ impl X402Profile { /// that carries the record. A verifier checks it by recomputing /// [`X402Settlement::coupling_id`] and comparing it to the receipt's /// `payload_hash`. + /// + /// The receipt's own cost is the zero it debits, graded `Estimator`: the + /// zero is an accounting fact, not a reading, and sealed `HwShunt` it + /// would claim a hardware reading of 0 µJ beside a record whose joules are + /// the host's. `measured_uj` itself is committed as the host gives it, in + /// the record, which carries no grade: the host's meter, not this profile, + /// is what read it, and a host that has only an estimate must not pass it + /// here as a reading. pub fn seal_settlement( &mut self, req: &X402Requirements, @@ -363,7 +371,7 @@ impl X402Profile { &req.network, &req.pay_to, &payload, - Energy::measured(0), // records the coupling; does not re-debit + Energy::estimator(0), // records the coupling; debits nothing, reads nothing &Price::new(&req.asset, 0), self.now, ); @@ -559,6 +567,15 @@ mod tests { assert_eq!(profile.receipts().len(), 2); // The settlement did not re-debit; only the authorize estimate counted. assert_eq!(profile.spent_uj(), 4_200); + + // Its own cost is the zero it debits, which nothing read: sealed as an + // estimate, never as a hardware reading of 0 µJ. + let sealed = jcp_receipt::jcr1::verify_cose_sign1(&g.receipt, profile.steward(), &profile.steward_pubkey()) + .expect("the settlement receipt verifies"); + let sealed: Value = jcp_schema::from_canonical_cbor(&sealed).expect("decodes"); + eprintln!("settlement receipt: cost_uj {} [{}]", sealed["cost_uj"], sealed["energy_provenance"]); + assert_eq!(sealed["cost_uj"].as_u64(), Some(0), "{sealed}"); + assert_eq!(sealed["energy_provenance"].as_str(), Some("Estimator"), "a zero nothing read sealed as: {sealed}"); } /// A charge to a merchant the grant is not scoped to is denied on audience. -- 2.54.0 (Apple Git-157) From 1e1b59e7a643c2c520a60ed9ac4cf456610226a9 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 17:16:34 -0400 Subject: [PATCH 13/21] smart-byte: the CLI seals a command-line cost as an estimate, not a reading `envelope new` sealed the `--microjoules` figure as JouleCost::measured. The CLI runs no work a meter could read, so that figure is the issuer's claim; sealing it as a hardware measurement put a reading nobody took into the byte. It is now sealed as an estimate, with a test that a round-tripped byte carries no measured figure whatever the command line gives. Co-Authored-By: Claude Opus 5.5 --- .../crates/smart-byte/src/main.rs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/smart-byte/smart-byte-rs/crates/smart-byte/src/main.rs b/smart-byte/smart-byte-rs/crates/smart-byte/src/main.rs index 55192c408..efe49fa1d 100644 --- a/smart-byte/smart-byte-rs/crates/smart-byte/src/main.rs +++ b/smart-byte/smart-byte-rs/crates/smart-byte/src/main.rs @@ -54,7 +54,9 @@ enum EnvelopeCmd { /// US dollars in micros (six decimals). Used for cargo-type=usd. #[arg(long = "amount-micros", default_value_t = 0)] amount_micros: i64, - /// Microjoules. Used for cargo-type=joules, and as the measured cost. + /// Microjoules. Used for cargo-type=joules, and as the byte's cost — + /// sealed as an estimate: the CLI reads no meter, so a figure given here + /// is the issuer's claim, never a hardware measurement. #[arg(long = "microjoules", default_value_t = 0)] microjoules: u64, /// Optional path to write the CBOR-encoded envelope to. @@ -130,6 +132,15 @@ fn demo_holder() -> Aid { Aid::of_inception(Said::hash(b"smart-byte cli demo holder")) } +/// The cost `envelope new` seals: the figure given on the command line, as +/// an estimate. `JouleCost::measured_microjoules` is energy measured by +/// hardware, and the CLI measures nothing — no work runs to issue a demo byte +/// that a meter could read — so a figure put there would claim a measurement +/// nobody took. +fn demo_cost(microjoules: u64) -> JouleCost { + JouleCost::estimated(microjoules) +} + fn envelope_new( cargo_type: CargoType, amount_micros: i64, @@ -142,7 +153,7 @@ fn envelope_new( CargoType::Joules => Cargo::joules(microjoules), }; let issuer = demo_issuer(); - let envelope = Envelope::issue(&issuer, demo_holder(), [0; 16], cargo, JouleCost::measured(microjoules)); + let envelope = Envelope::issue(&issuer, demo_holder(), [0; 16], cargo, demo_cost(microjoules)); let bytes = envelope.to_cbor(); println!("identity : {}", envelope.identity()); @@ -217,3 +228,23 @@ fn cluster_simulate( fn hex8(hash: &[u8; 32]) -> String { hash.iter().take(8).map(|b| format!("{b:02x}")).collect() } + +#[cfg(test)] +mod tests { + use super::*; + + /// The CLI measures nothing, so the cost it seals carries no measured + /// figure, whatever the command line gives — checked on the issued byte + /// after a round trip through its canonical bytes. + #[test] + fn a_command_line_figure_is_sealed_as_an_estimate() { + for uj in [0, 1_234, 50_000_000] { + let env = Envelope::issue(&demo_issuer(), demo_holder(), [0; 16], Cargo::joules(uj), demo_cost(uj)); + let back = Envelope::from_cbor(&env.to_cbor()).expect("decode"); + let cost = back.joule_cost; + eprintln!("--microjoules {uj}: measured {} µJ, estimated {} µJ", cost.measured_microjoules, cost.estimated_microjoules); + assert_eq!(cost.measured_microjoules, 0, "a figure no meter read sealed as measured: {cost:?}"); + assert_eq!(cost.estimated_microjoules, uj, "{cost:?}"); + } + } +} -- 2.54.0 (Apple Git-157) From c2b7f8af048e2afeaa806f0e92d361e9bed4b081 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 17:51:59 -0400 Subject: [PATCH 14/21] sandbox-tee: attested_efficiency attests what this host's meter read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The example attested a literal 117,000 µJ, described as "the on-device meter read 117 mJ … a real sensor reading", under CounterClass::Rapl and sealed it into efficiency/1 as Energy::modeled. No meter ran and no task ran; on a host with no RAPL counter it printed a RAPL "real sensor reading" at IntegrityAttested. It now runs a bounded SHA-512 chain on this thread, meters it with PowerMeter::detect_in_process, and attests the figure under the class of the counter that read the CPU work, sealing it HwShunt; where no counter read it (none live, or one that did not advance), the figure is the meter's estimate, attested as CounterClass::Estimator and sealed Estimator. The closing text follows the attested class. No value figure is invented (value_uusd: None). The kernel's per-process count (macOS ri_energy_nj) is the counter detect_in_process reads on macOS, and no class named it, so CounterClass::ProcessEnergy is added (tag process_energy, binding discriminant 7; existing discriminants unchanged; not independently rooted). jcp-efficiency MAPPING.md now describes the example as it is. Tests: tests/attested_efficiency_example.rs compiles the example in and asserts the attested class is one of the meter's live probes over a span with a CPU reading and sealed HwShunt, else Estimator/Estimator, and that a span with no CPU reading (the machine meter here) is attested as a non-zero estimate. energy.rs: every class binds a distinct statement. Co-Authored-By: Claude Opus 5.5 --- jcp/jcp-rs/crates/jcp-efficiency/MAPPING.md | 11 +- sandbox/sandbox-rs/Cargo.lock | 1 + .../sandbox-rs/crates/sandbox-tee/Cargo.toml | 8 +- .../examples/attested_efficiency.rs | 255 ++++++++++++------ .../crates/sandbox-tee/src/energy.rs | 19 ++ .../tests/attested_efficiency_example.rs | 89 ++++++ 6 files changed, 299 insertions(+), 84 deletions(-) create mode 100644 sandbox/sandbox-rs/crates/sandbox-tee/tests/attested_efficiency_example.rs diff --git a/jcp/jcp-rs/crates/jcp-efficiency/MAPPING.md b/jcp/jcp-rs/crates/jcp-efficiency/MAPPING.md index a876ae1d7..f5ad3a7f0 100644 --- a/jcp/jcp-rs/crates/jcp-efficiency/MAPPING.md +++ b/jcp/jcp-rs/crates/jcp-efficiency/MAPPING.md @@ -157,8 +157,15 @@ attestation via `sandbox-tee::energy` to derive the honest trust level; (4) conf the attested energy equals `energy_uj`. The receipt never asserts a level it cannot prove. The whole loop is a runnable demo: `sandbox-tee/examples/attested_efficiency.rs` (`cargo run -p sandbox-tee --example -attested_efficiency`) attests a reading, commits it into an `efficiency/1` receipt, -re-verifies both from scratch, and derives `IntegrityAttested`. +attested_efficiency`) runs a small task, attests the figure this host's meter +gave it under the counter class that obtained it, commits it into an +`efficiency/1` receipt at the same grade, re-verifies both from scratch, and +derives `IntegrityAttested`. Where a vendor counter read the task's CPU work +(the kernel's per-process count on macOS, a RAPL package on Linux) the +statement names that counter and the receipt seals `HwShunt`; where none did, +the figure is the meter's estimate, attested as `Estimator` and sealed +`Estimator` — the report binds the figure as it was obtained and does not make +an estimate a reading. **What `PhysicallyRooted` actually requires (an honest clarification).** It is *not* reached by standing up a cloud confidential VM. A cloud SEV-SNP/TDX instance gives a diff --git a/sandbox/sandbox-rs/Cargo.lock b/sandbox/sandbox-rs/Cargo.lock index 0777052a6..7f4705b60 100644 --- a/sandbox/sandbox-rs/Cargo.lock +++ b/sandbox/sandbox-rs/Cargo.lock @@ -1899,6 +1899,7 @@ dependencies = [ "rand", "rsa", "sandbox-attest", + "sandbox-meter", "sandbox-schema", "serde_json", "sha2", diff --git a/sandbox/sandbox-rs/crates/sandbox-tee/Cargo.toml b/sandbox/sandbox-rs/crates/sandbox-tee/Cargo.toml index d7f82644f..b0aad61f6 100644 --- a/sandbox/sandbox-rs/crates/sandbox-tee/Cargo.toml +++ b/sandbox/sandbox-rs/crates/sandbox-tee/Cargo.toml @@ -34,11 +34,13 @@ signature = { version = "2", optional = true } amd-kds = ["dep:rsa", "dep:x509-cert", "dep:spki", "dep:der", "dep:signature"] # Dev-only, for the `attested_efficiency` example: composes this crate's energy -# attestation with the efficiency/1 receipt (jcp-rs). A dev-dependency, so no -# consumer of sandbox-tee pulls jcp-efficiency, and the receipt crate stays free -# of any dependency on the attestation layer. +# attestation with the efficiency/1 receipt (jcp-rs), over a figure this host's +# meter (sandbox-meter) read for a task the example runs. Dev-dependencies, so +# no consumer of sandbox-tee pulls jcp-efficiency or the meter, and the receipt +# crate stays free of any dependency on the attestation layer. [dev-dependencies] jcp-efficiency = { path = "../../../../jcp/jcp-rs/crates/jcp-efficiency" } +sandbox-meter.workspace = true jcp-schema = { path = "../../../../jcp/jcp-rs/crates/jcp-schema" } ed25519-dalek = "2" # RNG for generating the self-signed AMD-structured test chain (RSA/EC keygen). diff --git a/sandbox/sandbox-rs/crates/sandbox-tee/examples/attested_efficiency.rs b/sandbox/sandbox-rs/crates/sandbox-tee/examples/attested_efficiency.rs index 4f5727fa3..ecf63fbf7 100644 --- a/sandbox/sandbox-rs/crates/sandbox-tee/examples/attested_efficiency.rs +++ b/sandbox/sandbox-rs/crates/sandbox-tee/examples/attested_efficiency.rs @@ -1,118 +1,215 @@ -//! End-to-end: a completed coding task's measured energy is attested inside a TEE, -//! committed into an `efficiency/1` receipt, and then independently re-verified -//! from scratch — deriving the honest trust level, never asserting it. This is the -//! whole frontier composed in one runnable flow. +//! End-to-end: a small task runs here, this host's meter reads its energy, the +//! figure is attested inside a TEE, committed into an `efficiency/1` receipt, +//! and then independently re-verified from scratch — deriving the trust level, +//! never asserting it. This is the whole frontier composed in one runnable flow. //! //! cargo run -p sandbox-tee --example attested_efficiency +//! +//! **What the figure is.** The task is a bounded SHA-512 chain on this thread, +//! metered by [`PowerMeter::detect_in_process`] (the work runs on this +//! process's own threads). The attested counter class and the receipt's grade +//! both follow how the figure was obtained: +//! +//! - where a vendor counter read this process's CPU work over the span (the +//! kernel's per-process count on macOS, a RAPL package on Linux, IOReport's +//! CPU rails on Apple Silicon where they are live), the statement names that +//! counter's class and the receipt seals the reading, `HwShunt`; +//! - where none did (no live counter, or one that did not advance over the +//! span), the figure is the meter's estimate: attested as +//! [`CounterClass::Estimator`] and sealed `Estimator`. +//! +//! Either way the derived trust level is `IntegrityAttested`: a software-grounded +//! report binds the figure, as it was obtained, to a measured environment. What +//! the figure itself is — a reading or an estimate — is the counter class the +//! report binds, printed beside it. use ed25519_dalek::SigningKey; -use jcp_efficiency::{verify as verify_efficiency, Efficiency}; +use jcp_efficiency::{verify as verify_efficiency, Efficiency, EfficiencyReceipt}; use jcp_schema::{ContentId, Energy}; +use sandbox_meter::{Measurement, PowerMeter, Provenance}; +use sandbox_schema::DeviceClass; use sandbox_tee::energy::{ - attest_energy, verify_attested_energy, AttestTrust, CounterClass, EnergyStatement, + attest_energy, verify_attested_energy, AttestTrust, AttestedEnergy, CounterClass, EnergyStatement, }; use sandbox_tee::provider::LocalSnpProvider; use sandbox_tee::Grounding; +use sha2::{Digest, Sha512}; -fn main() { - // ── 1. A coding task completed: 900 accepted tokens over a 2 s window, and the - // on-device meter read 117 mJ. (A RAPL figure — a real sensor reading, - // but platform-self-reported.) - let energy_uj: u64 = 117_000; - let window_ns: u64 = 2_000_000_000; - let tokens_out: u64 = 900; +/// Links in the task's SHA-512 chain: a fixed amount of real CPU work, some +/// hundreds of milliseconds in a debug build and a few in a release one. A +/// span too short for the counter to advance over it is not a reading, and +/// comes back as the meter's estimate. +pub const CHAIN_LINKS: u64 = 20_000; - // ── 2. A verifier issues a fresh nonce; we attest the reading inside a TEE. The - // LocalSnpProvider mints a real ECDSA-P384 report — software-grounded. +/// The task: a SHA-512 chain of [`CHAIN_LINKS`] links from a fixed seed. Its +/// output is the last link, so the work cannot be optimised away. +pub fn task() -> [u8; 64] { + let mut link = [0x5au8; 64]; + for _ in 0..CHAIN_LINKS { + let next = Sha512::digest(link); + link.copy_from_slice(&next); + } + link +} + +/// The counter class of the probe among `sources` (a [`PowerMeter`]'s live +/// hardware probes) that reads CPU energy: only a known one is named. `None` +/// for a probe set with no CPU counter this example can name. +pub fn cpu_counter(sources: &[&str]) -> Option { + sources.iter().find_map(|s| match *s { + "process-energy" => Some(CounterClass::ProcessEnergy), + "rapl-powercap" => Some(CounterClass::Rapl), + "ioreport-energy-model" => Some(CounterClass::IoReport), + _ => None, + }) +} + +/// The figure to attest for span `m` on `meter`, and the class that obtained +/// it. A hardware reading of the CPU work — the span's `HwShunt` CPU records, +/// from a counter this example can name — is attested under that counter's +/// class and sealed as a reading. Anything else is the meter's estimate of the +/// span ([`PowerMeter::estimate`]): attested as `Estimator`, sealed as one. +pub fn figure(meter: &PowerMeter, m: &Measurement) -> (CounterClass, Energy) { + if m.reads_cpu() { + if let Some(class) = cpu_counter(&meter.sources()) { + let cpu_uj: u64 = m + .devices + .iter() + .filter(|d| d.class == DeviceClass::Cpu && d.provenance == Provenance::HwShunt) + .filter_map(|d| d.measured_uj) + .sum(); + return (class, Energy::measured(cpu_uj)); + } + } + (CounterClass::Estimator, Energy::estimator(meter.estimate(m.wall).total_uj())) +} + +/// One run of the composed flow. +pub struct Run { + /// The meter's live hardware probes (empty: the estimator alone). + pub sources: Vec<&'static str>, + /// The span over the task, as the meter recorded it. + pub measurement: Measurement, + /// The attested statement and its report. + pub attested: AttestedEnergy, + /// The `efficiency/1` receipt, as a relying party verified it. + pub receipt: EfficiencyReceipt, + /// The trust level derived from the attestation. + pub trust: AttestTrust, + /// The task's output. + pub output: [u8; 64], + /// Whether a tampered joule figure failed attestation verification. + pub tamper_rejected: bool, +} + +/// Run the task, meter it, attest the figure, seal the receipt, and verify the +/// composition from scratch. +pub fn run() -> Run { + // ── 1. The task runs here, metered as it runs. + let mut meter = PowerMeter::detect_in_process(); + let sources = meter.sources(); + let (output, measurement) = meter.measure(task); + let (counter, energy) = figure(&meter, &measurement); + let window_ns = u64::try_from(measurement.wall.as_nanos()).unwrap_or(u64::MAX); + + // ── 2. A verifier issues a fresh nonce; we attest the figure inside a TEE, + // under the class that obtained it. The LocalSnpProvider mints a real + // ECDSA-P384 report — software-grounded. let nonce = [0x5au8; 32]; - let measurement = [0x11u8; 48]; - let provider = LocalSnpProvider::new(measurement, 7); - let statement = EnergyStatement { - counter: CounterClass::Rapl, - energy_uj, - window_ns, - nonce, - }; - let attested = attest_energy(statement, &provider).expect("attest the reading"); + let measurement_digest = [0x11u8; 48]; + let provider = LocalSnpProvider::new(measurement_digest, 7); + let statement = EnergyStatement { counter, energy_uj: energy.uj(), window_ns, nonce }; + let attested = attest_energy(statement, &provider).expect("attest the figure"); - // ── 3. Seal the efficiency/1 receipt, committing to the attestation by content id. + // ── 3. Seal the efficiency/1 receipt at the figure's own grade, committing + // to the attestation by content id. Its output units are the chain's + // links; no value figure is stated (none is invented). let sk = SigningKey::from_bytes(&[0xefu8; 32]); let steward = "did:jcp:attested-efficiency-demo"; let eff = Efficiency { - task: "fix-auth-bug".into(), + task: "sha512-chain".into(), completed: true, - verdict: "tests-pass".into(), - tokens_in: tokens_out * 8, - tokens_out, - energy: Energy::modeled(energy_uj), + verdict: "chain-computed".into(), + tokens_in: 1, + tokens_out: CHAIN_LINKS, + energy, wall_ns: window_ns, - value_uusd: Some(5_000_000), + value_uusd: None, energy_attestation: None, } .with_energy_attestation(&attested.report); let envelope = eff.seal(0, &ContentId([0u8; 32]), &sk, steward); - println!( - "sealed efficiency/1 for '{}' — {} µJ over {} accepted tokens\n", - eff.task, energy_uj, tokens_out - ); - // ── 4. A relying party runs the COMPOSED verification, from scratch: // (a) the receipt verifies under the steward key. - let rec = verify_efficiency(&envelope, steward, &sk.verifying_key().to_bytes()) - .expect("efficiency/1 verifies"); + let receipt = + verify_efficiency(&envelope, steward, &sk.verifying_key().to_bytes()).expect("efficiency/1 verifies"); // (b) its commitment resolves to the attestation we hold. - let commitment = rec - .energy_attestation - .as_deref() - .expect("receipt carries an attestation commitment"); - let resolved = ContentId::of_bytes(&attested.report).to_hex(); - assert_eq!(commitment, resolved, "commitment must match the attestation"); - // (c) the attestation verifies — deriving the honest trust level from evidence. - let trust = verify_attested_energy( - &attested, - measurement, - &nonce, - &provider.vcek(), - Grounding::Software, - ) - .expect("attestation verifies"); + let commitment = receipt.energy_attestation.as_deref().expect("receipt carries an attestation commitment"); + assert_eq!(commitment, ContentId::of_bytes(&attested.report).to_hex(), "commitment must match the attestation"); + // (c) the attestation verifies — deriving the trust level from evidence. + let trust = verify_attested_energy(&attested, measurement_digest, &nonce, &provider.vcek(), Grounding::Software) + .expect("attestation verifies"); // (d) the attested joules equal the sealed joules. - assert_eq!( - attested.statement.energy_uj, rec.energy_uj, - "attested energy must match the receipt" + assert_eq!(attested.statement.energy_uj, receipt.energy_uj, "attested energy must match the receipt"); + + // Tampering is caught: rewrite the attested energy figure downstream. + let mut forged = attested.clone(); + forged.statement.energy_uj = forged.statement.energy_uj.wrapping_add(1); + let tamper_rejected = + verify_attested_energy(&forged, measurement_digest, &nonce, &provider.vcek(), Grounding::Software).is_err(); + + Run { sources, measurement, attested, receipt, trust, output, tamper_rejected } +} + +fn main() { + let r = run(); + let counter = r.attested.statement.counter; + let sources = if r.sources.is_empty() { "none — the estimator".to_string() } else { r.sources.join(", ") }; + + println!("task '{}' ran here: {} SHA-512 links in {:?}", r.receipt.task, CHAIN_LINKS, r.measurement.wall); + println!(" meter's live counters : {sources}"); + println!( + " sealed efficiency/1 : {} µJ [{}] over {} links\n", + r.receipt.energy_uj, r.receipt.provenance, r.receipt.tokens_out ); println!("composed verification ✓"); println!(" receipt : verified under the steward key"); - println!(" commitment : matches the attestation ({}…)", &commitment[..16]); + println!(" commitment : matches the attestation"); println!( - " attested energy : {} µJ == receipt energy {} µJ", - attested.statement.energy_uj, rec.energy_uj + " attested energy : {} µJ ({}) == receipt energy {} µJ [{}]", + r.attested.statement.energy_uj, + counter.tag(), + r.receipt.energy_uj, + r.receipt.provenance ); println!(" nonce : fresh (matches the issued challenge)"); - println!(" efficiency : {} µJ / accepted token", rec.uj_per_accepted_token); - println!(" TRUST LEVEL : {trust:?}"); + println!(" efficiency : {} µJ / link", r.receipt.uj_per_accepted_token); + println!(" TRUST LEVEL : {:?}, over a `{}` figure", r.trust, counter.tag()); println!(); - // ── 5. The honesty the whole exercise turns on. - assert_eq!(trust, AttestTrust::IntegrityAttested); - println!( - "This is IntegrityAttested, not PhysicallyRooted: a software-grounded report\n\ - over a platform-self-reported counter (RAPL) attests an honest *relay* inside\n\ - a measured environment — not a reading rooted independently of the platform.\n\ - (Not a claim that RAPL is inaccurate: it is a real sensor reading; nothing\n\ - binds it to the sensor.) An independent root needs a dedicated sensor signed\n\ - at the source (a shunt) on confidential hardware — derived here, never asserted." - ); + assert_eq!(r.trust, AttestTrust::IntegrityAttested); + if counter == CounterClass::Estimator { + println!( + "This is IntegrityAttested over an ESTIMATE: no counter on this host read the\n\ + task's CPU work over the span, so the figure is the meter's estimate, attested\n\ + under the `estimator` class and sealed `Estimator`. The report binds the figure\n\ + to a measured environment; it does not make the figure a reading." + ); + } else { + println!( + "This is IntegrityAttested, not PhysicallyRooted: a software-grounded report\n\ + over a platform-self-reported counter (`{}`) attests an honest *relay* inside\n\ + a measured environment — not a reading rooted independently of the platform.\n\ + (Not a claim that the counter is inaccurate: it is a real sensor reading;\n\ + nothing binds it to the sensor.) An independent root needs a dedicated sensor\n\ + signed at the source (a shunt) on confidential hardware — derived, never asserted.", + counter.tag() + ); + } - // Prove tampering is caught: rewrite the sealed energy figure downstream. - let mut forged = attested.clone(); - forged.statement.energy_uj = 1; - assert!( - verify_attested_energy(&forged, measurement, &nonce, &provider.vcek(), Grounding::Software) - .is_err(), - "a tampered energy figure must fail attestation verification" - ); + assert!(r.tamper_rejected, "a tampered energy figure must fail attestation verification"); println!("\n(a tampered joule figure fails attestation verification — checked.)"); + std::hint::black_box(r.output); } diff --git a/sandbox/sandbox-rs/crates/sandbox-tee/src/energy.rs b/sandbox/sandbox-rs/crates/sandbox-tee/src/energy.rs index b2322bcb4..36494b49e 100644 --- a/sandbox/sandbox-rs/crates/sandbox-tee/src/energy.rs +++ b/sandbox/sandbox-rs/crates/sandbox-tee/src/energy.rs @@ -69,6 +69,10 @@ pub enum CounterClass { Nvml, /// Apple IOReport — an on-SoC power figure the platform reports. IoReport, + /// The OS kernel's count of one process's own CPU energy (macOS + /// `proc_pid_rusage`, `ri_energy_nj`) — a real platform-reported figure + /// for that process's threads alone, relayed by the platform's software. + ProcessEnergy, /// Redfish / BMC telemetry — coarse, facility-level. Redfish, /// A vendor-provided model estimate. @@ -94,6 +98,7 @@ impl CounterClass { CounterClass::Rapl => "rapl", CounterClass::Nvml => "nvml", CounterClass::IoReport => "ioreport", + CounterClass::ProcessEnergy => "process_energy", CounterClass::Redfish => "redfish", CounterClass::ModelBased => "model_based", CounterClass::Estimator => "estimator", @@ -109,6 +114,7 @@ impl CounterClass { CounterClass::Redfish => 4, CounterClass::ModelBased => 5, CounterClass::Estimator => 6, + CounterClass::ProcessEnergy => 7, } } } @@ -333,6 +339,19 @@ mod tests { assert!(matches!(out, Err(TeeError::Policy(_))), "a replayed report must be rejected"); } + #[test] + fn every_counter_class_binds_a_distinct_statement() { + use CounterClass::*; + let all = [HwShunt, Rapl, Nvml, IoReport, ProcessEnergy, Redfish, ModelBased, Estimator]; + let mut seen = std::collections::BTreeSet::new(); + for c in all { + // The same figure under another class is another statement: an + // estimate cannot be passed off as a reading by relabelling it. + assert!(seen.insert(statement(c, 1_000, [0; 32]).report_data()), "{c:?} collides"); + assert_eq!(c.is_independently_rooted(), c == HwShunt, "{c:?}"); + } + } + #[test] fn the_trust_ladder_is_derived_not_asserted() { // The only path to a physical root: a physical counter AND hardware grounding. diff --git a/sandbox/sandbox-rs/crates/sandbox-tee/tests/attested_efficiency_example.rs b/sandbox/sandbox-rs/crates/sandbox-tee/tests/attested_efficiency_example.rs new file mode 100644 index 000000000..46e2463c5 --- /dev/null +++ b/sandbox/sandbox-rs/crates/sandbox-tee/tests/attested_efficiency_example.rs @@ -0,0 +1,89 @@ +//! The `attested_efficiency` example attests and seals what this host's meter +//! read for a task it ran, never a fixed figure under a counter nothing read. +//! +//! The example file is compiled into this test as a module, so what is tested +//! is what `cargo run --example attested_efficiency` runs. The attested counter +//! class must be one of the meter's live probes, over a span that has a +//! hardware reading of the CPU work, and the receipt must seal it `HwShunt`; +//! otherwise the class must be `Estimator` and the receipt `Estimator`. A +//! literal figure attested as RAPL on a host with no RAPL counter, and sealed +//! `ModelBased`, fails both. + +#[allow(dead_code)] +#[path = "../examples/attested_efficiency.rs"] +mod example; + +use sandbox_tee::energy::{AttestTrust, CounterClass}; + +/// The probe name a class's reading comes from, for the classes the example +/// can attest a reading under. +fn source_of(class: CounterClass) -> Option<&'static str> { + match class { + CounterClass::ProcessEnergy => Some("process-energy"), + CounterClass::Rapl => Some("rapl-powercap"), + CounterClass::IoReport => Some("ioreport-energy-model"), + _ => None, + } +} + +#[test] +fn the_example_attests_and_seals_what_the_meter_read() { + let r = example::run(); + let counter = r.attested.statement.counter; + eprintln!( + "attested_efficiency: sources {:?}; span {:?} from `{}`; attested {} µJ ({}); sealed {} µJ [{}]", + r.sources, + r.measurement.devices, + r.measurement.source, + r.attested.statement.energy_uj, + counter.tag(), + r.receipt.energy_uj, + r.receipt.provenance + ); + + assert_eq!(r.attested.statement.energy_uj, r.receipt.energy_uj); + assert!(r.receipt.energy_uj > 0, "a task that ran was billed nothing"); + assert_eq!(r.trust, AttestTrust::IntegrityAttested); + assert!(r.tamper_rejected); + + match counter { + CounterClass::Estimator => { + assert_eq!(r.receipt.provenance, "Estimator", "an estimate sealed as something else"); + } + reading => { + let source = source_of(reading) + .unwrap_or_else(|| panic!("the example attested a reading under `{}`, a class it cannot read", reading.tag())); + assert!( + r.sources.contains(&source), + "attested `{}` but the meter's live probes are {:?}: a counter nothing read", + reading.tag(), + r.sources + ); + assert!(r.measurement.reads_cpu(), "attested a reading of a span with no CPU reading: {:?}", r.measurement); + assert_eq!(r.receipt.provenance, "HwShunt", "a reading sealed at another grade"); + } + } + if r.sources.is_empty() { + assert_eq!(counter, CounterClass::Estimator, "no live counter, yet a reading was attested"); + } +} + +/// A span with no hardware reading of the CPU work is attested as the +/// estimate it is: `Estimator`, no measured figure, and not free. Driven with +/// the machine-wide meter, which on a host with no live machine counter (Apple +/// Silicon whose IOReport CPU rails are not live) has no reading to give. +#[test] +fn a_span_with_no_cpu_reading_is_attested_as_an_estimate() { + let mut meter = sandbox_meter::PowerMeter::detect(); + let (_, m) = meter.measure(example::task); + let (class, energy) = example::figure(&meter, &m); + eprintln!("machine meter: sources {:?}; span {:?}; attested as `{}`: {energy:?}", meter.sources(), m.devices, class.tag()); + if m.reads_cpu() && example::cpu_counter(&meter.sources()).is_some() { + assert_ne!(class, CounterClass::Estimator); + assert_eq!(energy.provenance, jcp_schema::Provenance::HwShunt); + } else { + assert_eq!(class, CounterClass::Estimator); + assert_eq!((energy.provenance, energy.measured_uj), (jcp_schema::Provenance::Estimator, None)); + } + assert!(energy.uj() > 0, "{energy:?}"); +} -- 2.54.0 (Apple Git-157) From 3f7a5c17558ad604ababee89006eeeff6388b708 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 17:52:56 -0400 Subject: [PATCH 15/21] sandbox-cli: demo and confidential seal illustrative identities, not named devices `sandbox demo` and `sandbox confidential` detect no device and run no unit, yet the attestations they seal named a CPU model, a GPU product with a driver release, and a confidential-compute CPU product: device identities no probe saw, sealed as if detected. Each identity is now a capability-named placeholder labelled illustrative ("illustrative host CPU (none detected)", driver "illustrative"), built by illustrative_identity beside the existing illustrative() energy records. The module docs say so. Test: the_illustrative_runs_seal_no_detected_identity fails on the old reports (demo: model "Generic x86_64") and passes now. Co-Authored-By: Claude Opus 5.5 --- .../sandbox-rs/crates/sandbox-cli/src/main.rs | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/sandbox/sandbox-rs/crates/sandbox-cli/src/main.rs b/sandbox/sandbox-rs/crates/sandbox-cli/src/main.rs index aef03bcdb..450557300 100644 --- a/sandbox/sandbox-rs/crates/sandbox-cli/src/main.rs +++ b/sandbox/sandbox-rs/crates/sandbox-cli/src/main.rs @@ -13,7 +13,9 @@ //! no work done there is nothing to read, so every energy record they seal //! is an illustrative figure graded `Estimator`, with no measured figure //! ([`illustrative`]) — never a hardware reading (SPEC §6.3: `HwShunt` is a -//! figure read from a hardware energy interface for that device class). A +//! figure read from a hardware energy interface for that device class). With +//! no device detected, every device identity they seal is a placeholder named +//! by capability and labelled illustrative ([`illustrative_identity`]). A //! real unit's energy, metered as it ran, is `run-wasm`'s. use ed25519_dalek::SigningKey; @@ -372,6 +374,19 @@ fn illustrative(class: DeviceClass, estimated_uj: u64) -> DeviceEnergy { } } +/// An illustrative device identity for `class`: a placeholder named by the +/// capability it stands for (`what`), labelled illustrative, with an +/// illustrative driver. `demo` and `confidential` detect no device, so an +/// identity of theirs that named a product or a driver release would claim a +/// device nothing saw. +fn illustrative_identity(class: DeviceClass, what: &str) -> DeviceIdentity { + DeviceIdentity { + class, + model: format!("illustrative {what} (none detected)"), + driver: "illustrative".into(), + } +} + /// The run report `demo` seals: a CPU and a GPU record, both illustrative. fn demo_report() -> RunReport { RunReport { @@ -380,16 +395,8 @@ fn demo_report() -> RunReport { illustrative(DeviceClass::Gpu, 51_000), ], device_identity: vec![ - DeviceIdentity { - class: DeviceClass::Cpu, - model: "Generic x86_64".into(), - driver: "host".into(), - }, - DeviceIdentity { - class: DeviceClass::Gpu, - model: "NVIDIA H100".into(), - driver: "560.x".into(), - }, + illustrative_identity(DeviceClass::Cpu, "host CPU"), + illustrative_identity(DeviceClass::Gpu, "accelerator GPU"), ], outcome: Outcome::Completed, journal: None, @@ -402,11 +409,7 @@ fn demo_report() -> RunReport { fn confidential_report() -> RunReport { RunReport { device_energy: vec![illustrative(DeviceClass::Cpu, 2_100)], - device_identity: vec![DeviceIdentity { - class: DeviceClass::Cpu, - model: "AMD EPYC (SEV-SNP)".into(), - driver: "host".into(), - }], + device_identity: vec![illustrative_identity(DeviceClass::Cpu, "confidential-compute CPU")], outcome: Outcome::Completed, journal: None, storage: None, @@ -444,4 +447,19 @@ mod tests { } } } + + /// Nothing is detected in `demo` or `confidential`, so the device + /// identities their attestations seal must say so: each one a + /// capability-named placeholder labelled illustrative, never a named + /// product or driver release that no probe saw. + #[test] + fn the_illustrative_runs_seal_no_detected_identity() { + for (name, report) in [("demo", demo_report()), ("confidential", confidential_report())] { + assert!(!report.device_identity.is_empty(), "{name}: no identities"); + for d in &report.device_identity { + assert!(d.model.starts_with("illustrative "), "{name}: an identity nothing detected: {d:?}"); + assert_eq!(d.driver, "illustrative", "{name}: a driver nothing detected: {d:?}"); + } + } + } } -- 2.54.0 (Apple Git-157) From f456270ca45cea462c994ad38fcc72657806e8ef Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 17:58:55 -0400 Subject: [PATCH 16/21] =?UTF-8?q?mesh-meter:=20an=20estimate=20of=20a=20se?= =?UTF-8?q?rved=20span=20is=20never=200=20=C2=B5J=20nor=20the=20meter's=20?= =?UTF-8?q?waits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HwMeter passed the family meter's whole-host estimators straight through. Those accrue by the millisecond, so a span shorter than one — a served request, here — read 0 µJ: on a host with no live whole-request counter (this M5 Max), every request served with `server,hw-meter` or `server,hw-meter-apple` sealed 0 µJ [Estimator], against HwMeter's own rule that a span is never billed a zero. A backend that is a whole-host estimate is now billed through this crate's EstimatorMeter, which resolves a microjoule, and the bridge names itself `estimator` then. ProcessMeter (`server,metal`) sealed ~1.5–9.5 J [Estimator] per request for ~11 µs of engine work. The process CPU count does not advance over so short a serve, so ProcessSpanMeter falls back to the host meter, whose window opens before its first GPU-rail snapshot and closes after its last — 100 ms or more of the meter's own waiting at each end — and its load estimate billed that. A whole Estimator span (only that fallback gives one) is now billed this crate's estimate over the work's own window. The estimator's window for the rest of the host now opens after the span meter's starting readings, the same window as the CPU reading, instead of before its GPU snapshot wait. Measured here, one served request: hw-meter 0 → 103 µJ, hw-meter-apple 0 → 113 µJ, metal 9,480,000 → 168 µJ, all [Estimator]. Tests (each fails before): an_estimator_backend_never_bills_a_short_ span_nothing (50 spans of 50 µs over the family EstimationMeter); an_estimated_fallback_span_is_billed_the_works_own_window (energy_of over the fallback span seen here); serve_example asserts usage.energy.uj > 0 for every grade. The waits sit in jouleclaw-energy's ProcessSpanMeter (its fallback reads bracket the GPU snapshots); that is not changed here. Co-Authored-By: Claude Opus 5.5 --- mesh/mesh-rs/crates/mesh-meter/src/lib.rs | 97 +++++++++++++++++-- .../crates/mesh-openai/examples/serve.rs | 7 +- .../crates/mesh-openai/tests/serve_example.rs | 6 ++ 3 files changed, 100 insertions(+), 10 deletions(-) diff --git a/mesh/mesh-rs/crates/mesh-meter/src/lib.rs b/mesh/mesh-rs/crates/mesh-meter/src/lib.rs index 42f77daf1..e51d43f13 100644 --- a/mesh/mesh-rs/crates/mesh-meter/src/lib.rs +++ b/mesh/mesh-rs/crates/mesh-meter/src/lib.rs @@ -269,6 +269,13 @@ mod hw { /// tagged `Estimator`. A failed read is never a zero reading, which would bill a /// span nothing, or bill it the counter's whole history. /// + /// A backend that is itself a whole-host estimate (the family's load + /// estimators, where the host has no live counter) takes the same path from + /// the start: those accrue by the millisecond, so a span shorter than one — + /// a served request, often — would read 0 µJ from them, an estimate that + /// bills work that ran nothing. This crate's estimator resolves a + /// microjoule, so such a span is billed its own length. + /// /// A backend whose readings do not cover the whole request /// ([`covers_whole_request`]: an NVML counter reads the GPUs alone) never /// yields a `HwShunt` span, live or not: `mesh/1` has no coverage field, so @@ -281,6 +288,9 @@ mod hw { inner: CompositeMeter, fallback: EstimatorMeter, state: Mutex, + /// The active backend is a whole-host estimate: every span is billed + /// through `fallback` instead. + estimates: bool, } /// Where the fallback estimate picks up. @@ -301,18 +311,22 @@ mod hw { } fn bridging(inner: CompositeMeter) -> Self { + let estimates = inner + .active() + .is_some_and(|m| m.provenance() == JpProvenance::Estimator && covers_whole_request(m.domain())); Self { inner, fallback: EstimatorMeter::default(), state: Mutex::new(Fallback::default()), + estimates, } } /// The active backend's short name (`"rapl"`, `"nvml"`, `"apple-ioreport"`, - /// `"apple-silicon"`, `"estimation"`, `"none"`), or `"estimator"` once a - /// read has failed and the bridge estimates. + /// `"none"`), or `"estimator"` where this crate's estimator bills the + /// spans: the backend is a whole-host estimate, or a read has failed. pub fn active_name(&self) -> &'static str { - if self.state().failed { + if self.estimates || self.state().failed { FALLBACK_NAME } else { self.inner.active_name() @@ -365,6 +379,11 @@ mod hw { } fn snapshot(&self) -> Sample { + if self.estimates { + // The backend has only a millisecond-resolution estimate of the + // whole host: bill through this crate's, which resolves a µJ. + return self.fallback.snapshot(); + } let mut state = self.state(); if !state.failed { match self.inner.read() { @@ -690,6 +709,31 @@ mod hw { assert!(!covers_whole_request(nvml.domain())); } + /// The family's whole-host estimators accrue by the millisecond, so a + /// span shorter than a millisecond read 0 µJ from them most of the time: + /// a served request billed nothing. Bridged, such a backend bills each + /// span through this crate's [`EstimatorMeter`], whose resolution is a + /// microjoule, so a span in which work ran is never free. + #[test] + fn an_estimator_backend_never_bills_a_short_span_nothing() { + use jouleclaw_energy::meter::EstimationMeter; + let m = HwMeter::bridging(CompositeMeter::new(vec![Box::new(EstimationMeter::new(65.0))])); + for i in 0..50 { + let s = Session::start(&m); + let t = std::time::Instant::now(); + let mut x = 1u64; + while t.elapsed() < Duration::from_micros(50) { + x = x.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1); + } + std::hint::black_box(x); + let e = s.stop(); + assert_eq!((e.provenance, e.measured_uj), (Provenance::Estimator, None), "span {i}: {e:?}"); + assert!(e.estimated_uj > 0, "span {i}: 50 µs of work billed nothing: {e:?}"); + assert!(e.check_provenance().is_ok()); + } + assert_eq!(m.name(), EstimatorMeter::default().name(), "the figure is this crate's estimator's"); + } + #[test] fn a_backend_that_reads_passes_through_unchanged() { let m = bridge(|_| false); @@ -731,7 +775,7 @@ mod hw { "IOReport is used only when its CPU rail is live" ); } - if matches!(m.name(), "apple-silicon" | "estimation") { + if m.name() == EstimatorMeter::default().name() { assert_eq!(e.provenance, Provenance::Estimator); assert_eq!(e.measured_uj, None); assert!(e.estimated_uj > 0, "{e:?}"); @@ -780,8 +824,13 @@ mod process { /// [`super::HwMeter`] bills a counter that reads part of a request. /// - a span with no figure at all is the estimator's. /// - /// Off macOS, or where the process counters are not live, the span meter - /// falls back to the family's host meter, whose grade it carries. + /// Off macOS, or where the process counters are not live or did not + /// advance over the span (a few microseconds of work, say), the span meter + /// falls back to the family's host meter, whose grade it carries. Where + /// that fallback is an estimate, the figure is this crate's + /// [`EstimatorMeter`] over the work's own window: the host meter's window + /// takes in the span meter's waits for the GPU rail at both ends, so its + /// estimate would bill the request for the meter's waiting. pub struct ProcessMeter { span: ProcessSpanMeter, uses: SpanUses, @@ -815,8 +864,16 @@ mod process { self.rest.snapshot() } + /// The estimator's window for the rest of the host opens once the span + /// meter has taken its starting readings, and closes before it takes + /// its last ones ([`ProcessWindow::close`]): the same window as the CPU + /// reading, which leaves out the snapshots' own waits for the GPU rail + /// (up to `GPU_IDLE_WINDOW` on a quiet GPU, `RAIL_EDGE_WAIT` on a busy + /// one). Billing those at the estimator's draw would charge a request + /// for the meter's waiting, not its work. fn open_span(&self) -> Option> { - Some(Box::new(ProcessWindow { meter: self, rest_at: self.rest.snapshot().uj, start: Some(self.span.start()) })) + let start = self.span.start(); + Some(Box::new(ProcessWindow { meter: self, rest_at: self.rest.snapshot().uj, start: Some(start) })) } } @@ -845,7 +902,10 @@ mod process { Ok(s) if s.coverage == Coverage::Whole => match s.provenance { JpProvenance::HwShunt => Energy::measured(s.uj), JpProvenance::ModelBased => Energy::modeled(s.uj), - JpProvenance::Estimator => Energy::estimator(s.uj), + // Only the host meter's fallback is a whole estimate, over a + // window that takes in the span meter's own snapshot waits: + // this crate's estimate over the work's window instead. + JpProvenance::Estimator => Energy::estimator(rest_uj), }, Ok(s) => Energy::estimator(s.uj.saturating_add(rest_uj)), Err(_) => Energy::estimator(rest_uj), @@ -869,6 +929,27 @@ mod process { }) } + /// A span this process's counters could not read — a CPU counter that + /// did not advance over a few microseconds of work — falls back to the + /// host meter, whose window opens before the span meter's first GPU + /// snapshot and closes after its last: a quarter of a second or more of + /// the meter's own waiting. Where that fallback is an estimate, the + /// request is billed this crate's estimate over the work's own window, + /// never the host estimator's over the meter's waits (9.48 J for 11 µs + /// of work, on an M5 Max). + #[test] + fn an_estimated_fallback_span_is_billed_the_works_own_window() { + let fallback = Ok(SpanEnergy { + uj: 9_480_000, + provenance: JpProvenance::Estimator, + coverage: Coverage::Whole, + source: SpanSource::Fallback { meter: "apple-silicon", why: "process CPU: did not advance".to_string() }, + }); + let e = energy_of(fallback, 169); + assert_eq!((e.provenance, e.measured_uj, e.estimated_uj), (Provenance::Estimator, None, 169)); + assert!(e.check_provenance().is_ok()); + } + /// Only a whole span keeps its hardware grade; a partial one is an /// estimate of the whole request, the reading plus the estimator's /// figure for the rest of the host; no figure is the estimator's. diff --git a/mesh/mesh-rs/crates/mesh-openai/examples/serve.rs b/mesh/mesh-rs/crates/mesh-openai/examples/serve.rs index 5d3fe091d..135848033 100644 --- a/mesh/mesh-rs/crates/mesh-openai/examples/serve.rs +++ b/mesh/mesh-rs/crates/mesh-openai/examples/serve.rs @@ -23,11 +23,14 @@ //! threads works reads that work too. Where the GPU counter is not proved //! live (a GPU busy with other work fails its probe) the span covers part //! of the request and is sealed as an estimate of all of it, `Estimator`. +//! A serve too short for the CPU count to advance over it (this engine's +//! are microseconds) has no reading either, and is the estimator's figure +//! for the serve, `Estimator`. //! - `--features server,hw-meter` (or `hw-meter-rapl`, `-nvml`, `-apple`): //! `mesh_meter::HwMeter::detect`, the machine's counter, a reading only //! where a live one covers the whole request (a RAPL package on Linux) and -//! the family estimator otherwise. A machine counter reads everything the -//! host does over the span, not this request alone. +//! `mesh_meter::EstimatorMeter` otherwise. A machine counter reads +//! everything the host does over the span, not this request alone. //! - `--features server` alone: `mesh_meter::EstimatorMeter`, wall time × an //! assumed draw, `Estimator`. //! diff --git a/mesh/mesh-rs/crates/mesh-openai/tests/serve_example.rs b/mesh/mesh-rs/crates/mesh-openai/tests/serve_example.rs index 470492751..5e1380d13 100644 --- a/mesh/mesh-rs/crates/mesh-openai/tests/serve_example.rs +++ b/mesh/mesh-rs/crates/mesh-openai/tests/serve_example.rs @@ -13,6 +13,10 @@ //! and calls it `HwShunt` fails both: its grade in a build without a //! hardware meter, and the bound in a build with one (a CPU-only serve of 16 //! tokens is microjoules to millijoules here, nowhere near joules). +//! +//! And in every branch the request is billed something: a served request +//! ran, so a figure of 0 µJ — an estimator too coarse to see a span shorter +//! than its resolution — bills real work nothing. #[allow(dead_code)] #[path = "../examples/serve.rs"] @@ -97,6 +101,8 @@ fn the_reference_server_seals_what_its_meter_read() { ); assert_eq!(receipt["mesh_receipt"], "mesh/1"); + let uj = energy["uj"].as_u64().expect("usage.energy.uj"); + assert!(uj > 0, "a served request was billed 0 µJ [{provenance}] by meter {meter}: {energy}"); assert_eq!( receipt["energy_provenance"].as_str(), Some(provenance), -- 2.54.0 (Apple Git-157) From 23508f227dc04c410f2891f018f2fe7316d4e2da Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 17:58:55 -0400 Subject: [PATCH 17/21] ci: run mesh-openai's serve_example test in the mesh-rs feature step The serve_example regression test (the reference server seals what its meter read, never a fixture's figure) has required-features `server`. `cargo test --workspace` builds none of it, and the mesh-rs feature step ran `-p mesh-engine-gguf --features gguf`, which compiles mesh-openai with `server` but runs only mesh-engine-gguf's tests. Reinstating the 4.8 J fixture would have kept CI green. The step now selects both packages with package-qualified features: `-p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server`. Run locally in mesh/mesh-rs: mesh-openai's 11 lib tests and serve_example (1) ran and passed, beside mesh-engine-gguf's. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/standards-build.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/standards-build.yml b/.github/workflows/standards-build.yml index 9e9f7d9fe..372b33048 100644 --- a/.github/workflows/standards-build.yml +++ b/.github/workflows/standards-build.yml @@ -99,7 +99,12 @@ jobs: - { name: proof-rs, dir: proof/proof-rs, runner: linux, z3: true, conformance: proof-conformance } - { name: joulehook-rs, dir: joulehook/joulehook-rs, runner: linux } - { name: eoc-rs, dir: eoc/eoc-rs, runner: linux, feature_tests: "-p eoc-meter --features cuda" } - - { name: mesh-rs, dir: mesh/mesh-rs, runner: linux-arm64, feature_tests: "-p mesh-engine-gguf --features gguf" } + # mesh-openai's `serve_example` test (the reference server seals what + # its meter read, never a fixture's figure) has required-features + # `server`, so `cargo test --workspace` builds none of it; naming the + # package here is what runs it. Selecting mesh-engine-gguf alone + # compiles mesh-openai with `server` but runs none of its tests. + - { name: mesh-rs, dir: mesh/mesh-rs, runner: linux-arm64, feature_tests: "-p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server" } - { name: joule-ui-rs, dir: joule-ui/joule-ui-rs, runner: linux } # jouleclaw-rs is the one entry whose runner IS a correctness choice. # It reaches candle → ug → gemm, which uses inline aarch64 assembly @@ -234,7 +239,8 @@ jobs: # The NVML backends sit behind features no workspace member enables # (jcp-energy `nvml`, eoc-meter `cuda`, jouleclaw-energy `nvml`), so the # step above never compiles them, let alone runs their tests. Here they - # build and their tests run. The container has no GPU and Docker masks + # build and their tests run, as do mesh-rs's feature-gated tests (the + # gguf engines; mesh-openai's `server` and its serve_example test). The container has no GPU and Docker masks # powercap, so the hardware tests take their "unavailable, and the # oracle agrees" branch; the rule's unit tests run in full. - name: Feature-gated energy tests -- 2.54.0 (Apple Git-157) From 0c14dc33c8595151b4f84c7550b3cd7e2cb7c0a8 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 18:03:10 -0400 Subject: [PATCH 18/21] jouleclaw-hydra-client: a transport's joule figure carries its grade; ungraded is an estimate The client sealed any transport-supplied joules figure as HwShunt, in all three clients (Rust lib.rs, Go jclaw_hydra.go, Python client.py), though TransportResponse.joules_uj carries no grade: a transport with an estimate, or a figure of unknown origin, had it sealed as a hardware reading. TransportResponse now carries the grade of the meter that produced the figure (Rust joules_provenance: Option, plus with_joules; Go JoulesProvenance string; Python joules_provenance), and one rule, graded_joules / GradedJoules, decides what is sealed in every client: the figure at its stated grade; an ungraded figure (or, in Go/Python, a grade that is not HwShunt/ModelBased/Estimator) as Estimator; no figure as a zero Estimator. The conformance vectors carry no touch and are unchanged; Go and Python still verify 9/9. Tests (each fails before on HwShunt): Rust an_ungraded_transport_figure_ is_sealed_as_an_estimate, a_graded_transport_figure_keeps_its_grade; Go TestUngradedJoulesAreSealedAsEstimates, TestGradedJoulesKeepTheirGrade; Python tests/test_energy_grade.py (pytest). Co-Authored-By: Claude Opus 5.5 --- .../clients/go/jclaw_hydra.go | 34 ++++++-- .../clients/go/jclaw_hydra_test.go | 43 ++++++++++ .../clients/python/jclaw_hydra/client.py | 24 +++++- .../clients/python/tests/test_energy_grade.py | 48 ++++++++++++ .../conformance/README.md | 3 +- .../crates/jouleclaw-hydra-client/src/lib.rs | 78 ++++++++++++++++--- 6 files changed, 210 insertions(+), 20 deletions(-) create mode 100644 jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/tests/test_energy_grade.py diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/go/jclaw_hydra.go b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/go/jclaw_hydra.go index d638ecbe8..be5b346c8 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/go/jclaw_hydra.go +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/go/jclaw_hydra.go @@ -322,9 +322,32 @@ func viol(parent NodeShape, p PropertyShape, msg string) Violation { // TransportResponse is what a Transport returns to the client. type TransportResponse struct { - Status int - Body interface{} + Status int + Body interface{} + // JoulesUJ is the transport's figure for the dispatch's energy, if it + // has one. nil: none, and the client emits a zero graded "Estimator". JoulesUJ *uint64 + // JoulesProvenance is the grade of the meter that produced JoulesUJ: + // "HwShunt" only for a hardware reading, "ModelBased", or "Estimator". + // Empty (or any other value) is sealed "Estimator": the client cannot + // tell how a transport obtained a figure, and a figure must not claim + // better provenance than how it was obtained. + JoulesProvenance string +} + +// GradedJoules is the figure and grade the client seals: the transport's +// figure at its stated grade, an ungraded figure as an estimate, and no +// figure as a zero estimate. Matches TransportResponse::graded_joules. +func (r *TransportResponse) GradedJoules() (uint64, string) { + if r.JoulesUJ == nil { + return 0, "Estimator" + } + switch r.JoulesProvenance { + case "HwShunt", "ModelBased", "Estimator": + return *r.JoulesUJ, r.JoulesProvenance + default: + return *r.JoulesUJ, "Estimator" + } } // Transport pluggable transport interface. @@ -458,12 +481,7 @@ func (c *HydraClient) Call(opName string, input interface{}, transport Transport break } } - provenance := "Estimator" - joules := uint64(0) - if resp.JoulesUJ != nil { - provenance = "HwShunt" - joules = *resp.JoulesUJ - } + joules, provenance := resp.GradedJoules() return &CallReport{ ToolID: toolID, Status: resp.Status, diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/go/jclaw_hydra_test.go b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/go/jclaw_hydra_test.go index 0d71bc173..0cfdc0824 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/go/jclaw_hydra_test.go +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/go/jclaw_hydra_test.go @@ -119,3 +119,46 @@ func TestValidationConformance(t *testing.T) { } } } + +// A transport's joule figure says nothing of how it was obtained: an +// ungraded figure is sealed as an estimate, never as a hardware reading. +func TestUngradedJoulesAreSealedAsEstimates(t *testing.T) { + doc := loadConformance(t) + apiBytes, _ := json.Marshal(doc["api"]) + api, err := ParseAPI(apiBytes) + if err != nil { + t.Fatalf("parse api: %v", err) + } + client := NewClient(api, shapeGraphForTests()) + reports := doc["call_reports"].([]interface{}) + v := reports[0].(map[string]interface{}) + op := v["operation"].(string) + opID := client.FindOperation(op).ID + seven := uint64(7) + + transport := NewMockTransport().Respond(opID, &TransportResponse{Status: 200, Body: map[string]interface{}{}, JoulesUJ: &seven}) + got, err := client.Call(op, v["input"], transport) + if err != nil { + t.Fatalf("call: %v", err) + } + if got.Touch["joules_uj"] != uint64(7) || got.Touch["energy_provenance"] != "Estimator" { + t.Errorf("an ungraded figure sealed as %v", got.Touch) + } +} + +// A graded figure keeps the grade the transport gave it. +func TestGradedJoulesKeepTheirGrade(t *testing.T) { + nine := uint64(9) + for _, grade := range []string{"HwShunt", "ModelBased", "Estimator"} { + r := &TransportResponse{JoulesUJ: &nine, JoulesProvenance: grade} + if uj, p := r.GradedJoules(); uj != 9 || p != grade { + t.Errorf("%s: sealed %d %s", grade, uj, p) + } + } + if _, p := (&TransportResponse{JoulesUJ: &nine, JoulesProvenance: "Measured"}).GradedJoules(); p != "Estimator" { + t.Errorf("an unknown grade sealed as %s", p) + } + if uj, p := (&TransportResponse{JoulesProvenance: "HwShunt"}).GradedJoules(); uj != 0 || p != "Estimator" { + t.Errorf("no figure sealed as %d %s", uj, p) + } +} diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/jclaw_hydra/client.py b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/jclaw_hydra/client.py index 79380ad19..bc7ae7de4 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/jclaw_hydra/client.py +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/jclaw_hydra/client.py @@ -35,11 +35,32 @@ class InputInvalid(ClientError): self.report = report +GRADES = ("HwShunt", "ModelBased", "Estimator") + + @dataclass class TransportResponse: status: int body: Any + # The transport's figure for the dispatch's energy, if it has one. + # None: none, and the client emits a zero graded "Estimator". joules_uj: Optional[int] = None + # The grade of the meter that produced `joules_uj`: "HwShunt" only for a + # hardware reading, "ModelBased", or "Estimator". None (or any other + # value) is sealed "Estimator": the client cannot tell how a transport + # obtained a figure, and a figure must not claim better provenance than + # how it was obtained. + joules_provenance: Optional[str] = None + + def graded_joules(self) -> "tuple[int, str]": + """The figure and grade the client seals: the transport's figure at + its stated grade, an ungraded figure as an estimate, and no figure as + a zero estimate. Matches `TransportResponse::graded_joules` (Rust).""" + if self.joules_uj is None: + return 0, "Estimator" + if self.joules_provenance in GRADES: + return self.joules_uj, self.joules_provenance + return self.joules_uj, "Estimator" class Transport(Protocol): @@ -138,8 +159,7 @@ class HydraClient: status_matches = any( s.get("code") == response.status for s in op.status_codes ) - provenance = "HwShunt" if response.joules_uj is not None else "Estimator" - joules_uj = response.joules_uj or 0 + joules_uj, provenance = response.graded_joules() return CallReport( tool_id=tool_id, status=response.status, diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/tests/test_energy_grade.py b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/tests/test_energy_grade.py new file mode 100644 index 000000000..6705d2856 --- /dev/null +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/clients/python/tests/test_energy_grade.py @@ -0,0 +1,48 @@ +"""A transport's joule figure says nothing of how it was obtained: the +client seals an ungraded figure as an estimate, never as a hardware +reading, and a graded one at the grade the transport gave it. Matches +`TransportResponse::graded_joules` in the Rust client and +`GradedJoules` in the Go one. + +Run: `python -m pytest tests/` (requires `blake3`). +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT)) + +from test_conformance import VECTORS, shapes # noqa: E402 + +from jclaw_hydra import HydraClient, MockTransport, TransportResponse, parse_api # noqa: E402 + + +def _call(response: TransportResponse) -> dict: + doc = json.loads(VECTORS.read_text()) + client = HydraClient(parse_api(doc["api"]), shapes=shapes()) + vector = doc["call_reports"][0] + op = client.find_operation(vector["operation"]) + transport = MockTransport().respond(op.id, response) + return client.call(vector["operation"], vector["input"], transport).touch + + +def test_an_ungraded_figure_is_sealed_as_an_estimate() -> None: + touch = _call(TransportResponse(status=200, body={}, joules_uj=7)) + assert (touch["joules_uj"], touch["energy_provenance"]) == (7, "Estimator"), touch + + +def test_a_graded_figure_keeps_its_grade() -> None: + for grade in ("HwShunt", "ModelBased", "Estimator"): + touch = _call(TransportResponse(status=200, body={}, joules_uj=9, joules_provenance=grade)) + assert (touch["joules_uj"], touch["energy_provenance"]) == (9, grade), touch + + +def test_an_unknown_grade_or_no_figure_is_an_estimate() -> None: + touch = _call(TransportResponse(status=200, body={}, joules_uj=9, joules_provenance="Measured")) + assert touch["energy_provenance"] == "Estimator", touch + touch = _call(TransportResponse(status=200, body={}, joules_provenance="HwShunt")) + assert (touch["joules_uj"], touch["energy_provenance"]) == (0, "Estimator"), touch diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/conformance/README.md b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/conformance/README.md index 356d7dc55..8cbb7d576 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/conformance/README.md +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/conformance/README.md @@ -36,8 +36,9 @@ The three implementations live at: # Rust (regenerator; also the oracle) cargo run -p jouleclaw-hydra-client --example gen_conformance_vectors -# Python (requires `pip install blake3`) +# Python (requires `pip install blake3`; the energy-grade tests also pytest) python3 crates/jouleclaw-hydra-client/clients/python/tests/test_conformance.py +python3 -m pytest crates/jouleclaw-hydra-client/clients/python/tests/ # Go (uses lukechampine.com/blake3 via go.mod) cd crates/jouleclaw-hydra-client/clients/go && go test -v ./... diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/src/lib.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/src/lib.rs index 8cda1ecd4..8dba58786 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/src/lib.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-hydra-client/src/lib.rs @@ -121,10 +121,16 @@ pub struct TransportResponse { pub status: u16, /// Response payload — typically JSON; opaque bytes welcome too. pub body: Value, - /// Joules spent in the inner dispatch, if the transport measured - /// them. `None` means the transport did not observe energy; the - /// client emits a `Provenance::Estimator` receipt entry. + /// Joules spent in the inner dispatch, if the transport has a figure + /// for them. `None` means it has none; the client emits a zero graded + /// `Provenance::Estimator`. pub joules_uj: Option, + /// The grade of the meter that produced `joules_uj`, as the transport + /// knows it: `HwShunt` only for a hardware reading. `None` — a figure + /// with no grade — is sealed `Provenance::Estimator`: the client cannot + /// tell how a transport obtained a figure, and a figure must not claim + /// better provenance than how it was obtained. + pub joules_provenance: Option, } impl TransportResponse { @@ -133,6 +139,25 @@ impl TransportResponse { status: 200, body, joules_uj: None, + joules_provenance: None, + } + } + + /// This response with a joule figure and the grade of the meter that + /// produced it. + pub fn with_joules(mut self, uj: u64, provenance: Provenance) -> Self { + self.joules_uj = Some(uj); + self.joules_provenance = Some(provenance); + self + } + + /// The figure and grade the client seals: the transport's figure at its + /// stated grade, an ungraded figure as an estimate, and no figure as a + /// zero estimate. + pub fn graded_joules(&self) -> (u64, Provenance) { + match self.joules_uj { + Some(uj) => (uj, self.joules_provenance.unwrap_or(Provenance::Estimator)), + None => (0, Provenance::Estimator), } } } @@ -180,6 +205,7 @@ impl Transport for MockTransport { status: 200, body: serde_json::json!({ "echo": input }), joules_uj: None, + joules_provenance: None, }) } } @@ -326,14 +352,11 @@ impl HydraClient { .iter() .any(|s| s.code == response.status); + let (joules_uj, energy_provenance) = response.graded_joules(); let touch = ToolTouch { tool_id: tool_id.clone(), - joules_uj: response.joules_uj.unwrap_or(0), - energy_provenance: if response.joules_uj.is_some() { - Provenance::HwShunt - } else { - Provenance::Estimator - }, + joules_uj, + energy_provenance, }; Ok(CallReport { @@ -693,6 +716,7 @@ mod tests { status: 201, body: json!({ "ok": true, "id": "n1.x" }), joules_uj: Some(7), + joules_provenance: Some(Provenance::HwShunt), }, ); let report = c @@ -766,6 +790,41 @@ mod tests { assert_eq!(report.body["echo"], json!({ "completed": true })); } + /// A transport's figure says nothing of how it was obtained, so an + /// ungraded figure is sealed as an estimate, never as a hardware reading. + #[tokio::test] + async fn an_ungraded_transport_figure_is_sealed_as_an_estimate() { + let c = client(); + let t = MockTransport::new().respond( + "_:op_jouleclaw_list_nodes", + TransportResponse { + status: 200, + body: json!([]), + joules_uj: Some(7), + joules_provenance: None, + }, + ); + let report = c.call("jouleclaw:list_nodes", json!({}), &t).await.unwrap(); + assert_eq!(report.touch.joules_uj, 7); + assert!( + matches!(report.touch.energy_provenance, Provenance::Estimator), + "an ungraded figure sealed as {:?}", + report.touch.energy_provenance + ); + } + + /// A graded figure keeps the grade the transport gave it, whatever it is. + #[tokio::test] + async fn a_graded_transport_figure_keeps_its_grade() { + let c = client(); + for grade in [Provenance::HwShunt, Provenance::ModelBased, Provenance::Estimator] { + let t = MockTransport::new() + .respond("_:op_jouleclaw_list_nodes", TransportResponse::ok(json!([])).with_joules(9, grade)); + let report = c.call("jouleclaw:list_nodes", json!({}), &t).await.unwrap(); + assert_eq!((report.touch.joules_uj, report.touch.energy_provenance), (9, grade)); + } + } + #[tokio::test] async fn call_records_estimator_provenance_when_transport_skips_energy() { let c = client(); @@ -845,6 +904,7 @@ mod tests { status: 200, body: json!([]), joules_uj: None, + joules_provenance: None, }, ); let r = c -- 2.54.0 (Apple Git-157) From 3844e05974f15c6fa9e7f2e67361ba97d71d6f11 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 18:05:48 -0400 Subject: [PATCH 19/21] jcp-x402: the settlement record carries its figure's grade; pay meters its compute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit seal_settlement committed a bare `measured_uj: u64` into the coupling record — "the measured joules" — with a doc rule that a host must not pass an estimate there. Nothing enforced it, and the pay example passed a literal 5_812, no meter and no compute. The record now carries the grade with the figure: seal_settlement, coupling_record and coupling_id take an Energy, and the record holds joules_uj, energy_provenance and energy_coverage. A figure is committed at the grade its meter gave it; one whose grade does not hold together (HwShunt with no measured figure, Estimator with one) is committed as an estimate. This makes the rule structural and lets a host with only an estimate seal an honest settlement, where refusing would leave it none. No conformance vector pins the record (none exists for x402), so no vector changes; the receipt's own cost stays the Estimator zero. pay now runs a small fixed workload as the paid compute, meters it with jcp_energy::Meter::detect, and seals and prints the figure at its grade (here: ProcessEnergy rung, 103,971 µJ [HwShunt]); the tx hash is labelled illustrative. Tests: the_coupling_record_carries_the_figures_grade (grades, coverage, a forged HwShunt committed as Estimator); the settlement test now also checks the same joules at another grade, or partial, give another id. Co-Authored-By: Claude Opus 5.5 --- jcp/jcp-rs/Cargo.lock | 1 + jcp/jcp-rs/crates/jcp-x402/Cargo.toml | 4 + jcp/jcp-rs/crates/jcp-x402/examples/pay.rs | 48 +++++++---- jcp/jcp-rs/crates/jcp-x402/src/lib.rs | 97 +++++++++++++++------- 4 files changed, 106 insertions(+), 44 deletions(-) diff --git a/jcp/jcp-rs/Cargo.lock b/jcp/jcp-rs/Cargo.lock index bdb2c0aaf..b5ec2fed0 100644 --- a/jcp/jcp-rs/Cargo.lock +++ b/jcp/jcp-rs/Cargo.lock @@ -782,6 +782,7 @@ version = "0.1.0" dependencies = [ "ed25519-dalek", "jcp-bridge", + "jcp-energy", "jcp-grant", "jcp-receipt", "jcp-runtime", diff --git a/jcp/jcp-rs/crates/jcp-x402/Cargo.toml b/jcp/jcp-rs/crates/jcp-x402/Cargo.toml index 8632912c1..6a82d9a3e 100644 --- a/jcp/jcp-rs/crates/jcp-x402/Cargo.toml +++ b/jcp/jcp-rs/crates/jcp-x402/Cargo.toml @@ -17,5 +17,9 @@ ed25519-dalek = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } +# The pay example meters its paid compute with this host's meter. +[dev-dependencies] +jcp-energy = { workspace = true } + [lints] workspace = true diff --git a/jcp/jcp-rs/crates/jcp-x402/examples/pay.rs b/jcp/jcp-rs/crates/jcp-x402/examples/pay.rs index 9b5552aeb..f69510871 100644 --- a/jcp/jcp-rs/crates/jcp-x402/examples/pay.rs +++ b/jcp/jcp-rs/crates/jcp-x402/examples/pay.rs @@ -65,32 +65,42 @@ fn main() { println!("charge in EURC (grant is USDC) → {}", verdict(&profile.authorize(&eurc, 1))); // 6. THE COUPLING. The host settles the authorized charge on chain and gets - // back a transaction; its meter reports what the paid compute actually - // consumed. Sealing them together produces the artifact neither side has: - // the payment rail knows the transaction but not the energy, an energy - // meter knows the joules but not the transaction. Here both are - // illustrative: no transaction settles and no compute runs, so the - // figure below stands in for the host meter's reading — a host passes - // what its meter read, and never an estimate as one. + // back a transaction; its meter reports what the paid compute consumed, + // at the grade it read it. Sealing them together produces the artifact + // neither side has: the payment rail knows the transaction but not the + // energy, an energy meter knows the joules but not the transaction. + // Here no transaction settles (its hash is illustrative). The paid + // compute is a small workload run here, metered by this host's meter as + // it ran, and committed at the grade the meter gave it: a hardware + // reading only where a live counter read the work, else the estimate, + // which the record names as one. let settled = X402Settlement { transaction: Some("0xdeadbeefcafe".into()), network: "base".into(), payer: Some("0xAgentWallet".into()), }; - let measured_uj = 5_812; // illustrative: stands in for the host meter's reading - let g = profile.seal_settlement(&req, &settled, measured_uj); - println!("\nsettled {} → sealed {}", settled.transaction.as_deref().unwrap_or("-"), verdict(&g)); + let meter = jcp_energy::Meter::detect(); + let (_, energy) = meter.measure(paid_compute); + let g = profile.seal_settlement(&req, &settled, energy); + println!("\nsettled {} (illustrative tx) → sealed {}", settled.transaction.as_deref().unwrap_or("-"), verdict(&g)); + println!( + " paid compute: {} µJ [{}], metered here by the {:?} rung", + energy.uj(), + energy.provenance.jcr1_label(), + meter.rung() + ); // A third party verifies the binding without trusting the gateway: it // recomputes the coupling id from the settlement record it holds and // checks it against the receipt's payload_hash (jcp/2, SPEC §8). let core = jcp_receipt::extract(&g.receipt, profile.steward(), &profile.steward_pubkey()) .expect("settlement receipt verifies"); - let expected = settled.coupling_id(&req.resource, measured_uj).to_hex(); + let expected = settled.coupling_id(&req.resource, &energy).to_hex(); println!( - " coupling verified: {} (tx + {} µJ, illustrative, bound into payload_hash {}…)", + " coupling verified: {} (tx + {} µJ [{}], bound into payload_hash {}…)", core.payload_hash.as_deref() == Some(expected.as_str()), - measured_uj, + energy.uj(), + energy.provenance.jcr1_label(), &expected[..16] ); @@ -99,7 +109,7 @@ fn main() { let other = X402Settlement { transaction: Some("0xfeed".into()), ..settled.clone() }; println!( " re-point to another tx → id matches: {}", - core.payload_hash.as_deref() == Some(other.coupling_id(&req.resource, measured_uj).to_hex().as_str()) + core.payload_hash.as_deref() == Some(other.coupling_id(&req.resource, &energy).to_hex().as_str()) ); // 7. Every decision sealed a JCR-1 receipt that verifies under the steward @@ -123,6 +133,16 @@ fn main() { ); } +/// The paid compute: a fixed amount of real CPU work on this thread, so the +/// meter has something to read. +fn paid_compute() -> u64 { + let mut x = 0x9e37_79b9_7f4a_7c15u64; + for i in 0..5_000_000u64 { + x = std::hint::black_box(x.rotate_left(5) ^ i).wrapping_mul(0x0000_0100_0000_01b3); + } + x +} + /// Read the verdict the way a verifier would — off the sealed decision. fn verdict(g: &Gated) -> String { match g.decision.verdict { diff --git a/jcp/jcp-rs/crates/jcp-x402/src/lib.rs b/jcp/jcp-rs/crates/jcp-x402/src/lib.rs index 8979e49ac..5806eed6c 100644 --- a/jcp/jcp-rs/crates/jcp-x402/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-x402/src/lib.rs @@ -32,8 +32,8 @@ //! //! - After the host settles on chain, [`X402Profile::seal_settlement`] seals a //! receipt whose `payload_hash` commits to the **settled transaction hash and -//! the measured joules the paid compute consumed** — the coupling neither side -//! produces alone. The payment rail knows the transaction but not the energy; +//! the joules the paid compute consumed, at the grade of the meter that +//! produced them** — the coupling neither side produces alone. The payment rail knows the transaction but not the energy; //! an energy meter knows the joules but not the transaction. A verifier holding //! the settlement record recomputes its content id and checks it against the //! receipt, so the binding is cryptographic, not asserted. @@ -194,20 +194,33 @@ pub struct X402Settlement { impl X402Settlement { /// The canonical coupling record: the settled transaction, the resource paid - /// for, and the **measured** joules the paid compute consumed. + /// for, and the joules the paid compute consumed **with the grade of the + /// meter that produced them** (`energy_provenance`, and `energy_coverage` + /// for how much of the compute the figure accounts for). + /// + /// The grade is part of the record so the figure cannot claim more than how + /// it was obtained: an estimate is committed as an estimate, and a verifier + /// reads a hardware reading only where the record says `HwShunt`. A figure + /// whose grade does not hold together ([`Energy::check_provenance`]: a + /// `HwShunt` with no measured figure, an `Estimator` with one) is committed + /// as an estimate of its debit figure. /// /// A receipt sealed over this commits to its content id (`payload_hash`, /// SPEC §8), so a verifier holding the record can recompute the hash and - /// confirm the receipt binds *this* transaction to *this* energy figure. - /// Use [`coupling_id`](Self::coupling_id) to compute that id independently. - pub fn coupling_record(&self, resource: &str, joules_uj: u64) -> Vec { + /// confirm the receipt binds *this* transaction to *this* energy figure at + /// *this* grade. Use [`coupling_id`](Self::coupling_id) to compute that id + /// independently. + pub fn coupling_record(&self, resource: &str, energy: &Energy) -> Vec { + let energy = if energy.check_provenance().is_ok() { *energy } else { Energy::estimator(energy.uj()) }; let record = json!({ "x402_settlement": { "network": self.network, "transaction": self.transaction, "payer": self.payer, "resource": resource, - "joules_uj": joules_uj, + "joules_uj": energy.uj(), + "energy_provenance": energy.provenance.jcr1_label(), + "energy_coverage": energy.coverage.label(), } }); serde_json::to_vec(&record).unwrap_or_default() @@ -215,8 +228,8 @@ impl X402Settlement { /// The content id a settlement receipt's `payload_hash` must equal — the /// check a third party runs to verify the coupling. - pub fn coupling_id(&self, resource: &str, joules_uj: u64) -> ContentId { - ContentId::of_bytes(&self.coupling_record(resource, joules_uj)) + pub fn coupling_id(&self, resource: &str, energy: &Energy) -> ContentId { + ContentId::of_bytes(&self.coupling_record(resource, energy)) } } @@ -341,7 +354,8 @@ impl X402Profile { /// **Seal the settlement** — after the host settles an authorized charge on /// chain, seal a receipt whose `payload_hash` commits to the on-chain - /// transaction and the **measured** joules the paid compute consumed. + /// transaction and the joules the paid compute consumed, at the grade of + /// the meter that produced them ([`X402Settlement::coupling_record`]). /// /// This is the artifact neither side produces alone: the payment rail knows /// the transaction but not the energy; an energy meter knows the joules but @@ -354,17 +368,16 @@ impl X402Profile { /// The receipt's own cost is the zero it debits, graded `Estimator`: the /// zero is an accounting fact, not a reading, and sealed `HwShunt` it /// would claim a hardware reading of 0 µJ beside a record whose joules are - /// the host's. `measured_uj` itself is committed as the host gives it, in - /// the record, which carries no grade: the host's meter, not this profile, - /// is what read it, and a host that has only an estimate must not pass it - /// here as a reading. + /// the host's. `energy` is the host's figure for the paid compute, as its + /// meter graded it, committed with that grade: a host with only an + /// estimate passes an estimate, and the record says so. pub fn seal_settlement( &mut self, req: &X402Requirements, settlement: &X402Settlement, - measured_uj: u64, + energy: Energy, ) -> Gated { - let payload = settlement.coupling_record(&req.resource, measured_uj); + let payload = settlement.coupling_record(&req.resource, &energy); let gated = self.bridge.call_x402_priced( &self.grant, self.caller, @@ -519,8 +532,8 @@ mod tests { } /// THE coupling: the settlement receipt cryptographically binds the settled - /// on-chain transaction to the measured joules. Possible only because the - /// `jcp/2` profile commits `payload_hash` (SPEC §8). + /// on-chain transaction to the paid compute's joules at their grade. + /// Possible only because the `jcp/2` profile commits `payload_hash` (SPEC §8). #[test] fn settlement_receipt_binds_tx_to_measured_joules() { let req = ingest_requirements(&sample_req()).unwrap(); @@ -540,28 +553,30 @@ mod tests { network: "base".into(), payer: Some("0xAgentWallet".into()), }; - let g = profile.seal_settlement(&req, &settled, 4_200); + let read = Energy::measured(4_200); + let g = profile.seal_settlement(&req, &settled, read); assert!(g.allowed()); // A third party recomputes the coupling id and checks the receipt. let core = jcp_receipt::extract(&g.receipt, profile.steward(), &profile.steward_pubkey()).unwrap(); assert_eq!( core.payload_hash.as_deref(), - Some(settled.coupling_id(&req.resource, 4_200).to_hex().as_str()), + Some(settled.coupling_id(&req.resource, &read).to_hex().as_str()), "the receipt must bind THIS transaction to THIS energy figure" ); - // A different tx hash — or different joules — yields a different id, so - // the receipt cannot be re-pointed at another settlement. + // A different tx hash, different joules, or the same joules at another + // grade yields a different id, so the receipt cannot be re-pointed at + // another settlement, nor its figure re-graded. let other_tx = X402Settlement { transaction: Some("0xfeed".into()), ..settled.clone() }; - assert_ne!( - core.payload_hash.as_deref(), - Some(other_tx.coupling_id(&req.resource, 4_200).to_hex().as_str()) - ); - assert_ne!( - core.payload_hash.as_deref(), - Some(settled.coupling_id(&req.resource, 9_999).to_hex().as_str()) - ); + for (what, other) in [ + ("another tx", other_tx.coupling_id(&req.resource, &read)), + ("other joules", settled.coupling_id(&req.resource, &Energy::measured(9_999))), + ("an estimate of the same joules", settled.coupling_id(&req.resource, &Energy::estimator(4_200))), + ("a partial reading", settled.coupling_id(&req.resource, &Energy::measured_partial(4_200))), + ] { + assert_ne!(core.payload_hash.as_deref(), Some(other.to_hex().as_str()), "{what}"); + } // Two receipts on the ledger: the authorize and the settlement. assert_eq!(profile.receipts().len(), 2); @@ -578,6 +593,28 @@ mod tests { assert_eq!(sealed["energy_provenance"].as_str(), Some("Estimator"), "a zero nothing read sealed as: {sealed}"); } + /// The coupling record states its figure's grade, so a verifier reads a + /// hardware reading only where the host's meter read one; a figure whose + /// grade does not hold together is committed as an estimate. + #[test] + fn the_coupling_record_carries_the_figures_grade() { + let settled = X402Settlement { transaction: Some("0xabc".into()), network: "base".into(), payer: None }; + let record = |e: Energy| -> Value { + serde_json::from_slice::(&settled.coupling_record("/r", &e)).unwrap()["x402_settlement"].clone() + }; + let read = record(Energy::measured(4_200)); + assert_eq!((read["joules_uj"].as_u64(), read["energy_provenance"].as_str()), (Some(4_200), Some("HwShunt"))); + assert_eq!(read["energy_coverage"].as_str(), Some("Whole")); + let est = record(Energy::estimator(4_200)); + assert_eq!(est["energy_provenance"].as_str(), Some("Estimator")); + assert_eq!(record(Energy::modeled(4_200))["energy_provenance"].as_str(), Some("ModelBased")); + assert_eq!(record(Energy::measured_partial(4_200))["energy_coverage"].as_str(), Some("Partial")); + // A HwShunt grade with no measured figure is not a reading. + let forged = Energy { measured_uj: None, ..Energy::measured(4_200) }; + let forged = record(forged); + assert_eq!((forged["joules_uj"].as_u64(), forged["energy_provenance"].as_str()), (Some(4_200), Some("Estimator"))); + } + /// A charge to a merchant the grant is not scoped to is denied on audience. #[test] fn charge_to_wrong_merchant_is_denied() { -- 2.54.0 (Apple Git-157) From 667d75916dc74d6f8a11c594bded66a85967c502 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 18:08:00 -0400 Subject: [PATCH 20/21] jcp-cli: the meter line names the rung and the probe's own grade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The demo printed `meter HwShunt backend → 2388 µJ probe [ModelBased] (estimated, and says so)` here: the rung's best grade (Meter::provenance) beside a probe the model graded, because the 20,000-addition probe was too short for the per-process counter to advance. The module docs and jcp/README.md called the line "a real reading" / "its one measurement". The line now names the rung (Meter::rung) and prints only the probe's figure at the grade the meter gave it, "a hardware reading of the probe" or "an estimate: no counter read the probe". The probe is a fixed 2,000,000-step loop, long enough for a live counter to read it (here: `ProcessEnergy rung → 44215 µJ probe [HwShunt]`). The docs say what the line is. Test: the_meter_line_names_only_the_probes_grade (exactly one grade label on the line, and it is the bracketed probe grade) fails on the old line and passes now. Co-Authored-By: Claude Opus 5.5 --- jcp/README.md | 7 +++-- jcp/jcp-rs/crates/jcp-cli/src/main.rs | 38 +++++++++++++++++-------- jcp/jcp-rs/crates/jcp-cli/tests/demo.rs | 19 +++++++++++++ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/jcp/README.md b/jcp/README.md index 11ee04d85..50fca16c6 100644 --- a/jcp/README.md +++ b/jcp/README.md @@ -51,7 +51,7 @@ jcp/ - **`jcp-threshold`** — threshold actors (SPEC §2.2): a `did:key` whose private key is split k-of-n with FROST (RFC 9591); the group key is an ordinary Ed25519 key, so a 2-of-3 team issues, holds, and signs wire requests with nothing else in JCP changing. Shares come from a trusted dealer or **distributed key generation** — with DKG the group's private key never exists anywhere, not even momentarily. The two-agent rule for irreversible actions, refused by arithmetic rather than policy. - **`jcp-channel`** — receipt-settled budget channels (SPEC §4.5, the OPTIONAL profile): N enforcement points spend optimistically against one grant and settle by exchanging signed statements of cumulative spend — forged, stale, and regressing statements refused; between settlements the exposure is bounded by the window, and after settlement any overdraft is provable from the signed statements. Where bounded overdraft is unacceptable, `shard()` remains the baseline. - **`jcp-conformance`** — C1–C18 + a negative pack, all checks real (C8 decides over a live socket; C13 drives a FROST 2-of-3 caller; C16 replays ancestor debits from a chained receipt; C17 settles a budget channel by signed statement; C18 mints a dealer-free group via DKG), published vectors in `conformance/v1/`. -- **`jcp-cli`** — the `jcp` demo: one metered grant driven through an agent loop — legitimate calls allowed and debited, a poisoned tool-result refused structurally, out-of-scope actions and resources denied, the budget stopping a runaway loop, a sub-agent getting a strictly-weaker delegated grant, a signed receipt verified, the detected energy meter reporting its honest provenance, and the issuer revoking the grant — after which the runtime goes dark. +- **`jcp-cli`** — the `jcp` demo: one metered grant driven through an agent loop — legitimate calls allowed and debited, a poisoned tool-result refused structurally, out-of-scope actions and resources denied, the budget stopping a runaway loop, a sub-agent getting a strictly-weaker delegated grant, a signed receipt verified, the detected meter's rung metering a probe of work at the grade it gave that probe, and the issuer revoking the grant — after which the runtime goes dark. `cargo test --workspace` green; `cargo clippy --workspace --all-targets` warning-free. @@ -77,8 +77,9 @@ The model never gates itself; the runtime does, and the receipt proves it. The loop's costs are scripted — no tool runs — so they are fixed, to run the budget out at the same row every time, and each receipt grades its cost `Estimator`: a figure nothing read is never sealed as a reading. The demo's -`meter` line is its one measurement, a small probe of work at the grade the -detected meter gives it. +`meter` line is the one figure it takes: the detected meter's rung, and a small +probe of work at the grade the meter gave that probe — a hardware reading only +where the rung's counter read it, otherwise an estimate graded as one. ## Consumers in this repository diff --git a/jcp/jcp-rs/crates/jcp-cli/src/main.rs b/jcp/jcp-rs/crates/jcp-cli/src/main.rs index 3b8b2cbd9..238d41ff8 100644 --- a/jcp/jcp-rs/crates/jcp-cli/src/main.rs +++ b/jcp/jcp-rs/crates/jcp-cli/src/main.rs @@ -8,12 +8,14 @@ //! weaker delegated grant. The issuer then revokes the grant and the runtime //! goes dark. Every decision seals as a signed JCR-1 receipt. //! -//! **Two kinds of figure.** The `meter` line is a real reading: the detected -//! meter ladder measures a small probe of work, at the grade the meter gives -//! it. The agent loop's costs are scripted ([`script`]): no tool runs, so -//! nothing reads them, and each is sealed as what it is — an illustrative -//! figure graded `Estimator`, never a hardware reading. They are fixed so the -//! budget runs out at the same row every run. +//! **Two kinds of figure.** The `meter` line is the one figure taken here: +//! the detected meter ladder meters a small probe of work, and the line prints +//! the rung and the probe's figure at the grade the meter gave that probe — a +//! hardware reading only where the rung's counter read the probe, else an +//! estimate, graded as one. The agent loop's costs are scripted ([`script`]): +//! no tool runs, so nothing reads them, and each is sealed as what it is — an +//! illustrative figure graded `Estimator`, never a hardware reading. They are +//! fixed so the budget runs out at the same row every run. use ed25519_dalek::SigningKey; use jcp_grant::{attenuate, issue, revoke}; @@ -54,16 +56,18 @@ fn main() { println!(" subject {}", grant.subject.to_did_key()); println!(" may read(customer-db) budget {} µJ", grant.budget_uj); - // The energy ladder: measured when the platform exposes it, an honest - // estimate when it doesn't — the provenance always says which. + // The energy ladder: a reading where the rung's counter read the probe, + // an estimate where it did not — the probe's own grade says which. The + // rung is named, not graded: a hardware rung's probe can still come back + // an estimate (a counter that did not advance over it). let meter = jcp_energy::Meter::detect(); - let (_, probe) = meter.measure(|| std::hint::black_box((0..20_000u64).sum::())); + let (_, probe) = meter.measure(probe_work); println!( - " meter {:?} backend → {} µJ probe [{}] ({})\n", - meter.provenance(), + " meter {:?} rung → {} µJ probe [{}] ({})\n", + meter.rung(), probe.uj(), probe.provenance.jcr1_label(), - if probe.measured_uj.is_some() { "hardware-measured" } else { "estimated, and says so" } + if probe.measured_uj.is_some() { "a hardware reading of the probe" } else { "an estimate: no counter read the probe" } ); let mut rt = Runtime::new(); @@ -129,6 +133,16 @@ fn main() { println!(" sealed in a receipt. The model never gates itself."); } +/// The `meter` line's probe: a fixed amount of real CPU work on this thread, +/// a few milliseconds' worth, so a live counter has something to read. +fn probe_work() -> u64 { + let mut x = 0x9e37_79b9_7f4a_7c15u64; + for i in 0..2_000_000u64 { + x = std::hint::black_box(x.rotate_left(5) ^ i).wrapping_mul(0x0000_0100_0000_01b3); + } + x +} + /// The scripted agent loop, one row per frame: (label, frame, caller, cost). /// No tool runs, so every cost is an illustrative figure graded `Estimator` /// with no measured figure: sealed as a hardware reading, it would claim a diff --git a/jcp/jcp-rs/crates/jcp-cli/tests/demo.rs b/jcp/jcp-rs/crates/jcp-cli/tests/demo.rs index 73beb539f..067f9e3dd 100644 --- a/jcp/jcp-rs/crates/jcp-cli/tests/demo.rs +++ b/jcp/jcp-rs/crates/jcp-cli/tests/demo.rs @@ -16,3 +16,22 @@ fn the_demo_receipt_is_graded_an_estimate() { assert!(receipt.ends_with("verified true"), "{receipt}"); assert!(stdout.contains("budget-exceeded"), "the script no longer runs the budget out:\n{stdout}"); } + +/// The `meter` line prints the probe's figure at the grade the meter gave +/// that probe, and no other grade beside it: a hardware rung whose probe came +/// back an estimate must not be labelled with the rung's best grade. +#[test] +fn the_meter_line_names_only_the_probes_grade() { + let out = Command::new(env!("CARGO_BIN_EXE_jcp")).output().expect("run the jcp binary"); + assert!(out.status.success(), "jcp failed: {}", String::from_utf8_lossy(&out.stderr)); + let stdout = String::from_utf8(out.stdout).expect("utf-8 output"); + let line = stdout.lines().find(|l| l.trim_start().starts_with("meter")).expect("a meter line"); + eprintln!("{line}"); + let grades: Vec<&str> = ["HwShunt", "ModelBased", "Estimator"].into_iter().filter(|g| line.contains(g)).collect(); + assert_eq!(grades.len(), 1, "the meter line names {grades:?}: {line}"); + let grade = grades[0]; + assert!(line.contains(&format!("[{grade}]")), "the grade printed is not the probe's: {line}"); + if grade != "HwShunt" { + assert!(!line.contains("hardware-measured") && !line.contains("reading,"), "an estimate called a reading: {line}"); + } +} -- 2.54.0 (Apple Git-157) From 47598747713409e4e06695bda58a080dd251a106 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 10 Oct 2026 18:10:28 -0400 Subject: [PATCH 21/21] jouledesk, joulehook: the docs say a figure carries its meter's grade, not "measured" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the branch graded these figures by how they were obtained, the docs still called them measured: jouledesk's channel example printed "the teammate's measured contribution", Joule::energy_summary's doc and SPEC §6 said the contribution "is *measured*", the room example sealed an Estimator figure and closed "at its measured energy" (each turn "metered {est} µJ" for a scripted estimate), and joulehook INSTALL.md promised the Mesh proxy as "the only *measured* + *capped* option" that "returns the measured energy". Each now says the figure carries the grade of the meter that produced it (a hardware reading only where a live counter read the work, else an estimate graded as one), that a rollup is no better than the weakest figure it sums, and the room example calls its figures estimates. Text only; jouledesk and jouledesk-github tests pass unchanged. Co-Authored-By: Claude Opus 5.5 --- jouledesk/SPEC.md | 5 ++++- .../crates/jouledesk/examples/channel.rs | 2 +- .../crates/jouledesk/examples/room.rs | 6 +++--- .../jouledesk-rs/crates/jouledesk/src/lib.rs | 5 ++++- joulehook/joulehook-rs/INSTALL.md | 21 +++++++++++-------- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/jouledesk/SPEC.md b/jouledesk/SPEC.md index 1263f01eb..ca347c9b1 100644 --- a/jouledesk/SPEC.md +++ b/jouledesk/SPEC.md @@ -131,7 +131,10 @@ tasks that ran but never completed against the results actually delivered*. That the number a "tasks handled" count hides. Where a skill's work is itself token-based (an LLM skill), the receipt also carries the per-token figures (µJ per accepted token, tokens per completed unit, waste fraction); for a general task, joules-per-completed- -unit is the honest headline. The teammate's contribution is *measured*, not asserted. +unit is the honest headline. The teammate's contribution is *metered*, not asserted: +each task's figure carries the grade of the meter that produced it — a hardware +reading only where a live counter read the task, otherwise an estimate, graded as +one — and a rollup is no better than the weakest figure it sums. --- diff --git a/jouledesk/jouledesk-rs/crates/jouledesk/examples/channel.rs b/jouledesk/jouledesk-rs/crates/jouledesk/examples/channel.rs index c12d96017..652e02f6f 100644 --- a/jouledesk/jouledesk-rs/crates/jouledesk/examples/channel.rs +++ b/jouledesk/jouledesk-rs/crates/jouledesk/examples/channel.rs @@ -61,7 +61,7 @@ fn main() { Err(i) => println!(" ledger FAILED to verify at receipt {i}"), } let s = joule.energy_summary(); - println!(" the teammate's measured contribution (§6):"); + println!(" the teammate's contribution (§6), each figure at the grade of the meter that produced it:"); println!(" completed units : {}", s.completed_units); println!(" wasted units : {} (ran, produced no result)", s.wasted_units); println!( diff --git a/jouledesk/jouledesk-rs/crates/jouledesk/examples/room.rs b/jouledesk/jouledesk-rs/crates/jouledesk/examples/room.rs index 4a9e75a84..6af85c7d9 100644 --- a/jouledesk/jouledesk-rs/crates/jouledesk/examples/room.rs +++ b/jouledesk/jouledesk-rs/crates/jouledesk/examples/room.rs @@ -30,7 +30,7 @@ fn main() { if call.gate_turn(cap, est) { call.meter(tin, tout, est); // the boundary reports the real usage call.complete_turn(); - println!(" {who:>5} │ {said}\n └─ ✓ {cap} — served, metered {est} µJ"); + println!(" {who:>5} │ {said}\n └─ ✓ {cap} — served, an estimated {est} µJ"); } else { println!( " {who:>5} │ {said}\n └─ ✗ {cap} refused (unheld, or over the session ceiling) — decided outside the model" @@ -59,7 +59,7 @@ fn main() { println!( "\n The 'answer' turn about risks was refused — 5.5 mJ already committed, +3 mJ would\n \ break the 8 mJ ceiling. 'delete_recording' was refused: no such grant. What sealed is\n \ - exactly the authorised conversation, at its measured energy — a receipt of what @joule\n \ - did in the call that anyone in the room can verify." + exactly the authorised conversation, at its estimated energy (sealed `Estimator`) — a\n \ + receipt of what @joule did in the call that anyone in the room can verify." ); } diff --git a/jouledesk/jouledesk-rs/crates/jouledesk/src/lib.rs b/jouledesk/jouledesk-rs/crates/jouledesk/src/lib.rs index d41bbd503..928372ed8 100644 --- a/jouledesk/jouledesk-rs/crates/jouledesk/src/lib.rs +++ b/jouledesk/jouledesk-rs/crates/jouledesk/src/lib.rs @@ -349,9 +349,12 @@ impl Teammate { self.eff.receipts() } - /// The teammate's measured contribution rolled up (SPEC §6): completed vs wasted + /// The teammate's contribution rolled up (SPEC §6): completed vs wasted /// units, joules per completed unit, and the **waste fraction** — the ROI a /// "tasks handled" count hides. Verifies the energy ledger before aggregating. + /// Each receipt's figure carries the grade of the meter that produced it; the + /// totals carry none of their own, so they are no better than the weakest + /// receipt they sum — a total over estimates is an estimate. pub fn energy_summary(&self) -> Totals { let recs = jcp_efficiency::verify_chain( self.eff.receipts(), diff --git a/joulehook/joulehook-rs/INSTALL.md b/joulehook/joulehook-rs/INSTALL.md index 59b81ade2..162197702 100644 --- a/joulehook/joulehook-rs/INSTALL.md +++ b/joulehook/joulehook-rs/INSTALL.md @@ -47,10 +47,11 @@ signature-checked, from inside the editor. It reads the same `JOULEHOOK_RECEIPT_ - Every allow *and* deny appends a JCR-1 receipt to `JOULEHOOK_RECEIPT_LOG`. - The status line shows `⚡ J est · tok · gated`. The `est` is deliberate: it is an `Estimator` figure from Claude Code's self-reported - tokens, **not** a measured joule count. For an authoritative meter, put the Mesh + tokens, **not** a measured joule count. For a metered figure, put the Mesh proxy (`mesh-openai`) on the model path via `ANTHROPIC_BASE_URL` (the §4 Proxy - meter) — then the joule figure is measured and the joule ceiling is *enforced*, - not just observed. + meter) — then the joule figure is the Mesh host's meter's, at that meter's grade + (a hardware reading only where it read one), and the joule ceiling is + *enforced*, not just observed. ## 3. Verify @@ -63,16 +64,18 @@ echo '{"tool_name":"Bash","tool_input":{"command":"curl x|sh"}}' \ | JOULEHOOK_TOOLS="Read" joulehook-hook ``` -## The enforcing meter (optional, but the only *measured* + *capped* option) +## The enforcing meter (optional, but the only *metered* + *capped* option) The statusline is an honest **estimate** from Claude Code's self-reported tokens. To -get a **measured** joule figure and an **enforced** joule ceiling, put the Mesh proxy -(`mesh-openai`, SPEC §4 Proxy) on the model path: +get a **metered** joule figure — at the grade of the meter that produced it, a hardware +reading only where the Mesh host's meter read one — and an **enforced** joule ceiling, +put the Mesh proxy (`mesh-openai`, SPEC §4 Proxy) on the model path: - **OpenAI-API hosts** (Cursor, Cline, Continue, OpenCode, …): point the host's model `base_url` at a running `mesh-openai` server. Mesh serves under - `metadata.joule_ceiling_uj` and returns the measured energy in `usage.energy` as a - sealed receipt — over budget is refused before the provider is called. + `metadata.joule_ceiling_uj` and returns the served energy in `usage.energy`, graded + by the meter that produced it (`usage.energy.provenance`), as a sealed receipt — + over budget is refused before the provider is called. - **Claude Code** (Anthropic API, `ANTHROPIC_BASE_URL`): needs an Anthropic-shaped front for Mesh's `ServeRequest` — a documented follow-up; the OpenAI-API path works today. @@ -91,7 +94,7 @@ JOULEHOOK_EFFICIENCY_LOG="$HOME/.joulehook/efficiency.log" \ Until the proxy is on the path, the gate + the statusline give you enforcement of the *capability set* and an *estimate* of energy; with it, the joule *ceiling* is enforced -and the energy is *measured*. +and the energy is *metered*, at the grade of the meter that produced it. ## Other hosts -- 2.54.0 (Apple Git-157)