critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

`NoAccessConfig` Dart class exists with fields: `adminPortalUrl` (String) and `blockedRoles` (List<String>)
`NoAccessConfig` is immutable (`final` fields, `const` constructor)
`NoAccessConfig` implements `copyWith`, `==`, and `hashCode` (or uses `freezed`/`equatable`)
A `no_access_constants.dart` file defines `kFallbackAdminPortalUrl` (String const) and `kDefaultBlockedRoles` (List<String> const)
`kDefaultBlockedRoles` contains at minimum the `globalAdmin` role identifier string matching the value used in the auth/role system
Both files are placed in the correct feature directory (e.g., `lib/features/no_access/data/`)
File compiles without warnings with `flutter analyze`

Technical Requirements

frameworks
Flutter
Dart
equatable or freezed (whichever is used in the project)
data models
NoAccessConfig
UserRole (reference for role identifier strings)
performance requirements
Model is `const`-constructible for zero-cost fallback instantiation
security requirements
The fallback admin portal URL must be an HTTPS URL — validate at compile time with an assert or a unit test
Role identifier strings must exactly match the values stored in Supabase to prevent accidental access bypasses

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use `equatable` if the project already depends on it (check `pubspec.yaml`) to avoid boilerplate `==`/`hashCode`. If the project uses `freezed`, generate the model with `@freezed` for immutability and `copyWith` for free. The role identifier strings in `kDefaultBlockedRoles` must be discussed with the backend team to ensure they match Supabase `user_roles` table values exactly — a mismatch here would silently fail to block the right roles. Keep the constants file thin; any logic belongs in the repository or service layer.

Document the fallback URL's purpose with a single comment explaining it is used when Supabase remote config is unreachable.

Testing Requirements

No dedicated test file required for this task, but the constants and model will be exercised by repository unit tests in task-002. Optionally add a single smoke test asserting `kDefaultBlockedRoles.isNotEmpty` and `kFallbackAdminPortalUrl.startsWith('https://')` to catch accidental misconfiguration. Use `flutter_test`.

Epic Risks (2)
high impact medium prob technical

Supabase remote config may be unavailable at app startup (network error, cold start), causing the repository to return no blocked-role list. If the fallback is empty, blocked users could access the app.

Mitigation & Contingency

Mitigation: Define a local constants fallback list of blocked roles compiled into the app binary. Remote config enriches or overrides this list when available.

Contingency: If remote config repeatedly fails in production, pin the blocked-role list to local constants only and disable remote override until the Supabase config endpoint is stabilised.

medium impact low prob dependency

url_launcher package behaviour differs between iOS and Android (e.g. canLaunchUrl returning false on some Android configurations), leading to silent failures when the admin portal link is tapped.

Mitigation & Contingency

Mitigation: Run canLaunchUrl check before every launch attempt and surface a descriptive inline error message (e.g. 'Could not open link — visit admin.example.org manually') when the check fails.

Contingency: If canLaunchUrl is consistently unreliable on a target platform, replace the tap-to-open pattern with a copyable text field showing the URL as a fallback.