Matter-participant access model replaces implicit ownership for portal resources
MuseLegal rewrites portal authorization around an explicit many-to-many `matter_participants` join, enforced at both the database (Supabase RLS) and application tiers. This is infrastructure groundwork for a client-portal product layer sitting on top of the upstream assistant.
Two commits land this: b0ce349f adds the code, 62747299 is the PR merge with no additional changes.
The migration creates public.matter_participants - roles constrained to client, attorney, reviewer, paralegal - and two SQL functions: current_portal_role() reads auth.jwt() ->> 'role', and can_access_matter() evaluates membership. Partners and admins are unconditional pass-throughs; everyone else needs a row in the join table. A DO block wires RLS policies onto matters, documents, portal_messages, tasks, intake_submissions, and downloadable_files, using to_regclass() checks so the migration skips tables that don't exist yet.
backend/src/lib/access.ts gains canAccessMatterByRole (direct participants query) and ensureMatterScopedResourceAccess (fetch resource's matter_id, then check). Both fail safe - any database error returns false.
This sits alongside, rather than replacing, upstream's project/shared_with model. Both code paths remain live, which can produce confusing dual-layer authorization if you merge this without auditing which tables each path covers.
Three things to verify before pulling this in: the JWT role claim must be injected at the Supabase Auth level (not shown here); the matters table must already exist in your schema; and the new RLS policies interact with whatever policies your fork already applies to these tables.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?