End-to-end audit trail and compliance verification
epic-expense-approval-workflow-foundation-task-018 — Perform an end-to-end verification of the complete audit trail: submit a test claim, trigger threshold evaluation, record approval decision, verify claim_events table contains all state transitions in order, verify no events were mutated or deleted, and confirm the audit log is sufficient for Bufdir compliance reporting and accounting export. Document the verified audit trail schema for downstream consumers.
Acceptance Criteria
Technical Requirements
Execution Context
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.
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.
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.
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.