ADR 009: Org Admin Role Cascades Down the Tree
Status
Accepted
Date
2026-03-10
Context
The platform has a multi-level organizational hierarchy (Tenant → Organization tree → Groups). We need to define how admin permissions work across the org tree. Key question: if someone is an admin of “ICF Zürich”, do they automatically have admin access to sub-orgs like “ICF Zürich City” and “ICF Zürich Oerlikon”?
Decision
Org admin role cascades down the org tree via ltree ancestor queries.
The role hierarchy:
- Platform Super Admin — access to all tenants and system configuration
- Tenant Admin — admin access to all organizations within their tenant
- Org Admin — admin access to their org node AND all descendant org nodes
- Group Leader/Admin — manages a specific group (separate from OrgMembership, stored on GroupMembership)
How cascading works
When checking if a user has admin access to an organization, the system queries:
-- Does the user have admin role on this org OR any ancestor org?
SELECT 1 FROM org_memberships om
JOIN organizations o ON o.id = om.organization_id
WHERE om.user_id = :userId
AND om.role = 'admin'
AND :targetOrgPath <@ o.path -- ltree: targetOrg is descendant of admin's org
LIMIT 1;
This means:
- Admin of “ICF Zürich” → automatic admin of “ICF Zürich City”, “ICF Zürich Oerlikon”, and any future sub-orgs
- Admin of “ICF Switzerland” → automatic admin of all Swiss cities and their campuses
- No need to explicitly assign admin role at every level
What cascading does NOT do
- Group leader/admin roles are separate — stored on
GroupMembership, notOrgMembership - An org admin is not automatically a leader of every group in that org (but can manage groups via admin UI)
- Cascading is read-only — the user’s
OrgMembershiprecord only exists at the level where it was explicitly assigned
Consequences
- Simple permission model: One admin assignment covers the entire subtree
- Efficient queries: ltree ancestor queries are O(1) with GiST index
- No role drift: When the org tree is restructured (nodes moved), admin access automatically adjusts because it’s based on the live tree, not cached permissions
- Explicit at assignment, implicit at check: You assign admin at one level, the system checks ancestry at query time