critical priority medium complexity infrastructure pending infrastructure specialist Tier 4

Acceptance Criteria

PushNotificationDispatcher exposes a dispatch(NotificationPayload payload) method accepting title, body, data map, and target peer_mentor_id
Dispatcher resolves the FCM/APNs token for the target peer_mentor_id by querying the device_token table via the existing FCM token manager
Dispatch is executed server-side via a Supabase Edge Function — FCM server key is never present in the Flutter client binary
When a token is reported as invalid by FCM/APNs, the corresponding device_token row is soft-deleted or marked inactive in the database
On token refresh, the new token is upserted to the device_token table and the stale token is removed atomically
Delivery failures are logged to a structured log sink (console.error JSON) with peer_mentor_id, error code, and timestamp — never logging PII such as full names
If no active device token exists for the target peer_mentor_id, the method returns a DispatchResult.noToken result without throwing
NotificationPayload data map is limited to non-PII fields (row IDs, status codes) — full content is fetched by the client on notification open
Dispatcher is covered by unit tests with a mocked Edge Function client returning success, invalid-token, and network-error scenarios
Platform field in device_token (ios/android) is used to select the correct FCM message configuration (APNS headers vs. Android notification priority)

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
Supabase Edge Functions (Deno)
apis
Firebase Cloud Messaging (FCM) API v1 — server-side via Edge Function only
Supabase REST API for device_token CRUD
Supabase Edge Function invocation endpoint
data models
device_token (id, user_id, token, platform, registered_at)
performance requirements
dispatch() must resolve the device token and invoke the Edge Function within 3 seconds under normal network conditions
Batch dispatch for multiple mentors must process tokens concurrently using Future.wait with a concurrency cap of 10
security requirements
FCM server key and service account credentials stored exclusively in Edge Function environment — never in Flutter app bundle
All push dispatch calls routed through server-side Edge Functions only
Notification payloads contain only row IDs and status codes — no PII in push payload
FCM tokens treated as sensitive identifiers — stored in device_token table protected by RLS (user can read/write own tokens only)
Invalid token removal must be idempotent to prevent race conditions on concurrent dispatch

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement PushNotificationDispatcher as a plain Dart class (not a widget) injected via Riverpod. The class should hold a reference to SupabaseClient and invoke a named Edge Function (e.g., 'dispatch-push-notification') via supabase.functions.invoke(). The Edge Function receives { mentorId, title, body, data } and resolves the FCM token server-side before calling the FCM HTTP v1 API. On the Flutter side, keep the dispatcher thin — its only responsibilities are payload assembly and result parsing.

Use a sealed class (DispatchResult) with variants sent, noToken, tokenRevoked, failed for exhaustive handling by callers. Register device tokens using the platform field from the device_token schema to route APNS-specific headers correctly in the Edge Function. Never log the raw FCM token string at info level — use a hashed or truncated form for debugging.

Testing Requirements

Unit tests (flutter_test) with a mocked Supabase client and a stubbed Edge Function HTTP client. Cover: (1) successful dispatch returns DispatchResult.sent, (2) invalid FCM token triggers token deletion and returns DispatchResult.tokenRevoked, (3) no device token for mentor returns DispatchResult.noToken without exception, (4) Edge Function HTTP 500 returns DispatchResult.failed with logged error, (5) token refresh upserts new token and deletes old. Integration test against a local Supabase emulator verifying device_token table mutations. Minimum 80% line coverage on PushNotificationDispatcher.

Component
Push Notification Dispatcher
infrastructure 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.