Skip to content

Combining & Testing Rules

Once you know how single-condition allow rules work (Policy Fundamentals), you can combine conditions inside rule bodies, chain reusable helper predicates, and verify your policy locally with inline tests.

Use and when multiple conditions must hold at the same time. Repeating a variable name across conditions requires every occurrence to match the same value:

declare belongs_to(Document, Organization);
declare has_role(User, string, Organization);
allow(user: User, "read", document: Document) if
belongs_to(document, org: Organization) and
has_role(user, "member", org);

Here, org links the two conditions: the Organization that document belongs to must be the same Organization where user holds the "member" role.

When there are multiple ways to qualify for access, write separate rules with the same head predicate (or combine conditions with or). Each rule provides an independent path for the conclusion to hold.

Rule heads do not have to be allow. You can define helper predicates such as has_permission to collect role and relationship grants before forwarding them to allow:

declare is_owner(User, Document);
declare belongs_to(Document, Organization);
declare has_role(User, string, Organization);
// Owners can read and edit
has_permission(user: User, "read", document: Document) if
is_owner(user, document);
has_permission(user: User, "edit", document: Document) if
is_owner(user, document);
// Organization members can read documents in their organization
has_permission(user: User, "read", document: Document) if
belongs_to(document, org: Organization) and
has_role(user, "member", org);
allow(actor, action, resource) if
has_permission(actor, action, resource);

Use not to require that a disqualifying fact or derivation is absent, and comparison operators (=, !=, <, <=, >, >=) to check term values:

declare is_owner(User, Document);
declare is_archived(Document);
declare clearance_level(User, int);
declare required_clearance(Document, int);
// Owners can edit a document only when it is not archived
allow(user: User, "edit", document: Document) if
is_owner(user, document) and
not is_archived(document);
// Users can read a document when their clearance meets its requirement
allow(user: User, "read", document: Document) if
clearance_level(user, user_level: int) and
required_clearance(document, doc_level: int) and
user_level >= doc_level;

See Negation and missing facts for how not evaluates missing data versus explicit false values, and Attribute access for dot-syntax lookups (user.clearance >= document.required_clearance).

Because a .vd policy is self-contained, you can verify your rules without starting a database or application server. Add test blocks directly to your .vd file with temporary fixture facts and assert / assert not checks:

test "organization members and owners can access unarchived documents" {
belongs_to(Document:handbook, Organization:acme);
has_role(User:alice, "member", Organization:acme);
is_owner(User:bob, Document:handbook);
assert allow(User:alice, "read", Document:handbook);
assert not allow(User:alice, "edit", Document:handbook);
assert allow(User:bob, "edit", Document:handbook);
}
test "archived documents cannot be edited" {
is_owner(User:bob, Document:handbook);
is_archived(Document:handbook);
assert not allow(User:bob, "edit", Document:handbook);
}

Run verdict test from the CLI to execute all test blocks in a policy file:

verdict test policy.vd

Facts inside a test block are isolated to that test and are excluded when you publish the policy.