critical priority medium complexity backend pending fullstack developer Tier 2

Acceptance Criteria

FCM payload must include a 'route_type' field (e.g., 'contact_detail', 'activity_detail', 'assignment_detail') and a 'resource_id' field; missing either field falls back to the home route
resolveRoute(RemoteMessage message) parses the payload and constructs the target go_router path string (e.g., '/contacts/:id', '/activities/:id')
Before navigating, validateResource(String routeType, String resourceId) queries Supabase to confirm the resource exists and is accessible to the current user — navigation is blocked if the row is not found or RLS denies access
On missing resource, the handler navigates to a typed fallback route defined per routeType (e.g., '/contacts' for a missing contact detail) and shows a SnackBar: 'This item is no longer available'
Cold-start navigation (app terminated state) is handled via FirebaseMessaging.instance.getInitialMessage() called once in main.dart and passed to the handler after router is ready
Foreground tap navigation is wired to FirebaseMessaging.onMessageOpenedApp stream and routes via the same resolveRoute/validateResource pipeline
Role-aware guards: if the resolved route requires a role the current user does not have, navigate to '/no-access' instead
All supported routeType values and their corresponding go_router paths are defined in a single RouteTypeRegistry const map — no string literals scattered in handler logic
Handler is idempotent: receiving the same message twice does not trigger double navigation

Technical Requirements

frameworks
Flutter
go_router
firebase_messaging
Riverpod
apis
FirebaseMessaging.instance.getInitialMessage()
FirebaseMessaging.onMessageOpenedApp stream
Supabase PostgREST select with .maybeSingle()
go_router GoRouter.of(context).go()
data models
contacts
activities
assignments
user_roles
performance requirements
Resource validation must complete within 2 seconds; if it times out, navigate to the fallback route
Cold-start navigation must not delay app first render — defer until after the first frame via SchedulerBinding.instance.addPostFrameCallback
security requirements
Resource validation queries must use the authenticated Supabase client so RLS policies apply — never bypass RLS with a service role key on the client
Do not expose internal resource IDs in error messages shown to users
ui components
SnackBar for 'item no longer available' feedback

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define a RouteTypeRegistry as a const Map where DeepLinkRoute holds the path template, required roles, and fallback path. This makes adding new deep link types trivial — no handler logic changes needed. For resource validation, use Supabase's .maybeSingle() which returns null instead of throwing when the row is not found — avoids exception handling for the common case. The handler should NOT own a GoRouter reference directly; instead emit navigation intents via a Riverpod StateProvider and let a router-level listener consume them.

This avoids context dependency issues with cold-start. Implement a simple debounce (300ms) on the onMessageOpenedApp stream to guard against duplicate events.

Testing Requirements

Unit tests using flutter_test and mocktail. Mock FirebaseMessaging, Supabase client, and GoRouter. Test cases: (1) valid payload with existing resource — correct route navigated, (2) valid payload with missing resource — fallback route + SnackBar, (3) payload missing route_type — home fallback, (4) payload missing resource_id — home fallback, (5) user lacks required role — '/no-access' route, (6) cold-start message — deferred navigation after first frame, (7) onMessageOpenedApp fires — same pipeline triggered, (8) duplicate message — navigation triggered only once. Integration test: simulate cold start with a mock FCM message in flutter_test environment.

Target 90%+ line coverage.

Component
Notification Deep Link Handler
service medium
Epic Risks (4)
high impact high prob technical

Flutter's background message handler for FCM must run in a separate Dart isolate. Incorrect dependency initialization in the isolate (e.g., attempting to access Riverpod providers or Supabase before initialization) will cause silent crashes on Android when the app is terminated, resulting in missed notifications that are invisible in crash reporting.

Mitigation & Contingency

Mitigation: Use a minimal top-level background handler function annotated with @pragma('vm:entry-point') that only stores the raw RemoteMessage payload to a platform channel or shared preferences. Process the payload in the main isolate on next app launch. Write an explicit test for terminated-state message handling on Android.

Contingency: If isolate crashes are observed, implement a native Android FirebaseMessagingService subclass that handles background messages without Flutter isolate complexity, falling back to a database-insert-only approach for terminated-state notifications.

medium impact medium prob technical

Supabase Edge Functions can experience cold-start latency of 1–3 seconds after periods of inactivity. For high-frequency events like assignment creation, cumulative cold starts could cause dispatch delays exceeding the 30-second SLA, reducing the perceived reliability of the notification system.

Mitigation & Contingency

Mitigation: Configure the Edge Function with a keep-warm ping mechanism or use Supabase database webhooks that invoke the function directly on row insert to minimize cold-start frequency. Batch preference lookups within the function to reduce per-invocation Supabase round-trips.

Contingency: If latency SLA is consistently breached, move to a polling or Realtime-subscription architecture within the Edge Function, or pre-compute dispatch targets at preference-save time to eliminate per-dispatch preference queries.

high impact low prob security

If the deep link handler does not perform server-side role validation before rendering the target screen, a peer mentor who receives a mis-configured notification payload containing a coordinator-only route could access restricted data, violating the role-based access control invariants.

Mitigation & Contingency

Mitigation: The deep link handler must check the user's current role from the RoleStateManager before constructing the navigation route. Coordinator-only routes must be listed in a deny-list checked against the current role. The go_router route guard is a second line of defence.

Contingency: If a role bypass is discovered in testing, immediately add the affected route to the deep link handler deny-list and add a regression test. Audit all notification payload types for route targets that could expose cross-role data.

medium impact low prob dependency

FCM v1 HTTP API enforces per-project send quotas. For large organisations with many active peer mentors receiving simultaneous assignment notifications, batch dispatch events (e.g., bulk coordinator assignments) could approach quota limits and result in dropped notifications with 429 errors logged silently.

Mitigation & Contingency

Mitigation: Implement exponential backoff retry logic in the Edge Function for 429 responses. Design bulk assignment flows to dispatch notifications in batches with a configurable delay between batches. Monitor FCM console quotas during load testing.

Contingency: If quota limits are hit, implement a notification queue table in Supabase and a separate Edge Function that processes the queue with rate limiting, ensuring eventual delivery without exceeding FCM quotas.