critical priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

activatePause(mentorId, reason, expectedReturnDate?) succeeds when mentor's current status is 'active', persists a new pause_records row with status='paused', and triggers notifyCoordinatorOfPause
activatePause throws PauseInvariantException('already_paused') when mentor status is already 'paused'
activatePause throws PauseInvariantException('expired_cert') when mentor status is 'expired_cert' — expired cert is a distinct state, not resolved by self-service pause
reactivateMentor(mentorId) succeeds when mentor status is 'paused', updates row to status='active' with reactivated_at = now(), and triggers notifyCoordinatorOfReactivation
reactivateMentor throws PauseInvariantException('not_paused') when mentor is already active
reactivateMentor throws PauseInvariantException('expired_cert') when mentor has expired cert — cert renewal is a separate flow
expireCertification(mentorId) transitions mentor from any status to 'expired_cert', persists update, and triggers notifyCoordinatorOfPause with reason='expired_cert'
All three methods are atomic: if notification dispatch fails, the database persist is NOT rolled back (notifications are best-effort)
All three methods return the updated PauseRecord on success
Service is exposed via Riverpod Provider and is unit-testable via injected repository and notification service mocks
State machine transitions are documented in code comments with the valid transition diagram

Technical Requirements

frameworks
Flutter
Riverpod
BLoC (if consuming BLoC layer drives the service)
apis
PeerMentorStatusRepository (task-002)
PauseNotificationService (task-005)
data models
PauseRecord
PauseStatus (enum)
PauseInvariantException
performance requirements
activatePause end-to-end (DB persist + notification dispatch initiated) must complete in < 2 seconds on p95
State machine read-then-write must not have a TOCTOU race condition — use optimistic locking or Supabase RPC with status check inside a transaction
security requirements
activatePause must verify that the requesting user is either the mentor themselves or a coordinator with authority over that mentor — enforce at service layer, not just RLS
expireCertification must only be callable by the certification checker service (coordinator or admin role) — not by the mentor themselves
Audit all transitions: log mentorId, actorId, transition type, and timestamp to an audit_log table or Supabase logs

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Implement a private _assertTransition(currentStatus, allowedStatuses, errorCode) helper to centralise the invariant check pattern — this keeps each public method concise and the invariants explicit. To prevent race conditions on the read-then-write, consider wrapping the status check + update in a Supabase RPC (PostgreSQL function) that accepts mentorId, expectedCurrentStatus, and newStatus, and returns an error if the current status does not match — this is atomic. Define PauseInvariantException as a typed exception class with a String code and String message, so the UI layer can show appropriate localised error messages without pattern-matching on exception message strings. Keep notification dispatch outside the repository transaction: call notifyCoordinator AFTER the database persist returns successfully, wrapped in a try/catch that logs but does not rethrow.

Expose the service as a Riverpod Provider that takes the repository and notification service providers as dependencies for clean injection and testability.

Testing Requirements

Write comprehensive unit tests using flutter_test with mocked PeerMentorStatusRepository and mocked PauseNotificationService. Test every valid transition: (1) active → paused via activatePause succeeds. (2) paused → active via reactivateMentor succeeds. (3) active → expired_cert via expireCertification succeeds.

(4) paused → expired_cert via expireCertification succeeds. Test every invalid transition: (5) activatePause on paused mentor throws PauseInvariantException('already_paused'). (6) activatePause on expired_cert mentor throws PauseInvariantException('expired_cert'). (7) reactivateMentor on active mentor throws PauseInvariantException('not_paused').

(8) reactivateMentor on expired_cert mentor throws PauseInvariantException('expired_cert'). Test notification behaviour: (9) activatePause calls notifyCoordinatorOfPause exactly once. (10) Notification failure does not cause activatePause to throw. (11) reactivateMentor calls notifyCoordinatorOfReactivation exactly once.

Achieve 95%+ branch coverage. Write one integration test against a local Supabase instance covering the full activate → expire → reactivate flow.

Component
Pause Management Service
service 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.