ADR 009: Org Admin Role Cascades Down the Tree

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:

  1. Platform Super Admin — access to all tenants and system configuration
  2. Tenant Admin — admin access to all organizations within their tenant
  3. Org Admin — admin access to their org node AND all descendant org nodes
  4. 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, not OrgMembership
  • 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 OrgMembership record 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