Implement full export flow with audit record persistence
epic-bufdir-report-export-orchestration-task-007 — Implement requestFinalExport() in BufdirExportService. Invoke the edge function, route the response to the correct file generator, persist a finalised ExportAuditRecord via the generated-reports-repository (including scope, period, format, file URL, timestamp, and user ID), and return the BufdirExportResult. Use a database transaction to ensure the audit record and file registration are atomic.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Structure requestFinalExport() in three distinct phases: (A) edge function invocation via invokeEdgeFunction(), (B) file generation via the routing layer, (C) atomic persistence. For phase C, prefer a Supabase RPC function that inserts the audit record and registers the file in a single PostgreSQL transaction rather than two sequential client calls — this is the only reliable way to achieve atomicity from a mobile client. If an RPC is not yet available, implement compensating logic: if audit insert fails, call supabase.storage.from('bufdir-exports').remove([filePath]) before rethrowing. The BufdirExportResult should be a plain Dart data class (freezed recommended) for immutability.
Never store signed URLs in the audit record — store the storage path and generate signed URLs on demand.
Testing Requirements
Unit tests with flutter_test: (1) successful export inserts exactly one audit record and returns BufdirExportResult, (2) file generator failure (throws) results in zero audit record inserts (assert mock repository has zero calls), (3) audit repository throws on insert — assert storage cleanup is called (deleteFile mock invoked once), (4) returned BufdirExportResult fields match expected values, (5) user ID in audit record matches authenticated user from Supabase session. Integration test (if Supabase test instance available): verify atomicity by injecting a failure after file upload and before audit insert, confirm no orphaned storage objects.
The scope selector must accurately reflect each coordinator's access rights within the org hierarchy. If a coordinator can select a scope broader than their authorised access, the edge function's RLS enforcement must catch the attempt — but a permissive RLS policy or a bug in the scope resolver could allow unauthorised data to be exported.
Mitigation & Contingency
Mitigation: Implement permission enforcement at two independent layers: (1) the scope selector only renders options permitted by the user's role record, and (2) the edge function re-validates the requested scope against the user's JWT claims before executing any queries. Write integration tests that attempt to invoke the edge function with a scope beyond the user's permissions and assert rejection.
Contingency: If a permission bypass is discovered post-launch, immediately disable the export feature via the org-level feature flag while the fix is deployed. Review all audit records for exports that may have included out-of-scope data and notify affected organisations.
The export workflow has 7+ discrete states (idle, scope selected, period selected, preview loading, preview ready, confirming, exporting, complete, failed) and several conditional transitions. An incomplete BLoC state machine could allow duplicate submissions, stale preview data to be confirmed, or error states to be unrecoverable without a restart.
Mitigation & Contingency
Mitigation: Model the state machine explicitly as a sealed class hierarchy before coding. Review the state diagram against all user story acceptance criteria. Write bloc unit tests for every valid and invalid state transition, including the happy path and all documented error states.
Contingency: If the BLoC grows too complex to test reliably, decompose it into two cooperating blocs: one for configuration (scope + period selection) and one for execution (preview + confirm + export), linked by a coordinator object.