mesh: a grant is decided by JCP's runtime, so expiry, caveats, the model and spend bind #294
Merged
dcharlot
merged 2 commits from 2026-10-17 17:07:15 -04:00
fix/mesh-grant-decided-by-jcp into main 2026-10-17
joulehook-rs: lock mesh-core's new dependency on jcp-runtime
All checks were successful
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 57s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
standards workspaces build + test / macOS energy backends (pull_request) Has been skipped
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m24s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 2m49s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mesh: a grant is decided by JCP's runtime, so expiry, caveats, the model and spend bind
Some checks failed
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Failing after 1m55s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 13s
standards workspaces build + test / macOS energy backends (pull_request) Has been skipped
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Has been cancelled
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Has been cancelled
Mesh honoured a JCP grant by checking three things: the issuer signature, that the capability permits `infer` on the request, and that the budget covered the request's ceiling. Nothing else was checked: - An expired grant was served. - No caveat was evaluated, so `NotAfter`, `NotBefore`, `PayloadIs`, `MaxCostUj` and `ModelIn` were ignored. - Nothing was ever debited, so one grant paid for any number of serves. mesh-core had no unit test of the grant path. The steward now holds a jcp-runtime `Runtime` and decides every grant in JCP's two-phase metered path: - **Route.** With a grant, a backend whose model the grant's `ModelIn` does not permit is inadmissible, and so is one that names no model. The route is the cheapest permitted backend, and the candidates record why the others were refused (`Router::route_where`). - **Authorise,** before any work. The frame names `infer` on the request, the prompt hash as payload, and the chosen backend's weights as `model`. Signature, expiry, revocation and every cost-independent caveat apply, and what the grant has left must cover the request's ceiling. - **Debit,** after metering. The metered joules are charged whatever happens next. A per-call cost caveat binds here, and a breach is sealed on the receipt as `grant_breach`. - **Model check.** An engine that runs other weights than the ones it named before serving is refused a receipt. Engines name their weights before serving through a new `ServingEngine::weights_digest()`, which defaults to `None`. The refusals that existed keep their errors (`GrantInvalid`, `Unauthorized`, `BudgetTooLow`). `BudgetTooLow` now reports what the grant has left. SPEC §2, §6.1, §7 and §8 and the README describe the new order. §8 states that Mesh does not authenticate the caller, honouring a grant as a bearer token of its subject, and that spend is per steward. Checked on the build host: - mesh-rs: 65 tests pass, six of them new, and every regenerated `mesh/1` vector is byte-identical. - The gguf and openai server features build. - clippy is clean on the changed crates. - Mutations: no debit, a router that ignores the filter, ignoring other denials, and skipping the model check each fail tests. Co-Authored-By: Claude Opus 5.5 <[email protected]>