critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

ExportResult is a Dart class (or sealed class) with fields: exportRunId (String?), status (ExportStatus), fileUrl (String?), recordCount (int?), errorCode (String?), errorDetail (String?), exportedAt (DateTime?)
ExportStatus is a Dart enum with values: pending, completed, failed, duplicate, empty — no additional values added without updating all switch exhaustiveness checks
DateRange is a typed value object with start (DateTime) and end (DateTime), a validate() method that asserts start is not after end, and a named constructor fromIso8601(String start, String end)
AccountingExporterService is an abstract class (interface) with a single abstract method: Future<ExportResult> triggerExport(String orgId, DateRange dateRange, String requestingUserId)
All fields on ExportResult use Dart null-safety correctly: status is non-nullable, all other fields are nullable with documented nullability conditions
The interface file exports only the abstract class and the associated value types; no implementation code is present in this file
A factory constructor ExportResult.duplicate(String existingRunId) is provided as a convenience constructor
A factory constructor ExportResult.failure(String errorCode, String errorDetail) is provided as a convenience constructor
A factory constructor ExportResult.empty() is provided as a convenience constructor
All types are placed in a dedicated lib/src/accounting/export/ directory with barrel export via accounting_export.dart
Dart doc comments are present on all public members describing nullability contracts and usage intent

Technical Requirements

frameworks
Dart (null-safety, sealed classes if Dart >= 3.0)
data models
ExportResult
ExportStatus
DateRange
AccountingExporterService
performance requirements
Value objects must be immutable; use final fields and const constructors where applicable
DateRange.validate() must be a synchronous operation with O(1) complexity
security requirements
errorDetail field must never be populated with raw exception messages containing stack traces or credential fragments in production — enforce via code review and documentation
requestingUserId in the interface signature must be validated as non-empty by all concrete implementations

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart 3's sealed classes for ExportStatus if the project targets Dart >= 3.0 — this gives compiler-enforced exhaustive switches. If targeting Dart 2.x, use a regular enum and add a lint rule requiring exhaustive switch coverage via the exhaustive_cases lint. Keep DateRange in the same file as the interface since it is exclusively used as a parameter type. Avoid adding methods to ExportResult beyond factory constructors and equality/hashCode — keep it a pure data class.

Consider using package:equatable or implementing == and hashCode manually for reliable value equality in tests. The interface should be in lib/src/accounting/export/accounting_exporter_service.dart and re-exported via lib/accounting_export.dart. This file is the contract that both the foundation epic and orchestration epic tasks depend on — merge it before any dependent tasks begin.

Testing Requirements

Unit tests (dart test): (1) ExportResult.duplicate() sets status=duplicate and exportRunId correctly. (2) ExportResult.failure() sets status=failed with errorCode and errorDetail. (3) ExportResult.empty() sets status=empty and recordCount=0. (4) DateRange.fromIso8601() parses valid ISO strings correctly.

(5) DateRange.validate() throws ArgumentError when start > end. (6) DateRange.validate() passes when start == end (single-day export). (7) Verify that ExportStatus switch is exhaustive by compiling a switch over all values — failing to handle any value should be a compile error. All tests must pass with dart test and zero linter warnings.

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.