# Policy drafts

Describe an access rule in plain language and get a Cedar draft, checked against the schema, your policy tests and live policies, active only once approved.

Writing Cedar by hand is error-prone, and a wrong policy can lock owners out of an agent or quietly grant too much. **Policy drafts** let an administrator describe the rule in plain language, for example "contractors may not promote any agent outside working hours". Liyagent drafts one Cedar statement and proves it before anything is enforced.

Open **Governance** › **Policy drafts** (`/policydrafts`).

## How a draft is checked

1. **Draft.** Choose **Draft policy**, give the policy a name and describe the rule (up to 2,000 characters). One call goes through the model gateway, which is told the schema's entity types and actions and the policies already stored. To adjust the result, edit the Cedar and draft again: an edited draft is evaluated as written, with no model call.
2. **Validate.** The draft is validated strictly against the Cedar schema. Any schema warning, such as an unknown action or attribute, makes it invalid.
3. **Test.** The draft is run against your policy test suite (below), with the policy set as the draft would leave it.
4. **Examples.** It is run against example requests generated from this instance. The page shows the allow and deny cases the draft matches.
5. **Diff.** It is compared with the policies in force: what each principal gains and loses, the findings, and whether it locks the owners out of an agent.

A draft is stored with that evidence. Nothing is enforced.

## Approve and activate

Nothing is active until someone chooses **Approve and activate** (`POST /api/policy-drafts/{id}/approve`). Approval runs every check again against the policy set as it is at that moment, and is refused when:

- the policy changed since the draft was made (draft it again),
- the draft fails validation or a test, or
- it would lock the owners out of an agent.

An approved draft is saved through the ordinary policy write, as the approver, with every gate that write has. Choose **Reject** to close a draft without activating it. Both take an optional `reason` (up to 500 characters).

### Four eyes

`TICKETIQ_POLICY_DRAFT_FOUR_EYES` (default `true`) separates drafting from activating. It is adaptive:

| Situation | Who may activate |
| --- | --- |
| Setting on, two or more accounts can approve policy changes in the tenant | Someone other than the drafter |
| Setting on, exactly one account can approve | That admin may activate their own draft with an explicit reason. It is recorded on the draft and audited as `four_eyes_unavailable` |
| Setting off | The drafter may approve their own draft |
| Agent change control is on (any tier) | Someone other than the drafter, whatever the setting says |

An account counts as an approver when it is enabled and holds `policy.create` or `policy.update` in the tenant. Each draft in the API carries `four_eyes` and `four_eyes_mode` (`required`, `unavailable` or `off`) so you can see which rule applies.

A draft is never activated without an explicit approval in any mode.

## Revert

**Revert** (`POST /api/policy-drafts/{id}/revert`) restores the policy an active draft replaced, or removes the policy it created. It is refused if the policy has changed again since the draft was activated.

Draft statuses are `draft`, `active`, `rejected` and `reverted`.

## Policy tests

The **Policy tests** suite is a list of requests and the answer each must get. Every draft is run against it, and approval re-runs it.

| Field | Meaning |
| --- | --- |
| `name` | A label for the test |
| `principal_type`, `principal_id` | Who is asking, for example `User` and `alice` |
| `action` | The Cedar action |
| `resource_type`, `resource_id` | What is acted on |
| `context` | Optional request context |
| `expect` | `allow` or `deny` |

`GET /api/policy-tests` runs each test against the policy set as it is now and returns its result.

## Routes and permissions

| Task | Endpoint | Permission |
| --- | --- | --- |
| List drafts (optional `?status=`) | `GET /api/policy-drafts` | `policy.list` |
| Read a draft | `GET /api/policy-drafts/{id}` | `policy.list` |
| Create a draft | `POST /api/policy-drafts` | `policy.create` or `policy.update` |
| Approve and activate | `POST /api/policy-drafts/{id}/approve` | `policy.create` or `policy.update` |
| Reject | `POST /api/policy-drafts/{id}/reject` | `policy.create` or `policy.update` |
| Revert | `POST /api/policy-drafts/{id}/revert` | `policy.update` |
| List tests with results | `GET /api/policy-tests` | `policy.list` |
| Add a test | `POST /api/policy-tests` | `policy.create` or `policy.update` |
| Delete a test | `DELETE /api/policy-tests/{id}` | `policy.update` |

Activating also needs the permission the policy write itself asks for (create for a new policy, update for an existing one).

## Audit

Each step is in the [audit log](/docs/monitor/audit-log): `policy.draft.create`, `policy.draft.activate`, `policy.draft.reject`, `policy.draft.revert`, `policy.draft.refused`, `policy.save`, `policy.test.create` and `policy.test.delete`, plus `four_eyes_unavailable` when a sole admin activates their own draft.

Drafts and tests are stored per tenant, under row-level security.

## Next steps

- [Access](/docs/govern/access)
- [Agent permissions](/docs/govern/permissions)
