critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ScenarioRule model class exists with fields: ruleId (String), chapterId (String), triggerCondition (TriggerCondition), notificationTitle (String), notificationBody (String), isActive (bool), createdAt (DateTime), and fromJson/toJson methods
TriggerCondition model class exists with fields defining the scenario trigger type and parameters, with fromJson/toJson
ScenarioRuleRepository class is implemented with methods: fetchRulesForChapter(String chapterId) → Future<List<ScenarioRule>>, fetchRuleById(String ruleId) → Future<ScenarioRule?>, upsertRule(ScenarioRule rule) → Future<void>, deleteRule(String ruleId) → Future<void>
Repository uses the injected Supabase client (via Riverpod provider) — not a singleton or static access
fetchRulesForChapter returns only active rules by default (isActive == true) with an optional includeInactive parameter
upsertRule uses Supabase upsert with onConflict: 'rule_id' to handle both insert and update
All methods throw typed exceptions (ScenarioRuleNotFoundException, ScenarioRuleRepositoryException) on error — no raw exceptions bubble up
fromJson handles null or missing optional fields gracefully without throwing
Repository is registered as a Riverpod provider and accessible via ref.read(scenarioRuleRepositoryProvider)
All public methods are covered by unit tests with mocked Supabase client

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
apis
Supabase PostgREST API (scenario_rules table)
Supabase Auth (JWT for RLS)
data models
ScenarioRule
TriggerCondition
scenario_rules (DB table)
performance requirements
fetchRulesForChapter must use a single Supabase query with .eq('chapter_id', chapterId) — no client-side filtering
Results should be ordered by createdAt descending for consistent pagination-ready output
security requirements
Repository must use the authenticated Supabase client — never use service_role key in mobile code
upsertRule and deleteRule will fail for client users due to RLS — these methods are intended for coordinator/admin flows only; document this constraint
No raw SQL construction — use Supabase Flutter SDK fluent API exclusively to prevent injection

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Follow the existing repository pattern in the codebase — examine other repository classes (e.g. activity repository or contact repository) for consistent structure before implementing. Place model classes in `lib/features/scenario_rules/data/models/` and the repository in `lib/features/scenario_rules/data/repositories/`. Use `freezed` and `json_serializable` if the codebase already uses them for model generation — do not introduce a new serialization pattern.

Define a `ScenarioRuleRepositoryProvider` using Riverpod's `Provider` or `StateNotifierProvider` following the established pattern. For error handling, wrap all Supabase calls in try/catch and rethrow as domain-specific exceptions. The TriggerCondition class likely needs to handle multiple trigger types (e.g. inactivity duration, event type) — design it as a sealed class or use a type discriminator field in JSON.

Ensure toJson produces snake_case keys matching the database column names.

Testing Requirements

Unit tests using flutter_test and a mocked SupabaseClient: (1) fetchRulesForChapter returns a List when the mock returns valid JSON; (2) fetchRulesForChapter returns an empty list when no rules exist; (3) fetchRuleById returns null when ruleId not found; (4) fetchRuleById returns a ScenarioRule when found; (5) upsertRule calls the correct Supabase upsert method with the serialized rule; (6) deleteRule calls delete with the correct ruleId filter; (7) fromJson correctly deserializes all fields including nested TriggerCondition; (8) fromJson handles missing optional fields without throwing; (9) repository throws ScenarioRuleRepositoryException when Supabase returns an error response. Integration tests (against local Supabase): (10) fetchRulesForChapter with seeded data returns only chapter-scoped rules; (11) cross-chapter read returns empty list due to RLS.

Component
Scenario Rule Repository
data medium
Epic Risks (2)
high impact medium prob security

Supabase RLS policies for chapter-scoped rule access may interact unexpectedly with service-role keys used by the Edge Function, potentially blocking backend reads or leaking cross-chapter data.

Mitigation & Contingency

Mitigation: Write and review RLS policies in isolation with automated policy tests before merging; define a dedicated service-role bypass policy scoped to the edge function's Postgres role.

Contingency: If RLS blocks the edge function, temporarily use a bypass policy with audit logging while a permanent fix is implemented; escalate to a Supabase security review.

medium impact high prob integration

FCM device tokens become invalid when users reinstall the app or revoke permissions; stale tokens cause silent delivery failures that are hard to detect without explicit error handling.

Mitigation & Contingency

Mitigation: Implement token invalidation handling in PushNotificationDispatcher that removes stale tokens from the database on FCM 404/410 responses; log all delivery failures with structured output.

Contingency: If token hygiene proves unreliable, add a periodic token refresh job that re-registers all active users' tokens via the FCM registration endpoint.