high priority medium complexity testing pending integration specialist Tier 5

Acceptance Criteria

FCM notification is dispatched exactly once when claim transitions to 'approved' status
FCM notification is dispatched exactly once when claim transitions to 'rejected' status
No FCM notification is dispatched for intermediate status transitions (e.g., pending → under_review)
Realtime event is published to channel 'approval-status:{claimId}' upon status change
Coordinator notification payload contains claimant_name, amount, currency, and claim_id fields
When FCM token is stale/invalid, service refreshes token and retries delivery exactly once without throwing
When both FCM and Realtime channels are active, each notification is delivered exactly once across the two channels — no double delivery on the FCM channel
If FCM delivery fails after retry, a typed FcmDeliveryFailure is returned — Realtime delivery continues independently
All integration tests use mocked FCM client and mocked Supabase Realtime — no real push notification infrastructure required

Technical Requirements

frameworks
flutter_test
mocktail
apis
Firebase Cloud Messaging (FCM) HTTP v1 API
Supabase Realtime broadcast
data models
ApprovalNotificationPayload
FcmDeliveryResult
FcmDeliveryFailure
ClaimMetadata
performance requirements
Notification dispatch must complete within 3 seconds under normal conditions
FCM token refresh retry must not delay Realtime event publication
security requirements
FCM server key must never be stored client-side — all FCM calls must be proxied through a Supabase Edge Function
Coordinator notification must not include sensitive personal data beyond the required fields (name, amount, claim_id)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

ApprovalNotificationService should depend on an FcmGateway interface and a RealtimeBroadcaster interface — both injectable — so tests can mock them independently. FCM calls must be routed through a Supabase Edge Function (never direct from client) because FCM server keys cannot be embedded in mobile apps. In the service: only dispatch FCM for terminal states (approved, rejected); use a Set of already-dispatched claim_ids per session to prevent duplicate delivery when both channels are open simultaneously. Token refresh: catch InvalidTokenException, call FcmGateway.refreshToken(userId), then retry once; surface FcmDeliveryFailure if retry also fails.

This dual-channel pattern (FCM for background, Realtime for foreground) ensures coordinators always receive notifications regardless of app state — critical for the HLF follow-up workflow.

Testing Requirements

Integration tests using flutter_test with mocktail. Mock both the FCM gateway interface and the Supabase Realtime broadcast interface. Group tests: (1) approved/rejected trigger FCM, (2) non-terminal transitions do not trigger FCM, (3) Realtime broadcast correctness, (4) FCM token refresh flow, (5) duplicate suppression with both channels active, (6) FCM failure isolation. Use verify(mockFcmGateway.send(captureAny)) to assert payload contents.

Use verifyNever to assert no spurious FCM calls. For token refresh: stub the first FCM call to throw InvalidTokenException, then verify the service fetches a new token and retries once.

Component
Approval Status Notification Service
service medium
Epic Risks (3)
medium impact medium prob technical

Optimistic locking in ExpenseClaimStatusRepository may produce excessive concurrency exceptions in high-volume coordinator sessions where multiple coordinators process the same queue simultaneously, causing confusing UI errors and coordinator frustration.

Mitigation & Contingency

Mitigation: Design the locking strategy with a short retry window (1-2 automatic retries with 200ms back-off) before surfacing the error to the UI. Document the concurrency model clearly so the UI layer can display a contextual 'claim was already actioned' message rather than a generic error.

Contingency: If contention remains high under load testing, switch to a last-writer-wins update with a conflict notification rather than a hard block, and log all concurrent edits for audit purposes.

medium impact medium prob integration

FCM device tokens stored for peer mentors may be stale (app reinstalled, token rotated) causing push notifications for claim status changes to silently fail, leaving submitters unaware their claim was approved or rejected.

Mitigation & Contingency

Mitigation: Implement token refresh on every app launch and store updated tokens in Supabase. ApprovalNotificationService should fall back to in-app Realtime delivery when FCM returns an invalid-token error and should queue a token refresh request.

Contingency: If FCM delivery rates fall below acceptable thresholds in production monitoring, add a polling fallback in the peer mentor claim list screen that checks status on foreground resume.

high impact low prob dependency

Supabase Realtime has per-project channel and connection limits. If many coordinators and peer mentors are simultaneously subscribed across multiple screens, the project may hit quota limits causing subscription failures.

Mitigation & Contingency

Mitigation: Design RealtimeApprovalSubscription to use a single shared channel per user session rather than per-screen subscriptions. Implement subscription reference counting so channels are only opened once and reused across screens.

Contingency: Upgrade the Supabase plan tier if limits are reached, and implement graceful degradation to polling with a 30-second interval when Realtime is unavailable.