Skip to content

Negation and missing facts

Positive conditions check that a fact or rule derivation is present. Negated conditions (not) check that a disqualifying relationship or state is absent, such as allowing edits only when a document is not archived.

The not operator succeeds when no matching stored fact or rule derivation makes the inner statement true:

declare is_owner(User, Document);
declare is_archived(Document);
allow(user: User, "read", document: Document) if
is_owner(user, document) and
not is_archived(document);

When checking allow(User:alice, "read", Document:handbook), is_owner(User:alice, Document:handbook) must hold and is_archived(Document:handbook) must have no matching proof in the policy’s facts or rules. Once is_archived(Document:handbook) is recorded, not is_archived(document) fails and this rule no longer grants access.

Verdict does not fill in default false values for missing facts. Compare not is_archived(document) with a rule that checks an explicit boolean attribute:

explicitly_unarchived(document: Document) if
has_attribute(document, "archived", false);

When a newly created document has no archive facts recorded yet, the two checks behave differently:

Recorded facts for Document:handbooknot is_archived(Document:handbook)explicitly_unarchived(Document:handbook)
Neither is_archived nor has_attribute recordedTrue (no archive marker present)False (no "archived", false fact)
has_attribute(Document:handbook, "archived", false) recordedTrueTrue
is_archived(Document:handbook) and has_attribute(..., "archived", true) recordedFalseFalse
  • Use not is_archived(document) when the normal state is the absence of a marker, so your application only writes a fact when a document is archived.
  • Use has_attribute(document, "archived", false) (or document.archived = false) when you want the check to fail unless your application has explicitly recorded false.

Negation tests whether a condition is absent for known values; it cannot generate values for an unbound variable on its own. Bind variables in the rule head or in positive conditions (like is_owner(user, document)) before referencing them inside not.

Any variable introduced only inside a not condition stays local to that negated check and cannot supply bindings to conditions outside it. See the Language Reference and Policy Diagnostics for complete safety rules.

A not condition only constrains the rule in which it appears; it does not override other rules in your policy. If one allow rule checks not is_archived(document) while another allow rule grants access without that check, users matching the second rule can still access archived documents.

To apply a restriction like not is_archived(document) across every grant path for a resource type, attach it to a resource-qualified allow rule as shown in Permissions.

If you instead need an unconditional override that eliminates access across every grant path regardless of which allow rule matched (such as revoking access for a suspended user), Verdict Server supports top-level deny rules. See Designing Policies.