critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

A sealed class ClaimState is defined with exactly 5 concrete subtypes: ClaimStateDraft, ClaimStateSubmitted, ClaimStateApproved, ClaimStateRejected, ClaimStateExported
Each ClaimState subtype carries only the fields relevant to that state (e.g., ClaimStateApproved carries approvedAt, approvedByUserId; ClaimStateRejected carries rejectedAt, rejectedByUserId, justification)
A ClaimTransitionResult sealed class is defined with subtypes: ClaimTransitionSuccess(newState: ClaimState, approvalPath: ApprovalPath?) and ClaimTransitionFailure(reason: ClaimTransitionFailureReason)
ClaimTransitionFailureReason is an enum covering: invalidState, insufficientPermissions, claimNotFound, repositoryError
ApprovalPath is an enum with values: autoApproval, manualReview
An abstract class ApprovalWorkflowService is defined with method signatures: Future<ClaimTransitionResult> submitClaim(String claimId, String actorUserId), Future<ClaimTransitionResult> approveClaim(String claimId, String coordinatorUserId, {String? comment}), Future<ClaimTransitionResult> rejectClaim(String claimId, String coordinatorUserId, {String? justification}), Future<ClaimTransitionResult> exportClaim(String claimId, String actorUserId)
All types are exported from a single barrel file approval_workflow_types.dart
No implementation code exists in this task — only interfaces, sealed classes, and enums
Dart analyzer reports zero errors and zero warnings on the new files

Technical Requirements

frameworks
Flutter
Dart
data models
ClaimState
ClaimTransitionResult
ApprovalPath
ClaimTransitionFailureReason
performance requirements
All types are pure Dart value objects — no async work, no I/O, constant-time construction
security requirements
actorUserId and coordinatorUserId are required parameters on all transition methods — the service must never infer the acting user from ambient state
No sensitive claim data (amounts, submitter PII) is stored in state types — state types carry only IDs and transition metadata

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart 3 sealed classes for both ClaimState and ClaimTransitionResult to enable exhaustive pattern matching at all call sites — this is the primary design goal. Place all types in lib/src/approval_workflow/approval_workflow_types.dart. Use freezed or hand-written copyWith/== if immutability is required downstream — prefer hand-written for this low-complexity task to avoid code generation overhead at this stage. The ApprovalWorkflowService abstract class should be in a separate file lib/src/approval_workflow/approval_workflow_service.dart.

Document each state with a brief dartdoc comment explaining which transitions are valid from that state — this serves as the FSM specification for implementers of subsequent tasks.

Testing Requirements

Unit tests (flutter_test): verify pattern matching exhaustiveness — write a switch expression over ClaimState with all 5 subtypes and assert the Dart compiler requires all cases (no default). Verify ClaimTransitionResult can be pattern-matched for success/failure. Verify ClaimTransitionFailureReason enum has exactly the specified values. No mocking required — these are pure type definitions.

Component
Approval Workflow Service
service high
Epic Risks (3)
high impact high prob technical

The ThresholdEvaluationService is described as shared Dart logic used both client-side and in the Edge Function. Supabase Edge Functions run Deno/TypeScript, not Dart, meaning the threshold logic must be maintained in two languages and can diverge, causing the server to reject legitimate client submissions.

Mitigation & Contingency

Mitigation: Implement the threshold logic as a single TypeScript module in the Edge Function and call it via a thin Dart HTTP client wrapper for client-side preview feedback only. The server is always authoritative; the client version is purely for UX (showing the user whether their claim will auto-approve before they submit).

Contingency: If dual-language maintenance is unavoidable, create a shared golden test file (JSON fixtures with inputs and expected outputs) that is run against both implementations in CI to detect divergence immediately.

medium impact medium prob technical

A peer mentor could double-tap the submit button or a network retry could trigger a duplicate submission, causing the ApprovalWorkflowService to attempt two concurrent state transitions from draft→submitted for the same claim, potentially resulting in two audit events or conflicting statuses.

Mitigation & Contingency

Mitigation: Implement idempotency in the ApprovalWorkflowService using a database-level unique constraint on (claim_id, from_status, to_status) per transition, combined with a UI-level submission lock (disable button after first tap until response returns).

Contingency: Add a deduplication check at the start of every state transition method that returns the existing state if an identical transition is already in progress or completed within the last 10 seconds.

high impact medium prob scope

Claims with multiple expense lines (e.g., mileage + parking) must have their combined total evaluated against the threshold. If individual lines are added asynchronously or the evaluation runs before all lines are persisted, the auto-approval decision may be computed on an incomplete set of expense lines.

Mitigation & Contingency

Mitigation: The Edge Function always fetches all expense lines from the database (not from the client payload) before computing the threshold decision. Define a clear claim submission contract that requires all expense lines to be persisted before the submit action is called.

Contingency: Add a validation step in ApprovalWorkflowService that counts expected vs. persisted expense lines before allowing the transition, returning a validation error if lines are missing.