critical priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

dispatchPrompts(prompts) method iterates net-new ScenarioPrompt list and calls PushNotificationDispatcher.send() for each
PushNotificationDispatcher invokes the Supabase Edge Function (server-side FCM dispatch) — the FCM server key is never used on the mobile client
After each successful dispatch, a PromptHistoryRecord is written to the Prompt History Repository with: prompt_id, scenario_id, peer_mentor_id, dispatched_at (UTC), delivery_status='dispatched'
If dispatch fails for one prompt (network error, Edge Function 5xx), that prompt's history record is written with delivery_status='failed' and the pipeline continues for remaining prompts
If dispatch succeeds but history write fails, the error is logged and treated as non-fatal — the prompt was already sent
Final pipeline result returns a DispatchSummary with counts: dispatched, failed, skipped
Empty prompts list short-circuits both dispatch and history write stages — returns DispatchSummary(0,0,0)
Unit test verifies PushNotificationDispatcher.send() is called once per prompt
Unit test verifies PromptHistoryRepository.save() is called for each prompt regardless of dispatch success/failure
Unit test verifies DispatchSummary counts match expected dispatched/failed breakdown

Technical Requirements

frameworks
Flutter
Riverpod
supabase_flutter
apis
Supabase Edge Functions (Deno) — push notification dispatch endpoint
Firebase Cloud Messaging (FCM) API v1 — invoked server-side only via Edge Function
Supabase PostgreSQL 15 — scenario_prompt_history table write
data models
device_token
performance requirements
Dispatch calls should be made concurrently using Future.wait() when batch size <= 10 to reduce total pipeline time
History writes can be fire-and-forget (unawaited) after dispatch to avoid blocking the pipeline — log any async errors
Edge Function must respond within 3 seconds — implement client-side timeout
security requirements
FCM server key and service account are only available inside the Supabase Edge Function environment — never passed from mobile client
Notification payload sent to Edge Function contains only: peer_mentor_id, scenario_id, title_key, body_key — no PII
Full notification content is fetched by the device on open — not included in push payload (data-only notification pattern)
Edge Function call authenticated with user JWT (anon/service scoped) — not open endpoint
History records must not store notification body text — only IDs and status

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Structure the dispatch stage as: final results = await Future.wait(prompts.map((p) => _dispatchOne(p))). Inside _dispatchOne: call dispatcher.send(p), then call historyRepo.save(HistoryRecord(...)). Catch all errors from send() and save() independently. Use a local record-per-prompt approach rather than bulk insert to preserve individual delivery_status accuracy.

The DispatchSummary can be built by counting results. For the Edge Function call, use supabase.functions.invoke('dispatch-scenario-prompt', body: {...}) — this uses the authenticated client so JWT is forwarded. Keep dispatch payload minimal: { 'peer_mentor_id': p.peerId, 'scenario_id': p.scenarioId, 'device_token': p.deviceToken }. Device tokens should be pre-fetched earlier in the pipeline, not looked up per-dispatch call.

Testing Requirements

Unit tests with flutter_test. Mock both PushNotificationDispatcher and PromptHistoryRepository. Test cases: (1) 3 prompts, all dispatch successfully — assert send() called 3 times, save() called 3 times with status='dispatched', DispatchSummary(3,0,0); (2) 3 prompts, second dispatch fails — assert save() called 3 times (first status='dispatched', second status='failed', third status='dispatched'), DispatchSummary(2,1,0); (3) empty list — assert neither send() nor save() called, DispatchSummary(0,0,0); (4) dispatch succeeds but save() throws — assert exception logged, pipeline does not throw. Integration test: trigger full pipeline against Supabase dev instance with a test FCM token, verify history record written with correct fields.

Component
Scenario Prompt Scheduler Service
service high
Epic Risks (2)
high impact medium prob technical

If the scheduler runs concurrently (e.g., two overlapping cron invocations due to edge function retry), duplicate prompts could be dispatched before the first run's history records are committed, breaking the deduplication guarantee.

Mitigation & Contingency

Mitigation: Use a Postgres advisory lock or unique constraint on (user_id, scenario_id, activity_ref) in the prompt history table to make concurrent writes idempotent; design the scheduler to check history inside a transaction.

Contingency: If concurrency issues persist in production, add a distributed lock via Supabase Edge Function concurrency limit (max_instances=1) for the evaluation function as a hard guard.

medium impact medium prob scope

Coordinators may find scenario configuration unclear if trigger conditions are expressed as raw JSON or technical terminology, leading to misconfiguration and irrelevant prompts being sent to peer mentors.

Mitigation & Contingency

Mitigation: Design the ScenarioConfigurationScreen to display human-readable descriptions of each template's trigger condition (e.g., 'Send 3 days after first contact if wellbeing concern was flagged') rather than raw rule properties; validate with an HLF coordinator in a design review before implementation.

Contingency: If coordinators still misconfigure rules after launch, add a preview mode that shows a simulated prompt based on a test activity before the rule is enabled.