TL;DR — A resource that belongs to an organization but has its own membership (a course inside a school account, a project inside a company workspace) needs two independent roles checked together, not one: the user's role in the owning organization, and the user's role on that specific resource. Effective access is neither role alone — an org owner should be able to act on every resource without needing a resource-specific role, and a resource-specific role should grant real access even to a plain org member with no organization-wide power. In the harness below, one
effective_accessfunction correctly resolves seven scenarios by checking the org role first (granting everything for owners/admins) and falling through to the resource role otherwise. This model stops applying once a resource needs permissions that don't reduce to "org-wide power OR resource-specific power" — genuinely cross-cutting rules (a user banned platform-wide regardless of any role) need a check layered outside this model, not folded into it.
1. Simple explanation
Many systems have two levels of "belonging" stacked on top of each other: a user belongs to an organization, and separately, a specific resource inside that organization has its own list of members with their own roles. A permission check that only looks at one level gets the wrong answer in an entire category of real cases — checking only the org role means a resource's own members-only access controls are meaningless, since org membership alone would already grant or deny everything; checking only the resource role means an org owner has to be manually added to every resource individually just to retain the authority that owning the organization should already imply.
Analogy — a building manager and a tenant's own apartment key. A building's manager can enter any unit for maintenance regardless of which specific unit it is — that authority comes from managing the building, not from having a key to that particular apartment. A tenant, meanwhile, can enter their own apartment because they have a key to that unit specifically, even though they have no authority over any other unit in the building and no general "building manager" status. Checking only "does this person manage the building" would wrongly lock every tenant out of their own apartment; checking only "does this person have a key to this specific unit" would wrongly lock the manager out of units they're entitled to enter for a legitimate building-wide reason. A correct system checks both, in the right order: building-wide authority first, and the specific unit's own access list as the fallback.
2. Diagram
TWO ROLE SYSTEMS ON ONE RESOURCE
ORGANIZATION RESOURCE (e.g. one course,
role: owner | admin | member one project, inside the org)
role: instructor | ta | student
\ /
\ /
v v
+------------------------------------+
| effective_access(org_role, |
| resource_role, action) |
| |
| 1. org_role in {owner, admin}? |
| -> YES: full access, done |
| -> NO: fall through |
| |
| 2. resource_role's permission set |
| includes this action? |
| -> that's the answer |
+------------------------------------+
MEASURED (harness in §5, 7 scenarios):
org owner, no resource role -> manage_members : TRUE (org power)
org admin, no resource role -> edit_content : TRUE (org power)
org member, resource=instructor -> grade : TRUE (resource power)
org member, resource=student -> manage_members : FALSE (role too low)
org member, resource=student -> submit : TRUE (resource power)
org member, NO resource role -> view : FALSE (neither applies)
NOT an org member at all -> view : FALSE (org gate first)
3. How it works
3.1 Org role and resource role answer different questions
The org role answers "how much authority does this person have across the whole organization" — an owner or admin's authority is meant to span every resource the organization owns, precisely because managing the organization implies broad responsibility for what's inside it. The resource role answers a narrower question: "what can this specific person do on this specific resource," independent of whether they have any organization-wide standing at all. A plain org member can legitimately be, say, an instructor on one specific course and have no role whatsoever on any other course in the same organization — their resource role is local to that one resource, not a reflection of general organizational standing.
3.2 Check org-wide authority first, fall through to resource-specific role
The correct evaluation order matters: check whether the org role alone already grants the requested action (owners and admins get everything, by design, without needing a matching resource role), and only if that check doesn't resolve the question, fall through to whatever role the user holds on the specific resource. Checking it in the other order — resource role first — would require an org owner to hold an explicit resource role on every single resource just to retain authority that their organizational position should already confer, which defeats the purpose of having an org-wide role at all.
3.3 No org membership means no access, regardless of any resource role
A user who isn't a member of the organization at all should get no access to any resource inside it, even if some stale or leftover resource-level role record exists for them — a resource role is only meaningful in the context of an org membership that establishes the person belongs to the organization in the first place. This is why the org check isn't just "first" but a genuine gate: if there's no org role at all, the function returns false immediately, without ever consulting the resource role.
Where this stops working: this model assumes every access decision reduces to "sufficient org-wide power, or else a specific resource-level grant." Some real requirements don't fit that shape — a platform-wide suspension that should override every role at every level, or a permission that depends on something outside both role systems entirely (time-of-day restrictions, IP allowlisting, a legal hold on a specific resource). Those need a check that runs before or around the two-tier role logic, not folded into it, because trying to represent "banned, full stop" as just another combination of org and resource roles distorts a genuinely different kind of rule into the wrong shape.
4. The math
There's no formula to derive — the correctness claim is a two-branch decision procedure, and it's either implemented in the right order or it isn't:
effective_access(org_role, resource_role, action):
if org_role is None: return False
if org_role in {owner, admin}: return True
if resource_role is None: return False
return action in PERMS[resource_role]
The one design decision worth stating explicitly: this is a short-circuiting OR, not a combination requiring both roles to agree. An org owner needs no resource role at all to get full access; a plain member needs a resource role or gets nothing. There is no case in this model where a conflict between the two roles has to be resolved — the org role either already settles the question, or it explicitly doesn't apply and the resource role is the only remaining input.
5. Real code
# Two independent role systems on the same resource:
# ORG role -- membership in the organization that owns the resource
# RESOURCE role -- membership in the specific resource (e.g. a course,
# a project, a shared document) within that organization
#
# Effective access is neither role alone -- it's a combination: org
# owners/admins can act on every resource in the org regardless of whether
# they hold a resource-specific role, and a resource role can grant access
# to THAT resource even for an org member with no special org-level power.
ORG_ROLE_GRANTS_ALL = {"owner", "admin"}
RESOURCE_ROLE_PERMISSIONS = {
"instructor": {"view", "edit_content", "grade", "manage_resource_members"},
"ta": {"view", "grade"},
"student": {"view", "submit"},
}
def effective_access(org_role, resource_role, action):
"""org_role: the user's role in the organization that owns the resource,
or None if they aren't an org member at all.
resource_role: the user's role on THIS specific resource, or None."""
if org_role is None:
return False # not in the organization at all -> no access, period
if org_role in ORG_ROLE_GRANTS_ALL:
return True # org owner/admin can act on every resource in the org
if resource_role is None:
return False # plain org member, no resource-specific role -> nothing
return action in RESOURCE_ROLE_PERMISSIONS.get(resource_role, set())
CHECKS = [
("org owner, no resource role", "owner", None, "manage_resource_members"),
("org admin, no resource role", "admin", None, "edit_content"),
("plain org member, instructor role on THIS resource", "member", "instructor", "grade"),
("plain org member, student role on THIS resource", "member", "student", "manage_resource_members"),
("plain org member, student role on THIS resource", "member", "student", "submit"),
("plain org member, NO role on this resource", "member", None, "view"),
("not an org member at all", None, "instructor", "view"),
]
print(f"{'scenario':56}{'action':26}{'access'}")
for label, org_role, resource_role, action in CHECKS:
result = effective_access(org_role, resource_role, action)
print(f"{label:56}{action:26}{result}")
assert effective_access("admin", None, "manage_resource_members") is True
assert effective_access("member", None, "view") is False
assert effective_access("member", "student", "manage_resource_members") is False
assert effective_access("member", "student", "submit") is True
assert effective_access(None, "instructor", "view") is False
print("\nall asserts passed: effective access is neither the org role nor "
"the resource role alone -- it's org-role-grants-all-or-else-fall-"
"through-to-resource-role, computed the same way for every check above")
# Output:
# scenario action access
# org owner, no resource role manage_resource_members True
# org admin, no resource role edit_content True
# plain org member, instructor role on THIS resource grade True
# plain org member, student role on THIS resource manage_resource_members False
# plain org member, student role on THIS resource submit True
# plain org member, NO role on this resource view False
# not an org member at all view False
#
# all asserts passed: effective access is neither the org role nor the resource role alone -- it's org-role-grants-all-or-else-fall-through-to-resource-role, computed the same way for every check above
All five asserts passed on the run that produced this output, covering: org-wide authority granting access with no resource role at all, a plain member correctly denied without a resource role, a plain member correctly limited to exactly their resource role's permission set (denied one action, granted another), and a non-org-member correctly denied despite a resource role being present in the input.
6. Real-world example
A platform's permission system started with a single flat role per user — owner, admin, or member — checked against every resource uniformly. When the product added resources with their own finer-grained membership (a specific project having its own instructor-equivalent and student-equivalent roles, distinct from org-wide roles), the team initially bolted resource roles on by checking them instead of the org role for resource-specific actions, rather than in addition to it.
The result: an org owner who hadn't been explicitly added to a specific resource's member list lost the ability to manage that resource, even though owning the organization should have implied authority over everything inside it. Support tickets came in from confused organization owners who could see a resource existed but couldn't manage it, and the initial diagnosis assumed a display bug, since the owner clearly should have had access. The actual cause was that the resource-role check ran in isolation, with no fallback to org-level authority, so an owner with no explicit resource-role record was treated identically to a total stranger.
The fix was making the org check a genuine first gate that could grant access outright, with the resource role only consulted as a fallback for org members who weren't owners or admins — exactly the two-branch order in §4. The team's retrospective note was that "which role wins" isn't a question with a single universal answer; it depends entirely on which role represents broader authority in that specific system, and getting the order backwards is the kind of bug that looks, from a support ticket, exactly like a display or database issue rather than a logic-order mistake.
7. Interview questions companies actually ask
Q1. Why can't a single flat role (one role per user, checked the same way everywhere) handle a resource that belongs to an organization but has its own membership? A flat role can express "how much authority in the organization" or "what can this user do on this specific resource," but not both at once — a system with only one role has to pick one of those two questions and can't correctly answer a request that genuinely depends on both, like an org owner needing full access to a resource without an explicit resource-level grant, or a plain member needing resource-specific access without organization-wide power.
Q2. Why check the org role before the resource role, rather than the other way around? Because org-wide roles like owner or admin are meant to confer authority across every resource in the organization, and checking the resource role first would require an owner to hold an explicit role on every individual resource just to retain authority their organizational position should already imply — exactly the bug described in §6, where an org owner lost access to a resource simply because no resource-level role record existed for them.
Q3. A user has no organization membership at all, but somehow has a resource-level role recorded (say, from a stale invite). Should they get access? No — a resource role is only meaningful in the context of belonging to the organization that owns the resource in the first place; without organization membership, there's no context in which the resource role should mean anything, so the org check needs to be a genuine gate, not just a first-checked-but-bypassable condition.
Q4. How would you extend this model to support a platform-wide suspension that should override every role at every level? That kind of rule doesn't fit inside the org-role/resource-role combination at all — it needs to be checked before or around the two-tier logic, as an independent gate: if suspended, deny everything immediately, regardless of what the org-role and resource-role check would otherwise return. Trying to express "banned, full stop" as a special value within the existing role system distorts a fundamentally different kind of rule into a shape it doesn't fit.
Q5. How would you test that this two-tier permission logic is actually correct, rather than trusting a code review caught every case? Enumerate every combination of (has org role or not) × (org role grants all or not) × (has resource role or not) × (resource role permits the action or not), and assert the expected result for each — the harness in §5 covers exactly these combinations, including the two easy-to-get-backwards cases: an org owner with no resource role (should pass) and a non-org-member with a resource role present (should still fail).
Q6. What's the risk of adding a third role tier later — say, a team within a resource, sitting between the resource and individual members? Each additional tier adds another point where the evaluation order (which tier's authority takes precedence over which) has to be decided explicitly and correctly, and the number of interactions between tiers to reason about and test grows with each one added. Before adding a third tier, it's worth checking whether the new distinction can instead be expressed as a role within the existing resource tier, rather than as a genuinely new level of the hierarchy.
8. When to use / tradeoffs
Reach for a two-tier org-role/resource-role model when:
- resources belong to an organization but have their own, finer-grained membership distinct from organization-wide membership
- organization-wide roles (owner, admin) are meant to confer authority across every resource, not just specifically-assigned ones
- a plain organization member can legitimately have a meaningful role on one specific resource and none on another
| Situation | Why it breaks | Use instead |
|---|---|---|
| A rule that must override every role at every level (a platform-wide ban) | Trying to fold it into the two-tier role combination distorts a different kind of rule into the wrong shape | An independent gate checked before or around the role logic |
| Every resource in the org should be accessible identically regardless of resource-specific membership | The resource-role tier adds complexity with no case that actually differentiates between resources | A single flat org-wide role, no resource tier needed |
| A third, fourth, or deeper hierarchy of nested resource types | Each added tier multiplies the precedence decisions and test cases needed to get the order right | Reconsider whether the new distinction is really a new tier, or a role variant within an existing one |
| The evaluation order (org-first vs. resource-first) hasn't been deliberately decided and documented | The bug in §6 is exactly what happens when this is implicit rather than an explicit, tested design decision | Write down which tier wins and why, and test the two failure directions explicitly |
Honest limits. This model handles exactly the shape of "broad authority OR narrow, resource-specific authority," resolved by checking the broad one first. It does not, by itself, handle rules that are genuinely orthogonal to both role systems — time-based restrictions, geographic or IP-based rules, legal holds — those need their own explicit gate layered around this logic, not squeezed into an extra role value. And the model's correctness depends entirely on the evaluation order being right; the same two role systems, checked in the wrong order, produce the exact class of bug described in §6, which looks from the outside like a data or display problem rather than a logic-order mistake.
9. Summary + related articles
- A resource that belongs to an organization but has its own membership needs two roles checked together — the org role and the resource role — not one flat role for both questions.
- Org-wide roles that are meant to confer broad authority (owner, admin) must be checked first, with the resource role consulted only as a fallback — checking in the other order breaks exactly the authority those roles are supposed to represent.
- Measured: one
effective_accessfunction, evaluated against seven scenarios, correctly resolved org-wide grants, resource-specific grants, denials from insufficient resource role, and denial from lacking organization membership entirely. - Boundary: rules that must override every role at every level (a platform-wide ban) don't fit inside this two-tier combination — they need an independent gate, not a forced-in role value.
Related:
- Designing a Self-Serve Platform: One Model for Individuals and Organizations — the workspace/organization model this permission system sits on top of
- Guardrails & Output Validation — enforcement mechanisms for what a system is and isn't allowed to do, of which role-based access is one specific category