critical priority medium complexity testing pending backend specialist Tier 4

Acceptance Criteria

AccountingExporterService.triggerExport() executes guard check before any DB query and short-circuits with DuplicateExportException if a run for the same org+period already exists
Approved expense claims for the given org and date range are fetched and mapped to ExportFieldMap without any raw DB model leaking into the exporter layer
XledgerExporter path produces a valid CSV/JSON file artifact and ExportResult contains a non-null filePath and null externalRef
DynamicsExporter path calls the Dynamics REST endpoint and ExportResult contains a non-null externalRef and null filePath
Empty claims set returns ExportResult with status=NO_DATA and no file or external call is attempted
Unsupported accounting system enum value throws UnsupportedExportSystemException with a descriptive message
Export run is persisted to the accounting_export_runs table (or equivalent) after every terminal outcome including errors
All 6 unit test scenarios pass: Xledger happy path, Dynamics happy path, duplicate guard rejection, unsupported system, empty claims, field mapping validation
No Xledger or Dynamics credentials are ever passed as method arguments — they are injected via constructor or environment at service instantiation
Service is testable via dependency injection (mock credentials vault, mock exporters, mock DB client)

Technical Requirements

frameworks
Flutter/Dart (service layer)
flutter_test
mockito or mocktail for Dart mocking
apis
Xledger REST API (mocked in unit tests)
Microsoft Dynamics 365 REST API (mocked in unit tests)
Supabase PostgreSQL 15 (mocked in unit tests)
data models
activity
assignment
annual_summary
bufdir_export_audit_log
performance requirements
Field mapping for up to 500 claims must complete in under 200ms in-memory
Service must not block the calling isolate — async/await throughout
Unit test suite must run in under 10 seconds total
security requirements
Credentials never passed as plain arguments — injected at construction time only
ExportResult must not include raw credentials or API keys in any field
Duplicate guard must be atomic to prevent race conditions (use DB-level unique constraint as final arbiter)
All financial data in transit between layers must remain in memory only — no temp file writes during mapping

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Model the orchestration as a strict sequential pipeline using early-return guards. Define a sealed ExportResult class (or Dart equivalent with status enum + optional fields) to make all outcomes explicit. Inject all external dependencies (DB client, exporters, vault) through the constructor to enable clean mocking. The duplicate guard should query the export runs table using orgId + reportPeriodId as a compound key — do NOT rely on in-memory state.

For Dart, prefer Either or throw typed exceptions — choose one convention and document it. The field mapping step should be a pure function (no side effects) so it can be tested independently. Persist the export run record inside a try/finally block to guarantee persistence even on exporter failure — store the error message in the run record's status field.

Testing Requirements

Unit tests only (no integration or e2e at this layer). Use flutter_test with mocktail for mocking the credentials vault, Supabase client, XledgerExporter, and DynamicsExporter. Required test cases: (1) Xledger happy path — assert ExportResult.status=SUCCESS, filePath non-null, export run persisted; (2) Dynamics happy path — assert ExportResult.status=SUCCESS, externalRef non-null, export run persisted; (3) Duplicate guard — seed an existing run record, assert DuplicateExportException thrown before any claim query executes; (4) Unsupported system — assert UnsupportedExportSystemException with meaningful message; (5) Empty claims — assert ExportResult.status=NO_DATA, no exporter called; (6) Field mapping validation — provide a fixture claim and assert all required ExportFieldMap keys are populated. Aim for 100% branch coverage of the orchestration sequence.

Component
Accounting Exporter Service
service high
Epic Risks (3)
high impact medium prob technical

The Edge Function may exceed Supabase's execution time limit (default 150 seconds, but effectively constrained by the 10-second client SLA) when processing large batches of claims with complex chart-of-accounts mapping, causing the export to fail after partial processing.

Mitigation & Contingency

Mitigation: Implement the export pipeline with early termination on timeout and an in-progress export run status. Add a benchmark test in CI that runs the full pipeline against 500 claims and fails if it exceeds 8 seconds. Optimize the approved claims query with indexes on status, org_id, and date fields.

Contingency: If performance targets cannot be met synchronously, convert the Edge Function to an async job pattern: the function queues the export and returns a job ID immediately; the client polls a status endpoint and downloads the file when ready. This requires a job queue table and a polling UI state.

high impact medium prob security

Supabase Vault access from the Edge Function may require specific service role key configuration that differs between staging and production environments, causing credential retrieval to fail silently and producing export runs that appear successful but have no valid accounting system target.

Mitigation & Contingency

Mitigation: Test Vault read access in the Edge Function in staging before implementing any business logic. Add an explicit credential validation step at Edge Function startup that fails fast with a clear error if Vault is unreachable or the secret is missing.

Contingency: If Vault access fails in production, fall back to environment variable-based credentials temporarily (never returned to client) while the Vault configuration is corrected. Alert on-call via a monitoring rule that fires if credential retrieval fails.

medium impact low prob technical

AccountingExporter Service may become tightly coupled to specific exporter implementations if the factory pattern is not implemented cleanly, making it difficult to add a third exporter in the future without modifying the orchestrator.

Mitigation & Contingency

Mitigation: Define an AccountingExporter abstract class with a strict interface contract before implementing any concrete class. Use a registry pattern (Map<orgType, AccountingExporter>) in the factory rather than conditionals. Code review should verify no concrete class is imported directly in the orchestrator.

Contingency: If tight coupling is discovered after implementation, refactor the factory before the Edge Function epic ships so the interface is stable before any external callers are wired in.