Skip to content

Testing & Validating Policies

Run verdict validate to check policy syntax and lint diagnostics, and verdict test to verify access boundaries before publishing. Both commands run directly against your .vd file without needing a running server or live database.

Place test "name" { ... } blocks directly inside a .vd file to set up temporary fixture facts and verify decisions with assert and assert not:

declare has_role(User, string, Organization);
declare has_relation(Document, string, Organization);
allow(user: User, "read", document: Document) if
has_relation(document, "organization", org: Organization) and
has_role(user, "member", org);
test "organization members can read organization documents" {
has_relation(Document:handbook, "organization", Organization:acme);
has_role(User:alice, "member", Organization:acme);
assert allow(User:alice, "read", Document:handbook);
}

Each test block runs in isolation with its own fixture facts. When you publish a policy to Verdict Server, test blocks are excluded from the compiled rules and never enter your live fact store.

verdict test policy.vd

A reliable test suite checks both positive grants and the boundaries where access must be denied:

  • Action boundaries: A user granted "read" is denied "edit".
  • Unprivileged actors: A user with no matching role or share is denied access.
  • Cross-tenant isolation: A member of Organization:acme is denied access to Organization:globex resources.
  • Resource restrictions: Archiving a document blocks "edit" even when organization membership grants edit permission.

Suppose your policy derives "read" and "edit" permissions from organization membership, accepts direct has_permission shares, and blocks edits on archived documents (Permissions):

declare has_role(User, string, Organization);
declare has_relation(Document, string, Organization);
declare has_permission(User, string, Document);
declare is_archived(Document);
has_permission(user: User, "read", document: Document) if
has_relation(document, "organization", org: Organization) and
has_role(user, "member", org);
has_permission(user: User, "edit", document: Document) if
has_relation(document, "organization", org: Organization) and
has_role(user, "member", org);
allow(user: User, "read", document: Document) if
has_permission(user, "read", document);
allow(user: User, "edit", document: Document) if
has_permission(user, "edit", document) and
not is_archived(document);

You can test each boundary in focused test blocks:

test "enforces role, action, and tenant boundaries" {
has_relation(Document:handbook, "organization", Organization:acme);
has_relation(Document:roadmap, "organization", Organization:globex);
has_role(User:alice, "member", Organization:acme);
has_permission(User:bob, "read", Document:handbook);
// Positive grants
assert allow(User:alice, "edit", Document:handbook);
assert allow(User:bob, "read", Document:handbook);
// Action boundary: read-only collaborator cannot edit
assert not allow(User:bob, "edit", Document:handbook);
// Cross-tenant boundary: Acme member cannot read or edit Globex document
assert not allow(User:alice, "read", Document:roadmap);
assert not allow(User:alice, "edit", Document:roadmap);
}
test "archived documents reject edits" {
has_relation(Document:handbook, "organization", Organization:acme);
has_role(User:alice, "member", Organization:acme);
is_archived(Document:handbook);
assert allow(User:alice, "read", Document:handbook);
assert not allow(User:alice, "edit", Document:handbook);
}

assert and assert not can test any predicate in your policy, including intermediate helpers like has_permission(User:alice, "edit", Document:handbook).

Run verdict validate to catch syntax errors and lint warnings such as typos in predicate names (undefined-predicate) or accidental arity mismatches (mixed-arity):

verdict validate policy.vd

Static validation checks that your policy is well-formed, while verdict test verifies that your rules enforce the right access boundaries. See Policy Diagnostics for every diagnostic code and remedy.

By default, verdict validate exits with code 1 on warnings as well as errors. To print warnings while exiting 0 when no errors are present, pass --allow-warnings:

verdict validate --allow-warnings policy.vd

Pass --skip-warnings to suppress warning output entirely. See validation settings and the validate CLI reference for per-file directives and flags.

Run formatting (verdict fmt), validation, and tests together in CI so every policy change is checked before merge:

verdict fmt --check policy.vd
verdict validate policy.vd
verdict test policy.vd

Each command exits 0 on success and 1 on failure. Pass --json to verdict validate and verdict test for structured machine output.