Access policies

A deterministic, side-effect-free rules engine. Rules are sorted by descending priority; the first matching rule wins; the defaultEffect applies when nothing matches. No I/O of any kind.

Defining a policy

ts
import { definePolicy } from "@corri/sdk/server";

const policy = definePolicy({
  id: "demo-publisher",
  version: "1",
  defaultEffect: "deny", // when no rule matches
  rules: [
    { id: "require-verified-agent", priority: 100,
      when: { principalKinds: ["unknown"] },
      effect: "challenge",
      reason: "A signed agent identity is required" },

    { id: "deny-training", priority: 90,
      when: { purposes: ["training"] },
      effect: "deny",
      reason: "Training is not permitted" },

    { id: "charge-summarization", priority: 80,
      when: { principalKinds: ["verified_agent"], actions: ["read"],
              purposes: ["summarization"] },
      effect: "charge",
      entitlement: { type: "any", providers: ["api-key", "x402"] },
      payment: { amount: "0.01", currency: "USD" },
      obligations: [
        { type: "attribution", required: true },
        { type: "prohibit-republication" }
      ] }
  ]
});

definePolicy validates the definition and returns an evaluatable Policy; a malformed definition throws PolicyValidationError with a list of issues. Because the returned object carries a validation marker, raw unvalidated definitions cannot be passed where a Policy is required.

Effects

EffectHTTPBehavior
allow200Handler runs immediately; a receipt is issued.
challenge401The agent is told how to authenticate (Web Bot Auth challenge body).
charge402 → 200Entitlement providers are consulted. Satisfied → handler runs. Missing → 402 with payment requirements.
deny403Hard stop with the rule's reason. Payment cannot override a deny.

Matching

Every field present in a rule's when clause must match; absent fields match anything.

FieldMatchesExample
principalKindsverified_agent, api_client, unknown["verified_agent"]
principalIdsSpecific agent identities["demo-agent.corri.dev"]
operatorIdsThe organization operating the agent["operator:acme"]
actionsDeclared actions["read"]
purposesDeclared purposes["training", "summarization"]
resourcesResource ids, with a trailing * wildcard segment["report:*"] matches report:grid-brief
resourceAttributesResource attribute equality{ tier: "premium" }

Charge rules

A charge rule must say how it can be satisfied and what it costs:

  • entitlement, { type: "any" | "all", providers: [...] }. With "any", one satisfied provider is enough; with "all", every listed provider must be satisfied.
  • payment, { amount: "0.01", currency: "USD" }, a decimal string and an ISO 4217 / stablecoin code.
  • obligations, terms attached to the grant (e.g. attribution). They travel into the 402 body, the receipt, and the RSL document.

Evaluating directly

The engine is pure, useful for tests, previews, and admin UIs:

ts
import { evaluatePolicy, matchesResourcePattern } from "@corri/sdk";

const decision = evaluatePolicy(definition, {
  principal: { id: "demo-agent.corri.dev", kind: "verified_agent", trust: "verified" },
  resource: { id: "report:grid-brief", version: "2026-07-21" },
  action: "read",
  purpose: "summarization"
});
// decision: { effect: "charge", policyId, policyVersion, ruleId: "charge-summarization",
//             entitlementRequirement, payment, obligations }

matchesResourcePattern("report:*", "report:grid-brief"); // true
Determinism matters: the same request against the same policy always yields the same decision, which is what makes decisions auditable, receipts meaningful, and the RSL export an honest rendering of your actual behavior.