For AI Agents

Put contract changes behind a verifiable gate in three steps

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.

Wire it into your agent

Three steps: guard your tool calls, adopt the policy, read the verdict.

🚀

Production path (primary)

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.

production.ts
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.

1

Add the preflight guard

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.

Install
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:

guard.ts
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:

Fetch the snippet for your framework
curl https://app.coderifts.com/api/v1/snippets/langgraph
# full framework list: https://app.coderifts.com/api/v1/snippets
2

Adopt the recommended policy

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:

Default agent policy
curl https://app.coderifts.com/api/v1/policy/default
policy/default response (live model)
{
  "policy": {
    "name": "CodeRifts Default Agent Policy",
    "rules": [
      {
        "trigger": "before_tool_call",
        "condition": "contract_artifact_change",
        "action": "preflight_change_set"
      }
    ]
  }
}
3

Read the verdict (Decision Spec)

Acting paths use authorize (Decision Spec v2). Full field contract: /decision-spec/. Example authorize body (not analyze — analyze omits execution vocabulary):

Authorize response (act on this shape)
{
  "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:

Preflight response schema (v2)
curl https://coderifts.com/schemas/preflight-response.v2.consumer.json
# Prose + examples: https://coderifts.com/decision-spec/

Make CodeRifts the only path on the returned tool table

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.

The registry: only guarded tools are handed to the agent

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:

registry.ts
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.

The resolver: before/after from a git snapshot

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.

resolve-then-guard.ts
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.

Native discovery via MCP

CodeRifts is an MCP server. Point your agent at it and its three MCP tools appear automatically, with no integration code.

MCP endpoint
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.

Endpoints used in this guide

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

Add CodeRifts to your AGENTS.md

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.

AGENTS.md
## 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.