open-standards/mesh/SPEC.md
dcharlot f7d4fb66be
Some checks failed
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
Merge pull request 'mesh: say what the energy figure covers — the served span, not the request' (#385) from fix/mesh-energy-span-scope into main
2026-10-21 10:20:04 -04:00

749 lines
40 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Mesh — Energy-Accounted Inference Serving & Routing
**Version:** 0.1.0-draft.1 · **Status:** Draft · **Profile:** `mesh/1`
**Specification text:** CC-BY-4.0 · **Reference code (`mesh-rs/`):** Apache-2.0
Mesh is the open standard for **serving and routing inference as a metered
transaction**. A request declares a joule ceiling and a fidelity floor; the mesh
routes it to the cheapest-sufficient model on the cheapest-sufficient backend,
serves it under an energy meter, and seals a receipt whose headline figure is
**microjoules per accepted output token** — measured at the meter, not estimated
from a price list.
Mesh is one of the [Transaction Science open standards](../README.md).
Transaction Science is the steward, not the proprietor: the wire format and the
right to fork are public.
---
## §1 Why Mesh exists
The serving shelf reports two numbers: dollars per token and tokens per second.
Neither tells an operator, an auditor, or a regulator what an inference **cost**
— in the one unit that is physical, comparable across hardware, and impossible to
inflate: the joule. Mesh makes that number first-class. Every served inference
produces a signed receipt carrying the route that was taken and *why*, the recipe
that served it, the honest provenance of the energy figure, and the joules per
accepted token.
Mesh does not replace the runtime that executes one inference, nor the substrate
that supplies the compute. It is the **front door** in front of many models and
many backends: the layer that decides where a request goes, meters what it spent,
and proves it (§9).
---
## §2 Roles and the serve lifecycle
A Mesh deployment has four roles:
- **Caller** — issues a `ServeRequest` and, optionally, presents a JCP grant it
issued to the steward, signed for that request (§2.1).
- **Router** — selects a backend (§4).
- **Engine** — a backend that serves the request (§5).
- **Steward** — the endpoint that meters the span (§5/§6) and seals the receipt
(§6). The steward identity is the COSE `kid` and signs the receipt log.
One serve proceeds in four phases, in order:
1. **Route.** The router selects the cheapest admissible backend (§4). If none is
admissible, the serve fails with the router's recorded reason. When the
request rides on a grant whose chain carries a `ModelIn` caveat (JCP §4.9),
a backend whose model the grant does not permit is inadmissible, and so is a
backend that names no model.
2. **Authorise.** If the request rides on a grant, the steward first verifies
the presentation (§2.1), then decides the grant as a JCP enforcement point,
and as the grant's holder, in JCP's two-phase metered path over its chain
(JCP §4.2, §7, §4.9). The frame it decides over names the action `infer` on
the request's content id, the request's `prompt_hash` as payload, and, as
`model`, the content address of the weights the chosen backend runs, when the
backend names one. Every check of that decision applies, on every link of
the chain: signature, attenuation, expiry, revocation, and every
cost-independent caveat. The steward MUST also refuse the serve when what the
grant has left (the least any link has left, its budget less what earlier
serves on it were debited) is below the request's joule ceiling. The
decision is recorded in the steward's journal (§2.2) before the engine runs.
A failure at any step aborts the serve before any work is done.
3. **Meter.** The steward opens an energy window, calls the engine, and closes
the window. Energy MUST be metered **around** the engine; an engine MUST NOT
self-report energy. With a grant, the steward then debits the metered joules
against it (JCP's second phase), where a cost-dependent caveat such as
`MaxCostUj` binds. The work has run, so the joules are debited even when the
debit breaches a caveat, and the breach is recorded on the receipt
(`grant_breach`, §6.1). An engine that names its weights before serving MUST
report the same weights after; the steward MUST NOT seal a serve whose
engine reports others, since they are not the weights the grant was decided
over.
4. **Seal.** The steward builds the receipt (§6), stamps it into its hash-chained
log, and seals it as a JCR-1 envelope.
### §2.1 Presenting a grant
A grant is a signed public object, so holding one MUST NOT be enough to spend
it. A caller presents a grant as:
```
Presentation {
chain: [signed-grant…] ; root first, leaf last (JCP §4.2); a chain of one for a direct grant
caller: did:key
nonce: bytes(16)
signature: bytes(64) ; Ed25519 by `caller` over canonical_cbor({request_id, grant_id, caller, nonce})
}
```
where `request_id` is the request's content id and `grant_id` the leaf's. The
steward MUST refuse a presentation whose `caller` is not the leaf's issuer, or
whose signature does not verify for the request being served. The leaf's issuer
is the party that authorised the steward to spend on its behalf, so it is the
only party that may.
The leaf's **subject is the steward**: the steward decides the chain as its
holder, under the identity of the key it seals receipts with, and MUST refuse a
grant held by another steward. One grant therefore spends at one steward. A
budget meant for several is sharded by its holder into sibling children, one
held by each steward, whose budgets sum to no more than the parent's (JCP §4.5);
each steward enforces only its own, and the total stays bounded without the
stewards coordinating.
A presentation spends once at a steward. The steward MUST refuse one it has
already spent, identified by the leaf and the nonce, so a captured presentation
cannot be replayed there. A caller that wants the same request served again
signs a new presentation with a new nonce, and that serve is debited too.
### §2.2 The decision journal
A grant's spend at a steward is state, and a steward that restarts with its
spend in memory would give every grant its budget back (JCP §4.11). A steward
that can restart MUST therefore keep a durable journal of its grant decisions:
- An `Authorised` entry, holding the presentation, the request, the model, the
backend and the decision time, MUST be written **before** the engine runs. A
serve whose authorisation cannot be recorded is refused.
- A `Debited` entry, holding the metered energy, is written after the debit.
- A `Sealed` entry, holding a receipt's sequence and its envelope's content id,
MUST be written for **every** receipt, granted or not, **before** the receipt
is handed out. A receipt that cannot be recorded is not handed out, and the
receipt chain does not advance past it.
Each entry is a record of one chain: `{seq, prev, entry}`, where `seq` counts
records from 0 and `prev` is the BLAKE3 hash of the previous record's encoding.
On restart the steward MUST re-establish the spend before serving by replaying
the journal through the gate: each authorisation is verified and decided again,
at its recorded time, and each debit applied to the serve it belongs to. Records
that do not form one chain, a replay the gate refuses, a debit with no
authorisation, a presentation spent twice, or a receipt out of sequence means the
journal is not one the steward produced, and the steward MUST NOT serve from it.
Replayed presentations count as spent, and the receipt chain continues from the
last `Sealed` entry (§6.3).
**One steward per journal.** A steward MUST take exclusive access to the
journal when it opens it, and MUST fail to start rather than serve where
another holds it — or where the platform cannot provide exclusivity at all.
The chain above **detects** a second writer; it does not prevent one. Two
stewards sharing a journal each replay it, each re-establish the same spend and
each serve against it, so the grant is overspent *while both are running*.
Their records then collide on `seq`, the chain no longer forms, and the rule
above requires the steward not to serve from the journal at all — so the
detection arrives after the overspend and costs the steward its ability to
restart. An exclusive claim the operating system releases when the holding
process dies is preferable to one the steward must clean up, because a crash
then leaves nothing to recover.
An authorisation with no debit is a serve that was in flight when the steward
stopped, and whether the engine finished is not knowable from the journal. The
steward MUST NOT retry it or report it as completed or failed. It stays debited,
at its request's joule ceiling, which is what its authorisation admitted, and is
surfaced as unresolved, naming the caller and the backend that held it, for an
operator to settle.
**A cut journal.** Because the records form one chain, a record removed from the
middle, edited or moved fails the replay. Records cut from the **end** leave a
chain that is whole, and nothing on the steward's own storage can show they
existed; but every receipt the steward handed out was recorded first, so the
receipts in callers' hands can. Any party may show the steward a receipt it
sealed, with its envelope. If the steward's journal does not hold that receipt,
records were cut after it left, the spend the steward replayed is less than it
signed for, and the steward MUST refuse every grant from then on, until an
operator restores the journal. A caller SHOULD show its latest receipt from a
steward after the steward restarts. A cut that removes only a serve that
produced no receipt, one that was in flight, leaves no evidence outside the
steward.
The reference journal is an append-only file of one JSON record per line, each
synced before the append returns. A final line with no newline is an append cut
short and is dropped: an authorisation never recorded never started its serve, a
debit never recorded leaves its serve unresolved, and a seal never recorded was
never handed out.
### §2.3 Grants over the OpenAI-compatible binding (informative)
The reference binding (`mesh-openai`) serves `/v1/chat/completions` and
`/v1/responses`. A request rides on a grant by carrying its presentation (§2.1)
in the body field `jcp_presentation`. The presentation is signed for a request
id, so a caller must be able to compute the request a body is served as: a
granted request is served as exactly what the binding derives from the body
alone, with the prompt-token figure left as the body's estimate rather than
replaced by the server's tokenizer count, and it MUST name its own
`metadata.joule_ceiling_uj`, since a server default would otherwise enter the id
unseen. The binding exports that derivation as `granted_serve_request`.
`GET /v1/mesh/steward` returns the steward's `did:key`, the subject a grant must
name. `POST /v1/mesh/witness`, with a body of the `receipt` and `envelope_hex`
a response's `usage.energy` carried, answers 200 when the journal holds that
receipt and 409 when it does not, after which grants are refused (§2.2). A
refused grant answers 401 when the caller is not its issuer, 403 when the
steward refuses the grant, and 503 once the steward has stopped honouring grants.
---
## §3 The request and the energy record
### §3.1 `ServeRequest`
A request is content-addressed. Its identity `request_id = ContentId.of(request)`
is the resource a JCP capability authorises and the `request_id` on the receipt.
| Field | Type | Meaning |
|---|---|---|
| `prompt_hash` | ContentId (BLAKE3) | content address of the prompt bytes — never the prompt text |
| `prompt_tokens` | u32 | declared input token count (for the estimate) |
| `max_tokens` | u32 | output token ceiling |
| `fidelity_floor` | Fidelity | minimum acceptable fidelity (§3.2) |
| `joule_ceiling_uj` | u64 | hard energy budget, microjoules |
A conforming request MUST carry both a `fidelity_floor` and a `joule_ceiling_uj`.
The pair is the contract: *give me an answer at least this good, for at most this
much energy.*
A request MAY additionally carry a `decision` (§3.6), which says it is asking for
a bounded typed decision rather than generated text. Its absence is a generation,
and is omitted from the canonical form — so the `request_id` of every request
written before this field existed is unchanged.
### §3.2 Fidelity
Fidelity is an ordered scale `F0 … F4` — `F0` the coarsest sufficient answer
(cache / lawful / tiny model), `F4` a frontier-grade response. Ordering is
normative: a backend is admissible only when its level ≥ the request's floor
(§4).
#### §3.2.1 Advertised and delivered
A Mesh request carries two bounds, and until this revision only one of them was
held to a standard of honesty. Energy has a provenance model whose coupling MUST
be enforced before sealing (§3.3) — the rule that keeps "measured" from meaning
"estimated". Fidelity had none: a backend's level is an **advertisement**, read
out of the route table *before* the engine runs, and `fidelity_met` was derived
from it and never reconciled with what came back. A backend admitted at `F3`
that degraded at serve time still sealed `fidelity_met: 1` against an `F3`
floor.
Fidelity therefore has two figures, and they are different claims:
| figure | who says it | when |
|---|---|---|
| **advertised** — `ServingEngine::fidelity` | the route table | before the work; the basis the router admits on (§4) |
| **delivered** — `ServeOutcome.delivered_fidelity` | the backend that served | after the work |
The coupling is normative:
- Where the backend reports a delivered level, `fidelity_met` **MUST** be
derived from it, not from the advertisement.
- A backend **MUST NOT** report a delivered level **above** its advertised
rating. The router admitted it on the rating; it cannot raise that basis after
the fact. An implementation **MUST** refuse such a serve rather than clamp the
figure — clamping hides a misconfigured or dishonest engine behind a receipt
that looks ordinary.
- Where the backend reports nothing, `fidelity_met` rests on the advertisement.
This is the older and weaker basis and is retained, because an engine that
cannot report what it served is still conformant. A consumer that requires the
stronger basis **MUST** check for `fidelity_delivered` rather than reading
`fidelity_met` alone.
- The receipt **MUST** carry `fidelity_advertised` and `fidelity_delivered`
together or carry neither. Their purpose is the comparison; the advertisement
alone adds nothing a verifier does not already have from `backend`. Carrying
neither is what a receipt sealed before these fields existed means, so such a
receipt is unchanged and `mesh/1` stays byte-stable.
**What this does not claim.** A delivered figure is the account of the party that
did the work. It is better grounded than a table entry written before the work,
and it is not an independent assessment of the output — Mesh does not score
responses. What the pair buys is that a divergence between promise and delivery
is *visible in the receipt* and attributable to a named backend, where before it
was invisible.
### §3.3 Energy and provenance
Energy is a two-part integer-microjoule record with an honest provenance tag:
```
Energy { measured_uj: Option<u64>, estimated_uj: u64, provenance: Provenance }
Provenance ∈ { HwShunt, ModelBased, Estimator }
```
The coupling is normative and MUST be enforced before a receipt is sealed:
- `HwShunt` ⇒ `measured_uj` is present (a hardware reading exists).
- `Estimator` ⇒ `measured_uj` is absent (no measurement is implied).
- `ModelBased` ⇒ `measured_uj` MAY be absent.
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:
> A reading from a vendor energy counter (RAPL, NVML, Apple IOReport, …) MAY be
> graded as a hardware reading only while the counter is demonstrably live for
> the domain the figure claims: the implementation MUST have observed the
> counter covering that domain advance under load (for Apple IOReport: the CPU
> energy rail itself advancing in at least 5 sample intervals, in at least 4 of
> every 5, over at least 300 ms while the probing thread burns CPU). A counter
> that is present but not live — frozen, or republished in lumps — MUST NOT
> produce a hardware-graded figure; the implementation falls back to its
> estimator or reports the work unmetered, and SHOULD record why. A figure MUST
> NOT claim coverage of a domain its counter does not read; where the format
> has a coverage field, such a figure is Partial.
`mesh/1` has no coverage field. So a figure from a counter that does not cover
the whole request, such as a GPU counter on a host whose CPU has none, MUST NOT
be graded `HwShunt`.
### §3.6 Typed decisions — the other unit of work
A **decision model** takes state plus typed questions and returns a calibrated
probability per allowed answer, generating no text. The category converged
through 2026 on three primitives — a proposition (`noul`), a choice over 1–255
named options, and a score over an ordered rubric — and backends score the
schema **non-autoregressively** rather than emitting tokens.
Everything else in this spec measures work in tokens, and a decision's output
token count is zero. Measured on a 421M decision model: `output_tokens: 0`, with
132 input tokens of which **121 were the question schema and 11 the state under
judgement**. Three consequences follow, and a conforming implementation MUST
handle each.
A request asking for a decision carries:
| Field | Type | Meaning |
|---|---|---|
| `questions` | u32 | typed questions in the request |
| `options_scored` | u32 | options scored across all of them |
| `schema_tokens` | u32 | how many of `prompt_tokens` are schema, not state |
1. **It MUST NOT be priced for tokens it cannot emit.** The estimate for a
decision MUST exclude `max_tokens`. Including it refuses work at a ceiling
that would in fact have covered it.
2. **Cost MUST be allowed to follow the schema.** A profile MAY declare a cost
per scored option; one question over 20 options is more work than one over 2
at identical token counts, and no token count expresses the difference.
Declaring nothing reproduces the token-only model exactly, so an unmeasured
profile claims nothing.
3. **The efficiency figure MUST be defined.** `uj_per_accepted_token` (§6) is
undefined when no tokens were accepted, which is every request in this
category. A decision receipt MUST instead carry `uj_per_decision`, and
SHOULD carry `uj_per_option_scored`. The unit of work is the answered
question.
`schema_tokens` is reported because a fixed schema is a **cacheable input
prefix** — distinct from `cache_hit_tokens`, which counts output tokens served
from cache, of which a decision has none. In the measured request 92% of the
input was schema, so a reader that cannot see the split cannot tell a cold
decision from a warm one.
> **Why a decision receipt is worth more than a generation receipt.** A decision
> is a single forward pass with no sampling. Given the weight digest, revision
> and numeric precision, it is reproducible — so a receipt over one is verifiable
> **by re-execution**, which a receipt over sampled text can never be. A receipt
> for a decision SHOULD therefore bind the weight digest and the precision; the
> probabilities of a published decision model differ between `fp16` and `fp32`
> even where the selected answer agrees, so a receipt that omits precision does
> not reproduce.
>
> The fields are `weights_digest` and `precision` (§6.1), both OPTIONAL. A
> backend that can address its weights **SHOULD** report both; one that cannot
> omits them, and the receipt then binds the model by `recipe` alone — a name,
> which an operator can re-point at other weights while every receipt sealed
> under it goes on verifying. That is the weaker basis, and the absence of the
> fields is how a verifier can tell it apart from the stronger one, rather than
> having to assume.
A backend that loads its weights from files addresses them as BLAKE3 over the
canonical encoding of the list of files its forward pass reads, in path order,
each as its path relative to the model directory and the BLAKE3 of its bytes.
Every byte is hashed, once, when the backend loads: a digest over a sample, a
header or a size would look like a binding while leaving the weights free to
change beneath it, and is worse than none. The file names are part of the
address because they are part of what loads, and files that do not reach the
forward pass, such as a tokenizer, are not. The reference engines address
`config.json` with `model.safetensors`, or with `model.safetensors.index.json`
and every shard it names; Gemma reports `fp32`, the arithmetic its loader
widens to, and Falcon-H1 `bf16`. A backend that names its weights before
serving MUST report the same digest after (§2).
---
## §4 Routing — cheapest-sufficient
For each registered backend the router computes an estimate
`est_total_uj = uj_per_token × (prompt_tokens + max_tokens)`, or for a typed
decision (§3.6) `uj_per_token × prompt_tokens + uj_per_option_scored ×
options_scored`, and marks it **admissible** iff:
```
backend.fidelity ≥ request.fidelity_floor AND est_total_uj ≤ request.joule_ceiling_uj
```
The router MUST choose the admissible backend with the **lowest** `est_total_uj`.
Ties are broken deterministically (registration order). When no backend is
admissible the serve fails, and the decision records why.
The router MUST record, for every backend it weighed, a `RouteCandidate` with its
fidelity, estimate, admissibility, and — when inadmissible — a human-readable
reason (below floor / over ceiling). The chosen route MUST carry a one-line
justification. This is what makes a route **auditable from the receipt alone**:
the cheapest sufficient answer is preferred and a frontier model is the expensive
last resort, and the comparison is denominated in joules.
A model is addressed as a **recipe** — a base plus optional merges/adapters — not
a fixed blob, so the receipt can record *which* composed model answered.
---
## §5 The serving engine
A backend implements one interface: advertise a fidelity level and a modeled
per-token energy cost, and serve a request producing tokens and a `ServeOutcome`:
```
ServeOutcome {
tokens_in, tokens_out, tokens_accepted,
cache_hit_tokens, spec_proposed, spec_accepted,
delivered_fidelity: Option<Fidelity> // §3.2.1
}
```
The advertised level is a promise made before the work; `delivered_fidelity` is
what the backend says it actually served. An engine that cannot degrade may
report its rating; an engine that can — a fallback, a truncation, a timeout that
drops to a cheaper path — reports what it served. Omitting it is conformant and
means `fidelity_met` rests on the advertisement (§3.2.1).
The input token payload travels **with** the serve call, alongside the request —
it is never part of the request's sealed identity and never enters the receipt
in the clear. The receipt records the content-addressed `prompt_hash` (§3.1),
and the payload's digest only inside the output commitment, under its opening
(§6.4). A receipt is therefore safe to publish without disclosing the prompt.
`tokens_accepted` is the denominator of the wedge metric. For greedy decode it
equals `tokens_out`; under speculative decoding it is the number of proposed
tokens the target model accepted. `cache_hit_tokens` records output served from a
prefix/result cache (energy saved). An engine MUST do real work proportional to
the tokens it serves so the surrounding meter reads a real span; it MUST NOT
self-report energy.
An engine declares whether it is **deterministic**: whether serving the same
request and prompt payload again yields the same tokens. Only a deterministic
engine's serve can be re-executed against its receipt (§6.4). An engine that
samples MUST NOT declare it, and the default is not to.
The reference engine (`mesh-engine::ReferenceEngine`) is deterministic and
weights-free, for conformance. Production backends — a quantized device-resident
decoder, a high-throughput batching server — implement the same interface and
drop in unchanged; `mesh-engine-gguf::GgufEngine` (behind the `gguf` feature) is
one such backend over a pure-Rust GGUF/Metal decoder, so the mesh routes and
meters real model tokens.
---
## §6 The receipt — `mesh/1`
A `MeshReceipt` is sealed with **JCR-1**: canonical det-CBOR (RFC 8949 §4.2.1)
inside a COSE_Sign1 envelope (RFC 9052, EdDSA/Ed25519, `alg = -8`), byte-compatible
with the rest of the family's receipts. The signed core forbids floats, booleans,
and nulls; booleans are encoded as `0|1` uints and absent options dropped.
### §6.1 Wire fields (`mesh/1`)
| Key | Meaning |
|---|---|
| `mesh_receipt` | `"mesh/1"` |
| `request_id` | hex content id of the request |
| `grant_id` | hex JCP grant id this inference debited (omitted if ungranted) |
| `backend`, `recipe` | the backend and composed model that served it — `recipe` is a name, read it with the two fields below |
| `weights_digest` | hex content address of the weights that answered (OPTIONAL — §3.6; absent means the receipt binds the model by name alone) |
| `precision` | the numeric precision the forward pass ran at, as the backend names it — `fp32`, `fp16`, `bf16`, `int8` (OPTIONAL — §3.6; paired with `weights_digest`, since neither reproduces alone) |
| `route` | the router's one-line justification |
| `tokens_in/out/accepted`, `cache_hit_tokens`, `spec_proposed/accepted` | the outcome counters |
| `fidelity_floor` | the floor the request demanded (uint) |
| `fidelity_advertised` | the level the backend is rated for (uint, OPTIONAL — paired with the next) |
| `fidelity_delivered` | the level the backend says it served (uint, OPTIONAL — §3.2.1) |
| `fidelity_met` | `0|1` — was the floor met, on the delivered figure where there is one |
| `estimated_uj` | always present |
| `measured_uj` | present **iff** provenance is `HwShunt` |
| `uj_per_accepted_token` | **the wedge metric** for a generation — `energy.uj() / tokens_accepted`; omitted when no tokens were accepted |
| `questions_answered`, `options_scored`, `schema_tokens` | present **iff** the request was a typed decision (§3.6) |
| `uj_per_decision` | **the wedge metric** for a decision — `energy.uj() / questions_answered` |
| `uj_per_option_scored` | the finer decision figure — `energy.uj() / options_scored` |
| `energy_provenance` | `HwShunt | ModelBased | Estimator` |
| `grant_breach` | the JCP denial label when debiting the metered joules breached the grant, e.g. `caveat-unsatisfied` for a per-call cost caveat (OPTIONAL — §2; absent means the debit was within the grant, or the serve rode on none) |
| `output_commitment` | hex commitment to the output tokens of a generation and the prompt payload they were served on (§6.4; absent for a typed decision, whose answer the engine interface does not carry, and in receipts sealed before the field existed) |
| `sequence`, `prev` | hash-chain position and prior envelope's content id |
| `nonce`, `steward` | per-receipt nonce and the sealing identity (COSE `kid`) |
### §6.2 The wedge metric
`uj_per_accepted_token = energy.uj() / tokens_accepted`, where `energy.uj()` is
the measured figure if present, else the estimate. It is `null`/absent when no
tokens were accepted. This is the number Mesh exists to make legible, and it is
sealed into every receipt.
### §6.3 Hash chain
A steward keeps a receipt log. Appending a receipt stamps its `sequence` (from 0)
and `prev` (the content id of the previous sealed envelope; absent at genesis),
seals it, and advances the head. The log is tamper-evident end to end: altering
any envelope breaks verification of that receipt and the chain link from the next.
A steward that restarts continues its chain from its journal (§2.2): the next
receipt takes the next sequence and chains to the last envelope sealed, rather
than starting a new chain at 0, so a restart is not mistaken for a log that lost
its history.
### §6.4 The output commitment, and replay
A receipt that binds what was asked (`request_id`, which content-addresses the
prompt) and what it cost, but nothing about what came back, cannot show which
answer it was for. A generation's receipt therefore carries
`output_commitment`:
```text
payload = BLAKE3("mesh/1 prompt payload\0" ‖ prompt)
output_commitment = BLAKE3("mesh/1 output commitment\0" ‖ opening ‖ request_id ‖ payload ‖ tokens)
```
Here `prompt` is the input token payload the engine was handed (§5) and
`tokens` are the output token ids, each as little-endian `u32`s in order;
`request_id` and `payload` are their 32 bytes; and `opening` is a 32-byte
value the steward derives per request from a key it holds. In the reference
steward, the key is derived from its signing key, so a steward that restarts
can disclose any receipt's opening without having stored it.
- **It is a commitment, not a hash.** A plain hash of a short output, such as a
one-token answer, can be reversed by trying every output, and a receipt is
shown to parties who were not owed the answer. Without its opening, the
commitment reveals nothing about the tokens.
- **Disclosure.** A steward that discloses an output for one receipt discloses
that receipt's opening and `payload` with it. Anyone can then check that this
is the output the receipt sealed. The opening binds `request_id`, so it opens
no other receipt.
- **The payload is bound.** `prompt_hash` addresses the prompt's bytes, and an
engine is handed token ids. Nothing ties the two together without the
tokenizer, so `request_id` alone cannot confirm the payload a replay runs on.
The commitment binds it instead.
- **Replay.** Where the engine is deterministic (§5: its tokens are a function
of the request and the prompt payload alone), a party holding the prompt
payload and the opening can re-execute the serve and compare.
- Re-execution yields one of three verdicts: *verified* (the re-run opens the
commitment), *diverged*, or *not replayable*.
- It is *diverged* only when a disclosed output opens the commitment on the
payload the replay ran. That confirms the opening, the payload and the
output together, so the mismatch is the serve's. The verdict names the
first token at which the two outputs part.
- A replay MUST NOT report a divergence it cannot attribute to the serve. It
is *not replayable* when:
- the engine samples;
- it is not the backend and recipe the receipt names, or not the weights,
where the receipt sealed `weights_digest`;
- the request is not the receipt's;
- the receipt commits to no output;
- the re-run does not open the commitment, and no disclosed output opens it
on the replay's payload and opening. The mismatch might then be the
replayer's own input.
- A sampled generation is not expected to repeat, and re-running it settles
nothing.
These hold a serve to its record. Whether such a record carries any weight in a
given dispute is not this specification's to say. It is evidence anyone can
produce and check without the steward's help, opening apart.
### §6.5 The weights change record
A receipt's `weights_digest` (§3.6) names the weights that answered. Without a
further record, nothing shows when a backend's weights changed, from what, or
whether the change was one its steward had planned for. A backend's **change
record** is that record. It is a hash-chained sequence of entries, each sealed
as a JCR-1 envelope under the steward key that seals its receipts, with the
profile tag `mesh_change: "mesh/1"`.
Every entry carries `backend`, `recipe`, `seq` (its position, from 0), `prev`
(the content id of the envelope before it, absent for the first), `made_at`
(Unix seconds) and `kind`. Optional fields are omitted when absent. There are
two kinds:
| `kind` | Fields | Records |
|---|---|---|
| `plan` | `precisions`, `fidelity_floor`, `validation`, `foresees`, `risk_assessment`? | the changes the steward foresees for the backend, before them |
| `weights` | `from`?, `to`, `precision`?, `fidelity`, `plan`?, `validation_result`?, `effective_from`, `reason` | one change of the backend's weights |
The digests are hex content addresses. For a `weights` entry:
- `from` and `to` are the weights before and after the change; `from` is absent
for the backend's first entry of this kind.
- `plan` is the content id of the `plan` entry the change falls under, and is
absent for a change no plan foresaw.
- `effective_from` is the first sequence of the steward's receipt log (§6.3)
served on the new weights.
An entry MUST be refused when:
- it is for another backend, or does not chain: its `seq` is not the next, or
its `prev` is not the content id of the envelope before it;
- its `made_at` is earlier than the entry before it;
- it is a `weights` entry and:
- its `from` is not the `to` of the last `weights` entry before it;
- its `to` equals its `from`;
- its `effective_from` is earlier than the last change's;
- it names a `plan`, and that is not a `plan` entry earlier in the same record,
or the change is outside it. A change is outside its plan when it has:
- another `recipe`;
- a `precision` the plan's non-empty `precisions` does not list, or no
precision at all;
- a `fidelity` below the plan's `fidelity_floor`;
- no `validation_result`.
A plan therefore covers only the changes recorded after it. A change recorded
as planned was foreseen before it was made, by the record's own order. A plan
recorded later does not reach back over it. A change no plan covers is
recorded too, without a `plan`. The record shows what changed, and makes no
judgement of it.
**The gate.** Once a backend has a change record, the steward MUST serve on it
only the weights its last `weights` entry has in effect. It MUST refuse a serve
before any grant is decided or work runs where the engine names other weights,
or none. It MUST refuse to seal a serve whose engine reports other weights, or
none. A weights change takes effect only by being recorded, so every receipt
from `effective_from` on was served on the weights the change put in effect.
A steward that restarts holds a backend to the record it persisted, once every
entry verifies under its key, the record holds, and its weights in effect are
the ones the engine runs.
**Coverage.** A party holding a receipt and the record for its backend reads the
`weights` entry in effect at the receipt's `sequence`: the last one whose
`effective_from` is at or before it. The receipt is covered when that entry is
for its backend and recipe, its `to` is the receipt's `weights_digest`, and,
where both name one, its precision is the receipt's. Otherwise it is not
covered. The entry then says which change put the weights in effect, when the
steward made it, under which plan, and with which validation result. A position
in the receipt log orders a change against receipts without trusting a clock;
`made_at` is the steward's statement of the time.
---
## §7 Conformance
A conforming implementation MUST:
1. Decide a serve's grant as a JCP enforcement point and as its holder before any
work (§2): reject a presentation not signed for the request by the grant's
issuer, or already spent here (§2.1); reject a chain that fails verification,
is held by another steward, has expired or been revoked, does not permit
`infer` on the request, carries a cost-independent caveat that does not hold,
or has less left than the request's joule ceiling; debit the metered joules
against every link after the work, recording a breach on the receipt.
2. Where the steward can restart, journal each decision before its serve runs
and each receipt before it is handed out, re-establish spend and the receipt
chain from the journal before serving again, and refuse grants once shown a
receipt the journal does not hold (§2.2).
3. Choose the cheapest admissible backend and record every candidate weighed
(§4).
4. Meter energy around the engine and tag provenance honestly, enforcing the
`Energy` coupling before sealing (§3.3).
5. Seal the receipt as a JCR-1 `mesh/1` envelope and compute
`uj_per_accepted_token` (§6).
6. Maintain a tamper-evident hash chain (§6.3).
7. Where a backend has a change record, refuse its entries and its serves as
§6.5 sets out, so that its weights change only by being recorded.
The published vectors in [`../conformance/v1/`](../conformance/v1/) pin a
deterministic reference serve. `reference/signed_core.hex` MUST canonical-CBOR
encode identically across implementations, and `reference/envelope.hex` MUST be
reproducible from it with the steward key in `meta.json`. Regenerate with:
```text
cargo run -p mesh-conformance --bin gen-vectors -- ../conformance/v1
```
---
## §8 Security considerations
Mesh composes proven primitives: Ed25519 signatures, BLAKE3 content addressing,
RFC 9052 COSE_Sign1, and the JCP capability model. It introduces no new
cryptography. These properties are correctness, not style:
- **Authorisation happens outside the model.** The grant check (§2) is enforced
by the steward before any inference runs; a prompt cannot grant itself budget
or capability.
- **A grant spends only for its issuer, once per presentation, at one steward.**
A presentation is signed by the grant's issuer for one request (§2.1), so a
party that obtains a grant, or a captured presentation, cannot spend it; and
the steward is the grant's holder, so a grant cannot be spent at a steward it
was not issued to. Spreading a budget over stewards is sharding.
- **Spend survives the process.** The journal is written before the work and
replayed through the gate on restart (§2.2), so a restart gives no budget back
and a crash mid-serve leaves the serve debited and unresolved rather than
free. A journal is only as durable as the storage under it, and a steward
with an in-memory journal has no such guarantee. Records cut from its end are
invisible to the steward, but not to whoever holds a receipt it sealed after
the cut point: shown one, the steward stops honouring grants.
- **Honesty is enforced at seal time.** A receipt cannot claim a measurement it
does not have (§3.3). An operator without a hardware meter produces honest
`Estimator` receipts.
Energy figures are only as honest as the worst meter in the span. A receipt's
provenance tag states which meter produced its figure; consumers SHOULD treat
`Estimator` figures as bounds, not measurements.
---
## §9 Relationship to the family
Mesh is the serving + routing fabric. It is expressed in primitives the family
already publishes, and it composes with the neighbouring standards rather than
duplicating them:
- It **rides on [JCP](../jcp/)**: a serve request is authorised by a JCP `Grant`
(capability ⊗ joule-budget), and the receipt seals in JCR-1 — the same envelope
every family receipt uses.
- It **dispatches to runtimes** such as [JouleClaw](../jouleclaw/): a backend that
executes one inference cheaply is one engine behind the mesh.
- It **routes across compute** described by [EOC](../eoc/) and isolated by
[Sandbox](../sandbox/): a backend may be a metered, attested execution target.
The boundary is deliberate: a runtime executes *one* inference; Mesh routes
across *many* models and *many* backends and proves what each one cost.