Skip to content

Combining conditions

Policies combine individual conditions in two ways: requiring several conditions to hold at the same time (and), and offering independent reasons to grant access (or or multiple rules for the same predicate).

The and operator requires every combined condition to hold at the same time, using compatible values for any shared variables:

same_organization(user: User, document: Document) if
belongs_to(document, organization: Organization) and
member(user, organization);

Both belongs_to and member must match, and the repeated organization variable requires both facts to match the same Organization identity. See Variables and matching for how shared variables join facts.

Offering alternative paths with or and multiple rules

Section titled “Offering alternative paths with or and multiple rules”

When your application has more than one way to qualify for access, combine those alternatives inside a single rule with or or write separate rules for the same predicate:

// Using "or" inside one rule
can_read(user: User, document: Document) if
is_owner(user, document) or
same_organization(user, document);
// Equivalent: separate rules with the same head predicate and arity
can_read_split(user: User, document: Document) if
is_owner(user, document);
can_read_split(user: User, document: Document) if
same_organization(user, document);

Both forms mean the same thing. Unlike an imperative if / else if chain, rules in Verdict are additive alternatives: any matching rule is enough to derive the conclusion. Use or for compact choices inside one rule, and separate rules when each path has its own set of conditions.

Grouping alternatives within shared requirements

Section titled “Grouping alternatives within shared requirements”

In Verdict, and binds more tightly than or. When combining an or choice with shared and requirements in the same rule, wrap the or branch in parentheses:

allow(user: User, action: string, document: Document) if
(action = "read" or action = "download") and
can_read(user, document) and
is_published(document);

Without parentheses around action = "read" or action = "download", can_read and is_published would attach only to action = "download", leaving action = "read" unrestricted. See the Language Reference for operator precedence.

Extracting helper predicates like same_organization and can_read keeps top-level allow rules readable and lets you reuse the same logic across multiple actions. Permissions shows how policies use this technique to build a shared has_permission(actor, action, resource) layer.

Applying a shared restriction to every intended path

Section titled “Applying a shared restriction to every intended path”

Because rules for the same predicate are additive, a shared requirement only governs the grant paths that pass through it:

can_read(user: User, document: Document) if
is_owner(user, document) or
same_organization(user, document);
allow(user: User, action: string, document: Document) if
(action = "read" or action = "download") and
can_read(user, document) and
is_published(document);

Because both owners and organization members flow through can_read into a single allow rule that requires is_published(document), neither path can read or download an unpublished document. If you also left an unguarded allow(user, "read", document) if is_owner(user, document); rule in the policy, owners would bypass the is_published check.