jcp-wire: the endpoint's spend, revocations and memory of decided requests survive a restart #326

Merged
dcharlot merged 1 commit from feat/jcp-wire-durable-endpoint into main 2026-10-18 19:46:48 -04:00

2026-10-18

Transaction Science
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]>
2026-10-18 15:15:31 -04:00