Designing Policies
Verdict lets you model access directly from the relationships and state your application already tracks (such as who owns a document, which team a user belongs to, or whether a record is archived) without forcing your product into a single rigid paradigm.
The authorization decision boundary
Section titled “The authorization decision boundary”When your backend calls authorize, Verdict Server checks that allow(actor, action, resource) evaluates to true and no matching deny(actor, action, resource) holds.
For simple access rules, you can write allow rules directly over your domain facts:
declare is_owner(User, Document);
allow(user: User, "read", document: Document) if is_owner(user, document);If no rule proves allow, authorization is denied by default.
Blanket access denial with deny
Section titled “Blanket access denial with deny”Most policies model access entirely through positive allow rules and helper predicates. However, when you need a blanket restriction that overrides any grant (such as suspending an account or freezing a compromised credential), you can write an optional deny rule:
declare is_suspended(User);
deny(user: User, action, resource) if is_suspended(user);A matching deny rule eliminates any matching allow, regardless of which rules or roles proved access. Position deny as an optional tool: keep the bodies of deny rules simple, and rely on composing positive paths through allow and helper predicates for your standard access logic.
See Predicates, rules, and facts for how facts and rules evaluate.
Modeling patterns
Section titled “Modeling patterns”As your application grows, you can combine multiple patterns inside the same .vd policy:
| Pattern | Use it to express | Guide |
|---|---|---|
| Permissions | Direct action grants, a shared has_permission layer, and universal guards | Permissions |
| Roles | Responsibilities scoped to organizations or resources | Roles |
| Relationships | Ownership, organization membership, containment, and sharing | Relationships |
| Hierarchies | Access inherited through nested folders or user groups | Hierarchies & Recursion |
| Attributes | Conditions on actor and resource properties | Attributes |
Most policies start by establishing a permission layer, which gives roles, relationships, and direct grants a single target predicate (has_permission) before forwarding to allow.
Predicate conventions
Section titled “Predicate conventions”While you can name predicates anything in your domain (is_owner, belongs_to), conventional 3-term predicates are useful when you want to query or delete related facts with wildcards:
| Predicate | Wildcard example |
|---|---|
has_permission(actor, action, resource) | has_permission(User:alice, ?action, Document:handbook) |
has_role(actor, role, resource) | has_role(User:alice, ?role, Organization:acme) |
has_relation(resource, relation, related) | has_relation(Document:handbook, ?rel, _) |
has_attribute(entity, key, value) | has_attribute(Document:handbook, ?key, ?val) |
Putting the varying action, role, relation, or key in the middle slot lets your application list all roles a user holds or revoke all of a user’s roles in an organization (has_role(User:alice, _, Organization:acme)) in a single call instead of querying separate predicates like is_viewer and is_editor.
Dedicated predicates like is_owner or parent remain a good choice when they state a relationship more directly.
Next steps
Section titled “Next steps”- Start with Permissions to grant fine-grained actions, build a
has_permissionlayer, and apply resource-scoped guards. - Group permissions into scoped roles in Roles and derive access across resources in Relationships.
- Propagate permissions through nested trees in Hierarchies & Recursion and compare properties in Attributes.
- Verify your policy rules in Testing & Validating Policies.