Before a consequential contract-changing action, obtain authority: call CodeRifts in authorize mode, get a grant bound to that change set, and branch on execution_action. Analysis comes second — it tells the agent what the change is (a renamed field, a removed endpoint, a dropped auth scope) and what breaks, but it permits nothing. The tests stay green either way; the grant is what decides whether the call proceeds.
Pinned: model-facing result prose note_sha256 sha256:98fd28283a7e26cb5406a22d56a8caecf0e5cedb20bd3acae87846cf069b7ce1 — prose-claims table. Pins bytes, not behaviour.
Three steps: guard your tool calls, adopt the policy, read the verdict.
Atomic V2, one chain: challenge → authorize → executor → attestation → readback. Authorize with a cr.exec.v2 grant, hand that grant to the executor, atomic write, attestation back, provider readback. Lock the composition with profile: 'ENFORCING_ATOMIC_V2' and pin executionGrant.grantVersion: 'v2'. That is the canonical production path.
Current shipped guard is @coderifts/agent-guard@17.3.5 (current shipped major is 17). Current shipped CLI is coderifts@8.6.6. Both versions read from npm on 2026-09-19, not copied from an older page; the four CLI tokens below were re-measured against the shipped code the same day. The withCodeRifts composition below is the runtime fence. CLI onboarding: coderifts init --agents --atomic-v2 then coderifts verify atomic-v2 --target <id> …. init may exit 0 with PROFILE_CONFIGURED; verify must not exit 0 while wiring is incomplete (WIRING_REQUIRED, exit 2). Exit 0 is TARGET_ENFORCEMENT_VERIFIED for that named target.
Customer-facing states: PROFILE_CONFIGURED (the profile is selected); CUSTOMER_WIRING_REQUIRED until the five capabilities below are actual config (CLI token: WIRING_REQUIRED, exit 2); CUSTOMER_TARGET_VERIFIED for that named customer target (CLI token: TARGET_ENFORCEMENT_VERIFIED). A reference-executor TARGET_ENFORCEMENT_VERIFIED is not inherited. withCodeRifts({ profile: 'ENFORCING_ATOMIC_V2' }) refuses to construct (ATOMIC_PROFILE_UNSATISFIED) until customer-held executor wiring is present.
The guard requests the grant natively (since 9.6.0; current shipped major is 17). Set executionGrant: { enabled: true, grantVersion: 'v2' } and the guard's own authorize asks for it; the tool receives its own grant as the second argument to execute, scoped to that invocation. It defaults to OFF for an unprofiled composition — ENFORCING_ATOMIC_V2 requires the full execution chain.
If you are on 9.5.0 you may have used a host-side wrap that captured the last grant and handed it out via takeGrant(). Delete it. It was last-authorize, so two overlapping tool calls could hand a tool the wrong grant — our own 9.6.0 changelog says so. There is no supported version of that pattern here.
An absent profile is not ENFORCING_ATOMIC_V2 — that path stays today's opt-in defaults.
import { withCodeRifts } from '@coderifts/agent-guard';
import { CodeRifts } from '@coderifts/sdk';
const client = new CodeRifts({ apiKey: process.env.CODERIFTS_API_KEY });
// CUSTOMER EXECUTOR — CodeRifts never holds this credential.
// Reference: capability-demo POST /state-challenge.
// PER CALL, not hoisted: one shared nonce across overlapping calls is the same
// race the 9.5.0 host-side wrap had. Keyed by artifact so each call gets its own.
const challenges = new Map();
async function createStateChallenge(artifactId) {
const challenge = await fetch(`${process.env.EXECUTOR_URL}/state-challenge`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ target_id: artifactId }),
}).then((r) => r.json());
challenges.set(artifactId, challenge);
return challenge.state_nonce;
}
const { tools, registry_report, composition_assurance } = withCodeRifts({
tools: rawTools.map((t) => ({
...t,
// 2nd arg is THIS call's grant. The guard already failed the call closed if a
// grant was requested and did not arrive — there is nothing to check here.
execute: async (args, { execution_grant: grant }) => {
const challenge = challenges.get(args.artifactId);
// CUSTOMER EXECUTOR — mutation credential stays here.
// Grant + receipt digest in; changed payload / stale state / reused
// nonce / wrong operation → the executor writes nothing.
const committed = await fetch(`${process.env.EXECUTOR_URL}${executorPath}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${grant}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(args),
}).then((r) => r.json());
if (!committed.ok) {
throw new Error(committed.reason || committed.status || 'executor_refused');
}
return {
status: 'committed',
result: {
executor_attestation: committed.attestation,
execution_grant: grant,
},
version_token: challenge?.current_digest,
};
},
})),
client,
operation: 'merge',
profile: 'ENFORCING_ATOMIC_V2',
// Five mandatory capabilities — actual config, not comments.
// Missing any → ATOMIC_PROFILE_UNSATISFIED (CUSTOMER_WIRING_REQUIRED).
executionGrant: {
enabled: true,
grantVersion: 'v2',
resolveStateNonce: async ({ artifactId }) => createStateChallenge(artifactId),
},
executorId: process.env.EXECUTOR_ID,
adapterId: process.env.ADAPTER_ID,
targetUri: process.env.TARGET_URI,
resolvePriorContent: ({ artifactId }) => readPrior(artifactId),
executorAttestation: {
registry: executorKeyRegistry,
issuerKeyring: issuerGrantKeyring,
},
casAdapter: customerCasAdapter,
readBack: ({ artifactId }) => providerReadback(artifactId),
mutatorRegister,
credentialBoundary: {
postureReceipt: process.env.POSTURE_RECEIPT,
registry: postureKeyRegistry,
deploymentId: process.env.DEPLOYMENT_ID,
maxAgeMs: 60_000,
},
});
// Register ONLY `tools`. Host-side raw invokes stay invisible
// (calls_outside_guarded_path_invisible).
Do not also call client.preflightChangeSet({ include_execution_grant: true }) next to this. That is a second authorize and its grant is unused — executionGrant on the composition is the grant request.
Do not add derivation:"server" on this authorize. The guard already sends artifacts[] from the tool call; mixing derivation:"server" with artifacts[] is 400 (one source of truth). derivation:"server" is a different host-assembled authorize (proven tenant↔repo binding + context.repository + context.base + context.head) — not this chain. That path lists the contract-class change set via the proven binding's SCM platform (GitHub App Compare, or GitLab/Bitbucket Compare with header X-Coderifts-Scm-Token — short-lived, never stored).
executionGrant defaults OFF. ENFORCING_ATOMIC_V2 requires the chain (challenge, authorize, executor, attestation, readback) and aborts construction if customer wiring is incomplete. ENFORCING_STRICT has required an enabled grant since guard 10.0.0 (BREAKING); that is history, not the production fence.
What ATOMIC_V2 can demand and what it can only observe are different things: it enforces at construction that the six adapter operations are wired, and it can only report what the executor did afterwards — single-use nonce consumption happens at a remote party the guard never witnesses.
The grant (cr.exec.v2) is bound to operation + target + after-payload (scope_hash) and is short-lived. Never reuse it after the after-payload changes. An ATOMIC-profile grant carries state_nonce and is single-use at the executor — if the executor has consumed the nonce, re-preflight; do not retry the same grant.
It fails closed. An allow-class authorize that requested a grant and did not receive one fails the call (EXECUTION_GRANT_MISSING / SIGNER_UNAVAILABLE) before your factory runs, so there is no grant-less proceed to guard against in your own code. A resolveStateNonce that throws fails the call closed too (EXECUTION_GRANT_NONCE_UNRESOLVABLE). Once a grant has been requested, failPolicy: 'open' will not fall through to a grant-less OPEN_PASSTHROUGH. The outcome records only { requested, arrived } — never the token.
CAS / attestation: enforced is a pre-write fact. Under ENFORCING_ATOMIC_V2 a commit is only proven when an executor attestation verifies (cas_evidence: executor_attested) and provider readback matches. Treat authorized_not_committed / commit_evidence_missing as unfinished — do not close the operation. Attestation requires a customer-held executor key.
autoRecheck and autoDerive default OFF. Do not treat them as on.
Honest boundary: CodeRifts reports a governance decision and execution_action; it does not by itself block merges. Blocking needs repository configuration (required status checks). The composition cannot see a raw call the host makes outside the table it returns.
Install the runtime guard: @coderifts/agent-guard (withCodeRifts / the guarded registry). It runs an authorize preflight (operation-bound) for contract changes. On authorize responses, execution_action is a closed set of four: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP — anything unrecognised fails closed. This quickstart runs the tool only on CONTINUE (see Step 3 for monitoring). Fail-closed also on an unverifiable receipt or a degraded server. Analyze-mode responses omit execution vocabulary entirely — see Decision Spec v2. The runtime sees only calls through the returned tool table. Closes eager-execution ordering: mutating work is built inside the factory after the verdict — snapshot-to-commit correspondence requires a host conditional write (compare-and-swap on a version token), and the guard re-reads the result after the write (T3) — observed, not atomic. PR comments and MCP tools alone do not prevent a call — what each path does.
npm install @coderifts/agent-guard @coderifts/sdk
The canonical call — supply the change as artifacts[] with before/after content; the factory runs only if the verdict permits:
import { guardToolCall } from '@coderifts/agent-guard';
import { CodeRifts } from '@coderifts/sdk';
const client = new CodeRifts({ apiKey: process.env.CODERIFTS_API_KEY });
const outcome = await guardToolCall(
{
toolName: 'Edit',
arguments: { path: 'openapi.yaml' },
// The change CodeRifts preflights: id, type, before, after are all required.
artifacts: [{ id: 'public-api', type: 'openapi', before: baseSpec, after: proposedSpec }],
},
// The mutating work is created ONLY here, after the verdict — this closes eager-execution
// ordering. Snapshot-to-commit correspondence still requires a host conditional write
// (compare-and-swap on a version token).
async (envelope, redactedCall) => applyEdit(redactedCall),
{ client, operation: 'merge', environment: 'production' },
);
if (!outcome.executed) {
// Fail-closed: the tool never ran.
// GuardVerdict (agent-guard src/types.ts) carries verdict.action, not verdict.executionAction.
// Closed set when the field is present:
// CONTINUE | CONTINUE_WITH_MONITORING | REQUEST_APPROVAL | STOP
// A block is action STOP (kind BLOCK). Unknown is not permission.
if (outcome.verdict.action === 'STOP') {
console.error('CodeRifts blocked the call:', outcome.verdict.action);
}
}
If the guard detects a contract change but you did not supply artifacts[] with content, it fails closed locally with outcome.verdict.cause === 'MISSING_ARTIFACT_CONTENT' — an actionable error (pass the change), never a silent bypass. resolveArtifacts can fill before/after in from git automatically — see Make CodeRifts the only path below.
Prefer a framework drop-in? CodeRifts also ships ready-made snippets (LangGraph, AutoGen, and more) — each has a keyless quick check (one document; not a guard) plus an enforcement block that posts a change set and needs an API key — and a keyless /api/v1/public/preflight endpoint for trying it out without a key:
curl https://app.coderifts.com/api/v1/snippets/langgraph
# full framework list: https://app.coderifts.com/api/v1/snippets
CodeRifts publishes a default agent policy: when a contract artifact changes, call preflight_change_set with preflight_mode: "authorize" and context.operation, then branch on execution_action (not on ad-hoc on_block / on_warn strings, and not on analyze bodies). Fetch the policy and enforce it as-is, or customize:
curl https://app.coderifts.com/api/v1/policy/default
{
"policy": {
"name": "CodeRifts Default Agent Policy",
"rules": [
{
"trigger": "before_tool_call",
"condition": "contract_artifact_change",
"action": "preflight_change_set"
}
]
}
}
Acting paths use authorize (Decision Spec v2). Full field contract: /decision-spec/. Example authorize body (not analyze — analyze omits execution vocabulary):
{
"preflight_mode": "authorize",
"decision_spec_version": "2.0",
"receipt_kind": "operation_authorization",
"decision": "BLOCK",
"execution_action": "STOP",
"safe_for_agent": false,
"risk_score": 60,
"breaking_changes": 4,
"requires_migration": true,
"patterns": ["AUTH_SCHEME_REMOVAL", "ENDPOINT_REMOVAL", "TYPE_NARROWING"],
"chain_receipt": "…"
}
On authorize, branch on execution_action (CONTINUE | CONTINUE_WITH_MONITORING | REQUEST_APPROVAL | STOP). Unrecognised values fail closed. Use decision as explanation only; do not branch on safe_for_agent alone. This quickstart proceeds only on CONTINUE (CONTINUE_WITH_MONITORING needs monitoringSinkWired). Mode-less preflight returns 400. Analyze responses use analysis_outcome / may_execute:false and never grant permission. Schema:
curl https://coderifts.com/schemas/preflight-response.v2.consumer.json
# Prose + examples: https://coderifts.com/decision-spec/
Guarding one call is good; guarding the whole tool surface is what stops the model routing around it — on the wired returned-table path only, since a raw tool the host exports itself stays outside it (report.claim.inescapable_runtime stays false unless coverage is COMPLETE). The registry hands the agent only guarded tools, and the resolver supplies before/after from git — both fail-closed, both honest about scope.
withCodeRifts / guardToolRegistry takes your raw tool list and returns the only table the agent may see — every mutating tool wrapped so it cannot execute without going through CodeRifts. The runtime sees only calls through that returned table; host-side direct invokes are invisible. Unknown tools default to mutating (guarded, never assumed safe). Construction throws (fail-closed) if any mutator would remain raw:
import { guardToolRegistry } from '@coderifts/agent-guard';
// withCodeRifts is the composition entry that returns this same guarded table.
const { tools, coverage, report } = guardToolRegistry(rawTools, {
guard: { client }, // your CodeRifts client
// unknownToolPolicy: 'mutating' (default) — unknown tools are guarded, not trusted
});
// coverage: 'COMPLETE' (every mutator guarded) | 'PARTIAL' | 'BYPASSED' (break-glass)
// Register ONLY the guarded table with your agent runtime — never the raw tools.
agent.registerTools(tools);
Scope honesty. The registry secures the agent-runtime tool boundary only — report.claim.inescapable_runtime is true only when coverage is COMPLETE. It never claims inescapable_merge or inescapable_deploy (both always false): a human or a second process merging on the GitHub UI is a separate, repo-side layer. Making the PR check block a merge requires branch protection and related setup — see Make the Check Block a Merge.
resolveArtifacts turns a git diff plus blob reads into artifacts[] ready for the guard — the companion to MISSING_ARTIFACT_CONTENT. It produces content, never decides: report.claim.produces_verdict is always false, and the verdict fingerprint is untouched. A missing $ref, unreadable blob, or ambiguous source becomes an unresolved[] entry with a reason — never fabricated content.
import { resolveArtifacts, guardToolCall } from '@coderifts/agent-guard';
// The host provides a PURE git snapshot: base/head refs + blob reads.
const r = resolveArtifacts(
{ baseRef: 'main', headRef: prHeadSha, changedFiles, blobs },
{ openApiAssembly: 'bundle_inline' },
);
if (r.coverage === 'COMPLETE') {
// Resolved cleanly — hand the artifacts straight to the guard.
await guardToolCall(
{ toolName: 'merge', arguments: {}, artifacts: r.artifacts },
factory,
{ client, operation: 'merge' },
);
} else {
// PARTIAL / UNRESOLVED — fail closed; inspect r.unresolved (never invent content).
throw new Error('artifacts not resolvable: ' + JSON.stringify(r.unresolved));
}
Together: the guard demands content (MISSING_ARTIFACT_CONTENT if absent), and the resolver supplies it honestly (COMPLETE or fail-closed). Runtime coverage (registry) and repo-side merge enforcement are two layers — each states its own residual.
CodeRifts is an MCP server. Point your agent at it and its three MCP tools appear automatically, with no integration code.
MCP endpoint: https://app.coderifts.com/mcp (transport: streamable-http)
Manifest: https://coderifts.com/mcp.json
Registry: io.github.coderifts/api-governance
See the three MCP tools and the REST API endpoints on the MCP Integration page.
| Endpoint | Purpose | Auth |
|---|---|---|
POST /api/v1/preflight |
Change-set preflight — requires preflight_mode (analyze | authorize); authorize may return a signed receipt (Decision Spec v2) |
key |
GET /api/v1/public/preflight |
Keyless preflight verdict | none |
POST /api/v1/agent-readiness-score |
0-100 agent-readiness score for a spec | key |
GET /api/v1/decision-spec/schema |
Redirect to Decision Spec docs; preflight union schema: https://coderifts.com/schemas/preflight-response.v2.consumer.json | none |
GET /api/v1/policy/default |
Recommended agent policy | none |
GET /api/v1/snippets/{framework} |
Framework integration snippets | none |
Coding agents read AGENTS.md as procedural memory, and more than 60,000 repositories already ship one. Paste this section so any agent working in your repo obtains authority before it merges a contract change.
## Authority before a contract change
Before merging changes to API schemas, OpenAPI specs, or
.github/workflows files, obtain a CodeRifts authorization first:
- Runtime: wrap mutating tools with @coderifts/agent-guard (withCodeRifts /
the guarded registry). Authorize preflight with context.operation; branch on
execution_action: CONTINUE → proceed; REQUEST_APPROVAL or STOP → do not call;
unrecognised → fail closed. Analyze mode is risk-only (not permission).
Decision Spec v2: https://coderifts.com/decision-spec/
- PR report: the CodeRifts GitHub App comments on the PR. A red report alone
does not block merge — require the check under branch protection when you
want a merge gate (docs: Make the Check Block a Merge).
- MCP: preflight_change_set at https://app.coderifts.com/mcp — always pass
preflight_mode (authorize when acting; analyze for risk only).
Treat REQUEST_APPROVAL and STOP as non-proceed; do not auto-merge on WARN.