Problem
When a user is added to an org, they get both a policy (role binding — viewer/manager/owner) and an explicit relation (org#member or org#owner). This gives them role-based permissions and makes them visible in member listings.
Service users and PATs don't follow this model, creating inconsistencies.
Three principals, three different org-level role patterns
| Principal |
RPC |
Roles |
Mode |
Relation path |
Policy |
Visible in listings |
| User |
SetOrganizationMemberRole |
1 role |
Replace |
granted |
Yes |
Yes |
| PAT |
CreatePAT / UpdatePAT |
N roles via []PATScope |
Replace all |
pat_granted |
Yes |
N/A (separate listing) |
| Service User |
CreateServiceUser |
None |
N/A |
Direct member only |
No |
No |
Service Users
- On creation (
CreateServiceUser), only gets a direct org#member relation — no policy, no role
- The
serviceuser.Service struct doesn't even have a PolicyService dependency
- Invisible to policy-based member listing (e.g.,
ListOrganizationUsers won't show them with roles)
- Has no org-level role permissions (can't manage projects, roles, billing via policy path)
- Only gets basic
get and membership via the direct member relation
- Project-level access works fine via
SetProjectMemberRole which already supports principalType: app/serviceuser
PATs
- Access flows through
pat_granted on the org (not granted) — a completely separate relation path
- Accepts multiple roles via
[]PATScope — different from the user model which enforces single role
- Project access is granted at the org level via
pat_granted rolebindings, not at the project level
- Different from both users and service users
SpiceDB schema
The schema already supports org-level policies for both service users and PATs — no changes needed:
definition app/rolebinding {
relation bearer: app/user | app/group#member | app/serviceuser | app/pat
relation role: app/role
}
Both app/serviceuser and app/pat can be bearers on rolebindings.
Proposed direction
Service users
- Allow
AddOrganizationMember and SetOrganizationMemberRole in the membership package to accept app/serviceuser as a principal type
- Validate via
serviceUserService.Get (check exists + check su.OrgID == orgID) instead of userService.GetByID
- On
CreateServiceUser, optionally call membership.AddOrganizationMember to create both policy + relation (instead of just the relation)
- This brings service users into the same single-role replace model as users
PATs
- PATs use
pat_granted instead of granted on orgs — fundamentally different relation path
- Accepts multiple roles per scope — different from the single-role model for users
- Project access for PATs is granted at the org level (via org's synthetic
project_* permissions), not at the project level
- Needs separate analysis — may need a dedicated function rather than reusing the user membership functions
Related
Problem
When a user is added to an org, they get both a policy (role binding — viewer/manager/owner) and an explicit relation (
org#memberororg#owner). This gives them role-based permissions and makes them visible in member listings.Service users and PATs don't follow this model, creating inconsistencies.
Three principals, three different org-level role patterns
SetOrganizationMemberRolegrantedCreatePAT/UpdatePAT[]PATScopepat_grantedCreateServiceUsermemberonlyService Users
CreateServiceUser), only gets a directorg#memberrelation — no policy, no roleserviceuser.Servicestruct doesn't even have aPolicyServicedependencyListOrganizationUserswon't show them with roles)getandmembershipvia the directmemberrelationSetProjectMemberRolewhich already supportsprincipalType: app/serviceuserPATs
pat_grantedon the org (notgranted) — a completely separate relation path[]PATScope— different from the user model which enforces single rolepat_grantedrolebindings, not at the project levelSpiceDB schema
The schema already supports org-level policies for both service users and PATs — no changes needed:
Both
app/serviceuserandapp/patcan be bearers on rolebindings.Proposed direction
Service users
AddOrganizationMemberandSetOrganizationMemberRolein the membership package to acceptapp/serviceuseras a principal typeserviceUserService.Get(check exists + checksu.OrgID == orgID) instead ofuserService.GetByIDCreateServiceUser, optionally callmembership.AddOrganizationMemberto create both policy + relation (instead of just the relation)PATs
pat_grantedinstead ofgrantedon orgs — fundamentally different relation pathproject_*permissions), not at the project levelRelated
AddOrganizationMemberPR: feat: add membership package and AddOrganizationMembers RPC #1537SetOrganizationMemberRolemigration PR: feat: move SetOrganizationMemberRole to membership package #1541