> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentchain.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Policy Model

> Understand how AgentChain v2 policies are defined, versioned, and hashed.

## What is a policy?

A policy is a gateway-canonical set of rules that the guard engine evaluates against an incoming transaction intent. It binds to a specific **`policyId`** and **`chainId`** — no single policy spans multiple chains.

Every mutation of the rules set creates a new **immutable snapshot** (incremented `version`). The `policyHash` is a deterministic `keccak256(JCS(body))` fingerprint of the policy body at that version.

## Policy shape

```typescript theme={null}
type GuardPolicyRulesV2 = {
  allowedTargets: string[];           // contract addresses (lowercased)
  allowedSelectors: string[];         // 4-byte function selectors
  maxValueWei: string;                // max ETH value per tx (decimal string)
  maxSlippageBps: number;             // max slippage (basis points)
  dailyCapWei: string;                // rolling 24h aggregate cap
  approvalThresholdWei: string;       // value above which → approval_required
};

type GuardPolicyState = 'active' | 'frozen' | 'revoked';
```

All string values are decimal-encoded (not hex) for JSON interoperability. `allowedTargets` are stored and compared lowercased.

## Policy hash

The `policyHash` is computed by:

1. Strip ephemeral fields (`owner`, `createdAt`, `updatedAt`, `metadata`)
2. Lowercase all entries in `allowedTargets`
3. Serialize with **JCS (RFC 8785)** — canonical JSON
4. Hash with `keccak256(utf8(jcs_string))`

You can compute it locally (for offline verification) or server-side:

```typescript theme={null}
// Local (no network) — @agentchain/core
const { policyHash, canonical } = ac.policies.hash(policyBody);

// Remote (gateway confirmation)
const { policyHash } = await ac.policies.hashRemote(policyBody);
```

Both must return the same hash. A mismatch indicates a bug.

## Guard rules engine

| Rule               | Check                                                          | Deny reason code                   |
| ------------------ | -------------------------------------------------------------- | ---------------------------------- |
| Target allowlist   | `tx.to ∈ allowedTargets`                                       | `PROTOCOL_NOT_ALLOWED`             |
| Selector allowlist | `tx.data[0:4] ∈ allowedSelectors`                              | `SELECTOR_NOT_ALLOWED`             |
| Max value          | `tx.value ≤ maxValueWei`                                       | `VALUE_EXCEEDED`                   |
| Slippage           | **Not enforced in v0.2** — stored and hashed but not evaluated | *(deferred)*                       |
| Daily cap          | rolling 24h aggregate ≤ `dailyCapWei` (see note below)         | `DAILY_CAP_EXCEEDED`               |
| Simulation         | `eth_call` succeeds (when `simulate: true`)                    | `SIMULATION_REVERT`                |
| Approval threshold | `tx.value > approvalThresholdWei`                              | → `approval_required` (not denied) |

Evaluation order: target → selector → value → daily cap → simulation → threshold.

> **`maxSlippageBps` is not enforced in v0.2.** The field is stored in the policy body and
> included in the `policyHash`, but the guard engine does not evaluate it. Pilots setting
> `maxSlippageBps` should treat it as a label for future enforcement (planned v0.3).
> Transactions will not be denied based on slippage in the current release.

> **`dailyCapWei` uses a rolling 24-hour window, not a calendar day.**
> The cap counts executed + anchored transactions in the last 86,400 seconds from the current
> timestamp — not from midnight UTC. Compliance teams expecting a midnight reset should note this
> distinction. Calendar-day reset is planned as an opt-in flag in v0.3.

> **`from` address is not constrained in v0.2.**
> Any address can submit a guard request for your policy. The policy rules constrain what the
> transaction can **do** (target, selector, value) — not which agent can **request** the guard.
> Per-caller allowlists (`allowedCallers`) are a v0.3 roadmap item.

## CRUD

```typescript theme={null}
// Create
const policy = await ac.policies.create({
  policyId: 'pol_swap_safe',
  chainId: 84532,
  rules: { ... },
});

// Read
const current = await ac.policies.get('pol_swap_safe', 84532);

// Update (creates new snapshot version)
const updated = await ac.policies.update('pol_swap_safe', 84532, {
  rules: { ...current.rules, maxValueWei: '2000000000000000000' },
});

// Version history
const history = await ac.policies.listVersions('pol_swap_safe', 84532);
```

## Policy State Machine

Every policy has a `state` field that follows a strict one-way progression:

```mermaid theme={null}
stateDiagram-v2
    [*] --> active : create()
    active --> frozen : freeze()
    active --> revoked : revoke()
    frozen --> active : unfreeze()
    frozen --> revoked : revoke()
    revoked --> [*]
```

| State     | Guard requests                | Existing receipts | Reversible?        |
| --------- | ----------------------------- | ----------------- | ------------------ |
| `active`  | ✅ Accepted                    | ✅ Valid           | —                  |
| `frozen`  | ❌ Rejected (`POLICY_FROZEN`)  | ✅ Valid           | Yes — `unfreeze()` |
| `revoked` | ❌ Rejected (`POLICY_REVOKED`) | ❌ Invalidated     | No — permanent     |

<Warning>
  **`revoked` is irreversible.** Once a policy is revoked, no new guard requests are accepted and `verifyRemote()` returns `valid: false` for all receipts issued under it. Use `frozen` for temporary suspension.
</Warning>

```typescript theme={null}
// Freeze (pausable)
await ac.policies.freeze('pol_swap_safe', 84532);
await ac.policies.unfreeze('pol_swap_safe', 84532);

// Revoke (permanent)
await ac.policies.revoke('pol_swap_safe', 84532);
```

***

## policySource field in receipts

| Value                | Meaning                                                                                            |
| -------------------- | -------------------------------------------------------------------------------------------------- |
| `"gateway"`          | Policy resolved from the gateway database (default)                                                |
| `"anchored"`         | Policy hash is anchored on-chain via `PolicyAnchor` (`0xe69BCa52D31Ea05034252b5A4034F045A401DDAC`) |
| `"onchain_registry"` | Policy resolved from `AgentPolicy.sol` (future)                                                    |

## Chain binding

A policy binds to exactly one `chainId`. If a guard request arrives with a different `chainId` than the policy's, the gateway returns **HTTP 400 `CHAIN_MISMATCH`**.

***

## PolicyAnchor — Security Notes

The `PolicyAnchor` contract is **permissionless**: any address can anchor any `policyId` string for any
operator address. The first caller wins the `policyKey = keccak256(operatorAddress, policyId, chainId)`.

> **Front-running mitigation**: The AgentChain gateway anchors your policy key immediately on creation
> (best-effort, async). If the anchor call is delayed, a third party could theoretically claim your
> `policyKey` before the gateway does. This is a testnet-only risk — the gateway has SLA anchoring
> within 30 seconds of policy creation. For mainnet, EIP-712 signed anchor claims are planned (v0.3).

Anchoring does NOT affect guard enforcement — the gateway uses its database as the source of truth.
`PolicyAnchor` is an **audit log**, not a policy registry.
