From 3e1c6d4465fc54b7f5573ba6f1993ead53667abf Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Wed, 21 Oct 2026 05:42:26 -0400 Subject: [PATCH] =?UTF-8?q?mesh:=20say=20what=20the=20energy=20figure=20co?= =?UTF-8?q?vers=20=E2=80=94=20the=20served=20span,=20not=20the=20request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Mesh::serve` routes at step 1, authorises at step 2, and opens the meter session at step 3, immediately before `engine.serve`. So a receipt's energy is the served span and excludes routing and authorisation. Nothing said so: `grep -niE "routing (is|energy)|excludes? rout|before routing"` over `mesh/SPEC.md` and `mesh-core` found nothing, and the field was documented only as "the honest, provenance-tagged energy span" — which span was never stated. That omission matters in the direction this family has already decided is dangerous. The grant is debited **this** figure and admits further work on the strength of it, so a shortfall under-charges the ceiling — **under-reporting is the unsafe direction at a ceiling**, the rule written into `jouleclaw/SPEC.md` §1.3 and propagated across the meters in #354. For a table-driven router the gap is negligible. For a router that itself runs a model — a classifier or an LLM choosing the route — it is not, and a reader taking a mesh receipt's energy as the energy of handling the request would be wrong by exactly that inference. SPEC §3.3 now states the boundary, forbids presenting the figure as the request's energy, and says that an implementation metering a model-running router should seal that span as its own field so a verifier can still separate the two. `MeshReceipt::energy` and the step-3 comment in `mesh-core` say the same thing where someone reading the code will meet it. **This states the limit; it does not remove it.** Metering the routing span means moving the session boundary, which changes every receipt's figure and has conformance consequences — a scope decision, not a doc fix, and the digest item (sweep 2026-10-19 #17) carries it as a wedge rather than a defect. **No test pins the ordering, and the spec says so.** The meter is snapshot-driven and routing takes no samples, so the boundary is an ordering invariant in the serving path rather than an observable property of a receipt. Putting the router behind a trait, as the engine already is, is what would let a test pin it; `Mesh` holds `router: Router` concretely today while `meter` is already `&dyn Meter`. One citation trap avoided while writing this: `mesh/SPEC.md` has its own §5 and no §1.3, so a bare "JCP SPEC §5" would read as mesh's §5 and "jouleclaw SPEC §1.3" would dangle. `check-spec-refs` scans only `.rs` files, so neither would have been caught. Both cross-standard references name their file. Verified: `cargo check --workspace --all-targets --locked` 0 errors, 22 test binaries green with 0 failures, clippy over `mesh-core`/`mesh-receipt` exits 0 with no finding in a mesh file, `cargo doc` clean, `check-spec-refs` 1693 citations across 16 standards all resolving. Co-Authored-By: Claude Opus 5 --- mesh/SPEC.md | 22 +++++++++++++++++++++ mesh/mesh-rs/crates/mesh-core/src/lib.rs | 6 +++++- mesh/mesh-rs/crates/mesh-receipt/src/lib.rs | 11 ++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/mesh/SPEC.md b/mesh/SPEC.md index 2321f97d5..c02a6f500 100644 --- a/mesh/SPEC.md +++ b/mesh/SPEC.md @@ -294,6 +294,28 @@ A receipt that violates the coupling MUST NOT be sealable. This is the rule that keeps "measured" honest: a deployment with no hardware meter reports `Estimator` and says so, rather than dressing an estimate up as a measurement. +**What the figure covers, and what it does not.** The figure is the **served +span**: the meter session opens immediately before the engine runs and closes +when it returns. Routing (§2) and grant authorisation precede it and are +**not** included, and a receipt therefore understates the energy of handling the +request by whatever those steps cost. + +An implementation **MUST NOT** present a mesh receipt's energy as the energy of +the request. Where a router or an authorisation path itself runs a model — a +classifier or an LLM deciding the route — that omission stops being negligible, +and the shortfall lands in the direction this family treats as unsafe: +**under-reporting is the unsafe direction at a ceiling**, because the grant is +debited the served span only and admits further work on the strength of it +(`jouleclaw/SPEC.md` §1.3; `jcp/SPEC.md` §5 — named by file, because mesh has +a §5 of its own and no §1.3). An implementation that meters a +model-running router **SHOULD** seal that span as its own field rather than +folding it into this one, so the two remain separable by a verifier. + +This boundary is an ordering invariant in the serving path, not an observable +property of the receipt: the meter is snapshot-driven and routing takes no +meter samples, so no test pins it today. Putting the router behind a trait, as +the engine already is, is what would let one. + `HwShunt` covers a shunt and a vendor energy counter (RAPL, NVML, Apple IOReport), but a counter counts only while it is live: diff --git a/mesh/mesh-rs/crates/mesh-core/src/lib.rs b/mesh/mesh-rs/crates/mesh-core/src/lib.rs index 3623a78b9..918b3dcd0 100644 --- a/mesh/mesh-rs/crates/mesh-core/src/lib.rs +++ b/mesh/mesh-rs/crates/mesh-core/src/lib.rs @@ -498,7 +498,11 @@ impl<'m> Mesh<'m> { .expect("a chosen route carries its fidelity"); let recipe = route.recipe.clone().unwrap_or_default(); - // 3. Meter the served span. Copy the meter reference out so the engines + // 3. Meter the served span — and only that span. Routing (1) and + // authorisation (2) already ran, so their energy is outside every + // receipt this path seals; SPEC §3.3 states that limit, and says why + // it stops being negligible once a router runs a model of its own. + // Copy the meter reference out so the engines // borrow and the log borrow stay disjoint from it. let meter = self.meter; let engine = self diff --git a/mesh/mesh-rs/crates/mesh-receipt/src/lib.rs b/mesh/mesh-rs/crates/mesh-receipt/src/lib.rs index eb6f99b73..0b60792dd 100644 --- a/mesh/mesh-rs/crates/mesh-receipt/src/lib.rs +++ b/mesh/mesh-rs/crates/mesh-receipt/src/lib.rs @@ -108,7 +108,16 @@ pub struct MeshReceipt { /// `fidelity_delivered`: without that field it says the route table promised /// the floor, not that the backend delivered it. pub fidelity_met: bool, - /// The honest, provenance-tagged energy span. + /// The honest, provenance-tagged energy of the **served span** — the meter + /// session around `engine.serve`, and nothing before it. + /// + /// Routing and grant authorisation run first and are **not** in this + /// figure, so it understates the energy of handling the request by + /// whatever they cost (SPEC §3.3). Negligible for a table-driven router; + /// not negligible where the router itself runs a model, and the shortfall + /// falls in the unsafe direction, because the grant is debited this figure + /// and admits further work on it. Do not present it as the request's + /// energy. pub energy: Energy, /// The JCP denial label when debiting the metered joules against the grant /// breached it (SPEC §2): a per-call cost caveat the measured cost -- 2.54.0 (Apple Git-157)