medium priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

Existing fetch methods on ReportHistoryService are extended with optional `DateTime? startDate`, `DateTime? endDate`, and `String? reportingPeriodId` parameters using named parameters with null defaults
When startDate is provided, only records with `exported_at >= startDate` are returned
When endDate is provided, only records with `exported_at <= endDate` are returned
When reportingPeriodId is provided, only records matching that period are returned
All three filters may be combined — the repository applies all non-null filters with AND semantics
When all filter parameters are null, the method behaves identically to the original unfiltered version (no regression)
Filters are applied at the database query level (Supabase PostgREST `.gte()`, `.lte()`, `.eq()`) — no client-side filtering of result sets
Date parameters are converted to UTC ISO-8601 strings before being passed to the query builder
Unit tests cover: no filters (full list), startDate only, endDate only, reportingPeriodId only, all three combined, inverted date range (start > end) returns empty list without error
Inverted date range (startDate after endDate) is detected and results in an empty list returned immediately without a database round-trip

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
apis
Supabase PostgreSQL 15 (PostgREST query builder: .gte, .lte, .eq, .order)
data models
bufdir_export_audit_log
performance requirements
Filters applied server-side — no full table scans followed by client-side filtering
Query must use indexed columns: `exported_at` and `report_period_id` should have database indexes
Response time under 1 second for filtered queries returning up to 100 records
security requirements
RLS on bufdir_export_audit_log table enforces organisation scoping — filtering never bypasses row-level isolation
Date parameters sanitised by the PostgREST SDK — no raw SQL string interpolation
reportingPeriodId must be validated as a UUID format before being passed to the query to prevent injection

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use Dart named optional parameters with null defaults to maintain backwards compatibility — do not change the existing method signature in a breaking way. In the repository query builder, chain filter calls conditionally: `if (startDate != null) query = query.gte('exported_at', startDate.toUtc().toIso8601String())`. Always apply the organisation_id filter first (from the authenticated session JWT claim) before any user-supplied filters to ensure RLS is not the only safeguard. Add an early-exit guard at the service level: `if (startDate != null && endDate != null && startDate.isAfter(endDate)) return [];`.

Document the UUID validation pattern for reportingPeriodId in a shared utility rather than duplicating it.

Testing Requirements

Unit tests (flutter_test) with mock Supabase client: (1) verify correct PostgREST filter methods are called with correct arguments for each combination of parameters, (2) no filter call is made for null parameters, (3) empty list returned for inverted date range without a database call. Integration test against staging Supabase instance: seed 5 known records with distinct dates and period IDs, then assert each filter combination returns the correct subset. No e2e tests required at this layer.

Component
Report History Service
service low
Epic Risks (3)
high impact medium prob dependency

The ReportReexportCoordinator must invoke the Bufdir export pipeline defined in the bufdir-report-export feature. If that feature's internal API changes (renamed services, altered parameters), the re-export coordinator will break silently at runtime.

Mitigation & Contingency

Mitigation: Define a stable, versioned interface (abstract class or Dart interface) for the export pipeline entry point. The re-export coordinator depends only on this interface, not on concrete export service internals. Document the contract in both features.

Contingency: If the export pipeline breaks the re-export coordinator, fall back to surfacing a clear 'regeneration unavailable' message to the coordinator with instructions to use the primary export screen for the same period as a workaround, while the interface mismatch is fixed.

high impact low prob security

The audit trail must be immutable — coordinators must not be able to edit or delete past events. If the RLS policies allow UPDATE or DELETE on audit event rows, a coordinator could suppress evidence of a re-export or failed submission.

Mitigation & Contingency

Mitigation: Apply INSERT-only RLS policies to the audit events table (no UPDATE, no DELETE for any non-service-role user). Use a separate service-role key for writing audit events, never the user's JWT. Validate this in integration tests by asserting that UPDATE and DELETE calls from coordinator-role sessions are rejected with RLS errors.

Contingency: If immutability is compromised before detection, run a database audit comparing the audit log against the main history table timestamps to identify tampered records, restore from backup if needed, and issue a patch RLS migration immediately.

low impact low prob technical

The user stories require filter state (year, period type, status) to persist within a session so coordinators do not lose context when navigating away. Implementing this with Riverpod state management could cause stale filter state if the provider is not properly scoped to the session lifecycle.

Mitigation & Contingency

Mitigation: Scope the filter state provider to the router's history route scope, not globally. Use autoDispose with a keepAlive flag tied to the session so filters reset on logout but persist on tab switches within the same session.

Contingency: If filter state becomes stale or leaks between sessions, add an explicit reset in the logout handler that disposes all scoped providers. This is a UX degradation (coordinator must re-apply filters) rather than a data integrity issue.