Roles and permissions
Fovea's RBAC is a three-scope role system on top of a single
RolePermission matrix. Users hold roles at one or more scopes;
the matrix says what each role can do; CASL composes the union.
Scopes
system every authenticated user has a system role
group users in a group hold a group role
project users in a project hold a project role
User.systemRole is the system role. GroupMembership.role is
the group role; one row per (user, group) pair. ProjectMembership.role
is the project role; one row per (user, project) pair.
System roles
system_admin can('manage', 'all') — full access to every resource
user default; permissions come from group + project roles
plus baseline ownership rules
User.systemRole defaults to 'user'. system_admin is set
explicitly during seed or via an admin endpoint; in v0.1.x the
flag was the boolean User.isAdmin. Both columns exist on
v0.2.x; new code uses systemRole.
Group roles
group_owner update / delete group, manage members, create projects
group_admin update group, manage members, create projects
group_member read group info
Project roles
project_owner full content access + project admin (update / delete /
manage members)
project_manager full content access + project admin minus delete project
annotator create / read / update / delete own content; read all
content within the project
reviewer read all content; review action on
annotation / summary / claim
viewer read-only on every project content type
annotator write actions are ownOnly: true in the seed, so
CASL adds a createdByUserId = userId (or equivalent) condition;
read is unconditional within the project.
Baseline ownership
Independent of the role matrix, every authenticated user can always read / update / delete resources they own:
own Annotation by createdByUserId
own VideoSummary by createdBy
own Claim by createdBy
own Persona by userId
own WorldState by userId
These rules are added unconditionally in defineAbilitiesFor. A
user without any project or group role still owns their personal
content.
Seeding a custom role
server/prisma/seed-permissions.ts inserts the production matrix.
Adding a new role is a pair of writes plus a cache flush:
await prisma.rolePermission.create({
data: { scope: 'project', role: 'curator', resourceType: 'claim',
action: 'update', ownOnly: false }
})
invalidatePermissionCache()
invalidatePermissionCache() flushes the global matrix cache and
every per-user ability cache. Without it, the new rule does not
take effect until the matrix TTL elapses (5 minutes).