> ## Documentation Index
> Fetch the complete documentation index at: https://www.osohq.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Polar Rules and Facts

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](/reference/polar/facts-as-data))

## Rule Construction

A Polar rule has three parts:

| Part       | Definition                                                       |
| ---------- | ---------------------------------------------------------------- |
| Predicate  | Rule name. Not required to be unique.                            |
| Parameters | Named, typed variables that may unify with a query.              |
| Expression | *(Optional)* Boolean condition describing when the rule is true. |

Example:

```polar theme={null}
within(lower: Integer, upper: Integer, x: Integer) if lower < x and x < upper;
```

Breakdown:

| Text                                           | Part       |
| ---------------------------------------------- | ---------- |
| `within`                                       | Predicate  |
| `(lower: Integer, upper: Integer, x: Integer)` | Parameters |
| `if lower < x and x < upper`                   | Expression |

## 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**:

```polar theme={null}
#  Fact: Bernie has two parents, Pat and Morgan
parent("Bernie", "Pat");
parent("Bernie", "Morgan");
```

**Rule:**

```polar theme={null}
# Rule: A parent and their children are considered family.
family(a: String, b: String) if
  parent(a, b) or parent(b, a);
```

**Queries:**

| Query                                | Result                                                                          |
| ------------------------------------ | ------------------------------------------------------------------------------- |
| `family String:Bernie String:Pat`    | `family(String:Bernie, String:Pat)`                                             |
| `family String:Bernie String:Morgan` | `family(String:Bernie, String:Morgan)`                                          |
| `family String:Pat String:Morgan`    | no results                                                                      |
| `family String:Bernie _`             | `family(String:Bernie, String:Pat)`<br />`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`:

```polar theme={null}
group matches Group and has_group(user, group)
```

See Operators: matches for details.

## Literal Parameters

You can hardcode values in rules:

```polar theme={null}
cities_to_visit("Phoenix", season: String) if
  is_cold(season);
```

Is equivalent to:

```polar theme={null}
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:

```polar theme={null}
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`:

```polar theme={null}
# 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:

```polar theme={null}
user(first, last) if person("George", last);
```

Polar warning:

```text theme={null}
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 `_`:

```polar theme={null}
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:

```polar theme={null}
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`](/reference/polar/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](/reference/polar/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.
