critical priority high complexity backend pending backend specialist Tier 3

Acceptance Criteria

requestFinalExport() invokes the edge function without a preview flag
The response is routed to the correct file generator (PDF or CSV) based on ExportFormat
A single ExportAuditRecord is persisted containing: organisation_id, triggered_by_user_id, report_period_id, scope, format, file_url, created_at, and status='completed'
If file generation fails after edge function success, no audit record is persisted and an exception is thrown
If audit record persistence fails after file generation, the file is not made permanently accessible (storage object is deleted) and an exception is thrown
Audit record and file storage registration are executed atomically — partial state is not observable by other users
BufdirExportResult contains: auditRecordId (UUID), fileUrl (String), mimeType (String), format (ExportFormat), exportedAt (DateTime)
The user ID stored in the audit record is derived from the server-side JWT claim — never from client input
Unit tests cover: successful full export creates exactly one audit record, file generation failure creates zero audit records, audit persistence failure triggers storage cleanup

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Edge Functions (Deno)
Supabase Storage
Supabase PostgreSQL 15
apis
Supabase Edge Functions REST API
Supabase Storage API
Supabase Database (audit record insert via RPC or repository)
data models
bufdir_export_audit_log
bufdir_column_schema
performance requirements
Full export flow must complete within 60 seconds end-to-end
Audit record insert must succeed within 5 seconds
Atomicity enforced via Supabase RPC transaction — not client-side sequencing
security requirements
Audit record triggered_by_user_id extracted from JWT server-side — never trusted from mobile payload
File URL stored in audit record must be a permanent storage path, not a signed URL
RLS on bufdir_export_audit_log ensures only org-scoped coordinators can read their own records
Submitted report data transmitted over TLS only per integration security policy

Execution Context

Execution Tier
Tier 3

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.

Component
Bufdir Export Service
service high
Epic Risks (2)
high impact medium prob security

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.

medium impact medium prob technical

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.