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