high priority low complexity integration pending integration specialist Tier 2

Acceptance Criteria

getAdminPortalUrl() returns a non-null, non-empty String on first call without prior warm-up
Second and subsequent calls to getAdminPortalUrl() return the cached value without invoking the config repository again
Cache is invalidated and re-fetched if the service is re-created (i.e., cache is instance-scoped, not static)
URL returned matches the environment-specific value from the config repository (dev/staging/prod URLs differ)
If the config repository throws, getAdminPortalUrl() surfaces the exception — no silent swallowing
Unit test verifies the repository is called exactly once across three sequential getAdminPortalUrl() calls

Technical Requirements

frameworks
Flutter
dart:async (Future, then lazy init pattern)
apis
INoAccessConfigRepository.getAdminPortalUrl() (052-no-access-config-repository)
performance requirements
Repository call must not block the UI thread — getAdminPortalUrl() must return a Future<String>
Cache implemented as a nullable String? _cachedUrl field with lazy initialization — no third-party cache library needed
security requirements
Admin portal URL must originate from the server-side config repository, never hardcoded in the client binary
URL must be validated as HTTPS before being returned to callers

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement lazy caching with a nullable instance field: String? _cachedAdminPortalUrl. In getAdminPortalUrl(): if (_cachedAdminPortalUrl != null) return _cachedAdminPortalUrl!; final url = await _configRepository.getAdminPortalUrl(); _cachedAdminPortalUrl = url; return url;. This pattern is idiomatic Dart and avoids over-engineering with a separate cache abstraction.

Add a simple HTTPS validation assert in debug mode: assert(url.startsWith('https://'), 'Admin portal URL must use HTTPS'). Do not use static fields for the cache — this would make the cache survive service recreation, breaking test isolation and environment switching.

Testing Requirements

Unit tests using flutter_test and mocktail. Mock INoAccessConfigRepository. Test scenarios: (1) first call invokes repository and returns URL, (2) second call returns cached value and repository mock was called exactly once (verify(mockRepo.getAdminPortalUrl()).called(1) total across both calls), (3) repository throws → exception propagates through getAdminPortalUrl(). No integration tests needed for this isolated wiring task.

Component
Access Denial Service
service low
Epic Risks (2)
high impact medium prob technical

If the GoRouter redirect callback evaluates the no-access route itself as a blocked destination, it will trigger an infinite redirect loop, crashing the navigator.

Mitigation & Contingency

Mitigation: Add an explicit guard condition in the redirect callback: return null (no redirect) when the current location is already the no-access route or the logout route. Write a dedicated unit test covering this exact scenario.

Contingency: If the redirect loop is detected in production, deploy a hotfix that adds the null-return guard; the feature can be toggled off via the existing feature-flag infrastructure while the fix is prepared.

medium impact low prob integration

The access-denial-service may read role state before authentication completes (e.g. during app resume), causing a temporary false-positive block that redirects valid peer-mentor users to the no-access screen.

Mitigation & Contingency

Mitigation: Subscribe to the role-state-manager's loading/ready lifecycle and only evaluate role-based access once the RBAC state is confirmed as loaded. Return a 'pending' state that causes the guard to defer rather than redirect.

Contingency: Add a retry mechanism: if a user lands on the no-access screen but their role subsequently resolves as non-blocked, automatically navigate them to the role-based home screen.