The tests: block
The tests: block -- asserting policy behavior before you ship it.
tests: is how you assert "sensitive tool defers," "unknown tool denies," and similar
invariants directly in security.yaml, so a policy edit that silently breaks one is caught
before it ships — not discovered live.
Shape
tests:
- name: calc is permitted
action: calc
expect: permit
- name: stripe/refund defers (sensitive tool)
action: stripe/refund
expect: defer
- name: stripe/payouts is denied (forbidden)
action: stripe/payouts
expect: deny
- name: unknown_tool is denied (default-deny)
action: unknown_tool
expect: deny| Field | Required | Notes |
|---|---|---|
name | yes | unique within the document |
principal | no | defaults to the document's agent: |
action | yes | the resource under test |
args | no | scalar args, projected into Cedar context |
context | no | extra context fields (e.g. phase) |
expect | yes | exactly one of permit, defer, deny |
expect_reason | no | expected ReasonCode string; unset = not checked |
expect_type | no | one of ALLOW, DENY, MODIFY, STEP_UP, DEFER |
expect is a coarse 3-value check. expect_type is a finer, 5-value projection of the same
outcome, never a replacement — pairing expect: permit with expect_type: MODIFY asserts "this
permits, and specifically via a modify rule." A review rule's test correctly expects defer
(coarse) or STEP_UP (fine) — both are correct, at different resolution.
Errors: security.yaml: tests[0].name is required, security.yaml: tests: duplicate test name "x", security.yaml: tests[0].action is required, security.yaml: tests[0].expect "x" must be one of permit, defer, deny, security.yaml: tests[0].expect_type "x" must be one of ALLOW, DENY, MODIFY, STEP_UP, DEFER.
What tests: evaluates against
raxit test runs each case against the pure Cedar policy decision — not the full stateful
governor pipeline. Warrant upgrades, session taint, budgets, and other runtime governors are
excluded by design: a review rule's test correctly expects defer regardless of whether a
warrant would upgrade it to permit at runtime.
raxit test output
$ raxit test
ok 1 - calc is permitted
ok 2 - unknown_tool is denied (default-deny)
2/2 passedA failing case:
$ raxit test
FAIL 1 - calc should be denied but rule says allow (intentional failure): got "permit", want "deny"
0/1 passed, 1 failed: calc should be denied but rule says allow (intentional failure)
raxit test: 1 test case(s) failedExit code 1 on any failure — this is what makes tests: usable as a CI gate, not just a
manual sanity check.
apply runs this gate — but only apply
raxit apply's pipeline is validate → run tests: → push to the daemon. A policy that
fails its own tests: block is never applied:
$ raxit apply
FAIL 1 - calc should be denied but rule says allow (intentional failure): got "permit", want "deny"
0/1 passed, 1 failed: calc should be denied but rule says allow (intentional failure)
apply refused (tests): raxit test: 1 test case(s) failed
(no changes made; the last-applied policy is still enforced)The tests: gate is enforced client-side, inside raxit apply. A bare authenticated
POST /v1/reload against a running daemon (bypassing apply) loads and validates Cedar syntax,
but does not run tests: — it's possible to activate a policy directly against the daemon
that would have failed its own declared tests. Always ship policy changes through raxit apply,
not a raw reload call.

