open-standards/jcp/jcp-rs/crates/jcp-policy
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science 0d29764d00 jcp-policy: a receipt sealed the policy's name, so the rules could be swapped under it
`policy_wire` sealed `"policy": <name>` — a `&str` label. The receipt committed
to the action, the resource, the caller and a hash of the call's content, and to
the deciding ruleset only by what the operator called it. So a sealed receipt
attested "denied under `prod-egress`" while nothing committed to what
`prod-egress` *was*: change the rules, keep the name, and every past receipt
still verifies while describing a decision the current policy would not make.
That is the load-bearing claim of a policy receipt, and it was the one thing
unbound.

`Policy::content_id()` digests the name **and the ordered rules**, and the wire
now carries `policy_digest` beside `policy`, recovered by `verify` into
`PolicyReceipt`. The name stays, relabelled in its doc comment as the
operator's word rather than evidence.

The rendering is written out rather than derived, so the digest's definition is
readable and a second implementation can reproduce it: each rule contributes
its name, its effect, and its match as a uniform `{kind, values}` pair.
**Order is in the digest because order is in the meaning** — `evaluate` returns
on the first matching `Deny` and takes the first matching `RequireApproval`, so
reordering the same set of rules can change a verdict.

One of the new tests caught a bug in this commit before it shipped:
`Match::Always` first rendered as a JSON `true`, and the JCR-1 signed core
forbids bools, so `content_id()` panicked on "require approval for every
write" — the most ordinary policy there is. Every variant is now pinned to
render, to be stable, and to be distinguishable from the others.

No sealed `policy/1` vector exists in `jcp/conformance`, so nothing pinned
changes; `jcp/SPEC.md` does not describe this profile, and jouleclaw's
`PolicyReceipt` is an unrelated JSON-Lines sibling that is untouched. 376 tests
pass across the jcp workspace, 0 clippy findings in the crates touched.

Second of the family of "sealed by name, not by digest" holes the 2026-10-04
sweep named; the AP2 mandate pair is 845e6cc8. Still open from that family:
`jouleclaw-transparency-log::build_consistency_path` returns `[old_root,
new_root]` instead of an RFC 6962 proof, and no grant binds a tool's parameter
schema.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-05 07:22:12 -04:00
..
examples jcp-policy: contextual service policies (content-keyed + require-approval) 2026-06-27 18:58:55 -04:00
src jcp-policy: a receipt sealed the policy's name, so the rules could be swapped under it 2026-10-05 07:22:12 -04:00
Cargo.toml jcp-policy: contextual service policies (content-keyed + require-approval) 2026-06-27 18:58:55 -04:00