critical priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

ReportHistoryRepository class is implemented in `lib/features/bufdir_report/data/repositories/report_history_repository.dart`
fetchReportHistory(organizationId, {int limit = 20, int offset = 0}) returns a paginated List<ReportHistoryRecord> ordered by submitted_at DESC
fetchReportById(String id) returns a single ReportHistoryRecord or throws ReportNotFoundException when the id does not exist
insertReportRecord(ReportHistoryRecord record) inserts the record and returns the inserted ReportHistoryRecord with server-assigned id and created_at
updateReportStatus(String id, ReportStatus status) updates only the status column and returns the updated record
deleteReportRecord(String id) deletes the record and throws ReportNotFoundException if the id does not exist
All Supabase PostgrestException errors are caught and mapped to typed domain exceptions: ReportNotFoundException, ReportPermissionException, ReportValidationException, ReportNetworkException
A Riverpod Provider (reportHistoryRepositoryProvider) is defined and provides a singleton ReportHistoryRepository scoped to the app lifecycle
The repository is exposed via an abstract interface (IReportHistoryRepository) to allow mocking in tests
No raw Supabase client code leaks outside the repository — all callers use the typed interface
All methods are async and return Future<T>

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter client (supabase_flutter)
apis
Supabase PostgREST API (via supabase_flutter)
bufdir_report_history table
data models
ReportHistoryRecord
ReportStatus (enum)
bufdir_report_history (database table)
performance requirements
fetchReportHistory must use limit/offset pagination — never fetch all rows
Network requests must complete within 3 seconds on a standard mobile connection
Repository must not hold any in-memory cache — caching is handled by Riverpod AsyncNotifier consumers
security requirements
organizationId parameter must be validated against the current user's JWT claim before constructing the query — do not trust caller-supplied organizationId blindly
Repository must never expose raw Supabase error messages to UI — map all errors to domain exceptions with safe messages
Deleted records must be confirmed deleted by checking affected row count, not assumed

Execution Context

Execution Tier
Tier 2

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((ref) => ReportHistoryRepository(supabase: Supabase.instance.client))`. Avoid using `ref.watch` inside the repository itself — repositories are stateless data access objects.

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/`.

Component
Report History Repository
data low
Epic Risks (3)
high impact medium prob security

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.

medium impact medium prob technical

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.

medium impact low prob integration

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.