high priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

Service accepts a notification ID, coordinator user ID, and optional free-text note
The notification record's status is updated to 'acknowledged' in the certificate_expiry_notifications table
The acknowledged_by (coordinator user ID), acknowledged_at (ISO-8601 timestamp), and acknowledgement_note fields are written atomically with the status update
After successful acknowledgement, the service emits a state change event (via Riverpod/BLoC stream or Supabase Realtime) that the front end can subscribe to for clearing the expiry banner for that mentor
Attempting to acknowledge a notification that is already in 'acknowledged' status returns a typed AcknowledgementResult with status 'already_acknowledged' — no error thrown, no duplicate write
Attempting to acknowledge a notification that does not exist returns a typed AcknowledgementResult with status 'not_found'
Only the coordinator assigned to the mentor's organisation may acknowledge the notification — service validates organisational scope before writing
All acknowledgement writes include the threshold_tier of the acknowledged notification for downstream audit filtering
Unit tests cover: successful acknowledgement writes correct fields, already-acknowledged returns correct status, not-found returns correct status, organisational scope validation rejects out-of-scope coordinator

Technical Requirements

frameworks
Flutter (Dart)
BLoC
Riverpod
apis
Supabase REST API (certificate_expiry_notifications table)
Supabase Realtime (broadcast channel for banner clear signal)
data models
CertificateExpiryNotification
CoordinatorAcknowledgement
AcknowledgementResult
performance requirements
Acknowledgement round-trip (write + Realtime broadcast) must complete within 2 seconds on a 4G mobile connection
Notification lookup must use primary key index — no full table scans
security requirements
Coordinator user ID must be taken from the authenticated Supabase session — never from the client request body to prevent impersonation
RLS policy must restrict acknowledgement writes to coordinators whose organisation_id matches the notification's organisation_id
Free-text note must be sanitised and limited to 500 characters server-side before persistence
ui components
ExpiryBannerWidget (consumer of Realtime acknowledgement event)
AcknowledgeButtonWidget (triggers service call)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as a Dart service class injected via Riverpod Provider. The organisational scope check should query the coordinator's organisation_id from the authenticated session profile, not from a passed parameter — this prevents privilege escalation. For the Realtime signal, use a Supabase Realtime broadcast on a channel named 'expiry_acknowledgements:{organisation_id}' rather than database-level Realtime changes — this avoids exposing the full notification row to all subscribers and gives precise control over the event payload. The front-end BLoC/Riverpod listener should subscribe to this channel when the coordinator dashboard is active and update the relevant mentor's banner state.

Use Supabase's .update().eq('id', notificationId).eq('status', 'pending') pattern and check the affected row count — if 0, fetch the record to distinguish 'not_found' from 'already_acknowledged'. This avoids a separate pre-check query.

Testing Requirements

Unit tests (flutter_test): mock Supabase client, assert correct fields written on happy path, assert 'already_acknowledged' returned without DB write, assert 'not_found' returned, assert organisational scope check fires before write. Widget test: ExpiryBannerWidget subscribes to acknowledgement stream; simulate stream event and assert banner is removed from widget tree. Integration test against Supabase local emulator: create notification record, call acknowledge, verify all fields persisted, verify Realtime event received by a subscriber. Coverage target: 85% of service logic.

Component
Coordinator Acknowledgement Service
service low
Epic Risks (4)
high impact medium prob technical

If the daily edge function runs more than once in a 24-hour window due to a Supabase scheduling anomaly or manual re-trigger, the orchestrator could dispatch duplicate push notifications to the same mentor and coordinator for the same threshold, eroding user trust.

Mitigation & Contingency

Mitigation: Implement idempotency at the notification record level using a unique constraint on (mentor_id, threshold_days, certification_id). The orchestrator checks for an existing record before dispatching. Use a database-level upsert with ON CONFLICT DO NOTHING.

Contingency: If duplicate notifications are reported in production, add a rate-limiting guard in the edge function that aborts if a notification for the same mentor and threshold was created within the last 20 hours, and add an alerting rule to Supabase logs for duplicate dispatch attempts.

medium impact medium prob scope

The mentor visibility suppressor relies on the daily edge function to detect expiry and update suppression_status. A mentor whose certificate expires at midnight may remain visible for up to 24 hours if the cron runs at a fixed time, violating HLF's requirement that expired mentors disappear promptly.

Mitigation & Contingency

Mitigation: Schedule the edge function to run at 00:05 UTC to minimise lag after midnight transitions. Additionally, the RLS policy can include a direct date comparison (certification_expiry_date < now()) as a secondary predicate that does not rely on suppression_status, providing real-time enforcement at the database level.

Contingency: If the cron lag is unacceptable after launch, implement a Supabase database trigger on the certifications table that fires on UPDATE of expiry_date and calls the suppressor immediately, reducing lag to near-zero for renewal and expiry events.

medium impact low prob integration

The orchestrator needs to resolve the coordinator assigned to a specific peer mentor to dispatch coordinator-side notifications. If the assignment relationship is not normalised or is missing for some mentors, coordinator notifications will silently fail.

Mitigation & Contingency

Mitigation: Query the coordinator assignment from the existing assignments or user_roles table before dispatch. Log a structured warning (missing_coordinator_assignment: mentor_id) when no coordinator is found. Add a data quality check in the edge function that reports mentors without coordinators.

Contingency: If coordinator assignments are missing at scale, fall back to notifying the chapter-level admin role for the mentor's chapter, and surface a data quality report to the admin dashboard showing mentors without assigned coordinators.

medium impact low prob dependency

The course enrollment prompt service generates deep-link URLs targeting the course administration feature. If the course administration feature changes its deep-link schema or the Dynamics portal URL structure changes, enrollment prompts will navigate to broken destinations.

Mitigation & Contingency

Mitigation: Define the deep-link contract between the certificate expiry feature and the course administration feature as a shared constant in a cross-feature navigation config. Version the deep-link schema and validate the generated URL format in unit tests.

Contingency: If the deep-link breaks in production, the course enrollment prompt service should gracefully fall back to opening the course administration feature root screen with a query parameter indicating the notification context, allowing the user to manually locate the correct course.