critical priority medium complexity database pending database specialist Tier 1

Acceptance Criteria

RLS is enabled on cert_expiry_reminders: ALTER TABLE cert_expiry_reminders ENABLE ROW LEVEL SECURITY
Policy 'mentor_read_own': authenticated peer mentors can SELECT rows where mentor_id = auth.uid()
Policy 'coordinator_read_chapter': authenticated coordinators can SELECT rows where chapter_id matches any chapter_id assigned to them in the coordinator_chapters join table
Policy 'admin_full_access': org admins and global admins have full SELECT, INSERT, UPDATE, DELETE access
Policy 'system_service_role_full': service_role (used by backend Edge Functions / scheduled jobs) bypasses RLS for reminder dispatch jobs
A peer mentor querying with their JWT cannot see another mentor's certification records — verified by test query with a second test user's JWT
A coordinator can see all mentor certifications within their chapter but not those of a different chapter — verified by test query
INSERT and UPDATE are restricted to admin roles and the service_role — peer mentors cannot modify their own certification records directly
All policies are added in a new migration file (not modifying the original table creation migration)
Policies are documented with SQL comments explaining the access intent

Technical Requirements

frameworks
Supabase CLI
PostgreSQL RLS
apis
Supabase Auth (auth.uid(), auth.jwt())
data models
CertExpiryReminder
CoordinatorChapterAssignment
UserRole
performance requirements
RLS policy expressions must use indexed columns (mentor_id, chapter_id) to avoid full table scans on every authenticated query
Coordinator chapter membership check should use an EXISTS subquery against an indexed coordinator_chapters table rather than an IN list
security requirements
Default-deny: no policy means no access — confirm that a user with no matching policy gets zero rows, not an error
JWT role claim used to distinguish peer_mentor, coordinator, and admin roles — ensure claim name matches the actual JWT structure from Supabase Auth custom claims
Service role key must never be exposed to the Flutter client — only used server-side in Edge Functions

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The coordinator chapter membership check is the most complex policy. Use a pattern like: EXISTS (SELECT 1 FROM coordinator_chapters cc WHERE cc.coordinator_id = auth.uid() AND cc.chapter_id = cert_expiry_reminders.chapter_id). This requires the coordinator_chapters table to exist and be indexed on coordinator_id — confirm this dependency is met or add it. For role detection from JWT claims, Supabase custom claims are typically stored under auth.jwt() -> 'app_metadata' -> 'role'.

Agree on the exact claim path with the auth setup team before implementing. Use SECURITY DEFINER on helper functions if needed for the chapter membership check, but prefer direct subqueries to avoid function overhead on every row evaluation.

Testing Requirements

Test RLS policies using the Supabase SQL editor with SET ROLE and SET LOCAL request.jwt.claims. Create three test scenarios: (1) authenticate as a peer mentor JWT and confirm SELECT returns only their own row and zero rows for another mentor's id; (2) authenticate as a coordinator JWT and confirm SELECT returns all rows for their chapter but zero rows for a different chapter; (3) authenticate as an admin JWT and confirm full access. Verify that a direct INSERT from a peer mentor JWT is rejected with a 403/RLS violation. Document test queries in a test_rls.sql file committed alongside the migration.

Component
Certification Status Repository
data low
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for coordinator-scoped status queries may be difficult to express correctly, especially for peer mentors assigned to multiple coordinators or chapters, leading to data leakage or overly restrictive access blocking valid queries.

Mitigation & Contingency

Mitigation: Design RLS policies using security-definer RPCs rather than table-level policies for complex multi-coordinator scenarios. Write a comprehensive RLS test matrix covering all role and assignment permutations before marking complete.

Contingency: Fall back to application-level filtering in the repository layer with explicit coordinator_id parameter checks if RLS proves intractable, and document the trade-off for security review.

high impact medium prob dependency

The HLF Dynamics portal API contract may be undocumented or subject to change, causing the DynamicsPortalClient to break during development or production rollout.

Mitigation & Contingency

Mitigation: Obtain the full Dynamics portal API specification and credentials early in the sprint. Build the client behind a well-defined interface so the HLF-specific implementation can be swapped without affecting upstream services.

Contingency: If the Dynamics API is unavailable or unstable, stub the client with a feature-flag-guarded no-op implementation so all other epics can proceed to completion independently.

medium impact low prob technical

Supabase Edge Functions used as the nightly scheduler host may have cold-start latency or execution time limits that prevent reliable nightly certification checks on large mentor rosters.

Mitigation & Contingency

Mitigation: Benchmark Edge Function execution time against the expected roster size. Design the expiry check to process in paginated batches to stay within execution limits. Use pg_cron with a direct database function as an alternative trigger if Edge Functions prove unreliable.

Contingency: Migrate the scheduler trigger to pg_cron invoking a Postgres function directly, removing the Edge Function dependency entirely for the scheduling layer.