critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ReportReexportCoordinator exposes a method `Future<ReportPeriodParameters> loadPeriodParameters(String historyEntryId)` that fetches the corresponding record from bufdir_export_audit_log
historyEntryId is validated as a non-empty UUID before the database fetch; invalid IDs throw an `ArgumentError` with a descriptive message
The fetched record's stored period parameters (start date, end date, reporting period ID, aggregation scope) are parsed into a `ReportPeriodParameters` domain object
If any required period parameter field is null or missing in the stored record, the method throws a `ReportParametersIncompleteException` identifying which fields are missing
If the history record does not exist or belongs to a different organisation, the method throws a `RecordNotFoundException`
The `ReportPeriodParameters` domain object is immutable (all fields final) and uses named constructor parameters
The method is organisation-scoped via RLS — no additional organisation_id check is required in application code beyond what RLS enforces
Unit tests cover: successful load and parse, missing historyEntryId, invalid UUID format, record not found, missing required period fields (each field individually), correct domain object field mapping
Integration test confirms that a seeded history record with known parameters is loaded and mapped correctly

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
apis
Supabase PostgreSQL 15 (PostgREST .select().eq().single())
data models
bufdir_export_audit_log
performance requirements
Single database query — no N+1 lookups
Method completes in under 1 second under normal conditions
Domain object instantiation is synchronous after data fetch
security requirements
historyEntryId must pass UUID regex validation before being used in any database query
RLS on bufdir_export_audit_log prevents access to other organisations' records — service must not add a manual organisation_id filter that could be spoofed
Stored period parameters from the database are treated as trusted internal data — no HTML escaping required, but type coercion must be explicit to prevent Dart dynamic type surprises
No period parameter data should appear in error messages or logs to prevent leaking reporting period metadata

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define `ReportPeriodParameters` as a plain Dart class with all fields final and a `const` constructor. Include a static `fromJson(Map json)` factory that performs explicit field extraction with null checks and throws `ReportParametersIncompleteException` for missing required fields. Keep the coordinator method thin — it should only call the repository and invoke `ReportPeriodParameters.fromJson()`. Do not add business logic to the loader step; downstream pipeline steps receive the typed object.

Use Supabase's `.single()` method so a missing record throws a `PostgrestException` with code `PGRST116` (no rows returned) — catch this specifically and rethrow as `RecordNotFoundException`. Define the custom exception classes in a shared exceptions file for the bufdir feature, not inline.

Testing Requirements

Unit tests (flutter_test) with mock repository: (1) happy path — mock returns a valid record, assert domain object fields match, (2) empty historyEntryId throws ArgumentError, (3) non-UUID historyEntryId throws ArgumentError, (4) repository returns null — throws RecordNotFoundException, (5) record has null startDate — throws ReportParametersIncompleteException naming 'startDate', (6) record has null endDate — same, (7) record has null reportingPeriodId — same, (8) record has all fields — domain object constructed correctly. Integration test on staging: seed a record, call method, assert all fields round-trip correctly through the domain object.

Component
Report Re-export Coordinator
service medium
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.