critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

RoleName enum is defined with values: peerMentor, coordinator, orgAdmin and a static fromString() factory that maps 'peer_mentor' → peerMentor, 'coordinator' → coordinator, 'org_admin' → orgAdmin
fromString() throws an ArgumentError with a descriptive message for unrecognized role name strings
RoleAssignment is an immutable value object (all fields final) with: roleId (String), userId (String), roleName (RoleName), orgId (String), orgUnitId (String?), isActive (bool)
RoleAssignment.fromJson(Map<String, dynamic>) factory constructor correctly deserializes all fields
RoleAssignment.toJson() returns a Map<String, dynamic> with snake_case keys matching the database column names
RoleAssignment implements == and hashCode based on roleId
RoleAssignment implements copyWith() for immutable updates
Unit tests cover: fromJson round-trip, toJson round-trip, equality by roleId, RoleName.fromString() happy path and error case
Files are placed in lib/domain/role/ following the project's domain layer structure
No Flutter or Supabase SDK imports in the domain model files — pure Dart only

Technical Requirements

frameworks
Dart (pure, no Flutter dependencies)
data models
RoleAssignment
RoleName
performance requirements
Domain model classes must be plain Dart objects with no async operations — zero latency
security requirements
Domain model must not expose raw JSON maps to callers — all access is through typed fields
RoleName enum ensures only valid role values exist at compile time, preventing stringly-typed role checks in business logic

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart's built-in const constructors where possible for RoleAssignment to allow compile-time constant role assignments in tests. Implement == using roleId only (not all fields) since roleId is the stable identity — this prevents issues if isActive is updated without recreating the object. For copyWith(), generate all fields explicitly rather than using a code generator to keep the domain layer dependency-free. The RoleName enum's fromString() should be a static method on the enum using a switch expression (Dart 3+) for exhaustiveness checking.

Place RoleName in role_name.dart and RoleAssignment in role_assignment.dart within lib/domain/role/.

Testing Requirements

Write pure Dart unit tests (no WidgetTester needed) covering: RoleAssignment.fromJson() with all fields populated, RoleAssignment.fromJson() with orgUnitId null (optional field), RoleAssignment.toJson() produces correct snake_case keys, two RoleAssignment objects with the same roleId are equal via ==, RoleName.fromString('peer_mentor') returns RoleName.peerMentor, RoleName.fromString('invalid') throws ArgumentError. All tests should run with dart test or flutter test in under 1 second.

Component
Role Repository
data medium
Epic Risks (2)
medium impact medium prob technical

The get_my_roles RPC call adds a network round-trip immediately after login, potentially increasing the time before the home screen renders. If Supabase RPC is slow or the roles table lacks proper indexing, users with multiple org affiliations could experience noticeable delays.

Mitigation & Contingency

Mitigation: Index user_roles on user_id and org_unit_id. Use JWT claim extraction as the primary fast path; fall back to the RPC only when claims are absent or stale. Set a 3-second timeout with a fallback to cached roles.

Contingency: If RPC latency exceeds acceptable thresholds in production, pre-fetch and embed roles into the session JWT at login time via a Supabase Auth hook, eliminating the post-login RPC entirely.

high impact medium prob integration

Users who belong to multiple organizations (e.g., a coordinator in one NHF chapter who is also a peer mentor in another) may have conflicting role assignments. The repository layer must correctly scope roles to the active organization context set during the organization selection step, or it could return roles from the wrong org.

Mitigation & Contingency

Mitigation: Always filter role queries by the active org_unit_id stored in the tenant session. Write integration tests that simulate multi-org users and verify only the correct org's roles are returned.

Contingency: If org-scoping logic is found to be incorrect during QA, add an explicit org_unit_id parameter to get_my_roles RPC and require the client to always pass the active org context, making the scoping explicit rather than inferred.