medium priority low complexity database pending database specialist Tier 3

Acceptance Criteria

A dedicated coordinator_acknowledgement_audit table exists with columns: id (UUID PK), coordinator_id (UUID FK to user profiles), mentor_id (UUID FK to peer mentors), certificate_type (text), threshold_tier (enum: advance_60, warning_30, urgent_7, lapsed), acknowledged_at (timestamptz), note (text nullable, max 500 chars), organisation_id (UUID FK)
Every successful acknowledgement in task-009 triggers exactly one audit row insert — no acknowledgement is persisted without a corresponding audit entry
The audit table has a RLS policy that allows INSERT by coordinator role and SELECT by coordinator role (own organisation only) and admin role (all organisations)
No UPDATE or DELETE is permitted on audit rows by any role except database superuser
A Supabase read endpoint (RPC function or filtered table query) returns audit rows filtered by organisation_id and optionally by date range and coordinator_id, ordered by acknowledged_at DESC
The read endpoint is paginated with a default page size of 50 and a maximum of 200 rows per request
Audit rows are never deleted by application logic — they are permanent compliance records
Unit tests verify the audit row is written with all correct fields and that the read endpoint returns rows filtered by organisation correctly

Technical Requirements

frameworks
Flutter (Dart)
apis
Supabase REST API (coordinator_acknowledgement_audit table)
Supabase RPC (read endpoint for compliance reporting)
data models
CoordinatorAcknowledgementAudit
AuditQueryFilter
AuditQueryResult
performance requirements
Audit insert must add less than 100ms to the total acknowledgement flow
Read endpoint must respond within 1 second for queries returning up to 200 rows — ensure index on (organisation_id, acknowledged_at DESC)
security requirements
Audit table must be append-only enforced at PostgreSQL RLS level — no application-level delete should be possible
organisation_id on audit rows must be populated from the server-side session, not the client request
Read access to audit data must be scoped to the coordinator's own organisation — no cross-organisation audit visibility

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Create the coordinator_acknowledgement_audit table via a Supabase migration file — do not create it programmatically at runtime. Include the migration in the repository under supabase/migrations/. Define threshold_tier as a PostgreSQL custom enum type (advance_60, warning_30, urgent_7, lapsed) and reuse this type in the certificate_expiry_notifications table for consistency. For the read endpoint, prefer a Supabase RPC (SQL function) over a direct table query from the Dart client — this allows server-side filtering and pagination logic, avoids exposing table structure directly, and makes future query optimisation easier without client changes.

The RPC should accept: p_organisation_id UUID, p_from_date timestamptz DEFAULT NULL, p_to_date timestamptz DEFAULT NULL, p_coordinator_id UUID DEFAULT NULL, p_page INT DEFAULT 1, p_page_size INT DEFAULT 50. In the acknowledgement service (task-009), the audit write should happen inside the same method after the notification status update but in a separate try/catch — a failed audit write should not roll back the acknowledgement, but must emit a structured error log for ops monitoring.

Testing Requirements

Unit tests (flutter_test): mock Supabase insert, assert audit row constructed with all required fields, assert organisation_id matches session value. Integration test: run full acknowledgement flow (task-009) and verify audit row present in coordinator_acknowledgement_audit with correct coordinator_id, mentor_id, threshold_tier, and timestamp. Test read endpoint: insert 3 audit rows for org A and 2 for org B; query as org A coordinator; assert only 3 rows returned. Test pagination: insert 60 rows; query with default page size; assert 50 returned with a next_page cursor.

Coverage target: 80% of audit write and read 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.