critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

An abstract `INotificationRepository` interface is defined and `NotificationRepository` implements it, enabling dependency injection and testability
`getNotifications(String userId, {int limit = 20, int offset = 0, NotificationType? typeFilter})` returns a `Future<List<Notification>>` ordered by `created_at` descending
For coordinator and org-admin roles, `getNotifications` expands the query to also include notifications for all mentors under their scope (role-aware query expansion via Supabase join or RPC)
`getUnreadCount(String userId)` returns a `Future<int>` reflecting only unread notifications accessible to the user
`markAsRead(String notificationId)` updates `is_read = true` and `updated_at = now()` for the given notification; returns `Future<void>` and throws if the row is not found or not owned by the caller
`markAllAsRead(String userId)` bulk-updates all unread notifications for the user; returns `Future<int>` (count of rows updated)
`deleteNotification(String notificationId)` soft-deletes or hard-deletes the notification; returns `Future<void>` and throws on ownership mismatch
All methods surface Supabase errors as typed domain exceptions (e.g., `NotificationNotFoundException`, `NotificationAccessDeniedException`) rather than raw `PostgrestException`
All queries operate within the constraints of Supabase RLS policies — no query bypasses row-level security
Repository is injectable via Riverpod `Provider` or BLoC `ServiceLocator`
Integration tests verify each method against a local Supabase instance or mock, including the role-expansion path for coordinator role
Pagination (limit/offset) is correctly applied and tested with a dataset of >20 rows

Technical Requirements

frameworks
Flutter
Supabase Flutter SDK
Riverpod
apis
Supabase PostgREST API (notifications table)
Supabase RPC (if role-expansion requires a stored procedure)
data models
Notification
NotificationType
NotificationPayload
performance requirements
getNotifications with default limit=20 must return in under 500ms on a 4G connection
getUnreadCount must return in under 200ms (uses a COUNT query, not full row fetch)
markAllAsRead must use a single bulk UPDATE, not N individual updates
security requirements
Never pass raw user input directly into query filters without parameterization (use Supabase SDK's `.eq()` / `.filter()` methods, not string interpolation)
Role-expansion logic must be server-side (RLS or RPC) — client must not receive rows it then filters locally
Coordinator scope expansion must be validated server-side to prevent privilege escalation

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define `INotificationRepository` as an abstract class first — this enables BLoC/Riverpod layers and tests to depend on the interface, not the concrete Supabase implementation. For role-aware query expansion, prefer a Supabase RPC (stored function) over client-side joins to keep the logic server-side and RLS-safe. The coordinator expansion query should accept `userId` and return the union of the user's own notifications plus those of their managed mentors. Map all `PostgrestException` errors in a `_handleError(Object e)` helper that converts to domain exceptions — this keeps all Supabase-specific error codes in one place.

Use `Notification.fromJson` (from task-003) for row mapping. For Riverpod, expose as a `Provider` overridable in tests. Implement a `const NotificationRepositoryImpl` if no mutable state is needed at construction time.

Testing Requirements

Two test layers: (1) Unit tests using `flutter_test` with a mocked `SupabaseClient` (via `mockito` or `mocktail`) — verify each method calls the correct Supabase table/RPC with the expected arguments and maps responses to domain objects. (2) Integration tests against a local Supabase instance (Docker) — verify RLS policies are respected, role-expansion returns the correct superset for coordinator, pagination returns correct slices, and error paths (not found, forbidden) throw the expected domain exceptions. Target 90%+ branch coverage on the repository class itself.

Component
Notification Repository
data medium
Epic Risks (3)
high impact medium prob technical

Supabase Realtime channels on mobile networks can drop silently. If reconnection logic is flawed, users miss notifications without knowing it, undermining the audit-trail guarantee.

Mitigation & Contingency

Mitigation: Implement exponential-backoff reconnection with a maximum of 5 retries; expose a channel-status stream to the BLoC so it can trigger a full-fetch fallback when the channel reconnects after a gap.

Contingency: If Realtime reliability proves insufficient in production, fall back to polling the repository every 60 seconds as a background supplement to the Realtime channel.

high impact medium prob security

Coordinator and org-admin RLS expansions require joining user_roles and org_memberships tables. An incorrect policy could expose notifications to wrong users or block legitimate access entirely.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration tests for each role (peer mentor, coordinator, org admin) using separate Supabase test projects. Review policies with the security checklist before merging.

Contingency: If an RLS defect is discovered post-deployment, disable the expanded-scope policy and revert to user-scoped-only access while a corrected migration is prepared and tested.

medium impact medium prob integration

JSONB payload structure may vary across notification types created by different Edge Functions (reminder, expiry, scenario, pause). Missing or renamed fields will cause runtime parse failures.

Mitigation & Contingency

Mitigation: Define a canonical NotificationPayload union type in a shared schema document. Each Edge Function must validate its payload against this schema before inserting. Add fallback parsing with default values in the domain model.

Contingency: Wrap all payload parsing in try/catch and log malformed payloads to a monitoring channel; render a generic notification item rather than crashing when the payload cannot be parsed.