critical priority low complexity database pending database specialist Tier 0

Acceptance Criteria

Table org_feature_flags exists with columns: id (uuid PK default gen_random_uuid()), org_id (uuid FK → organizations, NOT NULL), feature_key (text NOT NULL), enabled (boolean NOT NULL DEFAULT false), created_at (timestamptz DEFAULT now()), updated_at (timestamptz DEFAULT now())
Unique constraint on (org_id, feature_key) prevents duplicate flag rows per org per feature
RLS is enabled; only authenticated users whose org_id matches the row's org_id AND whose role is 'admin' or 'org_admin' can SELECT the row
No role other than service_role (backend migration/seed scripts) can INSERT, UPDATE, or DELETE flag rows from the client — admin reads only, no client-side writes
Seed migration inserts a row for Blindeforbundet's org_id with feature_key='driver_and_confidentiality' and enabled=false
An updated_at trigger automatically sets updated_at=now() on any UPDATE
Migration is idempotent: re-running does not create duplicate seed rows (use INSERT ... ON CONFLICT DO NOTHING)
Index exists on (org_id, feature_key) for fast flag lookup

Technical Requirements

frameworks
Supabase Migrations (SQL)
apis
Supabase Auth (auth.uid() in RLS policies)
Supabase Management API
data models
org_feature_flags
organizations
user_profiles (for role resolution in RLS)
performance requirements
Flag lookup by (org_id, feature_key) must be O(1) via unique index — no table scans
security requirements
Client-side code must never be able to enable a feature flag — only service_role or a trusted backend function can UPDATE enabled=true
RLS must prevent any user from reading flags belonging to a different org
feature_key values should be validated via a CHECK constraint or enum to prevent typos (e.g., CHECK (feature_key IN ('driver_and_confidentiality')))

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Keep the feature_key CHECK constraint list in sync with the DriverFeatureFlagConfig constants defined in task-008 — these two must always agree. Use a Postgres trigger function (set_updated_at) that is likely already defined elsewhere in the schema for other tables; reuse it here rather than creating a duplicate. For the seed row, look up Blindeforbundet's org_id from the organizations table by slug or name in the migration script using a subquery: INSERT INTO org_feature_flags (org_id, feature_key, enabled) SELECT id, 'driver_and_confidentiality', false FROM organizations WHERE slug = 'blindeforbundet' ON CONFLICT DO NOTHING. This avoids hardcoding a UUID that might differ between environments.

The client-side restriction (no UPDATE) is enforced by having no RLS UPDATE policy defined — Supabase denies by default when no policy matches.

Testing Requirements

SQL migration tests: (1) verify table schema matches specification; (2) verify unique constraint rejects duplicate (org_id, feature_key) insert; (3) verify RLS allows org admin to SELECT their own flag; (4) verify RLS blocks coordinator role from SELECT; (5) verify RLS blocks cross-org SELECT for admin role; (6) verify client cannot INSERT or UPDATE (only service_role can); (7) verify seed row exists for Blindeforbundet with enabled=false; (8) verify idempotent re-run does not create duplicates. Dart unit test: verify FeatureFlagConfig.fromJson correctly deserializes the Supabase row format.

Component
Driver Feature Flag Configuration
infrastructure low
Epic Risks (3)
high impact medium prob security

Row-level security policies for driver assignments and declarations must correctly scope data to the coordinator's chapter without leaking records across organizations. An incorrect RLS predicate could silently return empty result sets or, worse, expose cross-org data, both of which are difficult to detect in unit tests.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration test scenarios with multiple org fixtures asserting both data isolation and correct data visibility. Use Supabase's built-in policy testing utilities and review policies with a second developer.

Contingency: If RLS policies prove too complex to get right quickly, implement application-layer org scoping as a temporary guard while RLS is fixed in a follow-up, with an explicit security review gate before production deployment.

high impact medium prob security

The declaration audit logger must produce tamper-evident records. If the database allows updates or deletes on audit rows, the compliance guarantee is broken. Supabase does not natively prevent row deletion by default.

Mitigation & Contingency

Mitigation: Implement an insert-only RLS policy on the audit table that denies UPDATE and DELETE for all roles including the service role. Add a database trigger that rejects mutation attempts and logs the attempt itself.

Contingency: If immutability cannot be enforced at the database level within the sprint, store audit entries in an append-only Supabase Edge Function log stream as a temporary alternative, with a migration plan to the proper table once constraints are implemented.

medium impact low prob technical

The org-feature-flag-service caches flag values to avoid repeated database reads. If the cache is not invalidated promptly after an admin toggles the flag, coordinators may see stale UI state — either seeing driver features when they should not, or not seeing them when they should.

Mitigation & Contingency

Mitigation: Use a Supabase Realtime subscription to listen for changes on the driver_feature_flag_config table and invalidate the in-memory cache immediately on change. Set a short TTL (60 seconds) as a safety net.

Contingency: If Realtime subscription proves unreliable, expose a manual cache-bust endpoint accessible from the admin toggle action, ensuring the cache is cleared synchronously on every flag change.