critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

getPauseStatus(mentorId: String) returns a PauseRecord? (nullable) representing the current active/paused/expired_cert row, or null if no record exists
savePauseRecord(record: PauseRecord) inserts a new row into pause_records and returns the persisted record with server-assigned id and created_at
updatePauseStatus(mentorId: String, status: PauseStatus, {reactivatedAt, coordinatorId}) updates the matching row and returns updated PauseRecord
getActivePausedMentors(coordinatorId: String) returns List<PauseRecord> for all mentors with status 'paused' or 'expired_cert' under the given coordinator's chapters, ordered by paused_at DESC
streamStatusChanges(mentorId: String) returns a Stream<PauseRecord?> that emits an updated value whenever the pause_records row for the mentor changes (Supabase Realtime)
All methods throw typed PauseRepositoryException subtypes (NetworkException, PermissionException, NotFoundError) rather than raw Supabase errors
Repository is unit-testable via a mockable SupabaseClient interface or abstraction layer
No raw SQL strings appear outside the repository class — all queries centralised here
Methods correctly handle the case where the mentor has no pause_records row (returns null, not exception)

Technical Requirements

frameworks
Flutter
Supabase Flutter SDK (supabase_flutter)
Riverpod (for provider exposure)
apis
Supabase PostgREST (pause_records table)
Supabase Realtime (postgres_changes on pause_records)
data models
pause_records
PauseRecord (Dart model)
PauseStatus (enum: active, paused, expiredCert)
performance requirements
getPauseStatus must resolve in < 300ms on a standard mobile connection
getActivePausedMentors must handle up to 500 mentors per coordinator without pagination failure — implement cursor-based pagination if result set exceeds 100 rows
streamStatusChanges must emit within 2 seconds of a database change
security requirements
All Supabase queries execute with the authenticated user's JWT — never use service role key on the client
Repository must not cache coordinator_id in local state; always derive from authenticated session
Realtime subscription must be cancelled on dispose to prevent memory leaks and stale listeners

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define a PauseRecord Dart class with fromJson/toJson using explicit field mapping — do not rely on generated code for this small model. Define PauseStatus as a Dart enum with a fromString factory handling all three values. For streamStatusChanges, use supabase.from('pause_records').stream(primaryKey: ['id']).eq('mentor_id', mentorId) — this returns a Stream> from which you take the first element. Cancel the subscription in a dispose method.

Expose the repository via a Riverpod Provider so the service layer can consume it cleanly. Keep the repository strictly data-layer: no business logic, no notification dispatch, no state machine — those belong in PauseManagementService (task-006).

Testing Requirements

Write unit tests using flutter_test with a mocked Supabase client. Test: (1) getPauseStatus returns correct PauseRecord when row exists. (2) getPauseStatus returns null when no row found. (3) savePauseRecord maps returned JSON to PauseRecord correctly.

(4) updatePauseStatus sends correct filter and update payload. (5) getActivePausedMentors filters by coordinator_id and status correctly. (6) All methods propagate typed exceptions on Supabase error responses (400, 401, 403, 503). (7) streamStatusChanges emits new value on simulated Realtime event.

Achieve 90%+ line coverage on repository class. Integration test: run against a local Supabase instance seeded with test data.

Component
Peer Mentor Status Repository
data medium
Epic Risks (3)
medium impact medium prob technical

Concurrent status transitions (e.g., coordinator and automated scheduler both attempting to update the same mentor's status simultaneously) may produce race conditions or inconsistent state in the database, leading to audit log gaps or incorrect notifications.

Mitigation & Contingency

Mitigation: Implement all status transitions as atomic Postgres RPC functions with optimistic locking (version column or updated_at check). Use database-level constraints rather than application-level guards as the final enforcement point.

Contingency: Add a compensation job that reconciles status and log table consistency on each nightly scheduler run, surfacing any discrepancies to coordinator dashboards.

medium impact medium prob integration

The coordinator-to-mentor assignment relationship may not always be 1:1 or may be stale (coordinator reassigned after a pause was set), causing notifications to be sent to the wrong coordinator or not sent at all.

Mitigation & Contingency

Mitigation: Query the assignment relationship at notification dispatch time rather than caching it at pause creation time. Add a fallback to notify the chapter administrator if no active coordinator assignment exists.

Contingency: Log all undeliverable notification attempts with the originating mentor ID so administrators can manually follow up, and surface undelivered notification counts on the coordinator dashboard.

medium impact low prob technical

The CoordinatorPauseRosterScreen may load slowly for coordinators managing large rosters with many concurrent certification expiry queries, degrading usability on low-bandwidth mobile connections.

Mitigation & Contingency

Mitigation: Use a single Supabase RPC that joins mentor status, certification expiry, and assignment data in one query rather than N+1 individual calls. Implement pagination with a configurable page size and skeleton loading states.

Contingency: Add an offline cache of the last-fetched roster state using Riverpod with SharedPreferences, ensuring coordinators can at minimum view stale data when connectivity is poor.