Skip to main content
Stable v2.0

CodeRifts Decision Spec

Machine-readable, mode-discriminated JSON for API contract governance. Preflight is a discriminated union on preflight_mode: analyze is informational only; authorize is operation-bound and may mint a receipt. Analyze responses cannot be read as permission.

Format coverage, read 2026-10-04, is the table on the documentation page. An incomplete analysis in authorize mode is ANALYSIS_FAILED and STOP, with the gap named.

Current: Decision Spec 2.0 (2026-08) | License: MIT | Preflight schema (v2) | Manifest

Compatibility

Receipts. Issued receipts carry v: 2 in the signed header, alongside the key id they verify against. Measured on the live endpoint 2026-09-20.

Grants. Two envelope versions are supported: cr.exec.v1 and cr.exec.v2. A request that omits grant_version is served cr.exec.v2 — that default changed on 2026-09-18. Pinning grant_version: "v1" still yields cr.exec.v1. Measured 2026-09-20 from the resolver the server uses.

What is additive. A new JSON field may appear in any response without a version change. Parse by name and ignore what you do not recognise; do not parse by position and do not treat an unknown field as an error.

What is breaking, and takes a major version. A new execution_action value, a new case state, or a new public tool name. Each one changes what a caller must branch on, so each one is a major, never a quiet addition. An unrecognised execution_action must fail closed — that is the rule that makes adding one survivable.

Preflight modes (Decision Spec 2.0)

preflight_mode is required on every preflight request (analyze or authorize). A request without it returns 400 with a machine-readable error naming both modes.

Legacy pin: send decision_spec_version: "1.0" to receive the pre-v2 shape (soft-default mode; analyze still carries execution vocabulary in the body and in x-coderifts-decision / x-coderifts-safe-for-agent). Responses and HTTP headers mark deprecation (Deprecation: true, Sunset: 2026-09-07T00:00:00Z, Link: rel="successor-version") — that machinery is intact. The announced sunset was 2026-09-07T00:00:00Z. The pin was retired at that date and no longer answers (measured 2026-09-18). Do not pin 1.0.

Mode Purpose JSON Schema branch
analyzeInformational risk / impact only. Branch on analysis_outcome and may_execute (always false). Not permission.Prohibits decision, execution_action, safe_for_agent
authorizeOperation-bound path. Requires context.operation. May mint chain_receipt. Downstream gates verify the receipt conjunctively.Requires decision + execution_action (+ safe_for_agent)

Honest note (risk vs permission). On Decision Spec 2.0 — the default — analyze responses do not carry decision, execution_action, or safe_for_agent, and cannot be read as permission.

The legacy pin was the exception until it was retired on 2026-09-07; it no longer answers (measured 2026-09-18). A request that pins decision_spec_version: "1.0" gets the pre-v2 analyze shape: decision, execution_action and safe_for_agent are present in the body and in the x-coderifts-decision and x-coderifts-safe-for-agent response headers. A benign change on that path returns execution_action: CONTINUE with no authorization and no receipt. Every such response carries deprecation: true, a sunset header and a successor-version link — but the pin has been answering past its announced sunset of 2026-09-07, and this page said otherwise until 2026-09-14. Do not pin 1.0. If you read a response you did not request, branch on preflight_mode before you branch on anything else.

The public, key-free path

Two endpoints answer without an account. Both are analysis. Neither issues a grant or a receipt, and neither is permission. They do not return the same shape, and that difference is the point of this section. Everything below was read from the running service on 2026-09-15, not copied from an earlier page.

GET /api/v1/public/preflight

Decision Spec 2.0 shape. It analyses and says so:

analysis_outcome     : "NO_BREAK_DETECTED"
decision             : null
execution_action     : null
authorization_effect : "NONE"
may_execute          : false
receipt_kind         : "NONE"

There is no decision field, and authorization_effect says NONE rather than staying silent. That is the contract working as intended: risk classification and permission are different vocabularies.

POST /api/v1/public/actionguard-check

A different shape, and older vocabulary. It returns a decision:

decision             : "ALLOW"
execution_action     : "CONTINUE"
safe_for_agent       : true
authorization_effect : (absent)
may_execute          : (absent)

Read the endpoint’s own scope_note before you read its decision. It ships with the response and states the limit: “ActionGuard v1 static diff of a single workflow file’s uses: refs. WARN-tier advisory only — never REQUIRE_APPROVAL or BLOCK, and contributes no risk_score. A finding means review, not a hard failure.” So an ALLOW here is not the gate allowing anything; it is one static check finding nothing in one file. ⚠ Unlike the endpoint above, this response carries no authorization_effect to say so in the envelope — branch on analysis_type, not on decision.

Limits

Measured from the response headers on 2026-09-15: the public preflight answers 60 requests per minute per IP, with no daily cap (x-ratelimit-limit: 60) — the agent-facing tier. Other keyless endpoints sit at 30 per minute per IP plus a 50-per-day anonymous cap; an API key raises the per-minute limit to 100.

Response shapes (analyze vs authorize)

The published preflight consumer schema is a oneOf on preflight_mode. The first example below is analyze (informational). The second is authorize (operation-bound).

ANALYZE response (informational — not permission)

{
  "preflight_mode": "analyze",
  "decision_spec_version": "2.0",
  "analysis_outcome": "BREAKS_DETECTED",
  "authorization_effect": "NONE",
  "may_execute": false,
  "receipt_kind": "NONE",
  "risk_score": 87,
  "breaking_changes": 3,
  "patterns": [
    "AUTH_SCOPE_REDUCTION",
    "FIELD_REMOVED",
    "ENDPOINT_REMOVED"
  ],
  "requires_migration": true,
  "timestamp": "2026-08-08T10:00:00Z"
}

No execution_action, no safe_for_agent, no decision, no chain_receipt. Branch on may_execute / analysis_outcome only for risk handling — never treat this body as a green light to merge, deploy, or call tools.

AUTHORIZE response (operation-bound)

{
  "preflight_mode": "authorize",
  "decision_spec_version": "2.0",
  "receipt_kind": "operation_authorization",
  "decision": "BLOCK",
  "execution_action": "STOP",
  "safe_for_agent": false,
  "risk_score": 87,
  "breaking_changes": 3,
  "patterns": ["AUTH_SCOPE_REDUCTION", "FIELD_REMOVED", "ENDPOINT_REMOVED"],
  "requires_migration": true,
  "operation": "merge",
  "chain_receipt": "<signed token when signer configured>",
  "timestamp": "2026-08-08T10:00:00Z"
}

Branch on execution_action for proceed/halt. Use decision as explanation. A downstream merge/deploy/runtime gate must verify the receipt (conjunctive checks) — possession of execution_action alone is not enough.

Field Definitions (by mode)

Field Mode Description
preflight_modebothRequired discriminator: analyze | authorize.
analysis_outcomeanalyzeRisk classification only: NO_BREAK_DETECTED | BREAKS_DETECTED | ANALYSIS_FAILED. Not authorization.
may_executeanalyzeAlways false on analyze. Analyze never grants execute.
authorization_effectanalyzeAlways NONE on analyze.
receipt_kindbothanalyze: NONE. authorize: operation_authorization when a receipt was issued, else NONE.
decisionauthorize onlyALLOW | WARN | REQUIRE_APPROVAL | BLOCK. Explanation of the authorize verdict. Omitted on analyze.
execution_actionauthorize onlyCONTINUE | REQUEST_APPROVAL | STOP. Proceed signal. Omitted on analyze. Values measured 2026-10-14 from the authorize branch of decision-spec/v2.0.json.
safe_for_agentauthorize onlyDashboard/legacy flag. Do not branch on it alone. Omitted on analyze.
chain_receiptauthorizeSigned token when issuer configured. Absent key when none issued. Never on analyze.
risk_scoreboth0–100 composite risk under supplied evidence.
breaking_changesbothCount of breaking changes detected.
patternsbothNamed patterns. See Pattern Registry.
decision_spec_versionboth"2.0" on both branches of decision-spec/v2.0.json (authorize const, analyze enum of one value). Measured 2026-10-14.

Decision Values

BLOCK

The change violates a governance rule or exceeds the risk threshold. The requested operation must not proceed through an enforcing path.

When agents see BLOCK: Do not proceed. The API contract has changed in a way that will break this agent's tool calls or workflow steps.

REQUIRE_APPROVAL

The change is high-risk but does not automatically block. Human approval required before merge.

When agents see REQUIRE_APPROVAL: Pause execution. Wait for human sign-off before calling the updated API.

WARN

The change is potentially risky but within policy. Not itself permission: on acting paths, proceed only with a verified receipt that is currently authorized and matches operation, fingerprint and required context — the warning travels with the decision.

When agents see WARN: not itself permission. On acting paths, proceed only with a currently authorized receipt matching operation, fingerprint and required context — and monitor downstream behavior after deployment.

ALLOW

No breaking changes detected. ALLOW means no configured blocking signal was detected under the supplied evidence. A contract-level ALLOW is not a runtime deployment guarantee.

When agents see ALLOW: no configured blocking condition was found. ALLOW alone is not sufficient authorization to execute. On acting paths, proceed only after the associated receipt is currently authorized and matches operation, fingerprint and required context.

safe_for_agent Flag (authorize only)

On authorize responses only. Analyze responses omit this field entirely. Prefer branching on execution_action for control flow; treat safe_for_agent as a dashboard/legacy signal. safe_for_agent: false is set when any of the following agent-breaking patterns are detected:

  • • Tool schema drift (TOOL_CALLING_SCHEMA_DRIFT)
  • • Tool result shape change (TOOL_RESULT_SHAPE_DRIFT)
  • • Agent protocol change (AGENT_PROTOCOL_DRIFT)
  • • Workflow chain break (WORKFLOW_CHAIN_BREAK)
  • • Auth delegation change (AUTH_DELEGATION_DRIFT)
  • • Shared state schema change (SHARED_STATE_SCHEMA_DRIFT)

If safe_for_agent: false, agents must not call the updated API without human review.

Pattern Registry (v1.0)

Patterns are named identifiers for categories of breaking changes. They are stable across spec versions.

Contract Patterns

Pattern Severity Description
FIELD_REMOVEDHIGHA response or request field was removed
FIELD_RENAMEDHIGHA field was renamed (old name no longer exists)
TYPE_CHANGEDHIGHA field's type changed incompatibly
REQUIRED_ADDEDHIGHA previously optional parameter is now required
ENDPOINT_REMOVEDCRITICALAn endpoint was removed
AUTH_SCOPE_REDUCTIONCRITICALAuthentication requirements were tightened
ENUM_RESTRICTEDMEDIUMEnum values were removed

Agent Patterns

Pattern Severity Description
TOOL_CALLING_SCHEMA_DRIFTCRITICALMCP/function calling schema changed
TOOL_RESULT_SHAPE_DRIFTCRITICALTool return value structure changed
AGENT_PROTOCOL_DRIFTCRITICALAgent communication protocol changed
WORKFLOW_CHAIN_BREAKCRITICALA field used by a downstream workflow step was removed
AUTH_DELEGATION_DRIFTCRITICALAgent authentication delegation changed
SHARED_STATE_SCHEMA_DRIFTCRITICALShared state between agent steps changed

Behavioral Patterns

These names are a fixed list, and no trained model reads them.

Pattern Severity Description
LATENCY_DRIFTMEDIUMResponse time degradation detected
SIGNAL_LOSSHIGHAPI signal quality degrading
SYNAPTIC_NOISE_RISEMEDIUMInconsistent response patterns
AUTH_CONDUCTION_DECAYHIGHAuth success rate declining

Extended Decision Object (Authenticated)

With an API key, the full decision object is returned including blast radius and a migration plan. The sample below is an illustration of shape. It is not a measured response. A letter grade is not part of it.

{
  "preflight_mode": "authorize",
  "decision_spec_version": "2.0",
  "decision": "BLOCK",
  "execution_action": "STOP",
  "safe_for_agent": false,
  "risk_score": 87,
  "breaking_changes": 3,
  "patterns": ["AUTH_SCOPE_REDUCTION", "FIELD_REMOVED"],
  "requires_migration": true,
  "timestamp": "2026-08-08T10:00:00Z",

  "extended": {
    "omega_api": 87.3,
    "reflex_triggers": [
      { "rule": "endpoint_removed ∧ public_api", "decision": "BLOCK" }
    ],
    "blast_radius": {
      "affected_consumers": 4,
      "estimated_cost": "$12,000–$45,000"
    },
    "migration_plan": "Restore removed field as deprecated with x-sunset date."
  }
}

estimated_cost is a configurable heuristic (engineer rate × migration hours), not a measured outcome: nothing in the product compares it against what a migration actually cost, so read it as an order of magnitude and not as a number. Workflow simulation is a separate endpoint and is not part of this object. — see methodology

What the fingerprint covers

The verdict_fingerprint is a SHA-256 hash computed over the canonical verdict core: the decision, per-module risk scores, gate results, and the aggregation values — nothing else.

Transport metadata is deliberately outside the hash: timestamps, correlation IDs and the receipt signature are not fingerprint inputs. The Ed25519 receipt signs over the fingerprint — a downstream step, never an input to it.

The same analysis therefore yields a byte-identical fingerprint across runs, regardless of when it ran or which receipt attests it — deterministic for a given complete decision input, the applied policy and configuration, a given engine build, and a given clock. Determinism lives in the verdict core; freshness lives in the receipt. Across engine builds it is not promised: a decision made by an earlier build is not a promise about the current one. scorer_version is bound into the fingerprint preimage, so a scorer change is visible in the fingerprint; an engine change that is not the scorer is not, because engine_build_id has no producer yet and comes back absent (measured 2026-09-24). The clock is a real verdict input, not a caveat: checkFreezeWindow is called with no injected time (src/policy-engine.js:197 → new Date()), and an active freeze reaches RELEASE_FREEZE_ACTIVE in the decision reason and flips the GitHub check conclusion to failure. The same change set, under the same policy and the same build, inside a configured freeze window and outside it, does not decide the same way.

How Agents Should Use This Spec

Always set preflight_mode. Use analyze for risk inspection; use authorize when a merge/deploy/tool path needs a receipt. Never treat an analyze body as permission.

For a language-by-language integration walkthrough (Python, Node.js, Go) built on this spec, see How Agents Use the Decision Spec — a practical guide, not a second protocol definition.

Step 1a: Analyze (informational)

POST https://app.coderifts.com/api/v1/preflight
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "preflight_mode": "analyze",
  "artifacts": [{ "id": "api", "type": "openapi", "before": "...", "after": "..." }]
}
// Analyze: no execution_action / safe_for_agent / decision — branch on may_execute / analysis_outcome
if (response.may_execute !== false) throw new Error("unexpected analyze body");
if (response.analysis_outcome === "ANALYSIS_FAILED") {
  // incomplete analysis — re-preflight or escalate; not permission
}
// Risk only: BREAKS_DETECTED is informational here, not a green light to proceed
logRisk(response.analysis_outcome, response.risk_score, response.patterns);

Step 1b: Authorize (operation-bound)

POST https://app.coderifts.com/api/v1/preflight
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "preflight_mode": "authorize",
  "context": { "operation": "merge" },
  "artifacts": [{ "id": "api", "type": "openapi", "before": "...", "after": "..." }]
}

Step 2: Branch on execution_action (authorize only)

const { execution_action, decision, patterns, chain_receipt } = response;

// Branch on execution_action (proceed signal); decision explains why.
// CONTINUE_WITH_MONITORING requires a wired monitoring sink (@coderifts/agent-guard).
if (execution_action !== "CONTINUE") {
  throw new Error(`CodeRifts halted: execution_action=${execution_action} (decision=${decision}); patterns=${(patterns||[]).join(",")}`);
}
// Downstream gates must still verify chain_receipt conjunctively (receipt + currently_authorized
// + operation + fingerprint + execution_action). Do not treat CONTINUE alone as permission.
await handToMergeGate(chain_receipt);

Step 3: Handle STOP gracefully

if (execution_action === "STOP") {
  // 1. Log the full authorize response (decision explains why)
  // 2. Notify the team
  // 3. Do not retry as if it were ALLOW — remediate, then re-preflight authorize
  // 4. Check extended.migration_plan when present
}

Integration Examples

LangChain

from langchain.tools import tool
from coderifts import CodeRifts

coderifts = CodeRifts(api_key="cr_live_...")

@tool
def safe_api_call(endpoint: str, payload: dict):
    """Call an API endpoint with CodeRifts governance check."""
    result = coderifts.authorize_change_set(
        context={"operation": "tool_call"},
        artifacts=[{
            "id": endpoint,
            "type": "openapi",
            "before": get_cached_schema(endpoint),
            "after": get_current_schema(endpoint),
        }],
    )
    # Authorize only: branch on execution_action; decision explains why.
    # Analyze would omit execution_action — never treat analyze as permission.
    action, decision = result.execution_action, result.decision
    # CONTINUE_WITH_MONITORING requires @coderifts/agent-guard monitoringSinkWired:true
    # (plus a real monitor callback); this example has no sink — proceed only on CONTINUE.
    if action != "CONTINUE":
        return {"error": f"CodeRifts aborted: execution_action={action!r} (decision={decision})"}
    # A valid signature is not authorization: currently_authorized comes from verify_receipt.
    receipt = getattr(result, "chain_receipt", None)
    authz = coderifts.verify_receipt(
        receipt,
        operation="tool_call",
        environment=getattr(result, "environment", None),
        target_id=getattr(result.decision_result, "artifact_digest", None),
        fingerprint=getattr(result, "verdict_fingerprint", None),
        decision_result=result.decision_result.to_dict(),
    ) if receipt else None
    if getattr(authz, "currently_authorized", None) is not True:
        return {"error": "CodeRifts aborted: receipt not currently_authorized"}

    return call_endpoint(endpoint, payload)

AutoGen

import autogen
from coderifts import CodeRifts

coderifts = CodeRifts(api_key="cr_live_...")

def coderifts_preflight_hook(agent, message):
    # One operation label for both calls: the receipt is scoped to the operation it was
    # authorized for, so the check must name the SAME one the preflight used.
    operation = message.get("operation", "tool_call")
    result = coderifts.authorize_change_set(
        context={"operation": operation},
        artifacts=message["artifacts"],  # [{id, type, before, after}, ...]
    )
    # Authorize: branch on execution_action; decision explains why.
    action = result.execution_action
    # CONTINUE_WITH_MONITORING requires @coderifts/agent-guard monitoringSinkWired:true
    # (plus a real monitor callback); this example has no sink — proceed only on CONTINUE.
    if action != "CONTINUE":
        raise ValueError(f"CodeRifts aborted: execution_action={action!r} (decision={result.decision})")
    # A valid signature is not authorization: currently_authorized comes from verify_receipt.
    receipt = getattr(result, "chain_receipt", None)
    authz = coderifts.verify_receipt(
        receipt,
        operation=operation,
        environment=getattr(result, "environment", None),
        target_id=getattr(result.decision_result, "artifact_digest", None),
        fingerprint=getattr(result, "verdict_fingerprint", None),
        decision_result=result.decision_result.to_dict(),
    ) if receipt else None
    if getattr(authz, "currently_authorized", None) is not True:
        raise ValueError("CodeRifts aborted: receipt not currently_authorized")
    return message

agent = autogen.AssistantAgent(
    name="SafeAgent",
    pre_message_hooks=[coderifts_preflight_hook]
)

Claude / MCP

{
  "mcpServers": {
    "coderifts": {
      "url": "https://app.coderifts.com/mcp",
      "headers": { "x-api-key": "YOUR_API_KEY" }
    }
  }
}

Add the instruction below to your agent's system prompt to have it run preflight_change_set before tool invocations.

Auto-Discovery

Agents can discover CodeRifts governance endpoints automatically:

GET https://coderifts.com/.well-known/coderifts.json

Returns the full manifest including available tools, endpoints, and this spec URL.

Versioning Policy

  • • Current: Decision Spec 2.0. Preflight responses are a mode-discriminated union. Analyze omits authorization vocabulary; authorize requires decision + execution_action.
  • • v1.0 legacy pin: request decision_spec_version: "1.0" for the old shape (soft-default mode; analyze still carries execution fields in the body and headers). Deprecation headers mark the pin. Announced sunset was 2026-09-07T00:00:00Z; the pin was retired at that date and no longer answers (measured 2026-09-18). Do not pin 1.0.
  • • Within a major, additive optional fields may appear without a major bump. Removing or renaming required control fields is a major.
  • • The authorize envelope schema for stored receipts remains under decision-result.v1*; the preflight flat body is preflight-response.v2.

Changelog

Version Date Changes
2.02026-08Mode-required preflight; analyze structurally omits decision/execution_action/safe_for_agent; oneOf schema; opt-in v1.0 pin (announced sunset 2026-09-07; retired at that date, no longer answers as of 2026-09-18)
1.02026-03-18Initial stable release. Legacy pin announced until 2026-09-07; retired at that date, no longer answers as of 2026-09-18

Links

During the public beta every organization gets Team features at $0 with a self-serve key, public and private repositories alike, no separate Team key to request, and each key allows 1,000 requests a month: after that, /preflight authorize returns BETA_LIMIT_REACHED while its analysis keeps working; other API routes return 429.