Skip to content

Attribute access

Verdict provides dot syntax (user.age > 25) as a concise shorthand for reading entity attributes stored as has_attribute facts inside policy rules.

The standard convention for storing entity properties is the three-term has_attribute(entity, key, value) predicate:

declare has_attribute(User, "age", int);
has_attribute(User:alice, "age", 30);

Using has_attribute lets multiple property keys share one predicate and enables dot-syntax lookups. Dedicated predicates (such as clearance_level(user, level)) are also valid when they read more naturally in your domain; see Attributes.

In a rule body, variable.attribute is shorthand for reading a has_attribute(variable, "attribute", value) fact inside a comparison or predicate argument:

// Dot syntax shorthand
user.age > 25
// Equivalent explicit has_attribute lookup
has_attribute(user, "age", age: int) and age > 25

Dot syntax is valid in rule-body comparisons and predicate arguments:

// Valid: single variable before '.', used in a comparison or argument
allow(user: User, "edit", doc: Document) if
doc.archived = false and
user.clearance >= doc.required_clearance;
// Invalid syntax:
// - User:alice.clearance >= 2 (receiver must be a variable, not an identity)
// - user.profile.email = "a@b.com" (cannot chain multiple dots)
// - if doc.archived; (boolean attributes must be compared explicitly)

If no matching has_attribute fact exists for an entity and key, the lookup fails. Missing attributes never default to 0, "", or false.

Multiple attribute values and independent lookups

Section titled “Multiple attribute values and independent lookups”

Each variable.attribute occurrence performs an independent has_attribute lookup. If an entity has multiple values stored under the same key, separate occurrences can match different values:

independent(user: User) if
user.age > 25 and
user.age <= 40;

If User:bob has two stored ages (20 and 50), user.age > 25 matches 50 and user.age <= 40 matches 20, so independent(User:bob) succeeds even though neither age falls inside (25, 40].

When multiple conditions must check the same attribute value, bind the lookup to a variable once and compare that variable:

within_range(user: User) if
age = user.age and
age > 25 and
age <= 40;
// Equivalent explicit has_attribute binding
explicit_range(user: User) if
has_attribute(user, "age", age: int) and
age > 25 and
age <= 40;
Stored "age" valuesindependent (user.age > 25 and user.age <= 40)within_range (age = user.age ...)
30TrueTrue
20, 50TrueFalse
NoneFalseFalse

When an attribute should only ever hold one current value in your fact store, replace previous values atomically on write using batchFacts.