Implement PromptHistoryRepository data layer
epic-scenario-based-follow-up-prompts-foundation-task-007 — Implement the PromptHistoryRepository Dart class that reads and writes to the follow_up_notification_log Supabase table. Expose methods: hasBeenDelivered(idempotencyKey) for deduplication checks, recordDelivery(PromptDeliveryRecord), getDeliveryHistoryForMentor(mentorId, chapterId), and getDeliveryHistoryForChapter(chapterId). Include the PromptDeliveryRecord typed model with fromJson/toJson.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
The critical design decision here is the insert pathway for recordDelivery. Since RLS blocks authenticated client inserts, two options exist: (a) Create a Supabase Edge Function `record_prompt_delivery` that accepts the PromptDeliveryRecord payload, validates the caller's JWT, and inserts using the service_role client internally. This is the recommended approach as it keeps service_role credentials server-side. (b) Create a SECURITY DEFINER Postgres function accessible via RPC that validates the caller matches the peer_mentor_id in the record before inserting.
Option (a) is preferred. Place the Edge Function in `supabase/functions/record-prompt-delivery/`. The mobile repository calls `supabase.functions.invoke('record-prompt-delivery', body: record.toJson())`. Coordinate with task-004 implementer to confirm which pathway was decided.
For the model, use Dart enums with a fromString factory for DeliveryChannel and DeliveryStatus to handle JSON deserialization robustly. Place the repository in `lib/features/scenario_rules/data/repositories/prompt_history_repository.dart` alongside the ScenarioRuleRepository.
Testing Requirements
Unit tests with flutter_test: (1) hasBeenDelivered returns true when mock returns a non-empty result for the given idempotency_key; (2) hasBeenDelivered returns false when mock returns empty result; (3) recordDelivery calls the correct Edge Function or RPC with the serialized PromptDeliveryRecord; (4) recordDelivery throws PromptHistoryRepositoryException when the insert fails; (5) getDeliveryHistoryForMentor returns a correctly deserialized list sorted by deliveredAt; (6) getDeliveryHistoryForChapter returns all chapter records; (7) fromJson handles all DeliveryChannel and DeliveryStatus enum values correctly; (8) fromJson with unknown enum value throws a descriptive error rather than crashing silently. Integration tests (local Supabase): (9) insert a record via service_role, then call hasBeenDelivered — assert true; (10) attempt direct insert via authenticated client — assert permission denied; (11) coordinator reads chapter history — assert all entries visible; (12) peer mentor reads own history via getDeliveryHistoryForMentor — assert only own entries.
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.
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.