mesh: say what the energy figure covers — the served span, not the request #385

Merged
dcharlot merged 1 commit from fix/mesh-energy-span-scope into main 2026-10-21 10:20:05 -04:00
3 changed files with 37 additions and 2 deletions

View file

@ -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:

View file

@ -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

View file

@ -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