Skip to main content
Polar’s type system offers subtype polymorphism through its extends feature. This lets you write rules that apply to a general type and automatically apply to all of its subtypes.

Actor and Resource types

Every actor or resource declaration in Polar implicitly extends the abstract Actor or Resource types. This means that any rule that references Actor can also accept types declared as actor. Consider this policy snippet:
actor User {};
resource Issue {};
This creates the following relationships:
SupertypeSubtype
ActorUser
ResourceIssue
When you use shorthand rules, Polar rewrites them into generic rules that rely on these supertypes. For example, a shorthand rule may expand into:
has_permission(actor: Actor, action: String, resource: Resource)...
You can then query this rule with concrete types:
oso-cloud query has_permission User:alice read Issue:123
This works because User is a subtype of Actor, and Issue is a subtype of Resource. Polar’s type system ensures that subtype values can be used wherever their supertypes appear.

Type inheritance with extends

Actor extends Resource

The built-in Actor type extends Resource. This means any parameter expecting a Resource value will also accept an Actor. As a result, you can define roles, permissions, and relations over actors using actor resource blocks. While uncommon, this is useful when modeling authorization logic over user-like entities.

Extending custom types

Use the extends keyword to define custom inheritance relationships:
(actor | resource) Subtype extends Supertype {}
Extending types enables two features:
  • Rule polymorphism: rules written for a supertype apply to all its subtypes.
  • Inheritance: subtypes inherit permissions, roles, and relations defined on the supertype.
This is useful when multiple resource types share common logic.

Rule polymorphism

Subtype values satisfy rules defined over their supertypes. For example:
actor User {}

resource File {
  permissions = ["read"];
  relations = {owner: User};
}

resource Document extends File {}

has_permission(actor: Actor, "read", file: File) if
  is_public(file) or
  has_relation(actor, "owner", file);

# Even though `has_permission` is defined for `File`, it applies to `Document`
test "extends" {
  setup {
    has_relation(User{"alice"}, "owner", Document{"public.txt"});
    is_public(Document{"public.txt"});
    has_relation(User{"alice"}, "owner", Document{"private.txt"});
  }

  # Though `has_permission` is defined on `File`, `Document` behaves in the
  # same way because it extends `File`.
  assert allow(User{"bob"}, "read", Document{"public.txt"});
  assert_not allow(User{"bob"}, "read", Document{"private.txt"});
  assert allow(User{"alice"}, "read", Document{"private.txt"});
}

Inherited roles, permissions, and relations

Subtypes inherit all roles, permissions, and relations from their supertype. The supertype definition remains unchanged. Example:
actor User {}

resource File {
  permissions = ["read", "write"];
  roles = ["reader", "writer"];

  "read" if "reader";
  "write" if "writer";

  "read" if "write";
}

resource Document extends File {}

test "extends" {
  setup {
    has_role(User{"alice"}, "writer", Document{"xyz.doc"});
  }

  # Though roles + permissions are defined on `File`, `Document` behaves in
  # the same way because it extends `File`.
  assert allow(User{"alice"}, "read", Document{"xyz.doc"});
}

Details

Unification

Subtype values do not unify with their supertypes. For example, given the following definition:
resource Document extends File {}
This condition always returns false:
Document{"a"} = File{"a"}

Query type filtering

Query filters do not match subtypes. For example:
resource Document extends File {}
This query excludes Document values, even with a wildcard:
oso-cloud query has_permission User:alice read File:_