Implement Accounting Credentials Vault
epic-accounting-system-export-foundation-task-006 — Build the AccountingCredentialsVault for secure server-side storage of per-org API credentials for Xledger and Dynamics. Store credentials encrypted in a Supabase vault table (or Supabase Secrets) accessible only from Edge Functions, never from the Flutter client. Expose getCredentials(orgId, targetSystem) for use within Edge Functions. Implement a coordinator-facing credential setup flow that POST credentials to an authenticated Edge Function endpoint — never returning secrets to the client.
Acceptance Criteria
Technical Requirements
Implementation Notes
Place Flutter code in lib/features/accounting/infrastructure/accounting_credentials_vault.dart and Edge Functions in supabase/functions/store-accounting-credentials/ and supabase/functions/get-accounting-credentials/. Use Supabase Vault (vault.create_secret / vault.read_secret) if available on the project's Supabase plan — it provides key rotation and audit logging out of the box. If Vault is not available, use pgsodium with a key stored in Supabase secrets (not hardcoded). The get-accounting-credentials Edge Function should only be invokable internally (via service role key from other Edge Functions, not from Flutter) — enforce this by checking that the Authorization header is the service role key, not a user JWT.
Coordinate with Xledger and Dynamics to understand the credential format (API key only vs. OAuth client_id/secret) before finalizing the encrypted_payload schema.
Testing Requirements
Unit tests (Flutter): Test submitCredentials invokes the correct Edge Function with correct payload shape. Test that the Flutter class has no method that returns credential values. Test CredentialStatusBadge renders 'Configured' when hasCredentials = true and 'Not Configured' when false. Edge Function tests (Deno): Test store-accounting-credentials with valid coordinator JWT — verify upsert is called.
Test with non-coordinator JWT — verify 403 response. Test with missing fields in body — verify 400 response with message. Test that credential_payload does not appear in any response body. Integration test: Call store then verify hasCredentialsConfigured returns true for same org+system and false for a different org.
Adding exported_at and export_run_id columns to expense_claims requires a live migration on a table shared with the approval workflow. A poorly timed migration could lock the table and block claim submissions or approvals.
Mitigation & Contingency
Mitigation: Use non-blocking ADD COLUMN with a DEFAULT of NULL (no backfill needed) executed during a low-traffic window. Test migration rollback on a staging replica before production deployment.
Contingency: If migration causes table lock contention, roll back and reschedule for a maintenance window. Use a feature flag to gate the export UI until the migration completes successfully.
Chart of accounts mapping configurations for Xledger and Dynamics may not be fully specified by stakeholders at development time, leaving the mapper with incomplete data and causing validation failures for unmapped expense categories.
Mitigation & Contingency
Mitigation: Implement the mapper to return a structured validation error (not a crash) for any unmapped field, and surface these errors clearly in the export confirmation dialog. Request full mapping tables from Blindeforbundet and HLF stakeholders as a pre-condition for this epic.
Contingency: If mappings arrive incomplete, ship the mapper with the available subset and mark unmapped categories as excluded (skipped with reason). Coordinators see which categories are skipped and can manually submit those records.
Supabase Vault configuration for storing per-org accounting credentials may require infra permissions or environment secrets not yet provisioned in staging or production, blocking development and testing of credential retrieval.
Mitigation & Contingency
Mitigation: Provision Vault secrets and environment configuration in staging as the first task of this epic. Document the exact secret naming convention and rotation procedure before implementation begins.
Contingency: If Vault is unavailable, use environment variables scoped to the Edge Function as a temporary fallback for development. Block production deployment until Vault-based storage is confirmed operational.