critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

export_runs table exists in Supabase with all required columns: run_id (UUID PK default gen_random_uuid()), org_id (UUID NOT NULL FK → organizations), initiated_by (UUID NOT NULL FK → users), status (TEXT NOT NULL CHECK IN ('pending','running','completed','failed')), date_range_start (DATE NOT NULL), date_range_end (DATE NOT NULL), target_system (TEXT NOT NULL CHECK IN ('xledger','dynamics')), record_count (INTEGER), file_url (TEXT), created_at (TIMESTAMPTZ NOT NULL DEFAULT now()), completed_at (TIMESTAMPTZ)
expense_claims table has an exported_at column of type TIMESTAMPTZ that is nullable with no default
RLS is enabled on export_runs with a SELECT policy allowing only rows where org_id matches the authenticated user's org_id
RLS INSERT/UPDATE policy on export_runs restricts to users with coordinator or org_admin role
No RLS policy allows direct DELETE on export_runs (export history is immutable)
An index exists on export_runs(org_id, created_at DESC) for efficient coordinator queries
An index exists on expense_claims(org_id, exported_at) to support filtering unexported claims
Migration script is idempotent (IF NOT EXISTS guards, safe to re-run)
Migration is committed to the repository migrations folder and reviewed before application
Rollback migration script exists that drops only the new columns/tables without affecting existing data

Technical Requirements

frameworks
Supabase Migrations (SQL)
PostgreSQL 15+
apis
Supabase Management API (for applying migrations in CI)
data models
export_runs
expense_claims
organizations
users
performance requirements
Composite index on (org_id, created_at DESC) to keep coordinator history queries under 100ms for up to 10,000 runs
Partial index on expense_claims(org_id) WHERE exported_at IS NULL to accelerate unexported-claims queries
security requirements
RLS must be enabled before any data is inserted — never disabled in production
initiated_by must reference the authenticated user's UUID from auth.uid() enforced at RLS level
file_url must be a signed Supabase Storage URL, never a public URL
No service-role key usage from Flutter client — all RLS must hold under anon/user roles

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Supabase CLI migration files (supabase/migrations/YYYYMMDDHHMMSS_export_runs.sql). Define RLS policies using CREATE POLICY with USING (org_id = (SELECT org_id FROM users WHERE id = auth.uid())) — join against the users table rather than a JWT claim to ensure accuracy. For the status CHECK constraint, consider a future-proof approach: use a lookup table or domain type rather than inline CHECK if the status enum is likely to grow. The exported_at column on expense_claims is a soft-mark approach — ensure downstream query services filter on IS NULL to detect unexported claims.

Coordinate with the team to confirm the existing expense_claims table column naming convention before migration.

Testing Requirements

Write integration tests using Supabase's local emulator (supabase start). Test 1: Insert a run as a coordinator user and verify SELECT succeeds for same org, fails for different org. Test 2: Attempt INSERT as a peer mentor role and verify RLS denial. Test 3: Verify expense_claims.exported_at accepts NULL on existing rows and TIMESTAMPTZ values on update.

Test 4: Run migration twice and confirm idempotency (no errors). Test 5: Apply rollback migration and verify original schema is restored. Tests should be executable in CI via supabase db reset + migration apply.

Component
Export Run Repository
data medium
Epic Risks (3)
high impact medium prob technical

Adding exported_at and export_run_id columns to expense_claims requires a live migration on a table shared with the approval workflow. A poorly timed migration could lock the table and block claim submissions or approvals.

Mitigation & Contingency

Mitigation: Use non-blocking ADD COLUMN with a DEFAULT of NULL (no backfill needed) executed during a low-traffic window. Test migration rollback on a staging replica before production deployment.

Contingency: If migration causes table lock contention, roll back and reschedule for a maintenance window. Use a feature flag to gate the export UI until the migration completes successfully.

medium impact high prob scope

Chart of accounts mapping configurations for Xledger and Dynamics may not be fully specified by stakeholders at development time, leaving the mapper with incomplete data and causing validation failures for unmapped expense categories.

Mitigation & Contingency

Mitigation: Implement the mapper to return a structured validation error (not a crash) for any unmapped field, and surface these errors clearly in the export confirmation dialog. Request full mapping tables from Blindeforbundet and HLF stakeholders as a pre-condition for this epic.

Contingency: If mappings arrive incomplete, ship the mapper with the available subset and mark unmapped categories as excluded (skipped with reason). Coordinators see which categories are skipped and can manually submit those records.

medium impact medium prob dependency

Supabase Vault configuration for storing per-org accounting credentials may require infra permissions or environment secrets not yet provisioned in staging or production, blocking development and testing of credential retrieval.

Mitigation & Contingency

Mitigation: Provision Vault secrets and environment configuration in staging as the first task of this epic. Document the exact secret naming convention and rotation procedure before implementation begins.

Contingency: If Vault is unavailable, use environment variables scoped to the Edge Function as a temporary fallback for development. Block production deployment until Vault-based storage is confirmed operational.