Contract-change authorization for AI agents
Only a granted change can proceed.
You won't have to trust our database to prove which one.
curl -sS https://app.coderifts.com/api/v1/action-verdict \
-d '{"action_type":"tool_call","tool":{"name":"get_customer","capabilities":["read"]}}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["chain_receipt"])' > receipt.txt
git clone -q --depth 1 https://github.com/coderifts/receipt-verifier
node receipt-verifier/cli.js "$(cat receipt.txt)"
valid: true · VERIFIED_CURRENT · kid 2026-07-k1
A signed decision, and a verifier that reads it without asking us. No API key, no account. Measured 2026-09-15.
Enforces on configured paths. Every decision names its coverage and limits.
Every answer carries its ceiling.
The left side is what the receipt above establishes. The right side is the same size, because it is the same receipt.
valid: true
The signature holds against the pinned keyring, offline.
currently_authorized: null
Whether that authorization is current could not be established from a token alone.
What a verdict can say.
Four outcomes, two evidence tiers, and two answers that are neither.
execution_action
evidence
no evidence
not decided
Where authorization cannot be established, the answer is never yes.
If current authorization cannot be established, the answer is null — never an assumed true.
What happens when an agent changes a contract
The agent requests a change
In the guarded tool table, an ungranted mutation does not exist.
Not blocked. Not there.
The agent does not receive an error. There is nothing to call.
npx @coderifts/conformance --assurance GUARDED_TOOL_TABLE
COVERED / LIVE · 6 vectors
Scope: the tool table CodeRifts hands the agent. A path the host registers separately is outside this claim.
The gate decides
A verdict is a named state.
Not a score. Not a boolean.
AUTHORIZED_AND_COMMITTED is not authorized. The output carries the difference, not the documentation.
curl -sS https://app.coderifts.com/api/v1/action-verdict \
-d '{"action_type":"tool_call","tool":{"name":"get_customer","capabilities":["read"]}}'
decision: REQUIRE_APPROVAL · execution_action: REQUEST_APPROVAL · signed
This public endpoint returns a fixed decision so you can see the shape of the envelope. Calls through your own installation are evaluated from their change set.
The decision carries its limits
Every proof names its limits.
The output separates what was proven, what was not, and what falls outside the path that was evaluated. A field, not a footnote.
curl -sS https://coderifts.com/.well-known/coderifts.json \
| python3 -c 'import sys,json;[print(s) for s in json.load(sys.stdin)["does_not_prove"][:2]]'
Evidence is RECORDED — a pinned capture, replayed — not a live provider run.
proof_scope TRUSTED_EXECUTOR, provider_witness NOT_APPLICABLE,
externally_witnessed false: CodeRifts did not witness or sign a provider state.
What the merge check says
This change. This head. This issuer.
Not "the pull request is green". This change set, bound to this commit, from a named issuing App.
RECORDED — coderifts/demo #4, 2026-09-03
BLOCK · 6 breaking changes · risk 84
check: CodeRifts / contract-gate · conclusion: failure
issuer: CodeRifts App (app_id 2860592) · bound to head 4b2062b9
enforcement: NOT VERIFIED — main is unprotected on that repo
Read the last line first. The check ran, failed, and named its issuer — but nothing was stopped, because that branch requires no check. The gate says so itself: its own summary reads “Enforcement status: not verified”. A second check on the same commit, contract-gate (Action), came from github-actions (app_id 15368) — an issuer every workflow in a repository shares. Requiring a context by name is not enough, and binding it to an app id narrows who can forge it without closing it; pinning the required workflow by immutable SHA is what closes it.
The grant is spent
One grant. One use.
No adapter can claim more than it can prove.
An adapter that cannot demonstrate single-use atomic consumption cannot report the atomic success state.
npx @coderifts/conformance --assurance ATOMIC_COMMIT
COVERED / RECORDED · 6 vectors
Anyone can verify it later
Prove that what executed matched what was authorized.
Offline. No API key. No CodeRifts server.
This is the command under the headline. The verifier reads a vendored key snapshot: it does not contact us, and it reports what the evidence does not establish.
currently_authorized: null
One screen: the definition moved, and the call stopped
An agent was authorized to call a tool. Upstream, the tool's description changed. Nothing else did.
Two layers. Run the first one; the second needs an issuer.
Layer 1 — run this now
# the clean tool, and the same tool with one sentence added to its description
curl -sS https://coderifts.com/examples/description-drift.json \
| curl -sS https://app.coderifts.com/api/v1/preflight \
-H 'Content-Type: application/json' -d @-
analysis_outcome : BREAKS_DETECTED
patterns : ["description_only_change", "TOOL_INSTRUCTION_DRIFT"]
may_execute : false
authorization_effect : NONE
verdict_fingerprint : sha256:5096f53d…006209c
The schema did not change. One sentence in the description did, and two separate mechanisms saw it — the pattern that names a description-only change, and the one that names an instruction appearing in a tool description.
This is analysis, not authorization.
may_execute: false, and no receipt was issued. Measured 2026-09-15, stable across three calls.
The public path analyses. The gate decides. You can run the first one now; the second needs an issuer.
Layer 2 — recorded, because it needs a key
The second layer is what the gate does when an issuer is present: a grant bound to that exact change, consumed once, with a receipt. It needs a key, so it cannot be a command you run from this page — and we do not publish a recording of it here until the recorded values are pinned by a test, the way the first layer is. Until then the honest answer is that you have to install it to see it.
A description is an instruction.
A tool whose schema never changed can still be told to behave differently. That is not a schema problem, so schema checks do not see it.
How the classifier decides → What it does not catch →
The gate runs where your agent already runs
One decision path, three entry points. The recognition, the call and the verdict mapping are the same file in all of them — only the host contract differs.
OpenClaw
before_tool_call
The host declares this hook fail-closed itself.
openclaw plugins install @coderifts/agent-hooks
openclaw plugins inspect coderifts-contract-gate
Status: loaded · Version: 0.2.0
One command. The host fetches it. Measured on host 2026.6.35. The runtime id is coderifts-contract-gate, not the package name.
Claude Code
PreToolUse
The host passes a stalled hook through. Our adapter forces the refusal.
npm i @coderifts/agent-hooks
Then one entry in .claude/settings.json — the package ships it at claude-code/settings.snippet.json. Matcher Write|Edit|MultiEdit, timeout 8s.
Docker MCP Gateway
before:exec interceptor
Measured: an empty response passes the call. Ours would never return one.
Not built. Measured 2026-09-13.
Three boundaries we will not leave implied.
On Claude Code a hook that outruns the host's timeout is passed through no matter what our adapter returns. A file written by a shell command is not seen by a Write matcher. And on OpenClaw, only host 2026.6.35 is measured — the current release is not.
Install Read the adapter source — no public URL yet; the adapters ship inside the packages
The part most vendors leave out
Every decision we emit carries its own limits. Here are three of the seven, served live at the address below.
Evidence is RECORDED — a pinned capture, replayed — not a live provider run.
The grant does not bind the canonical tool-call bytes, so what-was-called is not proven equal to what-was-authorized (planned, not available).
No DSSE or in-toto envelope is emitted by any published artifact.
curl -sS https://coderifts.com/.well-known/coderifts.json \
| python3 -c 'import sys,json;[print(s) for s in json.load(sys.stdin)["does_not_prove"]]'
A limit we publish is one you can hold us to. A limit we omit is one you discover in an incident.
Written for the person who has to answer for it
You run agents in production. Something changed a contract last week and the answer to "who allowed that" is a log line you also control.
You are adding a gate to an agent runtime. You need the decision to be a named state you can branch on, not a score.
You are the one an auditor will ask. You need evidence that survives us: no callback, no account, no vendor.
If none of these is you yet, the honest answer is that you do not need this today.
Start with the part that asks nothing of you
The second link is a conversation, not a form: tell us one consequential change your agents make, and we will tell you what we can and cannot prove about it.