Build BufdirExportFileStorage Dart adapter
epic-bufdir-reporting-export-foundation-task-009 — Implement the BufdirExportFileStorage adapter class in Dart wrapping Supabase Storage. Expose methods: uploadExportFile(String orgId, String exportId, Uint8List bytes, ExportFormat format), generateSignedUrl(String filePath, Duration ttl), deleteExpiredFiles(String orgId, DateTime olderThan). Enforce org-scoped path construction and handle upload errors with retry logic.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Place at lib/features/bufdir_reporting/data/adapters/bufdir_export_file_storage.dart. The retry logic should be implemented as a private _withRetry
Batch the remove calls: chunk the list into groups of 100 using a simple for-loop with sublist. Register as a Riverpod Provider
Testing Requirements
Unit tests with mocktail mocking SupabaseStorageClient: (1) uploadExportFile constructs correct path '{orgId}/{exportId}.csv' for ExportFormat.csv; (2) uploadExportFile retries exactly maxRetries times on StorageException before throwing ExportUploadException; (3) uploadExportFile succeeds on second attempt after one StorageException (retry logic working); (4) uploadExportFile throws PathValidationException for orgId containing '/'; (5) generateSignedUrl passes correct expiresIn to Supabase; (6) generateSignedUrl throws ArgumentError for Duration.zero; (7) deleteExpiredFiles calls list with correct prefix and remove with correct path list; (8) deleteExpiredFiles returns correct count of deleted files. Integration tests against Supabase test bucket: verify real upload, signed URL access, and deletion. Test the cross-org path guard: constructing a path with another org's ID must be structurally impossible given the method signature.
RLS policies for the audit log and schema config tables must correctly handle multi-chapter membership hierarchies (up to 1,400 local chapters for NHF). Incorrect policies could either over-expose data across organisations or prevent legitimate coordinator access, both of which are serious compliance failures.
Mitigation & Contingency
Mitigation: Design RLS policies using the existing org hierarchy resolver pattern. Write integration tests that verify cross-organisation isolation with representative fixture data covering NHF's multi-level hierarchy before merging.
Contingency: If RLS policies prove too complex to express safely in Postgres, implement a Supabase Edge Function as a data access proxy that enforces isolation in application code, with RLS serving as a secondary defence layer.
Bufdir's column schema is expected to evolve as Norse Digital Products negotiates a simplified digital reporting format. If the schema config versioning model is too rigid, applying Bufdir schema updates without a code deployment could fail, forcing emergency releases.
Mitigation & Contingency
Mitigation: Design the schema config table to store the full JSON column mapping as a JSONB field with a version number. Provide an admin API to upsert new versions without any schema migration required.
Contingency: If the versioning model is insufficient for a Bufdir schema change, fall back to a code deployment with the updated default schema, using the database config only for org-specific overrides.