critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

TenantSessionData is an immutable Dart class (or freezed data class) with fields: orgId (String, UUID), organizationName (String), userRole (enum UserRole), selectedAt (DateTime)
TenantSessionData has a toJson() method and a fromJson() factory constructor for serialization
TenantSessionData.fromJson() returns null or throws a typed exception (TenantSessionDataParseException) on malformed input — never returns a partially populated instance
Abstract class TenantSessionStore declares exactly three methods: Future<void> persistSelection(TenantSessionData data), Future<TenantSessionData?> restoreSelection(), Future<void> clearSelection()
Interface documentation (dartdoc) explicitly states the dual-persistence contract: both SecureStorage and Supabase custom claim must be updated atomically
A fake in-memory implementation (FakeTenantSessionStore) is provided alongside the interface for use in tests
All types are exported from a single barrel file; no circular imports
Code passes `dart analyze` with zero warnings

Technical Requirements

frameworks
Flutter
flutter_test
data models
assignment
performance requirements
TenantSessionData serialization/deserialization must be synchronous and complete in under 1 ms
security requirements
TenantSessionData must NOT include the user's national identity number, password, or raw JWT tokens
orgId must be validated as a non-empty UUID string in fromJson()
userRole enum must have a fallback/unknown value to prevent crashes on unrecognized server-returned roles

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Consider using the `freezed` package for TenantSessionData to get immutability, equality, copyWith, and JSON serialization with minimal boilerplate. Define UserRole as a Dart enum with values matching the roles in the database (peerMentor, coordinator, orgAdmin, globalAdmin, unknown). Place TenantSessionData and TenantSessionStore in `lib/data/tenant_session/` to keep them co-located. The abstract class should be thin — no logic, just the contract.

The FakeTenantSessionStore stores data in a `Map` keyed by user UID, suitable for widget tests.

Testing Requirements

Write unit tests in flutter_test for TenantSessionData: (1) round-trip toJson/fromJson preserves all fields exactly, (2) fromJson with missing required field throws TenantSessionDataParseException, (3) fromJson with unknown userRole falls back to UserRole.unknown without throwing. Write a simple test confirming FakeTenantSessionStore satisfies the TenantSessionStore contract (persist → restore returns the same data; clear → restore returns null). No integration tests needed for this pure model/interface task.

Component
Tenant Session Store
data medium
Epic Risks (2)
high impact low prob technical

TenantSessionStore must write to both SecureStorageAdapter and Supabase session synchronously. If the Supabase write succeeds but the local secure storage write fails (or vice versa), the system ends up in an inconsistent state: local app thinks org A is selected but Supabase queries are scoped to org B (or unscoped), causing silent data leakage or empty result sets.

Mitigation & Contingency

Mitigation: Implement the dual-write with compensating rollback: if the second write fails, undo the first write and surface a typed DualWriteFailureError to callers. Add a startup integrity check in restoreSession() that validates local storage and Supabase session agree on the current org_id.

Contingency: If integrity check fails on startup, clear both stores and redirect the user to the org selection screen rather than proceeding with potentially mismatched state.

medium impact low prob integration

An organization could be deactivated in Supabase between the time the org list is cached and the time the user taps to select it. If the repository serves stale cached data the org-selection-service will attempt to seed a context for an inactive org, potentially causing RLS scope issues or confusing error states.

Mitigation & Contingency

Mitigation: The OrganizationRepository.getOrganizationById() path used during selection validation always performs a fresh network fetch (bypassing cache) to confirm the org is still active before the TenantSessionStore writes anything.

Contingency: If a freshness check finds the org is inactive, surface a localized error message on the selection screen ('This organization is no longer available') and refresh the org list to show only currently active partners.