open-standards/joulehook/joulehook-rs/crates
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science 449172c36e
Some checks failed
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 13s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Failing after 2m24s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 1m46s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m7s
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 59s
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 2m38s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 1m21s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m39s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m12s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m14s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m26s
joulehook: the joule budget survives the process, so it can be exceeded
SPEC §3.3 is normative: "If the action carries an estimable cost, the
grant's joule budget MUST be ≥ the estimate; over budget is `deny`. On
`allow`, the spent amount is debited."

The debit lived in the `LocalToolGate` that `gate()` constructs, and
`gate()` runs once per process — this binary is a PreToolUse command the
host spawns per action. So the spend total died with the process on every
single call and the next action started again at the full
`JOULEHOOK_BUDGET_UJ`. **There was no sequence of actions that could
exhaust the budget**, so the deny branch could never be taken, and
`conformance/v1/receipt.json` records the symptom: `budget_remaining_uj:
1000000000`, the untouched ceiling.

Worse, the deny branch that *was* reachable reported the wrong thing: a
single message, "is not in the granted capability set", covered every
denial — so a budget denial would have been recorded as a capability
denial if one could ever have happened.

The hook already wrote the evidence and never read it: sealed receipts
are appended to `JOULEHOOK_RECEIPT_LOG`, hex per line.

- `LocalToolGate::resume` replays a sealed stream into the gate's spend
  ledger via `jcp_receipt::replay_spend`, so only verified allow-receipts
  of this gate's own steward contribute debits. `Bridge` gains
  `steward()`, `steward_pubkey()` and `restore()` to make that possible.
  The grants are deterministic (fixed issuer key, `nonce: [0; 16]`,
  `expiry: 0`), which is exactly why a later process rebuilds
  byte-identical grant ids for the debits to land on — and exactly why
  the budget refilled without them.
- `gate()` replays the stream before deciding, and the allow reason now
  records the spend and how many receipts it replayed.
- A budget denial says it is a budget denial.
- **No stream configured** ⇒ the ceiling covers one call only. That is
  now disclosed in the allow reason rather than asserted: §3.3 states
  the budget MUST hold, and per-call it cannot. The crate's env doc
  previously carried the disclosure ("Cross-call budget accounting needs
  a persistent gate") while the SPEC's MUST did not; the doc is updated
  to point at the stream that now provides it.
- **A stream that does not decode denies.** The prior spend is then
  unknown, the budget cannot be asserted, and allowing anyway is the
  refill the replay exists to prevent — fail closed, as SPEC §8 has this
  binary do for an unparseable payload and an unrecognised shape.
  A missing file is simply the first call.

The headline test runs three separate `run()` calls — each a fresh gate,
which is what a per-action process is — persisting between them as
`main` does: 2 mJ, 2 mJ, 2 mJ against a 5 mJ ceiling gives allow, allow,
**deny**, with the denial naming the budget. Previously that third call
allowed, and so would the three-hundredth.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-18 01:38:36 -04:00
..
joulehook-hook joulehook: the joule budget survives the process, so it can be exceeded 2026-10-18 01:38:36 -04:00
joulehook-mcp joulehook: receipt-surface MCP server (joulehook-mcp) 2026-07-04 12:12:22 -04:00
joulehook-meter jcp-efficiency: carry the Option through its cross-workspace consumers 2026-10-17 09:29:59 -04:00
joulehook-proxy joulehook-meter: read Mesh's own provenance labels when bridging usage.energy 2026-10-11 22:23:22 -04:00