critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

Method signature: `Future<PaginatedResult<ReportHistoryRecord>> fetchHistory({required int page, required int pageSize, String? cursor})` (or equivalent typed signature)
organization_id is sourced from the authenticated session (via auth provider / current user) — never passed as a parameter from UI layer
Fetch result includes: `items` (List<ReportHistoryRecord>), `totalCount` (int), `hasNextPage` (bool), `nextCursor` (String?) or `nextPage` (int?)
Requesting page beyond available records returns an empty `items` list and `hasNextPage: false` — does not throw
pageSize is validated: must be between 1 and 100 inclusive; values outside this range throw an `ArgumentError` before any repository call
Repository is called with the authenticated user's organization_id on every invocation — verified in unit tests
Service does not contain any SQL or Supabase-specific code — it delegates entirely to the repository interface
Method is covered by unit tests with mocked repository (see dependent testing tasks)
Dart analyzer reports zero errors and zero warnings on the service file
All field names and class names follow the project's existing naming conventions (camelCase for Dart)

Technical Requirements

frameworks
Flutter
Riverpod (for dependency injection of repository and auth state)
supabase_flutter (auth session access)
apis
ReportHistoryRepository.fetchPage() — underlying data access
Supabase auth session (current user organization_id)
data models
ReportHistoryRecord (domain model returned in list)
PaginatedResult<T> (generic pagination wrapper — create if not existing)
UserSession / AuthState (for organization_id extraction)
performance requirements
Default page size must not exceed 50 records to keep response payloads under 100KB
Method must return within 2 seconds for a page of 20 records under normal network conditions
security requirements
organization_id must be read from the server-validated auth session — never trust a client-supplied org ID
If the authenticated user has no organization_id in their session, throw an `AuthorizationException` before querying

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define a `PaginatedResult` generic class if one does not exist: `class PaginatedResult { final List items; final int totalCount; final bool hasNextPage; final String? nextCursor; }`. Place it in `lib/core/models/paginated_result.dart` for reuse across services. For organization_id sourcing, inject an `AuthRepository` or equivalent that exposes `String?

get currentOrganizationId` — do not call `Supabase.instance.client.auth.currentUser` directly in the service (breaks testability). Decide between offset pagination (simpler, acceptable for this use case — report history is append-only and low-volume) and cursor pagination (more correct for real-time data). Given Bufdir reporting context, offset with `page`/`pageSize` is sufficient and matches the repository's `.range()` implementation. Document this decision in the method's Dart doc comment.

Testing Requirements

Unit tests using Mockito mocks for `ReportHistoryRepository` and the auth session provider. Test cases: (1) happy path returns correctly structured `PaginatedResult`; (2) page beyond total count returns empty list with `hasNextPage: false`; (3) organization_id from session is passed to repository — never a hardcoded value; (4) pageSize outside 1–100 throws `ArgumentError`; (5) missing organization_id in session throws `AuthorizationException`; (6) repository exception propagates correctly (service does not swallow it). Use `ProviderContainer` from Riverpod test utilities to inject mock dependencies if service is a Riverpod provider.

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.