high priority medium complexity integration pending integration specialist Tier 3

Acceptance Criteria

BufdirExportService.submitReport(reportPeriodId) works identically from the caller's perspective regardless of submission_mode — same method signature, same return type
When submission_mode is direct_api: (1) bufdir-format-serializer produces the canonical payload, (2) BufdirRequestMapper maps it to BufdirApiRequest, (3) BufdirApiClient.submit() is called, (4) BufdirSubmissionResult is returned to the BLoC
The Bufdir export BLoC/Cubit emits BufdirExportState.submittedDirectApi(referenceNumber) on success, displayed on the confirmation screen with the Bufdir reference number
On BufdirSubmissionError, the BLoC emits BufdirExportState.failed(errorType, message) and the UI shows a user-readable error message (not a raw exception)
The file-based path continues to function correctly when submission_mode is 'file_based' — no regression
bufdir_export_audit_log is written with status 'submitted_direct_api' and the reference number on success
bufdir_export_audit_log is written with status 'submission_failed' and error detail on failure
No circular dependencies introduced between bufdir-api-client, bufdir-export-service, and bufdir-format-serializer
The confirmation screen shows the Bufdir reference number prominently for coordinators to record
End-to-end flow can be exercised in a Supabase sandbox / staging environment without touching production Bufdir API

Technical Requirements

frameworks
Flutter
Dart
BLoC
Riverpod
apis
Bufdir Reporting API
Supabase Edge Functions (Deno)
Supabase PostgreSQL 15
data models
bufdir_export_audit_log
bufdir_column_schema
annual_summary
performance requirements
End-to-end submission (serialise → map → HTTP → persist reference) must complete within 35 seconds on a 4G connection
UI must show a loading indicator within 200ms of submission button tap
security requirements
Organisation credentials never passed through Flutter code — Edge Function holds credentials
JWT validated on Edge Function before any Bufdir API call
bufdir_export_audit_log written with actor_id from JWT claims, not from client-supplied value
Error messages shown to coordinator must not contain raw Bufdir API error bodies (which may contain internal schema details)
ui components
SubmissionLoadingOverlay
BufdirConfirmationSheet
ErrorSnackbar

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Integration Task

Handles integration between different epics or system components. Requires coordination across multiple development streams.

Implementation Notes

The routing seam in BufdirExportService should be a single private `_submitDirectApi(payload)` method alongside the existing `_submitFileBased(payload)` method — the public `submitReport()` method selects between them via the feature flag. Inject BufdirApiClient as a constructor parameter so the service is testable with a mock. Use a try/catch around the entire direct-API path and rethrow as a domain-level BufdirExportException so the BLoC never handles raw Dio or http errors. For the confirmation screen, extend the existing BufdirExportState with a new `submittedDirectApi` variant carrying the reference number — do not reuse the file-based success state as they have different confirmation UI requirements.

Write the audit log entry inside the service (not the BLoC) so it is guaranteed regardless of BLoC state.

Testing Requirements

Integration tests using a fake BufdirApiClient and fake BufdirExportService: (1) direct_api mode routes through API client and emits submittedDirectApi state with reference number, (2) file_based mode routes through file path and emits submittedFileBased state, (3) API client error propagates to failed state with correct error type, (4) audit log written on both success and failure paths. Widget test for confirmation screen: reference number rendered when present, error message rendered on failure state. End-to-end test against Supabase staging (optional, CI-gated).

Component
Bufdir API Client
service high
Epic Risks (2)
medium impact high prob dependency

Norse Digital Products has not yet completed API negotiations with Bufdir. If negotiations stall or Bufdir's API design diverges significantly from expectations, the API client may need substantial rework, or the epic may be blocked indefinitely.

Mitigation & Contingency

Mitigation: Implement the client against a locally defined stub of the expected Bufdir API schema. Isolate all Bufdir-specific schema mapping in a single adapter class so that changes to the actual API schema require changes in only one place. Keep the epic in 'interface-ready' status until real API credentials are available for integration testing.

Contingency: If API negotiations are not completed within the planned window, defer this epic without impact on any other epic — the PDF/CSV fallback path from Epics 1–4 delivers full standalone value. Mark the epic as blocked and resurface when negotiations conclude.

high impact low prob security

Bufdir API credentials stored in the application or edge function environment could be exposed through misconfigured secrets management, log leakage, or a compromised deployment pipeline, allowing unauthorised Bufdir submissions on behalf of the organisation.

Mitigation & Contingency

Mitigation: Store all Bufdir API credentials exclusively in Supabase Vault (or the integration credential vault component), never in client-side code or environment variables accessible to the Flutter app. Transmit credentials only from within the edge function, not from the Flutter client. Implement credential rotation support from the outset.

Contingency: If a credential leak is detected, immediately revoke and rotate the affected API credentials through Bufdir's credential management portal, audit submission logs for any unauthorised calls, and notify Bufdir's technical contact per the API agreement's security incident clause.