critical priority high complexity backend pending backend specialist Tier 4

Acceptance Criteria

Expense record is persisted to Supabase via ExpenseRepository before edge function is called
AutoApprovalEdgeFunctionClient is called with the newly created expense ID
If edge function responds with auto_approved: true, expense status is updated to 'auto_approved' in the database
If edge function responds with auto_approved: false, expense status is set to 'pending_attestation'
SubmissionOutcome.autoApproved and SubmissionOutcome.pendingAttestation are distinct typed variants
If the edge function call fails (network error, timeout, non-2xx), the expense remains in 'pending_attestation' state — auto-approval is opportunistic, not a blocker
Edge function timeout is set to a maximum of 10 seconds
The entire flow (persist + edge function call + status update) is wrapped in a logical transaction or compensating rollback: if the DB persist succeeds but the status update fails, the record is not left in an inconsistent state
Expense record includes: user_id, org_id, expense_type, amount_ore, distance_metres, receipt_url, submitted_at timestamp
Integration test verifies auto-approval for a <50 km, no-receipt-required expense and pending-attestation for a >50 km expense

Technical Requirements

frameworks
Flutter
Riverpod
Dart
apis
Supabase Database (via ExpenseRepository)
Supabase Edge Functions (AutoApprovalEdgeFunctionClient)
data models
ExpenseDraft
ExpenseRecord
SubmissionOutcome
AutoApprovalResponse
performance requirements
Database insert must complete within 3 seconds on a 4G connection
Edge function call must have a 10-second timeout with graceful degradation to pending_attestation
security requirements
Row-level security (RLS) on the expenses table must ensure users can only insert their own records
org_id must be derived from the authenticated session, not from client-supplied input
Edge function must validate the expense ID belongs to the calling user before applying auto-approval

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use Supabase Dart client's `.rpc()` for the edge function call and `.from('expenses').insert()` for persistence. Wrap the two-step operation (insert + status update after edge function) in a Supabase RPC or database function if possible, to get atomic behaviour. If not feasible, implement an idempotent retry for the status update step. The AutoApprovalEdgeFunctionClient should be an abstract class injected via Riverpod.

The auto-approval criterion (distance < 50 km AND no manual-approval items) must be evaluated server-side in the edge function, not client-side — the client must not trust its own determination. Use `supabase.auth.currentUser!.id` for user_id; never accept user_id from the draft payload.

Testing Requirements

Unit tests: mock ExpenseRepository and AutoApprovalEdgeFunctionClient. Test (1) happy path auto-approval; (2) happy path pending-attestation; (3) edge function timeout → pending-attestation, no exception propagated; (4) DB insert failure → SubmissionError returned; (5) status update failure after successful insert → compensating action triggered. Integration tests against Supabase local emulator: verify RLS blocks cross-user reads, verify edge function sets correct status. Use flutter_test for all tests.

Component
Expense Submission Service
service high
Epic Risks (3)
high impact medium prob scope

Mutual exclusion rules are stored in the expense type catalogue's exclusive_groups field. If the catalogue schema or group definitions differ between HLF and Blindeforbundet, the validation service must handle multiple group configurations without hardcoding organisation-specific logic.

Mitigation & Contingency

Mitigation: Design the validation service to be purely data-driven: it reads exclusive_groups from the cached catalogue and enforces whichever groups are defined, with no hardcoded organisation names. Write parameterised unit tests covering at least 4 different catalogue configurations to verify generality.

Contingency: If an organisation requires non-standard exclusion semantics (e.g. partial exclusion within a group), introduce an exclusion_type field to the catalogue schema and extend the service, treating it as a catalogue configuration change rather than a code fork.

medium impact high prob technical

The attestation service subscribes to Supabase Realtime for live queue updates. On mobile, Realtime WebSocket connections can be dropped during network transitions, causing the coordinator queue to become stale without the user being aware.

Mitigation & Contingency

Mitigation: Implement connection lifecycle management: reconnect on network-change events, show a 'reconnecting' indicator when the subscription is broken, and perform a full queue refresh on reconnect rather than relying solely on delta events.

Contingency: Add a manual pull-to-refresh gesture on the attestation queue screen as a guaranteed fallback. If Realtime proves unreliable in production, switch to periodic polling (30-second interval) as a degraded but functional mode.

medium impact medium prob integration

If a peer mentor submits a draft while offline and then submits the same claim again after connectivity is restored (thinking the first attempt failed), duplicate claims may be persisted in Supabase.

Mitigation & Contingency

Mitigation: Assign a client-generated idempotency key (UUID) to each draft at creation time. The submission service sends this key as an upsert key to Supabase, preventing duplicate inserts. The draft is marked 'submitted' locally after first successful upload.

Contingency: Implement a server-side duplicate detection trigger on the expense_claims table checking (activity_id, claimant_id, created_date) within a 24-hour window and returning the existing record ID rather than inserting a duplicate.