Define TenantSessionStore interface and session data models
epic-organization-selection-and-onboarding-data-layer-task-005 — Create the TenantSessionData model encapsulating org_id, organization display name, user role within the org, and selection timestamp. Define the abstract TenantSessionStore interface with methods: persistSelection(TenantSessionData), restoreSelection() -> Future<TenantSessionData?>, and clearSelection(). Establish the dual-persistence contract (SecureStorage + Supabase session claim) that the concrete implementation must fulfil.
Acceptance Criteria
Technical Requirements
Execution Context
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
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.
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.
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.