critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

A Supabase migration file exists at supabase/migrations/<timestamp>_create_organization_configs.sql and applies cleanly via supabase db push
The organization_configs table contains columns: id (uuid, primary key, default gen_random_uuid()), organization_id (uuid, not null, foreign key → organizations.id on delete cascade), flag_key (text, not null), enabled (boolean, not null, default false), rollout_percentage (integer, not null, default 0, check 0–100), min_app_version (text, nullable), activation_date (timestamptz, nullable), metadata (jsonb, nullable, default '{}'), created_at (timestamptz, not null, default now()), updated_at (timestamptz, not null, default now())
A unique constraint exists on (organization_id, flag_key) to prevent duplicate flag entries per organisation
A check constraint enforces rollout_percentage between 0 and 100 inclusive
Row-Level Security (RLS) is enabled on the table — authenticated users may only SELECT rows where organization_id matches their own organisation (derived from the JWT claim or a profiles join)
Only service_role (backend/admin) may INSERT, UPDATE, and DELETE rows — no client-side mutation is permitted without the service key
An index exists on (organization_id, flag_key) for efficient per-organisation flag lookups
An index exists on (organization_id, enabled) for fast 'all enabled flags for org' queries
A trigger or generated column keeps updated_at current on every UPDATE
The migration is idempotent — running it twice does not error (use CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS)
A seed file or inline comment documents the canonical list of flag_key values expected by the app (e.g., 'bufdir_reporting', 'travel_expense', 'pause_peer_mentor') so that flag_key is self-documenting
The schema is reviewed and confirmed correct by running supabase db reset in a local dev environment with zero errors

Technical Requirements

frameworks
Supabase
apis
Supabase REST API (PostgREST)
Supabase Realtime (optional, for live flag updates)
data models
organization_configs
organizations (referenced via FK)
performance requirements
Fetching all enabled flags for a single organisation must complete in under 50ms on a cold Supabase query (ensured by the composite index on organization_id + enabled)
Flag resolution at app startup must not block the main thread — the Dart repository layer reads from Supabase asynchronously
security requirements
RLS policy: authenticated users may only read their own organisation's flags — no cross-tenant data leakage
Service role key must never be embedded in the Flutter app binary — all writes go through a Supabase Edge Function or backend service
metadata JSONB column must not store PII — it is for technical configuration only
The organizations.id foreign key with ON DELETE CASCADE ensures orphaned flag rows are cleaned up automatically when an organisation is removed

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Structure the migration as a single .sql file with clear sections: (1) CREATE TABLE, (2) CREATE INDEX statements, (3) ALTER TABLE ENABLE ROW LEVEL SECURITY, (4) CREATE POLICY statements, (5) CREATE TRIGGER for updated_at. For the updated_at trigger, use the standard moddatetime extension if available in your Supabase project (supabase/extensions), otherwise write a simple PL/pgSQL trigger function. The flag_key column should be constrained to lowercase alphanumeric with hyphens using a CHECK (flag_key ~ '^[a-z0-9-]+$') pattern to prevent typos and case mismatch bugs. The metadata JSONB column is intentionally flexible — document its expected shape in a comment rather than enforcing a strict schema, as feature flag metadata varies by flag type.

For the Dart repository layer (693-feature-flag-repository), the table name, column names, and RLS policy must match exactly — establish a shared constants file for table/column name strings to prevent typo-driven runtime errors. Consider adding a Supabase Realtime subscription channel for the organisation_configs table so the app can receive flag changes without polling — this supports the incremental rollout requirement described in the workshop findings.

Testing Requirements

Database tests should be written as Supabase pgTAP tests (if the project uses pgTAP) or as Dart integration tests that exercise the repository layer against a local Supabase instance. Test cases: (1) INSERT a valid flag row and SELECT it back — assert all columns match; (2) attempt INSERT with rollout_percentage = 101 — assert check constraint violation; (3) attempt INSERT with duplicate (organization_id, flag_key) — assert unique constraint violation; (4) authenticate as org A user and attempt to SELECT org B rows — assert zero rows returned (RLS test); (5) attempt INSERT via the anon key — assert RLS denies the write; (6) UPDATE a row and assert updated_at is newer than created_at. In Flutter: write a Dart unit test for the FeatureFlagRepository.getEnabledFlags(organizationId) method using a mocked Supabase client, asserting that the correct query filter and column selection are applied.

Component
Feature Flag Repository
data medium
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for organization_configs may have gaps that allow cross-organization reads if the JWT claim for organization_id is absent or malformed, leading to data leakage between tenants.

Mitigation & Contingency

Mitigation: Implement RLS policies using auth.uid() joined against a memberships table to derive organization_id rather than trusting a client-supplied claim. Write integration tests that simulate a cross-org read attempt and assert it returns zero rows.

Contingency: If a gap is discovered post-launch, immediately disable the affected RLS policy, roll back the migration, and re-implement with a parameterized policy tested against all organization fixture data.

medium impact medium prob technical

Dart does not have a built-in semantic version comparison library; a naive string comparison (e.g., '2.10.0' < '2.9.0' lexicographically) would cause rollout evaluator to produce incorrect eligibility results for organizations on different app versions.

Mitigation & Contingency

Mitigation: Use the pub.dev `pub_semver` package or implement a proper three-segment integer comparison. Add parameterized unit tests covering 20+ version pairs including double-digit minor/patch segments.

Contingency: If incorrect comparison is discovered in production, push a hotfix with corrected comparison logic and temporarily disable phase-gated flags until all affected organizations have updated to the corrected version.

medium impact low prob technical

Persistent local cache written to shared_preferences or Hive could become corrupted or deserialized incorrectly after an app update changes the FeatureFlag schema, causing startup crashes or all flags defaulting to disabled.

Mitigation & Contingency

Mitigation: Wrap all cache reads in try/catch with explicit fallback to the all-disabled default map. Version the cache key (e.g., `feature_flags_v2_{orgId}`) so schema changes automatically invalidate old entries.

Contingency: If cache corruption is detected in a release, publish an app update that clears the versioned cache key on first launch and re-fetches from Supabase.