critical priority high complexity database pending database specialist Tier 0

Acceptance Criteria

Migration file creates the organization_units table with columns: id (uuid, primary key, default gen_random_uuid()), parent_id (uuid, nullable, FK β†’ organization_units.id ON DELETE RESTRICT), name (text, not null), unit_type (text, not null, CHECK IN ('national','region','chapter')), org_id (uuid, not null, FK β†’ organizations.id), is_active (boolean, not null, default true), deleted_at (timestamptz, nullable), created_at (timestamptz, not null, default now())
Self-referential FK uses ON DELETE RESTRICT to prevent orphan nodes β€” parent cannot be deleted while it has children
A partial unique index ensures name is unique within the same parent scope: UNIQUE (parent_id, name) WHERE deleted_at IS NULL
Index on org_id for fast org-scoped queries: CREATE INDEX idx_org_units_org_id ON organization_units(org_id)
Index on parent_id for efficient child lookups: CREATE INDEX idx_org_units_parent_id ON organization_units(parent_id)
A recursive CTE view (org_unit_tree) is created that resolves the full path from root to each node including depth level and materialized path array
Soft-delete is implemented via deleted_at β€” all application queries must filter WHERE deleted_at IS NULL
Migration is idempotent and includes a rollback (down migration) script
Seed data inserts the NHF hierarchy structure as an example: 1 national node, 9 region nodes, and at least 3 sample chapter nodes under one region
Running `supabase db reset` with the migration and seed applies without errors
A CHECK constraint or trigger prevents circular references (a unit cannot be its own ancestor)

Technical Requirements

frameworks
Supabase (PostgreSQL migrations via supabase/migrations/)
PostgreSQL recursive CTEs
apis
Supabase CLI (supabase migration new, supabase db reset)
data models
organization_units
organizations
performance requirements
Child lookup by parent_id must use index scan β€” confirmed with EXPLAIN ANALYZE
Full tree traversal for a 1,400-node hierarchy (NHF scale) must complete in under 100ms using the recursive CTE view
Org-scoped flat listing must use idx_org_units_org_id index
security requirements
RLS must be enabled on organization_units from the start β€” add policy skeleton even if policies are finalized in a later task
org_id column is NOT NULL β€” every unit must belong to an organization, preventing data from being orphaned without org context
deleted_at soft-delete must be enforced at query level β€” never hard-delete organization units

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use an adjacency list (not a nested set or closure table) as specified — it is the simplest model for a hierarchy that changes infrequently and needs efficient parent→children traversal. The recursive CTE view is the right pattern for NHF's 3-level hierarchy (national → region → chapter). For circular reference prevention, a PostgreSQL trigger (BEFORE INSERT OR UPDATE) that walks the ancestor chain is the most reliable approach — the trigger calls a recursive function checking if the new parent_id is a descendant of the row being inserted. Keep the trigger simple: it only needs to walk up the tree (following parent_id) and abort if it ever reaches the current row's id.

NHF has up to 1,400 chapters β€” the hierarchy is wide, not deep (max 3 levels), so recursive traversal is cheap. Ensure the migration filename follows the existing project convention (timestamp prefix).

Testing Requirements

Write SQL-level tests (pg_tap or plain SQL assertions in a test migration): (1) insert a national β†’ region β†’ chapter chain and verify the CTE view returns correct depth values (0, 1, 2), (2) attempt to delete a region node that has chapter children and verify ON DELETE RESTRICT raises an error, (3) insert two chapters with the same name under the same parent and verify the unique constraint rejects the second insert, (4) insert a chapter with deleted_at set and verify the unique constraint does NOT block a new active chapter with the same name under the same parent, (5) verify that setting parent_id = id (self-reference) is rejected by the circular reference check. Also write a Dart integration test that calls OrganizationUnitRepository.getChildren(parentId) and verifies the returned list matches the seeded data.

Component
Organization Unit Repository
data high
Epic Risks (3)
high impact medium prob technical

Recursive CTE queries for large hierarchies (1,400+ nodes) may exceed Supabase query timeouts or produce unacceptably slow responses, degrading tree load time beyond the 1-second target.

Mitigation & Contingency

Mitigation: Implement Supabase RPC functions for subtree fetches rather than client-side recursive calls. Use materialized path or closure table as a supplemental index for depth-first traversal. Benchmark with realistic NHF data volumes during development.

Contingency: Fall back to a pre-computed flat unit list stored in the hierarchy cache with client-side tree reconstruction, trading freshness for speed. Add a background refresh job to keep the cache warm.

medium impact low prob technical

Concurrent writes from multiple admin sessions could cause cache staleness, leading to stale tree views and incorrect ancestor path computations that corrupt aggregation results.

Mitigation & Contingency

Mitigation: Use optimistic versioning on cache entries with a short TTL (5 minutes) as a safety net. Subscribe to Supabase Realtime on the organization_units table to push invalidation events to all connected clients.

Contingency: Provide a manual 'Refresh Hierarchy' action in the admin portal that forces a full cache bust, and display a staleness warning banner when the cache age exceeds the TTL.

high impact low prob security

Persisting the flat unit list to local storage may expose organization structure data if the device is compromised or the storage is not properly encrypted, violating data protection requirements.

Mitigation & Contingency

Mitigation: Use flutter_secure_storage (AES-256 backed by Keychain/Keystore) for the local unit list cache rather than SharedPreferences. Include only unit IDs, names, and types β€” no member PII.

Contingency: Disable local-storage persistence entirely and rely on in-memory cache only. Accept the trade-off of no offline hierarchy access for the security guarantee.