critical priority high complexity backend pending backend specialist Tier 5

Acceptance Criteria

computeScope(userId) returns an AccessScope object containing: assignedUnitIds (direct), descendantUnitIds (all descendants via recursive hierarchy traversal), and isNationalAdmin (bool)
For a coordinator assigned to a regional unit, descendantUnitIds includes all chapter and local units beneath that region
For a peer mentor, descendantUnitIds is empty — scope is limited to their directly assigned units only
For a national admin, isNationalAdmin=true and no unit filtering is applied to queries — they see all units in the organization
isUnitInScope(unitId) returns true for any unitId in the union of assignedUnitIds and descendantUnitIds, or always true for national admins
buildScopedQuery(baseQuery) returns the base Supabase query with an .inFilter('organization_unit_id', scopedUnitIds) clause injected automatically
AccessScope is cached in memory per session and invalidated when UnitAssignmentService emits an AssignmentChanged event
computeScope completes within 800ms on first call (cache miss) and under 10ms on subsequent calls (cache hit) for NHF-scale hierarchy
Two coordinators from different organizations cannot access each other's unit data even if they share the same unit name

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase PostgreSQL 15
data models
assignment
contact_chapter
contact
activity
performance requirements
Descendant computation uses a single recursive CTE SQL query — not application-level tree traversal
Scope cache hit (in-memory read) must return in under 10ms
buildScopedQuery overhead must not add more than 5ms to any query construction
security requirements
AccessScopeService is the single authoritative privacy gate — all data queries in the application MUST pass through buildScopedQuery or isUnitInScope before returning data
computeScope reads organization_id from the verified JWT — never accepts it as a client parameter
Scope cache must be fully cleared on logout to prevent scope leakage between users on shared devices
National admin flag must be derived from a database role claim, not a client-side boolean

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Implement as AccessScopeService with Riverpod Provider. Core method computeScope invokes a Postgres RPC function (get_user_scope) that uses two CTEs: first resolving the user's direct assignment unit IDs, then a recursive CTE expanding to all descendants. This single database round-trip is essential for NHF's 1400-chapter scale. Cache the AccessScope object in a Riverpod StateProvider per authenticated session.

Subscribe to UnitAssignmentService.assignmentChangedStream and call _invalidateCache() on each event. The buildScopedQuery helper should be designed as an extension method on PostgrestFilterBuilder so it reads naturally at call sites: supabase.from('activities').select().scopedToUser(scope). Expose isUnitInScope as a synchronous method that reads from the cached scope — callers must ensure computeScope() has been awaited before the first call. Add a scopeReady Future property that resolves when the first computeScope() completes, allowing UI layers to show a loading state cleanly.

Testing Requirements

Unit tests with mocked dependencies for all role types: peer mentor (scope = assigned only), coordinator (scope = assigned + descendants), national admin (scope = all). Test isUnitInScope for units inside and outside scope. Test buildScopedQuery returns correct .inFilter clause. Test cache invalidation on AssignmentChanged event.

Integration test: coordinator queries activities → only sees activities in their chapter and sub-chapters. Security test: two coordinators from different orgs — verify neither can see the other's data even with identical query structure. Use flutter_test and mockito. Target: 95% branch coverage.

Component
Access Scope Service
service high
Epic Risks (3)
high impact medium prob security

If the AccessScopeService and the Supabase RLS policies use different logic to determine accessible units, a coordinator could see data in the client that RLS blocks server-side, causing confusing empty states, or worse, RLS could block data the scope service declares accessible.

Mitigation & Contingency

Mitigation: Define the canonical scope computation in a single Supabase Postgres function shared by both the RLS policies and the RPC endpoint called by AccessScopeService. The client-side service calls this RPC rather than reimplementing the logic, ensuring a single source of truth.

Contingency: Add integration tests that execute the same access decision through both the RLS policy path and the AccessScopeService path and assert identical results. Use these as regression guards in the CI pipeline.

medium impact medium prob integration

When a user switches active chapter via the ChapterSwitcher, widgets that are already built may not receive the context-change event if they subscribe incorrectly to the ActiveChapterState BLoC, leading to stale data being displayed under the new chapter context.

Mitigation & Contingency

Mitigation: Use Riverpod's ref.watch on the active chapter provider at the root of each scoped data subtree rather than at individual leaf widgets. Trigger a global data refresh by invalidating all scoped providers when the chapter changes.

Contingency: Add an app-level chapter-change listener that forces a full navigation stack reset to the home screen on chapter switch, guaranteeing all widgets rebuild from scratch with the new context. Accept the UX cost of navigation reset for correctness.

medium impact medium prob scope

Non-technical organization administrators may find the hierarchy management interface too complex for the structural changes they need to make frequently (e.g., chapter renaming, coordinator reassignment), leading to low adoption and continued reliance on manual processes.

Mitigation & Contingency

Mitigation: Conduct usability testing with at least one NHF administrator before finalizing the admin portal screen layout. Prioritize the most common operations (rename, reparent, add child) as primary actions in the UI. Include inline help text and confirmation dialogs with plain-language descriptions of consequences.

Contingency: Provide a simplified 'quick edit' mode that exposes only the three most common operations (rename, deactivate, add child) and hides advanced structural operations behind an 'Advanced' toggle.