jcp: a chained decision enforces caveats, and a chain cannot drop one #300

Merged
dcharlot merged 1 commit from fix/jcp-chained-caveats into main 2026-10-17 16:03:08 -04:00
Owner

The defect

Runtime::decide_chained evaluated no caveat at all: not the leaf's, and not any ancestor's.

It is the decision path behind:

  • the jcp/1 wire endpoint (jcp-wire, SPEC §11)
  • jcp-channel
  • jouleclaw-jcp
  • joule-code-jcp

All of these present a chain, even a one-link chain for an undelegated grant. So PayloadIs, NotBefore, NotAfter, CallerIs, ActionIn, MaxCostUj and ModelIn were enforced only on the single-grant decide/authorize path. That path is the only one C24 exercises.

Separately, verify_chain checked capability, budget, expiry and depth at each hop, but not caveat inheritance or the money ceiling. attenuate refuses a child that drops a caveat or widens its money ceiling. A chain minted by hand, outside attenuate, verified anyway and conveyed the authority the caveat withheld.

The fix

  • verify_chain: refuses a hop that drops a parent caveat (caveats_narrow) or widens or drops its money ceiling (funds_narrows). Both are denied attenuation-violation.
  • check_chained: the chained decision's non-budget checks, moved into one function that now evaluates every link's caveats. Cost-dependent ones are evaluated when the cost is known.
  • authorize_chained / charge_chained: the two-phase metered path for a chain.
    • Authorize debits nothing and reports the least any link has left.
    • Charge debits every link, so siblings share their parent exactly as in decide_chained.
    • A per-call breach is reported with the joules still debited.
  • SPEC §4.2 states both rules and the chained metered path, and C7 covers an inherited caveat and a dropped one.

The mesh steward will use the two-phase chained path to accept delegated grants. That is the next PR.

Checked

On the build host:

  • jcp-rs: 501 tests pass, four of them new. The conformance suite passes all checks, including the extended C7.
  • Vectors: every vector gen-vectors writes is byte-identical to jcp/conformance/v1.
  • Consumers: jouleclaw-jcp, joule-code-jcp and sandbox-jcp pass their tests. mesh, joule-ui, joulehook and jouledesk compile.
  • Mutations: each fails tests:
    • chains ignoring caveats (also fails C7)
    • verify_chain allowing a dropped caveat (also fails C7)
    • a charge that debits only the leaf
  • Clippy: nothing new; the two slice::from_ref warnings predate this change.

🤖 Generated with Claude Code

## The defect `Runtime::decide_chained` evaluated **no caveat at all**: not the leaf's, and not any ancestor's. It is the decision path behind: - the `jcp/1` wire endpoint (`jcp-wire`, SPEC §11) - `jcp-channel` - `jouleclaw-jcp` - `joule-code-jcp` All of these present a chain, even a one-link chain for an undelegated grant. So `PayloadIs`, `NotBefore`, `NotAfter`, `CallerIs`, `ActionIn`, `MaxCostUj` and `ModelIn` were enforced only on the single-grant `decide`/`authorize` path. That path is the only one C24 exercises. Separately, `verify_chain` checked capability, budget, expiry and depth at each hop, but not caveat inheritance or the money ceiling. `attenuate` refuses a child that drops a caveat or widens its money ceiling. A chain minted by hand, outside `attenuate`, verified anyway and conveyed the authority the caveat withheld. ## The fix - **`verify_chain`:** refuses a hop that drops a parent caveat (`caveats_narrow`) or widens or drops its money ceiling (`funds_narrows`). Both are denied `attenuation-violation`. - **`check_chained`:** the chained decision's non-budget checks, moved into one function that now evaluates **every link's** caveats. Cost-dependent ones are evaluated when the cost is known. - **`authorize_chained` / `charge_chained`:** the two-phase metered path for a chain. - Authorize debits nothing and reports the least any link has left. - Charge debits every link, so siblings share their parent exactly as in `decide_chained`. - A per-call breach is reported with the joules still debited. - **SPEC §4.2** states both rules and the chained metered path, and **C7** covers an inherited caveat and a dropped one. The mesh steward will use the two-phase chained path to accept delegated grants. That is the next PR. ## Checked On the build host: - **jcp-rs:** 501 tests pass, four of them new. The conformance suite passes all checks, including the extended C7. - **Vectors:** every vector `gen-vectors` writes is byte-identical to `jcp/conformance/v1`. - **Consumers:** jouleclaw-jcp, joule-code-jcp and sandbox-jcp pass their tests. mesh, joule-ui, joulehook and jouledesk compile. - **Mutations:** each fails tests: - chains ignoring caveats (also fails C7) - `verify_chain` allowing a dropped caveat (also fails C7) - a charge that debits only the leaf - **Clippy:** nothing new; the two `slice::from_ref` warnings predate this change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jcp: a chained decision enforces caveats, and a chain cannot drop one
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 9s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m21s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 5m57s
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 1m34s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m9s
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 2m36s
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 1m9s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 2m1s
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 3m13s
f0952309c1
`decide_chained` evaluated no caveat at all: not the leaf's, not any
ancestor's. It is the decision path behind jcp-wire's endpoint (§11),
jcp-channel, jouleclaw-jcp and joule-code-jcp. Those always present a
chain, even a one-link chain for an undelegated grant. So `PayloadIs`,
`NotBefore`, `NotAfter`, `CallerIs`, `ActionIn`, `MaxCostUj` and
`ModelIn` were enforced only on the single-grant `decide`/`authorize`
path, which the conformance suite exercises.

Separately, `verify_chain` checked capability, budget, expiry and depth
at each hop, but not that a child carries its parent's caveats or no
wider money ceiling. `attenuate` refuses both when it mints. A chain
minted by hand verified anyway and conveyed the authority the caveat
withheld.

- `verify_chain` refuses a hop that drops a parent caveat
  (`caveats_narrow`) or widens or drops its money ceiling
  (`funds_narrows`). Both are denied `attenuation-violation`.
- The chained decision's checks move into `check_chained`, which now
  evaluates every link's caveats. Cost-dependent ones are evaluated when
  the cost is known.
- `authorize_chained` / `charge_chained` add the two-phase metered path
  for a chain. Authorize debits nothing and reports the least any link
  has left. Charge debits every link, so siblings share their parent as
  in `decide_chained`, and a per-call breach is reported with the joules
  still debited.
- SPEC §4.2 states both rules and the chained metered path. C7's
  conformance check covers an inherited caveat and a dropped one.

Checked on the build host:
- jcp-rs: 501 tests pass, and the conformance suite passes all checks.
- Every vector gen-vectors writes is byte-identical.
- jouleclaw-jcp, joule-code-jcp and sandbox-jcp pass their tests.
- mesh, joule-ui, joulehook and jouledesk compile.
- Mutations each fail tests: chains ignoring caveats, `verify_chain`
  allowing a dropped caveat, and a charge debiting only the leaf.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
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!300
No description provided.