Compatibility: Pactmark 0.1.x.

Policy decides by risk class; grants decide by identity

These are two different questions, answered by two different objects.
Note what this policy says about R5: nothing. Which means it is denied, because the default is deny and there is no way to change that.
allow_with_grant does not mean “allow”. It means “allow if a matching grant exists”. A tool at a permitted risk class with no grant is still denied — with KAF_POLICY_DENIED or KAF_AUTHORIZATION_BINDING_MISMATCH depending on which check fails first.

Choosing a risk mapping

Pactmark ships the R0–R5 scale and refuses to define what each level means for your business. That mapping belongs in your control framework. A workable starting point: Write your chosen mapping down somewhere an auditor can read it. The framework will enforce whatever you declare; it will not tell you that your declaration was reasonable.

The evaluation sequence

Figure 1. This runs per effect. Order matters: schema before registration, registration before policy, policy before grant, grant before budget, budget before dispatch.

What a grant binds

A CapabilityGrant binds:

Tenant and principal

Who it is for. Cross-tenant use is a binding mismatch, not a lookup failure.

Purpose

A grant issued for one declared purpose does not apply to another.

Tool registration digest

The exact tool contract, so a modified tool cannot inherit an old permission.

Scope and constraints

Normalized resource scopes with a canonicalization version.

Expiry

Grants end. KAF_AUTHORIZATION_EXPIRED is a distinct code from a mismatch.

One-use reservations

Concurrency and replay cannot spend the same permission twice.

Scope canonicalization matters more than it looks

Resource scopes carry a normalizationVersion because "/a/./b", "/a/b", "/a/%62" and "/a/b/" must be one value before any comparison happens. @pactmark/policy exports the canonicalization used by grant checks; property-based tests cover repeated decoding, traversal and symlink resolution.
If you build your own admin UI for grants, canonicalize with the same function the policy uses. Two different normalizations is how a grant that “looks right” fails to match — or worse, matches something it should not.

Revocation is a kill switch on a digest

Revocation operates on exact digests: a tool registration, a model adapter, an MCP server or tool, a policy, a verifier, or an agent definition.
1

Revoke the digest

The kill switch is authoritative from the moment it is set.
2

In-flight runs re-check

A running agent must query revocation again before its next high-risk reservation or dispatch. Cached admission is not authority.
3

Verify it took effect

Confirm the old digest is denied and that the next high-risk action re-checks — do not infer it from an absence of traffic.

Testing your policy

Cover the negatives, because they are the ones that matter:
The CLI can explain a decision against a fixture, which is useful in review:

Approvals

What happens when the decision is require_approval.

Testing agents

Denial, replay, concurrency and drift as first-class test scenarios.