critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

Organization domain model class is immutable (all fields final) with a named constructor and a fromJson factory
Organization model maps all Supabase row fields to strongly-typed Dart properties: id (String/UUID), name (String), logoUrl (String?), isActive (bool), brandingConfig (Map<String, dynamic>?), labelOverrides (Map<String, String>?), featureFlags (Map<String, bool>?)
Organization model implements == and hashCode (or uses freezed/equatable) for value equality
Organization model has a copyWith method for immutable updates
Abstract OrganizationRepository interface defines: Future<List<Organization>> fetchAllActive(), Future<Organization> fetchById(String orgId), Stream<List<Organization>> watchAll()
OrganizationRepository interface is in its own file under the repository abstraction layer (not co-located with the Supabase implementation)
OrganizationRepository interface methods have typed exceptions documented in dartdoc comments: OrgNotFoundException, OrgNetworkException
A barrel export file exposes the model and interface under a clean import path
The domain model has NO dependency on Supabase SDK — it is a plain Dart class
Code compiles cleanly with `flutter analyze` showing zero warnings

Technical Requirements

frameworks
Flutter
Dart (latest)
freezed or equatable (for immutable value objects — use whichever is already in the project)
data models
contact (referenced by org membership — org domain model must be compatible with assignment join queries)
assignment (org membership context)
performance requirements
Domain model fromJson must parse without dynamic casts — use typed access throughout
security requirements
Organization model must NOT expose raw Supabase service role credentials or internal schema details
brandingConfig and labelOverrides maps must be typed as Map<String, dynamic> / Map<String, String> — never dynamic at the top level

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Place the domain model in `lib/domain/models/organization.dart` and the repository interface in `lib/domain/repositories/organization_repository.dart` — keep domain layer free of framework and SDK imports. Use `freezed` if the project already uses it (check pubspec.yaml); otherwise use `equatable` for value equality — do not introduce a new code generation dependency just for this task. The fromJson factory should handle both snake_case (Supabase default) and camelCase keys by normalizing in the factory. For the Stream> watchAll() contract, document that implementations are expected to emit on initial subscription and on any subsequent change — this is the Supabase Realtime pattern.

Define OrganizationRepository as abstract class (not abstract interface class) for compatibility with Dart versions that predate the `interface` keyword unless the project's Dart version supports it.

Testing Requirements

Unit tests for the Organization domain model: test fromJson with a complete JSON fixture, test fromJson with missing optional fields (logoUrl, brandingConfig), test equality (two instances with same data are equal), test copyWith creates a new instance with updated field. No tests needed for the abstract interface itself — it is a contract. Tests should live in `test/data/models/organization_model_test.dart`. All tests pass with `flutter test`.

Component
Organization Repository
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.