high priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

checkPermissionBeforeSubmit(action) is async and always re-fetches the current session role from Supabase Auth — not from cached state
Returns PermissionCheckResult.allowed when the active session role still grants the requested action
Returns PermissionCheckResult.revoked when the role has been changed server-side since the session started
Returns PermissionCheckResult.denied when the action is not in the permission matrix for the current role
The three sensitive actions guarded by this method are: bulkRegister, attestExpense, proxyRegister
If Supabase session is expired or invalid during check, returns PermissionCheckResult.sessionExpired
checkPermissionBeforeSubmit does not proceed with any write operation — it only validates; the caller is responsible for proceeding or aborting
PermissionCheckResult is a sealed class with four subtypes: allowed, denied, revoked, sessionExpired
The method completes within 3 seconds or returns a timeout variant of sessionExpired
Partial operation prevention: callers must check result before any multi-step write begins

Technical Requirements

frameworks
Flutter
Riverpod
Dart
apis
Supabase Auth (getSession / refreshSession)
Supabase PostgREST (user_roles re-fetch)
data models
activity
assignment
performance requirements
Network call timeout set to 3 seconds with explicit TimeoutException handling
Only triggered before sensitive write operations — not on every navigation event
security requirements
Must re-query the database for current role — in-memory cache cannot be trusted for sensitive actions
JWT claims re-validated on each call via Supabase session refresh
Supabase RLS provides server-side enforcement; this is an additional client-side UX guard to prevent user confusion
Revoked state must trigger logout or role-downgrade flow, not silent failure

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

The re-fetch should use a dedicated lightweight query: SELECT role FROM user_roles WHERE user_id = $uid LIMIT 1. Compare fetched role against RoleStateManager's current in-memory role. If different, emit a RoleChangedEvent to RoleStateManager before returning revoked. Use Dart's Future.timeout() with a 3-second duration.

The sealed class PermissionCheckResult should be defined in a shared types file alongside PermissionAction to avoid circular imports. Coordinators doing bulk registration for NHF's 1,400+ local chapters represent the highest-risk operation — this guard is specifically called out by the workshop as a required safety net.

Testing Requirements

Unit tests with flutter_test and mocktail: mock Supabase Auth to return (a) valid session with same role, (b) valid session with downgraded role, (c) expired session, (d) network timeout. Assert correct PermissionCheckResult subtype in each case. Integration test: use Supabase emulator — revoke a role server-side mid-test, then call checkPermissionBeforeSubmit and assert revoked result. Test that method does not mutate any state — pure validation only.

Target 95%+ branch coverage.

Component
Permission Checker Service
service medium
Epic Risks (3)
high impact low prob security

A coordinator's permissions could be revoked by an admin while they are actively using the app. If the permission checker relies solely on the cached role state from login, the coordinator could continue performing actions they are no longer authorized for until the next login.

Mitigation & Contingency

Mitigation: The Permission Checker Service must re-validate against the Role Repository (not just in-memory state) before high-impact actions. Implement a configurable staleness window (e.g., 15 minutes) after which role data is refreshed from Supabase in the background.

Contingency: If a revoked permission is detected during a pre-action check, immediately clear the cached role state, force a re-resolution from Supabase, and display an inline error explaining the permission change rather than crashing or silently failing.

medium impact medium prob technical

Using both BLoC and Riverpod in the same state management layer for roles risks state synchronization bugs where one system updates before the other, causing widgets to render with stale role data during the switch transition.

Mitigation & Contingency

Mitigation: Choose a single primary state management approach (Riverpod StateNotifier is recommended) for role state and wrap the BLoC pattern within it if legacy code requires BLoC interfaces. Establish a single source-of-truth provider that all consumers read from.

Contingency: If synchronization bugs appear during integration testing, introduce a RoleStateReady gate widget that delays rendering of role-dependent UI until the state notifier emits a confirmed resolved state, preventing partial renders.

medium impact high prob scope

Hardcoded permission constants per role can become a maintenance burden as new features are added across 61 total features, leading to permission definitions that are scattered, stale, or inconsistent.

Mitigation & Contingency

Mitigation: Centralize all role-permission mappings in a single RolePermissions constants file with named action keys. Enforce that no widget or service directly checks role type strings; all checks must go through the Permission Checker Service.

Contingency: If permission definitions drift out of sync, introduce a validation test suite that cross-references all registered permission constants against their usage sites and fails the CI build if an undefined permission key is referenced.