Skip to main content
Polar policies encode domain logic as rules—relationships that let Polar infer new facts from existing ones. A fact is a rule with no conditional clause (if), meaning it is always true when its parameters match. When evaluating rules, Polar uses:
  • Other rules in your policy
  • Facts in the policy or stored externally (see Facts as data)

Rule construction

A Polar rule has three parts:
PartDefinition
PredicateRule name. Not required to be unique.
ParametersNamed, typed variables that may unify with a query.
Expression(Optional) Boolean condition describing when the rule is true.
Example:
within(lower: Integer, upper: Integer, x: Integer) if lower < x and x < upper;
Breakdown:
TextPart
withinPredicate
(lower: Integer, upper: Integer, x: Integer)Parameters
if lower < x and x < upperExpression

Rule evaluation

Polar evaluates a rule when:
  • Another rule calls it
  • A query directly references its predicate
It searches for values that make the expression true using your policy rules and fact data.

Example

Facts:
#  Fact: Bernie has two parents, Pat and Morgan
parent("Bernie", "Pat");
parent("Bernie", "Morgan");
Rule:
# Rule: A parent and their children are considered family.
family(a: String, b: String) if
  parent(a, b) or parent(b, a);
Queries:
QueryResult
family String:Bernie String:Patfamily(String:Bernie, String:Pat)
family String:Bernie String:Morganfamily(String:Bernie, String:Morgan)
family String:Pat String:Morganno results
family String:Bernie _family(String:Bernie, String:Pat)
family(String:Bernie, String:Morgan)
family String:K _no results

Typing variables in rules

When joining rules with new variables, explicitly type them with matches:
group matches Group and has_group(user, group)
See Operators: matches for details.

Literal parameters

You can hardcode values in rules:
cities_to_visit("Phoenix", season: String) if
  is_cold(season);
Is equivalent to:
cities_to_visit(city: String, season: String) if
  city = "Phoenix" and is_cold(season);

Facts: rules without expressions

Facts are rules without an if clause. Any matching parameters make them true. When writing facts, you might want to consider whether the fact is best served being stored in your policy, or whether you should store it as fact data.

Facts in policies

  • Stored in the policy file
  • Require policy re-deploy to change
  • Best for long-lived, static data

Facts as data

  • Stored outside the policy via Oso APIs/clients
  • Can be updated without redeploying
  • Suitable for dynamic or frequently changing data

Default & custom allow rules

If your policy has no allow rule, Polar injects:
allow(actor, action, resource) if has_permission(actor, action, resource);
# For global permissions
allow(actor, action) if has_permission(actor, action);
We introduce this default rule because all authorization APIs (e.g. authorize, list) query allow. You can override it with your own allow rule to apply global logic (e.g., impersonation).

Deny logic

Polar supports deny logic, sometimes referred to as a deny rule or deny override approach. There is no explicit deny keyword, use not inside allow:
# deny all permissions to banned users
allow(actor: Actor, action: String, resource: Resource) if
  # The actor has permission to perform the requested action on the resource ...
  has_permission(actor, action, resource) and
  # ... and the user isn't banned
  not is_banned(actor);
Any matching is_banned fact blocks access, even if other rules allow it.

Singleton variables

A singleton variable is one that appears only once in a rule—it can match any value, and often indicates a mistake. Example:
user(first, last) if person("George", last);
Polar warning:
Singleton variable first is unused or undefined
001: user(first, last) if person("George", last);
          ^
Here, first is unused because the rule calls person("George", last) instead of person(first, last). Singletons act like wildcards: they unify with any value if the rest of the rule matches. To keep an unused parameter, prefix it with _:
user(_first, last) if person("George", last);
The _ prefix explicitly marks it as intentionally unused. A variable named just _ is an anonymous variable: always a singleton, never triggers warnings, and is unique each time it appears:
user(_, _) if person("George", _);

Polymorphism

Polar types support polymorphism. For example, Actor is a subtype of Resource, so any Resource can also be an Actor. See extends for details.

Fact limits

Oso Cloud is designed to scale with your data. There’s no fixed limit to the number of facts you can store in Oso Cloud, and the number of context facts you can send along with a request is only limited by a 10 MiB request payload limit.

Stored facts

  • No fixed limit on the number of facts per environment.
  • Scaling is managed automatically by Oso Cloud, you do not need to provision or tune for larger data volumes.

Context facts

  • When sending context facts in a request, the only limit is the total request payload size (10 MiB).
  • The number of facts you can send depends on their individual size.

Fact argument length limit

  • Each argument in a fact must be ≤ 384 bytes.
  • See built-in types for more details.
If you plan to store or query billions of facts, contact the Oso team for guidance on maintaining performance and reliability.