high priority low complexity backend pending backend specialist Tier 3

Acceptance Criteria

initializeDefaultPreferences(userId, orgId) inserts one row per notification category with enabled=true when no preferences exist for the user
Calling initializeDefaultPreferences() a second time for the same userId does not duplicate rows or throw an error (idempotent via ON CONFLICT DO NOTHING)
All notification categories defined in the system are seeded — no category is missing after initialization
Method is invoked automatically after role resolution completes in the post-authentication flow
RLS policy allows users to insert their own preference rows and prevents inserting rows for other users
If the database call fails (network error), the failure is logged and does not crash the auth flow — the app continues with in-memory defaults
Preferences are scoped to orgId so a user belonging to multiple organizations receives separate default rows per org
Unit test confirms that a second call returns successfully without creating duplicate records

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Dart SDK
apis
Supabase PostgreSQL REST API (upsert with onConflict: 'user_id,org_id,category')
data models
accessibility_preferences
performance requirements
Upsert must complete within 500ms on a standard mobile connection
Batch-insert all categories in a single upsert call — avoid N+1 per-category inserts
security requirements
RLS policy: authenticated users may only insert/select rows where user_id = auth.uid()
orgId must be validated against the user's JWT claims — do not accept arbitrary orgId from client
Service role key must never be used client-side; use anon key with RLS

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use supabase.from('notification_preferences').upsert(rows, onConflict: 'user_id,organization_id,category'). Build the rows list by iterating over a const NotificationCategory enum defined in constants — this ensures a single source of truth for category names. Wrap the call in a try/catch; on failure emit a PreferencesInitFailed event to a Riverpod StateNotifier so the UI can show a non-blocking snackbar. Do NOT await this call on the critical path of the auth redirect — fire-and-forget with error capture.

Add a composite unique index on (user_id, organization_id, category) if not already present in the migration.

Testing Requirements

Unit tests (flutter_test + mock Supabase client): (1) fresh user — verify all categories inserted with enabled=true; (2) existing user — verify upsert returns success with zero new rows; (3) network failure — verify error is swallowed and in-memory defaults applied; (4) multi-org user — verify separate rows created per orgId. Integration test: run full auth flow in test environment and assert preference rows exist in Supabase after login. Target 80% branch coverage on NotificationPreferencesRepository.

Epic Risks (3)
high impact medium prob scope

iOS only allows one system permission prompt per app install. If the rationale dialog timing or content is wrong the user may permanently deny permissions during onboarding, permanently blocking push delivery for that device with no recovery path short of manual system settings navigation.

Mitigation & Contingency

Mitigation: Design and user-test the rationale dialog content and trigger point (after onboarding value-demonstration step, not at first launch). Implement the settings-deep-link fallback in NotificationPermissionManager so the permission state screen always offers a path to system settings if denied.

Contingency: If denial rates are high in TestFlight testing, revise the rationale copy and trigger timing before production release. Ensure the in-app notification centre provides full value without push so denied users are not blocked from the feature.

medium impact medium prob technical

FCM token rotation callbacks can fire at any time, including during app termination or network outage. If the token rotation is not persisted reliably the backend trigger service will dispatch to a stale token, resulting in silent notification failures that are hard to diagnose.

Mitigation & Contingency

Mitigation: Persist token rotation updates with a local queue that retries on next app foreground if network is unavailable. Use Supabase upsert by (user_id, device_id) to prevent duplicate token rows and ensure the latest token always wins.

Contingency: If token staleness is observed in production, add a token validity check on each app foreground and force a re-registration if the stored token does not match the FCM-reported current token.

high impact low prob security

Incorrect RLS policies on notification_preferences or fcm_tokens could expose one user's preferences or device tokens to another user, or could block the backend Edge Function service role from reading token lists needed for dispatch, silently dropping all notifications.

Mitigation & Contingency

Mitigation: Write explicit RLS policy tests using the Supabase test harness covering user-scoped read/write, service-role read for dispatch, and cross-user access denial. Review policies during code review with a security checklist.

Contingency: Maintain a rollback migration that reverts the RLS changes, and add an integration test in CI that asserts the service role can query all tokens and that a normal user JWT cannot access another user's token rows.