critical priority medium complexity infrastructure pending infrastructure specialist Tier 2

Acceptance Criteria

RLS is confirmed ENABLED on both mileage_claims and org_configuration via SELECT relrowsecurity FROM pg_class WHERE relname = 'mileage_claims'
A peer mentor JWT can SELECT only rows where peer_mentor_id = auth.uid() — querying another peer mentor's claim returns 0 rows (not an error, not a 403)
A peer mentor JWT cannot INSERT a row with a peer_mentor_id different from auth.uid() — attempt returns PostgreSQL permission denied
A coordinator JWT can SELECT all claims within their organization_id scope — cross-organization claims return 0 rows
A coordinator JWT cannot INSERT a mileage claim directly — claim creation is restricted to peer mentor role
An unauthenticated (anon) request returns 0 rows for both tables with no server error
org_configuration SELECT is readable by any authenticated user belonging to the matching organization_id
org_configuration INSERT and UPDATE are rejected for peer mentor role — coordinator or admin role required
Service role bypasses all RLS policies (verified via service_role key in Supabase dashboard)
All RLS policies are defined in a versioned migration file and applied via supabase db push
Policy names follow the convention: {table}_{role}_{operation} (e.g. mileage_claims_peer_mentor_select)
A test report document is produced listing each policy, its test scenario, and pass/fail status

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
Supabase PostgreSQL 15
Supabase Auth
data models
activity
assignment
performance requirements
RLS policy evaluation overhead must not increase query time by more than 20ms for standard paginated queries
Coordinator chapter-scope policy join must use an indexed path — verify with EXPLAIN ANALYZE
security requirements
Row Level Security enforced on all tables — no table-level grants to anon or authenticated roles beyond what RLS permits
Service role key never distributed to mobile clients — RLS bypass only available server-side in Edge Functions
All policies must be PERMISSIVE (not RESTRICTIVE) and default-deny (no policy = no access)
organization_id on mileage_claims validated against authenticated user's org membership in the policy expression to prevent cross-org injection
RLS policies must survive a Supabase project reset — store all policies in migration files, not applied manually via dashboard

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

The coordinator chapter-scope policy is the most complex. The recommended pattern is: EXISTS (SELECT 1 FROM organization_members WHERE user_id = auth.uid() AND organization_id = mileage_claims.organization_id AND role IN ('coordinator', 'admin')). Ensure organization_members is also RLS-protected to prevent policy bypass via a subquery. Use Supabase's auth.jwt() → 'app_metadata' → 'role' claim as an alternative if org membership is embedded in the JWT at login time — this avoids the subquery entirely and is faster.

Test both approaches and choose based on how roles are currently issued in the project's auth flow. Avoid using SECURITY DEFINER functions in RLS policies unless absolutely necessary — they are harder to audit.

Testing Requirements

Write verification tests using the Supabase JS client or psql with explicit role switching (SET LOCAL ROLE). Cover all policy scenarios listed in acceptance criteria. For Flutter integration tests, use ProviderContainer with a real Supabase test instance and two separate test user accounts (one peer_mentor, one coordinator in the same org, one coordinator in a different org). Assert that cross-org and cross-user queries return empty lists, not exceptions.

Document the test matrix in a Markdown table committed alongside the migration file.

Component
Supabase Mileage Adapter
infrastructure low
Epic Risks (3)
high impact medium prob security

Supabase Row Level Security policies for mileage_claims may require complex join conditions to distinguish peer mentor (own claims only) from coordinator (chapter-scoped claims) access. If the RLS policy is misconfigured, coordinators could see claims outside their chapter scope or peer mentors could read other users' data, causing a data privacy incident.

Mitigation & Contingency

Mitigation: Write RLS policy SQL as part of this epic with explicit test cases for each role. Use Supabase's built-in policy testing tools and add integration tests that assert cross-user data isolation before merging.

Contingency: If RLS configuration proves too complex to test reliably within the epic, add an application-layer guard in the adapter that filters query results by authenticated user ID as a defence-in-depth measure while the policy is corrected.

medium impact low prob scope

Norwegian tax authority reimbursement rounding rules may change or may not be publicly documented in machine-readable form. Using the wrong rounding convention could cause systematic over- or under-payment, leading to compliance issues for the organisation.

Mitigation & Contingency

Mitigation: Source the exact rounding specification from HLF's finance team before implementing MileageCalculationService. Document the rule as a comment in the source code and link to the authoritative reference.

Contingency: If the rule is ambiguous, implement both truncation and half-up rounding behind a configuration flag so the organisation can switch without a code release.

low impact low prob technical

SharedPreferences reads and writes are asynchronous. If the distance prefill service (built in the next epic) calls LocalDistanceCache concurrently with a post-submission write, a race condition could result in the cache returning a stale or partially written value, causing the next form load to show an incorrect default.

Mitigation & Contingency

Mitigation: Wrap all SharedPreferences access in LocalDistanceCache with sequential async operations and document the non-concurrent usage contract in the class API.

Contingency: If race conditions are observed in testing, introduce a simple mutex pattern using a Completer to serialise cache operations.