critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

UserRole enum defines exactly four values: coordinator, peerMentor, globalAdmin, orgAdmin — no others
UserRole has a fromString() factory that parses database string values case-insensitively and throws ArgumentError for unknown values
UserRole has a toJson() method returning the snake_case string expected by Supabase
RoleAssignment is an immutable Dart class with fields: userId (String), role (UserRole), orgId (String), chapterId (String?) — chapterId is nullable for org-level roles
RoleAssignment has fromJson(Map<String, dynamic>) and toJson() methods that survive a round-trip without data loss
RoleAssignment implements == and hashCode based on all fields
RoleState is a sealed class (Dart 3 sealed keyword) with three subclasses: RoleStateInitial, RoleStateLoaded(List<RoleAssignment> assignments), RoleStateError(String message)
All model files are placed in lib/domain/role/ or lib/data/role/models/ consistent with existing project structure
No Flutter framework imports in domain model files — pure Dart only
Models compile cleanly with `dart analyze` showing zero errors or warnings

Technical Requirements

frameworks
Dart
data models
assignment
performance requirements
Models are immutable value types — use final fields and const constructors where possible to enable compile-time constant creation
security requirements
UserRole enum must not have a default/unknown fallback that silently grants access — unknown role strings must throw, forcing explicit handling

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart 3 sealed classes for RoleState — this enables exhaustive pattern matching in widgets and BLoCs, preventing forgotten state variants at compile time. Define UserRole as an enum with a `value` field storing the database string: `enum UserRole { coordinator('coordinator'), peerMentor('peer_mentor'), ... }`. Implement `fromString` as a static method that iterates `UserRole.values` and matches on `.value`.

Keep RoleAssignment as a plain Dart class with manual fromJson/toJson rather than code generation (json_serializable) unless the project already uses it — check existing models first and match the pattern. Place all three types in separate files for clarity: `user_role.dart`, `role_assignment.dart`, `role_state.dart`. Export from a single `role_models.dart` barrel.

Testing Requirements

Unit tests for all models: test UserRole.fromString() with all four valid values, mixed case inputs, and an unknown value (expect ArgumentError). Test RoleAssignment fromJson/toJson round-trip with orgId-only and orgId+chapterId variants. Test RoleAssignment equality: two instances with same fields are equal, different chapterId values are not equal. Test RoleState pattern matching: switch on all three variants without a default branch — Dart's exhaustiveness check validates coverage.

No integration tests needed for pure domain models. 100% statement coverage expected given the simplicity.

Component
Role State Manager
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.