Define role-aware filter predicate interfaces
epic-in-app-notification-centre-services-task-001 — Define the Dart abstract interfaces and data models for the Role-Aware Notification Filter Service. Establish the FilterPredicate sealed class hierarchy covering peer_mentor, coordinator, and org_admin scopes, and define the NotificationFilterResult type that wraps the filtered list with metadata (role used, applied rules).
Acceptance Criteria
Technical Requirements
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
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.
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.
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.
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.