Implement ReportHistoryRepository Dart class
epic-bufdir-report-history-foundation-task-004 — Create the ReportHistoryRepository class in Dart using the Supabase Flutter client. Implement methods: fetchReportHistory(organizationId, {limit, offset}) returning paginated List<ReportHistoryRecord>, fetchReportById(id), insertReportRecord(ReportHistoryRecord), updateReportStatus(id, status), and deleteReportRecord(id). Use Riverpod provider for dependency injection. Handle Supabase errors and map to domain exceptions.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Define an abstract interface `IReportHistoryRepository` before implementing `ReportHistoryRepository` — this enables Riverpod overrides in tests. Use `supabase.from('bufdir_report_history').select()` with `.eq('organization_id', organizationId)` for org scoping (RLS also enforces this, but defense-in-depth). For pagination, use `.range(offset, offset + limit - 1)` and `.order('submitted_at', ascending: false)`. Map Supabase errors using a switch on `PostgrestException.code`: PGRST116 (not found), 42501 (permission denied), 23505 (duplicate).
Keep the error mapping in a private `_mapError(Object e)` method to avoid repetition. Use `freezed` for the domain exception classes if other exceptions in the codebase use freezed. Register the Riverpod provider as `final reportHistoryRepositoryProvider = Provider
Testing Requirements
Write unit tests using flutter_test with a mocked Supabase client (implement a mock conforming to the Supabase client interface, or use Mocktail): (1) fetchReportHistory returns correct paginated list when Supabase returns valid JSON; (2) fetchReportHistory with offset=20 generates correct range query; (3) fetchReportById throws ReportNotFoundException when Supabase returns empty list; (4) insertReportRecord maps returned JSON to ReportHistoryRecord correctly; (5) updateReportStatus sends only the status field in the update payload; (6) deleteReportRecord throws ReportNotFoundException when no rows are affected; (7) PostgrestException with code 42501 maps to ReportPermissionException. Target 90%+ line coverage on the repository class. Run with `flutter test test/features/bufdir_report/data/repositories/`.
Incorrectly authored RLS policies could silently allow cross-organization data reads, exposing sensitive report history of one organization to coordinators of another in a multi-tenant environment.
Mitigation & Contingency
Mitigation: Write integration tests that explicitly authenticate as a user from organization A and assert zero rows are returned for organization B's history records. Use Supabase's built-in RLS testing utilities and review policies with a second developer.
Contingency: If a cross-tenant leak is discovered post-deployment, immediately revoke all active sessions for affected organizations, audit query logs for unauthorized access, and patch the RLS policy in a hotfix migration before re-enabling access.
The 5-year retention policy for report files may conflict with Supabase Storage's lack of native lifecycle rules, requiring a custom pg_cron job that could fail silently and either delete files prematurely or never clean up.
Mitigation & Contingency
Mitigation: Implement the retention cleanup as a documented pg_cron job with explicit logging to a separate audit_jobs table. Add a Supabase Edge Function health check that verifies the cron job ran within the last 25 hours.
Contingency: If the cron job fails, files accumulate in storage (non-critical for compliance — over-retention is safer than under-retention). Alert the ops team via monitoring and manually trigger the cleanup function once the cron issue is resolved.
Signed URL generation depends on the requesting user's Supabase session being valid at the time of the call. If sessions expire during a long screen interaction, URL generation will fail with an authorization error and confuse the coordinator.
Mitigation & Contingency
Mitigation: Wrap signed URL generation in the service layer with a session-refresh check before calling Supabase Storage. Generate URLs on demand (tap-to-download) rather than pre-generating them for all list items on screen load.
Contingency: If a URL generation fails due to session expiry, surface a clear error message prompting the coordinator to re-authenticate, then automatically retry URL generation after session refresh completes.