jcp-wire: the endpoint's spend, revocations and memory of decided requests survive a restart #326
Merged
dcharlot
merged 1 commit from 2026-10-18 19:46:48 -04:00
feat/jcp-wire-durable-endpoint into main 2026-10-18
jcp-wire: the endpoint's spend, revocations and memory of decided requests survive a restart
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 5s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 48s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m17s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m32s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m11s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m16s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m47s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m11s
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 1m51s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 4m31s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 6m11s
The `jcp/1` endpoint held everything it enforces in memory. A restart therefore: - gave every grant its budget back, - made every revoked grant valid again, and - forgot which requests it had decided, so a captured request still inside the freshness window (#322) could be decided again. JCP §4.11 requires an enforcement point that can restart to record its decisions durably and re-establish them before deciding anything new. - **Recording.** `Endpoint` keeps a `Journal`. Each admitted request is recorded (`Decided { at, request }`) before it is decided. A request that cannot be recorded is denied `unrecorded` (new `DenyReason`), debiting nothing and remembered as nothing. - **Revocation.** `Endpoint::revoke` verifies a revocation, records it, then applies it. A revocation that does not verify is neither recorded nor applied. - **Replay.** `Endpoint::with_journal` replays the records through the gate, each request at the time it was decided. That re-establishes the spend, the revocations, and the memory of requests still in the window. A request whose signature fails, or that was decided twice, makes the journal `Inconsistent`. - **File journal.** `FileJournal` uses the wire's own framing (length prefix, canonical CBOR) and syncs per record. A torn final record is dropped and cut from the file. - **Spec.** SPEC §11 ties the endpoint to §4.11. Checked on the build host: - jcp-wire: 14 tests pass, five of them new. They cover a restart keeping spend, a revocation surviving, a decided request still refused after a restart, an unrecordable request, and a torn file record. - jcp-rs: 522 tests pass, and the conformance suite passes all checks. Clippy is clean. - Mutations each fail tests: replay skipping decisions, a request not recorded, a revocation not recorded, and replay forgetting requests. Stacked linearly on #322 (its head plus this commit). Co-Authored-By: Claude Opus 5.5 <[email protected]>