critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

A `FilterPredicate` sealed class is defined in the domain layer with three concrete subtypes: `PeerMentorFilterPredicate`, `CoordinatorFilterPredicate`, `OrgAdminFilterPredicate`
Each `FilterPredicate` subtype exposes a `bool matches(Notification notification)` method
A `NotificationFilterResult` class is defined with fields: `filteredNotifications` (List<Notification>), `roleUsed` (UserRole), `appliedRules` (List<String> — human-readable rule descriptions)
A `NotificationFilterService` abstract interface (abstract class) is defined with at least: `NotificationFilterResult filter(List<Notification> notifications, UserRole role)`
All types are placed in the correct domain layer path: `lib/features/notifications/domain/`
No concrete filtering logic is implemented in this task — only interfaces and data models (logic is deferred to the implementation task)
All new classes have correct Dart nullability annotations and no dynamic types
The file compiles without errors via `flutter build` or `flutter analyze`
The `UserRole` enum used is the app's existing shared role enum (not a new duplicate)
The sealed class hierarchy prevents exhaustiveness gaps: a switch over `FilterPredicate` subtypes produces a compile warning if a case is missing

Technical Requirements

frameworks
Flutter
Dart sealed classes (Dart 3.0+)
data models
Notification
UserRole
FilterPredicate
NotificationFilterResult
NotificationFilterService
performance requirements
Interfaces must not introduce any runtime overhead — pure abstract definitions
security requirements
Role types must map exactly to the app's existing RBAC role definitions (coordinator, peer_mentor, org_admin) — no new role names introduced
Filter logic must enforce that a user can only receive notifications scoped to their own role

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart 3.0 `sealed class` for `FilterPredicate` to get compiler-enforced exhaustiveness in switch expressions — this is critical for maintainability as new roles may be added. Keep `NotificationFilterResult` immutable: use `final` fields and a `const` constructor where possible. Define the `appliedRules` field as `List` rather than an enum so rule descriptions can be human-readable strings useful for debugging and audit logging. The abstract `NotificationFilterService` interface should be a pure abstract class (no `extends` or `with` mixins) to serve as a clean DI contract.

Ensure the `UserRole` import comes from the shared auth domain (`lib/core/auth/domain/user_role.dart` or equivalent) — do not redefine roles. This task is a prerequisite for the filter service implementation and BLoC integration, so correctness of the interface contract is more important than completeness of logic.

Testing Requirements

Unit tests are not the primary deliverable of this task (interfaces have no behavior), but write at minimum: (1) an instantiation test for each FilterPredicate subtype to verify they can be constructed, (2) a NotificationFilterResult construction test verifying all fields are set correctly, (3) a sealed class exhaustiveness test — a switch statement over all FilterPredicate subtypes compiles without a default case. Place in `test/features/notifications/domain/filter_predicate_test.dart`. These tests serve as compile-time contracts ensuring the interfaces are correctly shaped.

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.