critical priority low complexity database pending database specialist Tier 0

Acceptance Criteria

Supabase table notification_preferences exists with columns: id (uuid PK), user_id (uuid FK → auth.users), category (text), enabled (boolean), updated_at (timestamptz)
Category column has a CHECK constraint permitting only 'new_assignment', 'deadline_approaching', 'status_change' (extensible via migration, not hardcoded in app)
RLS policy SELECT: users can only read rows where user_id = auth.uid()
RLS policy INSERT/UPDATE: users can only insert or update rows where user_id = auth.uid(); no DELETE needed — use upsert
NotificationPreferencesRepository.getPreferences(userId) returns a Map<String, bool> of all category preferences for the user, defaulting missing categories to true
NotificationPreferencesRepository.setPreference(userId, category, enabled) upserts the row and returns the updated preference
Riverpod provider caches the preference map and invalidates on setPreference so consumers rebuild reactively
All repository methods throw a typed NotificationRepositoryException (not raw PostgrestException) on network or RLS errors
Repository is covered by integration tests against a local Supabase instance confirming RLS prevents cross-user reads

Technical Requirements

frameworks
Flutter
Riverpod
Supabase
apis
Supabase PostgREST (notification_preferences table)
Supabase Auth (auth.uid())
data models
NotificationPreference
NotificationCategory (enum)
performance requirements
getPreferences reads from local Riverpod cache on subsequent calls — at most one network round trip per session unless invalidated
setPreference upsert completes within 2 seconds on standard network
security requirements
RLS is the enforcement layer — repository never filters by userId client-side as the sole guard
user_id is always sourced from the current Supabase session (auth.uid()) — never passed as a raw parameter that could be spoofed
No preference data is written to device storage (SharedPreferences or similar) — only in-memory Riverpod cache

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define NotificationCategory as a Dart enum with a toKey() method returning the string values — this prevents typos and centralises the category key definitions. In the repository, use Supabase's .upsert() with onConflict: 'user_id,category' to handle both insert and update in a single call. The Riverpod provider should be a FutureProvider.family, String>(userId) or an AsyncNotifierProvider so it can be invalidated after setPreference. Defaulting missing categories to true (opt-in by default) aligns with the workshop requirement that notifications are on unless explicitly disabled.

Keep the schema migration in a versioned SQL file under supabase/migrations/. Add an index on (user_id, category) for fast lookups.

Testing Requirements

Unit tests with a mocked Supabase client: verify getPreferences returns correct map including defaults for missing categories; verify setPreference calls upsert with correct parameters; verify typed exception is thrown on client error. Integration test against local Supabase: (1) user A can read and write their own preferences; (2) user A cannot read user B's preferences (RLS enforcement); (3) upsert correctly updates an existing row without creating duplicates. Use flutter_test + mocktail for unit tests. Run integration tests in CI against supabase start local instance.

Epic Risks (4)
high impact high prob technical

Flutter's background message handler for FCM must run in a separate Dart isolate. Incorrect dependency initialization in the isolate (e.g., attempting to access Riverpod providers or Supabase before initialization) will cause silent crashes on Android when the app is terminated, resulting in missed notifications that are invisible in crash reporting.

Mitigation & Contingency

Mitigation: Use a minimal top-level background handler function annotated with @pragma('vm:entry-point') that only stores the raw RemoteMessage payload to a platform channel or shared preferences. Process the payload in the main isolate on next app launch. Write an explicit test for terminated-state message handling on Android.

Contingency: If isolate crashes are observed, implement a native Android FirebaseMessagingService subclass that handles background messages without Flutter isolate complexity, falling back to a database-insert-only approach for terminated-state notifications.

medium impact medium prob technical

Supabase Edge Functions can experience cold-start latency of 1–3 seconds after periods of inactivity. For high-frequency events like assignment creation, cumulative cold starts could cause dispatch delays exceeding the 30-second SLA, reducing the perceived reliability of the notification system.

Mitigation & Contingency

Mitigation: Configure the Edge Function with a keep-warm ping mechanism or use Supabase database webhooks that invoke the function directly on row insert to minimize cold-start frequency. Batch preference lookups within the function to reduce per-invocation Supabase round-trips.

Contingency: If latency SLA is consistently breached, move to a polling or Realtime-subscription architecture within the Edge Function, or pre-compute dispatch targets at preference-save time to eliminate per-dispatch preference queries.

high impact low prob security

If the deep link handler does not perform server-side role validation before rendering the target screen, a peer mentor who receives a mis-configured notification payload containing a coordinator-only route could access restricted data, violating the role-based access control invariants.

Mitigation & Contingency

Mitigation: The deep link handler must check the user's current role from the RoleStateManager before constructing the navigation route. Coordinator-only routes must be listed in a deny-list checked against the current role. The go_router route guard is a second line of defence.

Contingency: If a role bypass is discovered in testing, immediately add the affected route to the deep link handler deny-list and add a regression test. Audit all notification payload types for route targets that could expose cross-role data.

medium impact low prob dependency

FCM v1 HTTP API enforces per-project send quotas. For large organisations with many active peer mentors receiving simultaneous assignment notifications, batch dispatch events (e.g., bulk coordinator assignments) could approach quota limits and result in dropped notifications with 429 errors logged silently.

Mitigation & Contingency

Mitigation: Implement exponential backoff retry logic in the Edge Function for 429 responses. Design bulk assignment flows to dispatch notifications in batches with a configurable delay between batches. Monitor FCM console quotas during load testing.

Contingency: If quota limits are hit, implement a notification queue table in Supabase and a separate Edge Function that processes the queue with rate limiting, ensuring eventual delivery without exceeding FCM quotas.