critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

In-memory filter for peer_mentor returns only notifications directly addressed to that user — no peer mentor sees another's notifications
In-memory filter for coordinator includes notifications the coordinator must act on (e.g., escalations from assigned mentors flagged with requires_coordinator_action=true)
In-memory filter for org_admin surfaces escalation-type notifications (notification_type in ['escalation','system_alert']) at the top of the filtered list
Filter functions accept a List<NotificationModel> and a filter context object, returning a new List<NotificationModel> without mutating the input
Notifications already excluded by the DB predicate are not re-introduced by the in-memory pass
All filter functions are pure — no async operations, no database calls, no global state reads
Filter pipeline is composable: DB predicate narrowing always runs before in-memory pass
Edge case: empty input list returns empty list for all roles without throwing
Edge case: notification with null notification_type is handled gracefully (excluded or treated as non-escalation)

Technical Requirements

frameworks
Flutter
BLoC
data models
NotificationModel
UserRole
NotificationFilterContext
NotificationQueryContext
performance requirements
In-memory filter over a list of 200 notifications must complete in under 5ms on a mid-range device
No allocations of large intermediate collections — prefer where() + toList() over multiple passes
security requirements
Filter logic must never elevate a user's visibility — when in doubt, exclude rather than include
requires_coordinator_action flag must be set server-side (via Supabase function or trigger), never trusted from client payload

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define a `NotificationFilterService` class with static methods `filterForRole(UserRole role, List notifications, NotificationFilterContext ctx)` that dispatches to private `_filterPeerMentor`, `_filterCoordinator`, `_filterOrgAdmin` methods. Keep each private method as a simple functional pipeline using Dart's `where`, `sorted`, and `toList`. For coordinator, the context object must carry the set of assigned mentee IDs so the filter can check `notification.recipientId` membership. For org_admin escalation surfacing, use a stable sort that brings escalation types first without removing non-escalations.

Document the contract: this filter runs after DB results are returned and before state emission — it is never a replacement for RLS.

Testing Requirements

Unit tests (flutter_test) with no Supabase dependency. Test each role's filter with: (1) a mixed list containing notifications for self, other users, and escalations, (2) empty list, (3) list with notifications containing null fields. Verify coordinator filter correctly includes only mentee escalations for their assigned mentees, not all escalations in the org. Verify org_admin filter surfaces escalation types.

Use test fixtures (factory constructors or helper functions) to generate NotificationModel instances rather than manual JSON parsing. Aim for 100% branch coverage on filter functions. Add a regression test ensuring the filter is pure (call twice on same input, assert identical output and no mutation of original list).

Component
Role-Aware Notification Filter Service
service medium
Epic Risks (3)
medium impact medium prob technical

A Realtime INSERT event arriving during an in-flight mark-all-read operation can cause the new notification to be incorrectly marked read in the optimistic state update, silently hiding it from the user.

Mitigation & Contingency

Mitigation: Process Realtime events sequentially in the BLoC event queue using bloc_concurrency's sequential transformer. The mark-all-read event should only affect notifications whose IDs were fetched before the operation started.

Contingency: Add a reconciliation step after mark-all-read that re-fetches the unread count from the repository and corrects the BLoC state if it diverges from the server value.

medium impact medium prob technical

A coordinator assigned to many peer mentors may trigger a query returning hundreds or thousands of notifications. Without pagination and query optimisation, the initial load will be slow and memory-heavy.

Mitigation & Contingency

Mitigation: Enforce server-side pagination (50 items per page) in the Role-Aware Filter's query predicates. Add a composite index on (org_id, user_id, created_at DESC) and profile query plans before shipping.

Contingency: If query performance is insufficient for large coordinator scopes, introduce a server-side RPC function that pre-aggregates visible notification IDs and returns only the first page, deferring full scope resolution to lazy-load.

medium impact low prob scope

If the read-state optimistic update rolls back frequently due to intermittent connectivity, users will observe notifications toggling between read and unread, creating confusion and distrust of the feature.

Mitigation & Contingency

Mitigation: Queue failed mutations in a local retry store and re-attempt on next connectivity event using a connectivity-aware retry service. Show a non-intrusive banner if offline rather than applying optimistic updates.

Contingency: Disable optimistic updates for mark-as-read in low-connectivity scenarios detected by the connectivity provider, instead showing a loading indicator until server confirmation.