# Set a review policy

Source: https://opvs.ai/docs/factory/review-policy

> Decide whether an AI reviewer, a planner or a person must clear each pull request.

A review policy decides who must clear each pull request a builder opens: an AI reviewer, a planner, or a person. Set it once for the workspace, and override it for a project when that project needs something stricter.

**You need:** to be a workspace admin, and a token in `$OPVS_PAT` for the API calls.

## How a pull request is classified

OPVS checks the changed paths (and, for migrations, the diff) against your policy, in this order. The first match wins:

Order

Rule

Level

`matched_by`

1

No policy set

person

`policy absent`

2

A path matches `levels.person.paths`

person

`person path`

3

`levels.person.destructive_migrations` is on and the change drops, renames or retypes data

person

`destructive migration`

4

A path matches `levels.planner.paths`

planner

`planner path`

5

A path contains a word in `levels.planner.words`

planner

`planner word`

6

`levels.planner.sql_files` is on and a `.sql` file changed

planner

`SQL file`

7

`levels.planner.raw_sql` is on and the added lines contain raw SQL

planner

`raw SQL`

8

Anything else

ai

`default`

With no policy, every pull request needs a person. That is the safe default, not a mistake.

## 1. Write the policy

In the dashboard: **Settings → Review policy** for the workspace, or the project's **Settings → Review** to override it. Path patterns are shell-style (`billing/**`, `*.sql`), and a pattern that starts with `**/` also matches at the top level. `required_checks` names the CI checks that must pass before a merge; the workspace's list and the project's list are combined. Over the API:

```bash
curl -s -X PUT https://api.opvs.ai/api/v1/projects/<project-id>/review-policy \
  -H "X-Brand-ID: $BRAND_ID" -H "Authorization: Bearer $OPVS_PAT" \
  -H 'Content-Type: application/json' \
  -d '{
    "ai_reviewer_mode": "shadow",
    "ai_graduation": {"rule": "record", "agree_streak": 30},
    "levels": {
      "person":  {"paths": ["billing/**", "auth/**"], "destructive_migrations": true},
      "planner": {"words": ["payment"], "sql_files": true, "raw_sql": true}
    },
    "required_checks": ["test"]
  }'
```

For the whole workspace, send the same body to `PUT /api/v1/brands/<workspace-id>/review-policy`.

## 2. Choose how much the AI reviewer may do

`ai_reviewer_mode`

The AI reviewer…

`forbidden` (default)

Does not review

`shadow`

Reviews, but its verdict does not clear anything. A person or planner still decides

`allowed`

Clears the pull requests the policy classifies as `ai`

`ai_graduation` moves a project from `shadow` to `allowed` on its own:

`rule`

Graduates when

`record`

The AI reviewer has agreed with the human reviewer `agree_streak` times in a row (10 to 500, default 30)

`date`

On `on_date` (today or later)

`never`

Never; you switch it by hand

The policy you read back carries `effective_ai_reviewer_mode` and, for `record`, `graduation_progress` (`agreeing_in_a_row` of `needed`).

## 3. Test it before a real pull request

Ask how a set of paths would be classified:

```bash
curl -s -X POST https://api.opvs.ai/api/v1/review-policy/classify \
  -H "X-Brand-ID: $BRAND_ID" -H "Authorization: Bearer $OPVS_PAT" \
  -H 'Content-Type: application/json' \
  -d '{"paths": ["billing/charge.py"], "project_id": "<project-id>"}'
```

The reply has two fields: `level` (`ai`, `planner` or `person`) and `matched_by`, from the table above.

## Errors

Status

When

What to do

`401` `Not authenticated. Please log in.`

No token sent

Send `Authorization: Bearer $OPVS_PAT`

`422` `levels.<level>.paths must be a list of strings`

A level's `paths` or `words` is not a list of strings

Send a JSON array of strings

`422` `levels.<level>.<flag> must be a boolean`

`sql_files`, `raw_sql` or `destructive_migrations` is not true or false

Send `true` or `false`

`422` `ai_graduation.on_date must be today or later`

A `date` rule in the past

Pick today or a later date

`422`

`agree_streak` below 10 or above 500

Use 10 to 500

Related: [How a card moves](/developers/factory/card-flow), [Projects](/developers/factory/projects).
