Skip to content

Variables and matching

Variables let a single rule apply across any user or resource in your domain. In .vd, a variable is a logical placeholder: when the same variable name appears in multiple places in a rule, every occurrence must match the exact same value.

Variables can appear in both rule definitions and evaluation goals:

allow(user: User, "read", document: Document) if
same_organization(user, document);
  • Concrete check (allow(User:alice, "read", Document:handbook)): Supplies concrete values for every position. Verdict binds user to User:alice and document to Document:handbook, then evaluates same_organization(User:alice, Document:handbook).
  • Open query (allow(User:alice, "read", doc: Document)): Leaves one or more positions as variables. Verdict finds every Document identity that satisfies the rule for User:alice and returns those bindings.

If a variable appears in a rule head without appearing in the body, it acts as a wildcard for concrete checks (and triggers an unconstrained-head-variable warning by default):

// Grants admins any action on any resource during a concrete check,
// but leaves "action" and "resource" unconstrained for open queries
allow(actor: User, action, resource) if
is_admin(actor);

Rules often need to inspect an intermediate entity that was not passed in the check, such as the organization a document belongs to. Repeating a variable across conditions joins those facts on the same entity:

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

Because organization appears in both conditions, belongs_to and member must match the same Organization identity. If the two conditions used different variable names (doc_org and user_org), the rule would succeed whenever the document belonged to some organization and the user belonged to any organization.

Repeating a variable name is the most concise way to require two positions to match. You can also bind or compare values explicitly with =:

is_read(action: string) if
action = "read";
allow(user: User, action: string, document: Document) if
same_organization(user, document) and
is_read(action);
  • Equality (=): Unifies its two sides. If both sides already have values, = checks that they match; if one side is an unbound variable, = binds it to the other side’s value.
  • Inequality and ordering (!=, <, <=, >, >=): Filter values that have already been bound by the check, a positive fact, or =. They cannot generate a value for an unbound variable. See the Language Reference for complete operator rules.

An unqualified variable like resource can match any identity or scalar value. Adding a : Type qualifier narrows what the variable can match:

SyntaxMatches
User:aliceConcrete identity of type User with ID "alice"
user: UserVariable user restricted to User identities
action: stringVariable action restricted to scalar string values

You only need to qualify a variable once in a rule (usually in the head or where it first appears in the body); that constraint applies to every use of the variable in the rule. See Values and identities for how typed identities and scalar values differ.