Documentation › Sponsor guides
Org membership (seats + roles)
Steward's authorization model is Relationship-Based Access Control (ReBAC) via SpiceDB — every "can X do Y on Z" check runs against a relationship graph, not a flat role enum. For the sponsor persona, the two surfaces that matter are seats (who counts against the tier cap) and roles (what each member can actually do).
This doc covers both. The full schema lives at
internal/api/web/internal/auth/spicedb/schema.zed.
Seats — what counts against the cap
A seat is a user who holds at least one role on at least one of your covered orgs. So:
- A sponsor with the App installed on one org → 1 seat (themselves).
- That same sponsor grants
maintainerto four engineers → 5 seats. - An engineer with
maintaineron three of the sponsor's orgs → still 1 seat (one user, three rows).
The tier's seat cap from
Tiers + credits
applies to distinct users with any role on any covered org.
Hitting the cap blocks new role grants (the form returns a
SEAT_LIMIT_EXCEEDED envelope); existing roles aren't
retroactively revoked.
There is no per-role seat distinction. An auditor seat costs the same as a maintainer seat.
Reading your seat count
/sponsor/members shows the per-org member list. The header
of each org's card shows X / N members where N is the org's
seat limit (when the org is on a paid subscription with a
non-NULL seat_limit). The total across orgs is your seat
count against the cap.
Two configurations:
- Per-org sponsorship (Team and below) — each org you sponsor has its own seat allocation. The user counted on org A is independent of the user counted on org B.
- Business — single sponsor identity, seats shared across
every org you cover. The org-row
seat_limitis non-NULL (which is also how the tier resolver detects Business — seeinternal/api/web/internal/contributorreports/tier.go).
The role taxonomy
Seven org-level roles. They're SpiceDB relations, so a user can hold any combination on any org.
| Role | What it grants |
|---|---|
owner |
Full org administration. Almost always the installer. Manages everything; usually exactly one per org. |
sponsor |
Equivalent power to owner for operating purposes. The buyer's identity on a Business-tier setup. Both owner + sponsor satisfy the manage permission — the highest authority. |
project_manager |
Org-level read + repo-level write across every repo the org covers. The "I orchestrate but don't own" role. Surface lives at /app/pm. |
maintainer |
Repo-level read across the org's covered repos via the org#repo_reader cascade. Plus contributor:read on every contributor (does NOT grant contributor:write — that's mentorship-only). |
billing_admin |
Org-level billing_view + member_manage. Finance can manage seats + see invoices without org-manage. The right grant for a finance lead who isn't an engineer. |
auditor |
Org-level read + audit-log + usage-report read. No write. The right grant for compliance / SOC2 review. |
member_manager |
Just member_manage. The narrowest grant — the user can invite + remove + change roles for this org but holds no read / write / integration_manage powers. Held by the installer (auto-granted) and by anyone they delegate to via the Members tab. |
How they compose
Three patterns to recognise:
1. Single grant. A user gets auditor on org A. They can
read the org's audit log + usage report; they can't write.
2. Multi-role on one org. A user gets maintainer + auditor.
They get the union of permissions — read access across covered
repos AND audit-log access. Still 1 seat.
3. Same role across multiple orgs. A user gets maintainer
on three different orgs covered by the same sponsor. Still 1
seat (the user, not the relationships).
What owner vs sponsor actually differs on
In day-to-day operation, nothing. Both satisfy org#manage,
which gates everything that isn't explicitly delegated
elsewhere. The distinction is:
owneris the GitHub identity that installed the App. Auto-granted on installation.sponsoris the identity that pays the bill. Granted explicitly when the buyer is not the installer (common in enterprise — IT installs, Finance pays).
A small team usually has one person with both. A larger team benefits from splitting them so the buyer's GitHub identity can change without re-installing.
Where roles cascade
Two cascades the schema codifies:
Org → repo (the most common case)
A user with org#sponsor / org#owner / org#project_manager
holds repo#write on every repo the org covers, without
needing per-repo grants. A user with org#maintainer /
org#auditor / org#billing_admin holds repo#read on every
covered repo via org#repo_reader.
This is the day-one setup that makes Steward feel "natural" —
you grant maintainer once at the org level and everyone has
read access where they need it.
Repo → child resources
A repo's evaluation, governance, cla child objects
inherit through parent_repo->read / parent_repo->write /
parent_repo->manage. So a maintainer with repo#read can
read every evaluation, governance file, and Contributor
License Agreement (CLA) on that repo for free.
There's one exception: the PR author has a direct
evaluation#author relation, so a contributor can read their
own PR's evaluation report even without any org or repo role.
Subtractive override (explicit_deny)
The schema's repo#explicit_deny lets you carve a single repo
out of a user's cascade access. Use it when:
- A repo contains code the org otherwise has access to, but one user shouldn't (a security-sensitive subsystem, an inherited legacy with an HR-flag attached).
- You're testing access patterns and want to revoke without removing the underlying role.
Mechanic: even if parent_org->read would grant access, an
explicit_deny: user row on the repo subtracts that user out.
This is rarely needed on day one. Mention it exists; reach for it when the precise need arises.
Granting + revoking via /sponsor/members
The surface lives at /sponsor/members. Per-org cards list
current members with their role + a Source column:
| Source | Meaning |
|---|---|
install |
The row matches app.installations.installed_by_user. The installer's auto-grant. |
grant |
Everything else — org-admin webhook auto-grant, explicit GrantRole from this page, or a Members-tab delegation. |
The per-org grant form lets you pick a GitHub login and a role. Owner is excluded from the default dropdown — every install already has one, and granting a second is rare (it's allowed, but typed manually).
Last-manager protection
You cannot revoke the last user with member_manage on an
org. The backend returns FAILED_PRECONDITION and the form
surfaces a help line telling you to grant the role to someone
else first.
This guard exists for a reason: an org with zero member-managers becomes unmanageable through the UI; recovery requires DB-side intervention. The protection makes sure that never happens by accident.
Who can see this page
/sponsor/members checks org#member_manage server-side and
silently drops orgs the caller can't manage from the per-org
cards. So a member_manager on one of three orgs only sees
that one org's card; they don't see permission-denied errors
for the other two.
What's next
- Tiers + credits — the tier's seat cap your member count gates against.
- Audit + compliance (forthcoming in slice 4) — how every role-change shows up in the audit trail for Service Organization Control 2 (SOC2) review.
- Set up your sponsorship —
the first-time setup that produces the initial seat (the
installer's
member_manager).
Steward