high priority low complexity database pending database specialist Tier 3

Acceptance Criteria

Migration file exists at supabase/migrations/<timestamp>_add_location_privacy_settings.sql and is idempotent (uses IF NOT EXISTS / DO $$ BEGIN … EXCEPTION WHEN duplicate_column THEN … END $$)
location_privacy_settings JSONB column added to organisations table with a NOT NULL DEFAULT matching the agreed default config JSON
Default JSON value is: {"consent_expiry_days": 365, "retention_days": 730, "privacy_policy_url": null, "location_precision": "approximate"}
All existing organisation rows are updated with the default value via an UPDATE statement in the migration
A CHECK constraint validates that location_precision is one of ('approximate', 'precise')
A CHECK constraint validates that consent_expiry_days and retention_days are positive integers
RLS policy UPDATE on organisations restricts writes to users with role 'org_admin' for their own organisation (using auth.jwt() claims or a role lookup)
RLS policy SELECT on organisations allows coordinators and peer mentors to read their own organisation row (location_privacy_settings is non-sensitive config, not PII)
Migration rollback (DOWN) script removes the column cleanly
Supabase type definitions (supabase gen types typescript or equivalent Dart types) regenerated after migration

Technical Requirements

frameworks
Supabase PostgreSQL 15
apis
Supabase RLS policy DDL
Supabase Migrations CLI
data models
accessibility_preferences
performance requirements
UPDATE for seeding defaults uses a single bulk UPDATE WHERE location_privacy_settings IS NULL — no row-by-row processing
JSONB column is not indexed by default; add a GIN index only if query patterns require filtering by specific JSON keys (not needed for this use case)
security requirements
privacy_policy_url stored in JSONB may contain URLs — validate format at application layer before render, not at DB layer
RLS UPDATE policy must check that the authenticated user's organisation_id matches the row being updated — prevent cross-org config tampering
Service role is required for the seeding UPDATE in the migration — this is acceptable as migrations run server-side only

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Prefer adding a JSONB column to the existing organisations table over creating a separate table — the config is a 1:1 relationship and a separate table adds join complexity for minimal benefit at this scale. Use ALTER TABLE organisations ADD COLUMN IF NOT EXISTS location_privacy_settings JSONB NOT NULL DEFAULT '{...}'::jsonb. The DEFAULT clause handles future INSERT statements; the explicit UPDATE handles existing rows. For the RLS UPDATE policy, use: CREATE POLICY update_own_org_privacy ON organisations FOR UPDATE USING (id = (SELECT organisation_id FROM user_profiles WHERE id = auth.uid()) AND EXISTS (SELECT 1 FROM user_roles WHERE user_id = auth.uid() AND role = 'org_admin')).

Ensure the migration is tested in the local Supabase stack before merging.

Testing Requirements

Run supabase db reset locally and confirm the migration applies cleanly. Test RLS policies manually via psql as a peer_mentor role (should be able to SELECT, must not be able to UPDATE). Test as org_admin role (should be able to UPDATE own org, must not be able to UPDATE other org). Verify the CHECK constraint rejects invalid location_precision values ('exact', 'gps', etc.).

Verify existing rows have the default JSONB value after migration. Run supabase db diff to confirm no unexpected schema changes.

Component
Location Privacy Configuration
infrastructure low
Epic Risks (3)
high impact medium prob integration

Supabase's hosted PostGIS extension behaviour may differ from the local emulator for spatial RPC functions, causing bounding-box queries to return incorrect results or fail in production while passing locally.

Mitigation & Contingency

Mitigation: Write integration tests against the Supabase emulator from the start and run the same test suite against a staging Supabase project before merging. Use ST_DWithin and ST_MakeEnvelope in plain SQL first, validate with psql, then wrap as RPC.

Contingency: If PostGIS RPC proves unreliable, fall back to client-side bounding box filtering on a full fetch of consented mentor locations (acceptable for up to ~200 mentors per chapter) until the spatial query is stabilised.

medium impact low prob dependency

OpenStreetMap tile usage may require attribution handling and rate limiting. Switching to Google Maps Flutter plugin mid-implementation would require significant rework of the map-provider-integration abstraction.

Mitigation & Contingency

Mitigation: Define the map-provider-integration abstraction interface before selecting the SDK so that the concrete implementation is swappable. Implement OSM first with correct attribution. Document Google Maps as the alternate with its API key setup steps.

Contingency: If OSM tiles are rejected by stakeholders or tile server limits are hit, activate the Google Maps Flutter plugin implementation behind the same interface without touching any UI or service code.

high impact low prob security

Incorrect RLS configuration could allow a coordinator to query mentor locations from a different organisation, constituting a GDPR data breach.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration tests with two isolated test organisations and assert that cross-organisation queries return zero rows. Include these tests in CI. Have a second developer review all RLS policy SQL before migration is applied.

Contingency: If a cross-organisation data leak is discovered post-deployment, immediately disable the map feature via the organisation feature flag, revoke the affected Supabase RLS policy, and notify the data protection officer per the organisation's GDPR incident response procedure.