critical priority medium complexity infrastructure pending infrastructure specialist Tier 3

Acceptance Criteria

`SupabaseRealtimeSubscriptionService` is a class with a `subscribe(String userId)` method that opens a Supabase Realtime channel filtered to `user_id=eq.{userId}` on the `notifications` table
The service exposes a `Stream<Notification> notificationStream` that emits a typed `Notification` domain object for every INSERT event received on the channel
The service exposes a `Stream<RealtimeConnectionStatus> connectionStatusStream` with states: connected, disconnected, reconnecting, error
On `SupabaseClient.auth.onAuthStateChange` emitting a new session (e.g., token refresh), the service automatically unsubscribes the old channel and re-subscribes with the updated userId
On auth sign-out, the service unsubscribes and closes the channel without emitting errors downstream
`dispose()` method unsubscribes from all channels, closes all `StreamController`s, and cancels all auth-state listeners — safe to call multiple times (idempotent)
Malformed INSERT payloads (JSONB parse failures) are caught, logged in verbose mode only, and do NOT crash the stream or stop future events
The channel uses `RealtimeChannelConfig` with `ack: false` and the postgres_changes event for INSERT only (not UPDATE or DELETE) to minimize traffic
Integration test verifies that inserting a row into the notifications table emits a matching `Notification` on the stream within 3 seconds
Unit tests verify reconnection logic is triggered on auth state change
The service is injectable via a Riverpod `Provider` or registered with a service locator; consumers do not instantiate it directly

Technical Requirements

frameworks
Flutter
Supabase Flutter SDK (realtime)
Riverpod
apis
Supabase Realtime (postgres_changes, INSERT filter)
Supabase Auth (onAuthStateChange)
data models
Notification
NotificationType
NotificationPayload
performance requirements
INSERT events must be received and emitted to the stream within 1 second of the database write under normal network conditions
Re-subscription after auth token refresh must complete within 2 seconds
Memory: no stream events should be buffered indefinitely — use broadcast streams
security requirements
The Realtime channel filter `user_id=eq.{userId}` ensures server-side row filtering — the client never receives other users' notifications
The userId used in the filter must be sourced exclusively from the authenticated Supabase session, never from client-supplied parameters
Channel names must not contain sensitive data (use a UUID-based channel name, not the user's email or display name)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use `supabase.channel('notifications-{uuid}').on(RealtimeListenTypes.postgresChanges, ChannelFilter(event: 'INSERT', schema: 'public', table: 'notifications', filter: 'user_id=eq.$userId'), callback)` API. Use a `StreamController.broadcast()` so multiple listeners (BLoC + accessibility announcer) can subscribe independently. In the INSERT callback, call `Notification.fromJson(payload['new'])` inside a try/catch — on error, call `debugPrint` (or the app's verbose logger) and return without adding to the stream. For the connection status stream, listen to the channel's `subscribe((status, error) { ...

})` callback and map `RealtimeSubscribeStatus` values to your `RealtimeConnectionStatus` enum. Store the auth subscription as a `StreamSubscription` and cancel it in `dispose()`. Expose via Riverpod as a `StateNotifierProvider` or a plain `Provider` that is `.autoDispose`-aware so the channel is closed when no UI is listening. For the reconnection path, call `channel.unsubscribe()` then re-call `subscribe(newUserId)` — do not reuse the old channel object.

Testing Requirements

Two test layers: (1) Unit tests with mocked `SupabaseClient` and `RealtimeChannel` — verify `subscribe()` constructs the correct filter string, `dispose()` calls `unsubscribe()` on the channel, malformed payloads do not propagate errors to the stream, and auth state change triggers re-subscription. (2) Integration test against a local Supabase instance — insert a notification row for the subscribed userId and assert the stream emits a correctly typed `Notification` within 3 seconds; insert a row for a different userId and assert no emission. Use `StreamQueue` from the `async` package for stream testing. Target 85%+ branch coverage on the service class.

Component
Supabase Realtime Subscription Service
infrastructure 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.