jcp: WithinWindow, a sliding-window caveat over calls, payloads and measured joules #388

Merged
dcharlot merged 1 commit from feat/jcp-window-caveat into main 2026-10-21 10:20:15 -04:00
Owner

Sweep 2026-10-19 item #7. A grant's budget bounded its whole life and not its rate: a day's budget could go in a minute. jcp-runtime budgets were cumulative only.

The caveat (jcp-schema)

Caveat::WithinWindow(Window { secs, limit }) with WindowLimit::{MaxUj, MaxCalls, MaxDistinctPayloads}, plus DenyReason::WindowExceeded (window-exceeded). As with FlowBelow, a window is decided by a runtime gate against state, not from the frame; holds() refuses only a zero-second window.

Enforcement (jcp-runtime, SPEC §4.12)

  • History. Each grant that carries a window keeps a history of admissions (time, payload) and debits (time, µJ), pruned to its longest window.
  • What counts. A call counts once it is admitted: allowed in a one-shot decide, or authorised in the two-phase path. A denied call counts for nothing and is debited nothing.
    • Every path gates and records.
    • Every link of a chain is counted, so a parent's window bounds its delegates together.
  • Joule windows.
    • A one-shot call whose cost would overfill the window is refused.
    • In the two-phase path the window admits a call while any of it is left, and binds at the debit. An overrun is denied and stays debited, as MaxCostUj does.
  • A debit without its time (charge, charge_chained) is placed at the grant's latest admission, which is never earlier than its own call's, so its joules stay in a window at least as long as they should. A debit with no admission at all stays in every window. No caller signature changed.
  • After a restore. restore() rebuilds spend from a timeless ledger, so afterwards every window counts as full until resume_windows_at(t) names when the history is complete and a whole window has passed since. An unknown history is not an empty one.
  • authorize_chained_for(…, need_uj) puts a caller's ceiling into the budget test, so a call refused for its ceiling is refused before it is admitted.

Elsewhere

  • mesh-core authorises with the request's ceiling through authorize_chained_for, replacing the after-the-fact BudgetTooLow check. A serve refused for its ceiling therefore fills no window. The errors and their figures are unchanged.
  • jcp-bridge: a window caveat cannot be projected into an OAuth token.
  • Durability. jcp-wire and mesh-core replay each decision through the gate at its recorded time, so windows rebuild across a restart, with a test in each. The gateway resumes from receipts, which carry no time, so its windows stay full after a resume. Its clock is also fixed at 0, which is a separate gap.
  • SPEC:
    • §4.12 covers semantics, durability and sharding: n points that don't share history admit n × the limit.
    • The §4.9 wire form.
    • Conformance class C27, with a C27 check in jcp-conformance.
  • Also: two clippy errors that predate this change (cloned_ref_to_slice_refs) are fixed in jcp-runtime's tests.

Verification (build host)

  • jcp-rs: 575 tests, 119 behind features, and every conformance class passes, including C27.
  • mesh-rs: 95 tests.
  • Clippy -D warnings is clean on jcp-schema, -runtime, -bridge, -wire, -conformance and mesh-core.
  • Dependent workspaces: sandbox-rs, joule-code-rs, jouledesk-rs, joulehook-rs and joule-ui-rs compile with --all-targets.
  • Mutation testing: 30 mutants, all killed. They cover:
    • every limit's boundary and the slide;
    • where a debit is placed (latest admission, not first; and with no admission, kept in the window rather than dropped);
    • the restore gap and pruning;
    • the ceiling-before-admission test;
    • each path's gate and recording.
  • Repo checks pass.

Not in this PR

From the same sweep item: carrying flow state across sandbox checkpoints. That is a decision about the sandbox capability Enforcer's conformance class (stateless on purpose, C2), not wiring. Compiling a policy subset into these caveats is also left out.

🤖 Generated with Claude Code

Sweep 2026-10-19 item #7. A grant's budget bounded its whole life and not its rate: a day's budget could go in a minute. jcp-runtime budgets were cumulative only. ## The caveat (jcp-schema) `Caveat::WithinWindow(Window { secs, limit })` with `WindowLimit::{MaxUj, MaxCalls, MaxDistinctPayloads}`, plus `DenyReason::WindowExceeded` (`window-exceeded`). As with `FlowBelow`, a window is decided by a runtime gate against state, not from the frame; `holds()` refuses only a zero-second window. ## Enforcement (jcp-runtime, SPEC §4.12) - **History.** Each grant that carries a window keeps a history of admissions (time, payload) and debits (time, µJ), pruned to its longest window. - **What counts.** A call counts once it is **admitted**: allowed in a one-shot decide, or authorised in the two-phase path. A denied call counts for nothing and is debited nothing. - Every path gates and records. - Every link of a chain is counted, so a parent's window bounds its delegates together. - **Joule windows.** - A one-shot call whose cost would overfill the window is refused. - In the two-phase path the window admits a call while any of it is left, and binds at the debit. An overrun is denied and **stays debited**, as `MaxCostUj` does. - **A debit without its time** (`charge`, `charge_chained`) is placed at the grant's **latest** admission, which is never earlier than its own call's, so its joules stay in a window at least as long as they should. A debit with no admission at all stays in every window. **No caller signature changed.** - **After a restore.** `restore()` rebuilds spend from a timeless ledger, so afterwards every window counts as **full** until `resume_windows_at(t)` names when the history is complete and a whole window has passed since. An unknown history is not an empty one. - **`authorize_chained_for(…, need_uj)`** puts a caller's ceiling into the budget test, so a call refused for its ceiling is refused *before* it is admitted. ## Elsewhere - **mesh-core** authorises with the request's ceiling through `authorize_chained_for`, replacing the after-the-fact `BudgetTooLow` check. A serve refused for its ceiling therefore fills no window. The errors and their figures are unchanged. - **jcp-bridge:** a window caveat cannot be projected into an OAuth token. - **Durability.** jcp-wire and mesh-core replay each decision through the gate at its recorded time, so windows rebuild across a restart, with a test in each. The gateway resumes from receipts, which carry no time, so its windows stay full after a resume. Its clock is also fixed at 0, which is a separate gap. - **SPEC:** - §4.12 covers semantics, durability and sharding: n points that don't share history admit n × the limit. - The §4.9 wire form. - Conformance class **C27**, with a C27 check in jcp-conformance. - **Also:** two clippy errors that predate this change (`cloned_ref_to_slice_refs`) are fixed in jcp-runtime's tests. ## Verification (build host) - **jcp-rs:** 575 tests, 119 behind features, and every conformance class passes, including C27. - **mesh-rs:** 95 tests. - **Clippy** `-D warnings` is clean on jcp-schema, -runtime, -bridge, -wire, -conformance and mesh-core. - **Dependent workspaces:** sandbox-rs, joule-code-rs, jouledesk-rs, joulehook-rs and joule-ui-rs compile with `--all-targets`. - **Mutation testing:** 30 mutants, all killed. They cover: - every limit's boundary and the slide; - where a debit is placed (latest admission, not first; and with no admission, kept in the window rather than dropped); - the restore gap and pruning; - the ceiling-before-admission test; - each path's gate and recording. - **Repo checks** pass. ## Not in this PR From the same sweep item: carrying flow state across sandbox checkpoints. That is a decision about the sandbox capability `Enforcer`'s conformance class (stateless on purpose, C2), not wiring. Compiling a policy subset into these caveats is also left out. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jcp: WithinWindow, a sliding-window caveat over calls, payloads and measured joules
Some checks failed
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
cc52ca08c7
A grant's budget bounded its whole life and not its rate: a day's budget
could go in a minute. jcp-runtime budgets were cumulative only.

jcp-schema:
- Caveat::WithinWindow(Window { secs, limit }), where limit is
  WindowLimit::{MaxUj, MaxCalls, MaxDistinctPayloads}.
- DenyReason::WindowExceeded ("window-exceeded").
- holds() refuses only a zero-second window. Like FlowBelow, a window is
  decided by a runtime gate against state, not from the frame.

jcp-runtime (SPEC §4.12):
- Per grant carrying a window, a history of admissions (time, payload) and
  debits (time, µJ), pruned to the grant's longest window.
- A call counts once admitted: allowed in a one-shot decide, or authorised
  in the two-phase path. A denied call counts for nothing and is debited
  nothing. Every path gates and records: decide, decide_priced,
  decide_flow, authorize, authorize_flow, the chained paths and every
  charge. Every link of a chain is counted, so a parent's window bounds its
  delegates together.
- A joule window refuses a one-shot call whose cost would overfill it. In
  the two-phase path it admits while any of the window is left and binds at
  the debit: an overrun is denied window-exceeded and stays debited, as
  MaxCostUj does.
- A debit that arrives without its time (charge, charge_chained) is placed
  at the grant's latest admission, never earlier than its own call's, so
  its joules stay in a window at least as long as they should. A debit with
  no admission at all stays in every window. No caller signature changed.
- restore(), a timeless spend ledger, leaves every window counted as full
  until resume_windows_at(t) names when history is complete and a whole
  window has passed since. An unknown history is not an empty one.
- authorize_chained_for(…, need_uj) takes a caller's ceiling into the
  budget test, so a call refused for its ceiling is refused before it is
  admitted.

mesh-core: authorise with the request's joule ceiling via
authorize_chained_for, replacing the after-the-fact BudgetTooLow check.
A serve refused for its ceiling therefore fills no window. The errors and
their figures are unchanged.

jcp-bridge: a window caveat is unprojectable into an OAuth token.

Durability: jcp-wire and mesh-core replay each decision through the gate at
its recorded time, so windows rebuild across a restart (tests in both). The
gateway resumes from receipts, which carry no time, so its windows stay
full after a resume (its clock is also fixed at 0, a separate gap).

SPEC:
- §4.12: semantics, durability, sharding (n points admit n × the limit
  unless they share history).
- The §4.9 wire form.
- Conformance class C27, with a C27 check in jcp-conformance.

Also: two pre-existing clippy errors (cloned_ref_to_slice_refs) in
jcp-runtime's tests, so the crates touched are clippy-clean.

Tests: 10 in jcp-runtime, 1 in jcp-wire, 1 in mesh-core, plus the bridge
case and C27. Mutation testing: 30 mutants, all killed. They cover every
limit's boundary, the slide, debit placement (latest, not first; MAX, not
0, without an admission), the restore gap, pruning, the ceiling-before-
admission test, and each path's gate and recording.

Verified on the build host:
- jcp-rs: 575 tests, 119 behind features, every conformance class.
- mesh-rs: 95 tests.
- Clippy -D warnings clean on jcp-schema, -runtime, -bridge, -wire,
  -conformance and mesh-core.
- sandbox-rs, joule-code-rs, jouledesk-rs, joulehook-rs and joule-ui-rs
  compile with --all-targets.
- Repo checks pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
dcharlot force-pushed feat/jcp-window-caveat from cc52ca08c7
Some checks failed
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
to b1510772d1
All checks were successful
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 4s
wai-pack / wai-pack · linux (pull_request) Has been skipped
wai-pack / wai-pack · linux-arm64 (pull_request) Has been skipped
wai-pack / wai-pack · macos (pull_request) Has been skipped
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 4s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been skipped
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been skipped
wai-quantum studio and desktop / studio · browser (pull_request) Has been skipped
wai-pack / wai-pack · dependency licences (pull_request) Has been skipped
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been skipped
wai-quantum WebGPU member / does this change touch it (pull_request) Successful in 5s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Has been skipped
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Has been skipped
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Has been skipped
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Has been skipped
WAI mbt2018_mean integer decode (regenerated vectors) / does this change touch it (pull_request) Successful in 4s
wai-quantum WebGPU member / member · integrated GPU, Vulkan (framework-128gb) (pull_request) Has been skipped
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Has been skipped
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Has been skipped
wai-web wasm32 / does this change touch it (pull_request) Successful in 4s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been skipped
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m47s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m11s
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 2m33s
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 2m22s
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 1m2s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 3m16s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m34s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m48s
2026-10-21 07:31:31 -04:00
Compare
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!388
No description provided.