Skip to content

Authorization matrix (Dispatch Phase 1) โ€‹

Row-level security is enforced in Postgres (Supabase) โ€” see supabase/migrations/20260616010000_rls_policies.sql. This is the source of truth the Go API (WS3) and Angular guards (WS1/WS4) must agree with.

Roles โ€‹

RoleIn this docNotes
Member PartnerMemberDefault role on first sign-in.
Managing PartnerManagerBack-office: validates skills, manages all entities.
AdminManagerSame access as Managing Partner.

public.is_manager() (SECURITY DEFINER) returns true for Managing Partner and Admin. The Board role was removed. Anonymous (anon) users have no table access โ€” every policy is scoped to authenticated.

public.is_member() (migration 20260715000100_open_signup.sql) is a restrictive "member gate" ANDed onto every member-data table: being authenticated no longer implies membership โ€” an open-signup Pending user sees only their own public.users row and nothing else, whatever the permissive policies below would otherwise allow.

Two principals bypass RLS entirely:

  • service_role โ€” the Go API; performs validated server-side writes.
  • superuser โ€” migrations and the non-prod seed (supabase/seed.sql).

Matrix โ€‹

Legend: โœ… allowed ยท โšช own rows only ยท โŒ denied.

EntityRead (Member)Read (Manager)Write (Member)Write (Manager)
usersโšช own profileโœ… allโšช own full_name / function onlyโœ… all
organizationsโœ… all (catalog)โœ… allโŒโœ… all
mandatesโšช assigned to me or Availableโœ… allโŒโœ… all
tasksโšช assigned to meโœ… allโŒโœ… all
skillsโœ… all (catalog)โœ… allโŒโœ… all
user_skillsโšช ownโœ… allโšช insert/update own self_score onlyโœ… all (incl. validated_score)
votesโœ… allโœ… allโŒโœ… all
vote_ballotsโšช ownโœ… allโšช cast own ballotโœ… all
notificationsโšช own (recipient)โœ… allโšช mark own readโœ… all (create/fan-out)

Column-level rule: skill validation โ€‹

RLS governs rows, not columns. The invariant "only a Manager sets validated_score / coefficient / status" is enforced by the user_skills_enforce_validation trigger:

  • Member INSERT must be validated_score IS NULL, status = 'pending', coefficient = 0 โ€” i.e. self-registration only.
  • Member UPDATE may change self_score but not validated_score, coefficient, or status (compared against the prior row).
  • Manager / service_role / superuser are unrestricted.

This is what lets a member self-assess a skill at registration while the Managing Partner sets the validated score and scheduling coefficient later, in the validation queue.

Column-level rule: member self-update โ€‹

The users Member-write cell is column-scoped. A member may update only their own full_name and function; the users_enforce_self_update trigger (20260617000000_member_self_update.sql) rejects any change to id, email, membership_role, status, or gacx_balance by a non-manager. The Go API's PUT /api/v1/me enforces the same restriction independently (it only ever writes those two columns for the caller's own uid). Managers, service_role, and the superuser are unrestricted.

Verification โ€‹

Policy tests run against the local stack on every supabase db reset (see the WS2.2 commit). They assert: a Member reads only their own tasks; a Manager reads all; a Member cannot set or pre-insert a validated_score; a Manager can; anon has no table privilege.