critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

NotificationPreferencesRepository is a Dart class with a private constructor and a Riverpod Provider exposing the singleton instance
getPreferences(userId, scenarioType) returns a NotificationPreference model with opted_in = true when no row exists in the database (virtual default)
getAllForUser(userId) returns a List<NotificationPreference> for all scenario types, with opted_in = true for any scenario type not yet persisted
upsertPreference(userId, scenarioType, optedIn) performs an INSERT ... ON CONFLICT (user_id, scenario_type) DO UPDATE and returns the saved model
All methods throw a typed NotificationPreferencesException (not a raw Exception) on Supabase network or auth errors
Repository does not hold mutable state — it is a stateless data access object
Riverpod provider is exported from a providers.dart barrel file
All public methods are covered by unit tests using a mocked SupabaseClient

Technical Requirements

frameworks
Dart
Riverpod
Supabase Dart client
apis
Supabase REST (from(), upsert(), select())
data models
NotificationPreference (userId, scenarioType, optedIn, updatedAt, orgId)
notification_preferences Supabase table
performance requirements
getPreferences() must return in under 500ms on a standard mobile connection
No unnecessary network calls — do not fetch all preferences when only one scenario type is needed
security requirements
Repository uses the authenticated user's Supabase client — never the service role key on the client side
userId parameter must match auth.uid() — repository should assert or let RLS enforce this
No caching of opted_in state beyond the current screen lifecycle — stale opt-out state is a privacy risk

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define a NotificationPreference Dart model with fromJson/toJson using the standard Supabase response map format. For the 'virtual default' pattern in getPreferences() — when the Supabase query returns an empty list, return NotificationPreference(userId: userId, scenarioType: scenarioType, optedIn: true, ...) without inserting a row. This avoids unnecessary writes and keeps the database clean until the user explicitly changes a preference. For getAllForUser(), define a list of known ScenarioType enum values and merge the database results against that list, filling in opted_in=true defaults for missing types.

Register the repository with a Provider that receives SupabaseClient from a supabaseClientProvider. Follow the existing Riverpod patterns in the codebase — do not introduce a new DI pattern.

Testing Requirements

Unit tests using flutter_test with a mocked SupabaseClient (via mockito or manual stub). Test cases: (1) getPreferences returns NotificationPreference with opted_in=true when Supabase returns empty list. (2) getPreferences returns correct model when Supabase returns a row with opted_in=false. (3) upsertPreference calls upsert with correct payload and returns mapped model.

(4) upsertPreference wraps SupabaseException in NotificationPreferencesException. (5) getAllForUser returns list with opted_in=true defaults for missing scenario types. Minimum 5 unit tests. No integration tests in this task — those are covered by the scheduler integration tests.

Epic Risks (3)
high impact medium prob dependency

FCM service account key and APNs certificate configuration may be missing or misconfigured in the Supabase Edge Function secrets store, blocking end-to-end push delivery testing until resolved by the infrastructure owner.

Mitigation & Contingency

Mitigation: Raise a credentials-setup task in the project board at epic start; document the exact secret names required in scenario-evaluation-config so the infrastructure owner can provision them independently of development work.

Contingency: Implement a mock push-notification-dispatcher stub that records payloads to the database for local testing, allowing the rest of the feature to proceed while credentials are obtained.

high impact low prob security

Incorrect RLS policies on the scenario_notifications or notification_preferences tables could allow one user to read or modify another user's notification records, constituting a data privacy breach.

Mitigation & Contingency

Mitigation: Write dedicated RLS policy tests using Supabase's built-in test framework before any application code touches the tables; require a peer security review of all policy definitions before merging.

Contingency: If a policy gap is discovered post-merge, immediately disable the affected table's read policy, notify the security lead, and deploy a hotfix with corrected policies before re-enabling access.

medium impact medium prob dependency

Norwegian Bokmål ARB localisation strings for all scenario message templates may not be available at implementation time, causing content-builder tests to fail and delaying integration.

Mitigation & Contingency

Mitigation: Define all required ARB message keys in a tracked document shared with the content owner at epic kickoff; use English placeholder strings that follow the final format so template injection logic can be tested independently.

Contingency: Ship with English-only strings in the first release and gate Norwegian strings behind a feature flag that is enabled once translations are reviewed and approved.