Hierarchies & Recursion
When resources live inside nested folders or users belong to nested teams, store only the direct parent-child links as facts and use recursive rules to propagate access across any number of levels.
Recursive folder hierarchies
Section titled “Recursive folder hierarchies”Suppose Folder:engineering contains Folder:guides, which contains Document:handbook. Record each direct link with a parent(child, container) fact, then define a recursive is_ancestor predicate with two rules:
declare parent(Folder, Folder);declare parent(Document, Folder);
// Base case: a direct parent folder is an ancestoris_ancestor(folder: Folder, resource) if parent(resource, folder);
// Recursive step: an ancestor of a parent folder is also an ancestoris_ancestor(ancestor: Folder, resource) if parent(resource, folder: Folder) and is_ancestor(ancestor, folder);Verdict evaluates recursive rules to a fixed point and terminates cleanly even if the stored facts contain a cycle.
Separating ancestry from permissions
Section titled “Separating ancestry from permissions”Joining is_ancestor with has_role lets you control which folder roles propagate down to descendant folders and documents:
declare has_role(User, string, Folder);
// A viewer on any ancestor folder can read descendant folders and documentshas_permission(user: User, "read", resource) if is_ancestor(folder: Folder, resource) and has_role(user, "viewer", folder);
allow(actor, action, resource) if has_permission(actor, action, resource);test "folder roles propagate two levels deep" { parent(Folder:guides, Folder:engineering); parent(Document:handbook, Folder:guides); has_role(User:alice, "viewer", Folder:engineering);
assert allow(User:alice, "read", Folder:guides); assert allow(User:alice, "read", Document:handbook); assert not allow(User:alice, "edit", Document:handbook);}If every permission on a parent folder should propagate directly to its children, you can make has_permission itself recursive without introducing is_ancestor:
has_permission(user: User, "read", resource) if parent(resource, folder: Folder) and has_permission(user, "read", folder);Nested groups and teams
Section titled “Nested groups and teams”Recursion works the same way on the actor side when users belong to nested teams. If User:alice belongs to Team:platform, and Team:platform is a sub-team of Team:engineering, a recursive team_member predicate derives her membership in the parent team automatically:
team_member(user: User, team: Team) if direct_member(user, team);
team_member(user: User, parent: Team) if team_member(user, child: Team) and parent_team(child, parent);
allow(user: User, "read", folder: Folder) if team_member(user, team: Team) and has_role(team, "viewer", folder);Next steps
Section titled “Next steps”- Layer attribute comparisons onto your rules in Attributes.
- Test deep hierarchies and boundary cases in Testing & Validating Policies.