jcp-bridge: an OAuth access token narrows to the grant's caveats or is not minted, and a delegated grant needs its chain #321

Merged
dcharlot merged 1 commit from fix/jcp-oauth-projects-caveats into main 2026-10-18 19:02:29 -04:00

2026-10-18

Transaction Science
jcp-bridge: an OAuth access token narrows to the grant's caveats or is not minted, and a delegated grant needs its chain
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 19s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m51s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m38s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m15s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m13s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m34s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m52s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m38s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 2m11s
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 2m43s
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 56s
`grant_to_access_token` projects a JCP grant into an RFC 9068 access
token that a resource server enforces with ordinary JWT tooling. It
carried the capability, the expiry and the joule budget, and dropped
everything else. The resource server therefore enforced more authority
than the grant conveyed:
- A `NotAfter` narrower than the expiry was ignored, and so was any
  `NotBefore`.
- An `ActionIn` did not narrow `scope`.
- `PayloadIs`, `MaxCostUj`, `ModelIn`, a flow ceiling and a money
  ceiling were dropped silently.
- A delegated grant was minted from its leaf's signature alone, with the
  chain that shows it narrows never checked.
- `verify_access_token` never checked `nbf`.

A projection may narrow, never widen:
- **Narrowed.** `NotAfter` lowers `exp`, `NotBefore` raises `nbf`,
  `ActionIn` intersects `scope`, and a `CallerIs` naming the subject
  adds nothing.
- **Refused.** A condition no claim a resource server is known to
  enforce can carry gets no token (`Unprojectable`): `PayloadIs`,
  `MaxCostUj`, `ModelIn`, a flow ceiling, a money ceiling. So does a
  grant whose conditions leave nothing to permit (`PermitsNothing`): no
  action, an empty window, or a caller other than the subject.
- **Chains.** `grant_to_access_token` refuses a delegated grant
  (`Delegated`). `chain_to_access_token` projects a chain, verified hop
  by hop, with every link bounding `exp` and every link's caveats
  applied.
- **Verification.** `verify_access_token` enforces `nbf` as well as
  `exp`.

Checked on the build host:
- jcp-bridge: 18 tests pass, three of them new. jcp-rs: 517 pass.
- The mint example runs, and clippy is clean.
- Mutations each fail tests: dropping NotAfter, dropping PayloadIs,
  minting a lone delegated grant, and not enforcing nbf.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 13:23:01 -04:00