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

# Policies and Gates: Transition Guards and Conditions

> Define transition guards that block group moves until conditions pass, and agent_responsibility policies that attach advisory messages without blocking.

A policy is a rule attached to a board. Policies are substrate-as-code: they live in the board's JSON file, are versioned with your repo, and are read fresh on every call. Substrate v0.7.0 ships two policy classes — one that blocks writes and one that never does.

| Type                   | Effect                                                |
| ---------------------- | ----------------------------------------------------- |
| `transition_guard`     | Blocks a group move until all conditions pass         |
| `agent_responsibility` | Attaches an advisory message to a write; never blocks |

Every policy carries `id`, `name`, `description`, `priority`, `enabled`, and `version`. Set `enabled: false` to keep a policy as living documentation without enforcing it.

## transition\_guard

A `transition_guard` engages when a task moves from a matching `from_group` to a matching `to_group`. Use `"*"` as a wildcard on either side. When engaged, the move is blocked with `transition_blocked` unless every condition in `require` passes — conditions are evaluated against the task as it would look **after** the move.

```json theme={null}
{
  "from_group": "spec",
  "to_group": "build",
  "require": [
    { "field": "task.plan_approved", "op": "eq", "value": true }
  ],
  "on_failure_message": "Get the plan approved before building."
}
```

`require` is an implicit `all_of`. A `"*" → done` guard is the idiomatic definition-of-done gate — it fires no matter which column a task ships from.

## agent\_responsibility

An `agent_responsibility` policy never blocks. When its `when` conditions match a written task, the policy appears in that write's `policies_fired` list with its `message`. Omit or leave `when` empty and the policy always matches.

```json theme={null}
{
  "when": [
    { "field": "task.group_id", "op": "eq", "value": "build" }
  ],
  "message": "Write tests during development, not after."
}
```

## Conditions

A leaf condition takes a field reference, an operator, and a value:

```json theme={null}
{ "field": "task.tests_passing", "op": "eq", "value": true }
```

Field references resolve literal-first, then against `custom_data`. Use `task.<name>` for custom fields. Built-in field references are `task.group_id`, `task.title`, `task.description`, and `task.parent_id`.

### Operators

| Group     | Operators                                                                                                     |
| --------- | ------------------------------------------------------------------------------------------------------------- |
| Existence | `exists`, `not_exists`, `is_empty`, `not_empty`                                                               |
| Equality  | `eq`, `neq`                                                                                                   |
| Sets      | `in`, `not_in` (use `values`)                                                                                 |
| Numeric   | `gt`, `gte`, `lt`, `lte`                                                                                      |
| String    | `contains`, `not_contains`, `starts_with`, `ends_with`, `matches_regex`, `matches_any_keyword` (use `values`) |
| Array     | `has_any`, `has_all` (use `values`)                                                                           |

<Note>
  Operators never throw. A type mismatch, bad operand, or invalid regex evaluates to "condition not met" rather than crashing a write. String operators coerce scalars but **not** arrays — use `has_any` or `has_all` for `string_list` fields.
</Note>

### Compound conditions

Nest conditions with `all_of`, `any_of`, or `none_of`:

```json theme={null}
{
  "any_of": [
    { "field": "task.tests_passing", "op": "eq", "value": true },
    {
      "all_of": [
        { "field": "task.priority", "op": "eq", "value": "low" },
        { "field": "task.labels", "op": "has_any", "values": ["chore"] }
      ]
    }
  ]
}
```

<Note>
  A misspelled key is a validation error, not a silently-ignored field. Policy definitions are validated strictly at `create_policy` / `update_policy` time and on load.
</Note>

## What a gate actually guarantees

<Warning>
  A `transition_guard` is a real structural rail — it blocks the move and returns `transition_blocked` at write time. But the field it checks is **self-attested**. The agent sets `tests_passing: true` itself; nothing runs your tests.
</Warning>

A gate is a **confession step**, not a control. It records that the claim was made and blocks until it is. An agent that would skip review under pressure can also set the flag under pressure.

The exception is a `human_only` field: an agent's write tools structurally refuse to set it, so a guard requiring it genuinely waits on a person. See [Fields and Schema](/concepts/fields-and-schema#human-only-fields).

Two habits follow from this:

<CardGroup cols={2}>
  <Card title="Prove your rails fire" icon="flask" href="/authoring/validate-gates">
    Run `substrate validate` and exercise your guards before relying on them. Definitions that parse don't always do what you expect at runtime.
  </Card>

  <Card title="Dry-run instead of guessing" icon="play">
    Call `check_transition` before attempting a move. It returns `allowed`, `from_group`, `to_group`, and the blocking policy with its message — no side effects, no version bump.
  </Card>
</CardGroup>

## Reading the result

A successful write returns an `ok: true` envelope with the updated entity state and any `policies_fired`:

```json theme={null}
{
  "ok": true,
  "applied": {
    "entity": "task",
    "id": "…",
    "version": 4,
    "state": { "…": "…" }
  },
  "policies_fired": [
    {
      "policy_id": "…",
      "policy_name": "Tests before review",
      "policy_type": "agent_responsibility",
      "message": "Write tests during development."
    }
  ]
}
```

A blocked write returns an error envelope with code `transition_blocked`. See [Envelopes and Errors](/mcp/envelopes-and-errors) for the full error shape.

<Card title="See it run" icon="clapperboard" href="/concepts/gates-walkthrough">
  Walk through a complete session — creation, blocked moves, gate satisfaction, human approval, and revocation — with real request and response payloads.
</Card>
