Last updated 2026-06-29

Hierarchies & Inheritance

This guide models parent/child resource hierarchies so that access flows down a tree: grant someone a role on a workspace once, and they automatically get the right access on every resource inside it — with zero per-resource tuples.

What you'll learn: how the tuple_to_userset rewrite makes a resource inherit from its parent, how to write the parent link, a step-by-step walk of the Check evaluation, and how to extend the same pattern to deeper folder/document trees. This is the Linear-issue and folder-tree pattern.

The resource namespace

The built-in resource namespace inherits from a parent workspace via tupleToUserset:

parent := this()
owner := this()
editor := union(this(), computed("owner"), tupleToUserset("parent", "admin"))
viewer := union(this(), computed("editor"), tupleToUserset("parent", "member"))
  • parent stores the link to the enclosing object — a userset like workspace:W#member.
  • editor = direct editors ∪ the resource's ownerevery admin of the parent workspace.
  • viewer = direct viewers ∪ everyone who is an editorevery member of the parent workspace.

tupleToUserset("parent", "admin") reads as: "for every userset stored under the parent relation, evaluate admin on the object it points to." That single primitive is the inheritance edge.

Linking an issue to its workspace

Write one parent tuple from the issue to the workspace. The subject is a userset pointing at the workspace's member relation — the conventional anchor the rewrite walks from.

resource:issue-42#parent@workspace:W#member
curl -X POST http://localhost:8080/workspace.v1.AuthzService/WriteRelationTuples \
-H "Authorization: Bearer $WS_SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"updates":[
{"op":"INSERT","tuple":{
"namespace":"resource","object_id":"issue-42","relation":"parent",
"subject":{"set":{"namespace":"workspace","object_id":"W","relation":"member"}}}}
]}'

That is the only tuple the issue needs. No per-user grants on the issue — viewers and editors are derived from the workspace.

Walking the Check

Suppose bob is an admin of workspace W (workspace:W#admin@bob) and the issue has only the parent tuple above. Ask whether bob can edit the issue:

curl -X POST http://localhost:8080/workspace.v1.AuthzService/Check \
-H "Authorization: Bearer $WS_SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"namespace":"resource","object_id":"issue-42","relation":"editor","subject_user_id":"bob"}'
{ "allowed": true }

Check evaluates the editor rewrite, left to right:

  1. this() — is there a direct resource:issue-42#editor@bob? No.
  2. computed("owner") — is bob the issue's owner? No direct owner tuple. No.
  3. tupleToUserset("parent", "admin") — read the parent tuples on issue-42. There is one, pointing at workspace:W. Recurse into Check(workspace, W, admin, bob).
  4. workspace:W#admin@bob is a stored tuple — the admin leaf matches. Allowed.

A plain workspace member (not admin) would fail the editor check at step 3 but pass a viewer check, because viewer inherits the parent's member relation. The role hierarchy on the workspace and the inheritance edge compose cleanly.

Deeper hierarchies: folders and documents

The same parent link chains. Model a folder as a resource, put the folder under a workspace, and put a document under the folder:

resource:folder-design#parent@workspace:W#member
resource:doc-spec#parent@resource:folder-design#viewer

Here doc-spec's parent points at the folder's viewer relation. A Check(resource, doc-spec, viewer, bob) recurses into Check(resource, folder-design, viewer, bob), which in turn recurses up to the workspace via the folder's own parent tuple. Access granted at the top of the tree reaches the leaf document, and moving a subtree is one tuple change.

Point the parent at the right relation. The second argument of the inheritance edge decides what gets inherited: the built-in editor walks the parent's admin, and viewer walks the parent's member. When you chain your own levels, choose the relation on the parent that should confer access at the child — as in doc-spec#parent@resource:folder-design#viewer above.

Where to next