critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

mileage_claims table exists with columns: id (uuid PRIMARY KEY DEFAULT gen_random_uuid()), peer_mentor_id (uuid NOT NULL REFERENCES auth.users), organization_id (uuid NOT NULL), distance_km (numeric(8,2) NOT NULL CHECK (distance_km > 0)), route_description (text), status (text NOT NULL CHECK (status IN ('draft','submitted','approved','rejected'))), submitted_at (timestamptz), approved_at (timestamptz), approved_by (uuid REFERENCES auth.users), created_at (timestamptz NOT NULL DEFAULT now()), updated_at (timestamptz NOT NULL DEFAULT now())
org_configuration table exists with columns: id (uuid PRIMARY KEY DEFAULT gen_random_uuid()), organization_id (uuid NOT NULL UNIQUE), rate_per_km (numeric(6,2) NOT NULL CHECK (rate_per_km > 0)), auto_approval_threshold_km (numeric(8,2) NOT NULL DEFAULT 50), receipt_required_threshold_nok (numeric(8,2) NOT NULL DEFAULT 100), created_at (timestamptz NOT NULL DEFAULT now()), updated_at (timestamptz NOT NULL DEFAULT now())
RLS is enabled on both tables (ALTER TABLE ... ENABLE ROW LEVEL SECURITY)
Peer mentor SELECT policy on mileage_claims restricts rows to peer_mentor_id = auth.uid()
Peer mentor INSERT policy on mileage_claims restricts inserted rows to peer_mentor_id = auth.uid()
Coordinator SELECT policy on mileage_claims restricts rows to organization_id matching the coordinator's chapter scope (via org membership join)
Service role can bypass RLS for admin operations
org_configuration SELECT is restricted to authenticated users belonging to the matching organization_id
org_configuration INSERT/UPDATE is restricted to coordinator or admin roles only
Index exists on mileage_claims(peer_mentor_id, created_at DESC) for paginated user history queries
Index exists on mileage_claims(organization_id, status) for coordinator dashboard queries
Index exists on org_configuration(organization_id) (unique index covering the UNIQUE constraint)
Migration script is idempotent (safe to run twice without error)
All constraints verified via psql or Supabase dashboard query against a test schema

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase PostgreSQL 15
data models
activity
assignment
activity_type
performance requirements
Paginated query for a peer mentor's own claims (last 50) must execute in under 100ms with the composite index
Coordinator dashboard query filtering by organization_id and status must execute in under 200ms for up to 10,000 claims
org_configuration lookup by organization_id must execute in under 50ms
security requirements
Row Level Security enforced on both tables — no table-level grants to anon role
Service role key never exposed to mobile clients — only used in Edge Functions
organization_id on mileage_claims must be validated against the authenticated user's organization membership via RLS policy to prevent cross-org data insertion
All PII fields (peer_mentor_id, route_description) subject to existing org-level encryption policy
Migration must not DROP existing tables — use CREATE TABLE IF NOT EXISTS pattern

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Write the schema as a Supabase migration file (supabase/migrations/YYYYMMDDHHMMSS_mileage_schema.sql). Use gen_random_uuid() not uuid_generate_v4() as the former is available without extensions in modern Postgres. For the coordinator RLS policy, join against the org membership table (e.g. organization_members or user_roles) rather than embedding org logic in the policy expression directly — this keeps policies readable and maintainable.

Define the updated_at trigger using the standard Supabase moddatetime extension pattern. The auto_approval_threshold_km default of 50 and receipt_required_threshold_nok default of 100 match the HLF requirements from the product documentation — confirm with the team before finalizing these defaults as they may differ per organization.

Testing Requirements

Write SQL-level tests (pgTAP or Supabase test helpers) covering: (1) peer mentor cannot SELECT claims belonging to another peer mentor; (2) peer mentor cannot INSERT a claim with a different peer_mentor_id; (3) coordinator can SELECT claims within their organization but not outside it; (4) unauthenticated request returns zero rows (not an error); (5) org_configuration INSERT by a non-coordinator role is rejected with permission denied; (6) CHECK constraints reject distance_km <= 0 and rate_per_km <= 0; (7) UNIQUE constraint on org_configuration(organization_id) rejects duplicate inserts. Run migration script twice and assert no error on second run (idempotency test).

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.