critical priority medium complexity testing pending testing specialist Tier 6

Acceptance Criteria

E2E test submits a claim and reads back at least 3 ordered claim_events rows: submitted → under_evaluation → approved (or rejected)
Every event row contains event_type, claim_id, actor_id, occurred_at (UTC), and event_payload fields — none are null
Events are immutable: a test attempt to UPDATE or DELETE any claim_events row is rejected by the database (RLS or trigger)
Event ordering is deterministic: occurred_at timestamps are strictly increasing for the test claim's lifecycle
The audit trail contains sufficient data to reconstruct the full decision: claimant_id, coordinator_id, threshold_at_decision, decision (approved/rejected), decided_at, expense_amount, distance_km
A Bufdir compliance report can be generated from claim_events alone without joining other tables for required fields
An accounting export (Xledger / Dynamics format) can be derived from the audit trail rows — verified by running the export query against test data
The verified schema is documented as a JSON Schema or TypeScript interface in /docs/audit-trail-schema.ts and committed alongside this task
E2E tests run against an isolated Supabase test project (separate from production) with deterministic seed data

Technical Requirements

frameworks
flutter_test
supabase_flutter
apis
Supabase PostgREST (claim_events table)
Supabase RLS policies
Xledger export API (read-only verification)
Microsoft Dynamics export format (read-only verification)
data models
ClaimEvent
ClaimApprovalRecord
ExpenseClaim
BufdirComplianceReport
AccountingExportRow
performance requirements
Full E2E test suite completes within 60 seconds against test Supabase project
claim_events query for a single claim must return in under 200ms
security requirements
RLS must prevent any authenticated user from deleting or updating claim_events rows
Only the service role (Edge Function) may insert into claim_events — end-user JWT must be rejected for direct inserts
Test Supabase project must use separate service role key — never share with production
Audit trail must retain events for at least 7 years per Norwegian accounting regulations (Bokføringsloven §13)

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

The claim_events table must be append-only: enforce immutability via a PostgreSQL trigger (BEFORE UPDATE OR DELETE RAISE EXCEPTION) in addition to RLS policies — defence in depth. Each event payload should be a JSONB column containing the full state snapshot at the time of transition, not just deltas, so the audit trail is self-contained. For the Bufdir compliance report: the required fields per activity are activity_type, peer_mentor_id, org_id, date, duration_minutes, and any expense amounts — verify all are present in the event payload JSONB. For accounting export: Xledger requires project_code, cost_centre, amount, currency (NOK), and voucher_date — map these from claim_events in the export query.

This task is the final quality gate for the entire epic and must be green before the epic is marked complete. The documented schema (/docs/audit-trail-schema.ts) becomes the contract for the Bufdir reporting pipeline built in a later epic.

Testing Requirements

End-to-end integration tests using flutter_test against a dedicated test Supabase project with seed data. Test lifecycle: (1) insert seed claim via service role, (2) invoke threshold evaluation Edge Function, (3) invoke approval decision Edge Function, (4) read claim_events and assert row count, ordering, and field completeness, (5) attempt DELETE on claim_events row and assert 403/RLS rejection, (6) run compliance export query and assert all required Bufdir fields present, (7) run accounting export query and assert Xledger-compatible columns. Use a beforeAll hook to seed and an afterAll hook to clean up test data. Document each assertion with a comment referencing the specific Bufdir or accounting requirement it satisfies.

Component
Claim Events Repository
data low
Dependencies (6)
Write widget tests for ClaimStatusBadge verifying: correct colour rendered for each ClaimStatus enum value, correct semantics label set for each status (for VoiceOver/TalkBack), badge renders at default and custom sizes without overflow, and badge is visually distinct (contrast ratio meets WCAG 2.2 AA) for all status variants. epic-expense-approval-workflow-foundation-task-016 Write unit tests for ClaimEventsRepository covering: successful event insertion, rejection of update calls (UnsupportedError), rejection of delete calls (UnsupportedError), correct event ordering by created_at, and proper error mapping for Supabase constraint violations. Use flutter_test with a mocked Supabase client. Target 100% branch coverage for the repository class. epic-expense-approval-workflow-foundation-task-011 Write unit tests for ExpenseClaimStatusRepository covering: successful status update with correct version, OptimisticLockException when concurrent update detected, stream emission on status change, and all valid status transition paths. Mock Supabase responses to simulate concurrent modification. Verify the WHERE clause includes both claim_id and version in update operations. epic-expense-approval-workflow-foundation-task-012 Write unit tests for ClaimApprovalRepository covering: successful decision insertion with full coordinator metadata snapshot, correct threshold value captured at decision time, rejection of decisions for claims outside coordinator scope, and error handling for constraint violations. Verify that every recorded decision contains actor_id, threshold_at_decision, and decided_at fields. epic-expense-approval-workflow-foundation-task-015 Write integration tests for ApprovalNotificationService verifying: FCM notification dispatched when claim status changes to approved/rejected, Realtime event published to correct channel, coordinator notification includes required claim metadata (claimant name, amount, claim_id), FCM token refresh handled gracefully, and no duplicate notifications sent when both channels active simultaneously. epic-expense-approval-workflow-foundation-task-017 Write comprehensive unit tests for ThresholdEvaluationService covering: auto-approval below distance threshold (under 50 km, no receipts), manual approval required above threshold, receipt requirement triggered by amount, org-specific threshold configuration overrides, and edge cases at exact threshold boundaries. Verify outputs match expected Edge Function behaviour for each scenario. epic-expense-approval-workflow-foundation-task-013
Epic Risks (3)
medium impact medium prob technical

Optimistic locking in ExpenseClaimStatusRepository may produce excessive concurrency exceptions in high-volume coordinator sessions where multiple coordinators process the same queue simultaneously, causing confusing UI errors and coordinator frustration.

Mitigation & Contingency

Mitigation: Design the locking strategy with a short retry window (1-2 automatic retries with 200ms back-off) before surfacing the error to the UI. Document the concurrency model clearly so the UI layer can display a contextual 'claim was already actioned' message rather than a generic error.

Contingency: If contention remains high under load testing, switch to a last-writer-wins update with a conflict notification rather than a hard block, and log all concurrent edits for audit purposes.

medium impact medium prob integration

FCM device tokens stored for peer mentors may be stale (app reinstalled, token rotated) causing push notifications for claim status changes to silently fail, leaving submitters unaware their claim was approved or rejected.

Mitigation & Contingency

Mitigation: Implement token refresh on every app launch and store updated tokens in Supabase. ApprovalNotificationService should fall back to in-app Realtime delivery when FCM returns an invalid-token error and should queue a token refresh request.

Contingency: If FCM delivery rates fall below acceptable thresholds in production monitoring, add a polling fallback in the peer mentor claim list screen that checks status on foreground resume.

high impact low prob dependency

Supabase Realtime has per-project channel and connection limits. If many coordinators and peer mentors are simultaneously subscribed across multiple screens, the project may hit quota limits causing subscription failures.

Mitigation & Contingency

Mitigation: Design RealtimeApprovalSubscription to use a single shared channel per user session rather than per-screen subscriptions. Implement subscription reference counting so channels are only opened once and reused across screens.

Contingency: Upgrade the Supabase plan tier if limits are reached, and implement graceful degradation to polling with a 30-second interval when Realtime is unavailable.