Create bufdir_column_schema_config database table
epic-bufdir-reporting-export-foundation-task-002 — Design and implement the bufdir_column_schema_config table to store per-organisation Bufdir column definitions with versioning support. Include columns for org_id, schema_version, column_definitions (JSONB), is_active, created_at, updated_at. Add unique constraint on (org_id, schema_version) and a partial index to enforce a single active schema per organisation.
Acceptance Criteria
Technical Requirements
Implementation Notes
The partial unique index (CREATE UNIQUE INDEX ... WHERE is_active = true) is the key mechanism for single-active-schema enforcement — do not attempt this with a trigger or application logic alone, as concurrent transactions could race. The JSONB CHECK constraint (jsonb_typeof(column_definitions) = 'array') is a lightweight guard; deeper structural validation of individual column definition objects should happen at the application layer in the Dart BufdirColumnMapperService. For the updated_at auto-update trigger, create a reusable trigger function set_updated_at() if one does not already exist in the project migrations — avoid duplicating trigger function definitions.
Seed data for NHF, Blindeforbundet, and HLF should define column_definitions as a JSONB array of objects with at minimum 'column_key', 'display_name', and 'null_value_policy' fields matching the ColumnMappingConfig schema documented in task-014.
Testing Requirements
SQL smoke-test or Dart integration test covering: (1) INSERT a schema with valid JSONB array succeeds; (2) INSERT with JSONB object (not array) is rejected by CHECK constraint; (3) INSERT duplicate (org_id, schema_version) pair is rejected by UNIQUE constraint; (4) setting is_active = true on a second row for the same org_id is rejected; (5) atomic swap — deactivate old, activate new in one transaction — succeeds and only one active row exists afterward; (6) updated_at is automatically updated on UPDATE. Run against local Supabase instance in CI.
RLS policies for the audit log and schema config tables must correctly handle multi-chapter membership hierarchies (up to 1,400 local chapters for NHF). Incorrect policies could either over-expose data across organisations or prevent legitimate coordinator access, both of which are serious compliance failures.
Mitigation & Contingency
Mitigation: Design RLS policies using the existing org hierarchy resolver pattern. Write integration tests that verify cross-organisation isolation with representative fixture data covering NHF's multi-level hierarchy before merging.
Contingency: If RLS policies prove too complex to express safely in Postgres, implement a Supabase Edge Function as a data access proxy that enforces isolation in application code, with RLS serving as a secondary defence layer.
Bufdir's column schema is expected to evolve as Norse Digital Products negotiates a simplified digital reporting format. If the schema config versioning model is too rigid, applying Bufdir schema updates without a code deployment could fail, forcing emergency releases.
Mitigation & Contingency
Mitigation: Design the schema config table to store the full JSON column mapping as a JSONB field with a version number. Provide an admin API to upsert new versions without any schema migration required.
Contingency: If the versioning model is insufficient for a Bufdir schema change, fall back to a code deployment with the updated default schema, using the database config only for org-specific overrides.