critical priority low complexity frontend pending frontend specialist Tier 6

Acceptance Criteria

NoAccessRouteGuard redirect is registered in the app's GoRouter configuration and active on every navigation
Guard execution order is: (1) authentication guard → (2) NoAccessRouteGuard → (3) role-route-guard (048) — verified by test
When authentication guard redirects to /auth/login (unauthenticated), NoAccessRouteGuard does not execute (short-circuit respected)
When NoAccessRouteGuard redirects to /no-access, role-route-guard (048) does not further redirect away from /no-access
Coordinator login flow: auth guard passes → NoAccessRouteGuard passes → role-route-guard routes to /coordinator-home — no regressions
Peer mentor login flow: auth guard passes → NoAccessRouteGuard passes → role-route-guard routes to /mentor-home — no regressions
Global admin login flow: auth guard passes → NoAccessRouteGuard redirects to /no-access → role-route-guard does not redirect away
GoRouter refreshListenable includes both the existing auth notifier and the new NoAccessRouterNotifier (combined ChangeNotifier or Listenable.merge)
No duplicate /no-access redirects or navigation loops observed in golden path integration tests
Existing integration tests for coordinator and peer mentor login flows pass without modification

Technical Requirements

frameworks
Flutter
GoRouter (multi-redirect chain, Listenable.merge for refreshListenable)
Riverpod (provider references in router configuration)
apis
GoRouter constructor: redirect, refreshListenable
Listenable.merge([authNotifier, noAccessNotifier]) for combined refresh
NoAccessRouteGuard.redirect (callback from task-005)
Existing role-route-guard (048) redirect callback
performance requirements
Combined redirect chain must complete synchronously — both guard callbacks must be pure synchronous functions
Listenable.merge avoids redundant router refreshes — merge rather than chaining separate listenables
security requirements
Guard order is security-sensitive: NoAccessRouteGuard must never run before the authentication guard — an unauthenticated global_admin must be sent to /auth/login, not /no-access
Role-route-guard (048) must not be able to route a global_admin away from /no-access — verify /no-access is in its passthrough set

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

In the GoRouter configuration (typically in app_router.dart or router_provider.dart): chain the redirect callbacks using a composed function rather than a single monolithic redirect. Pattern: redirect: (context, state) { final authRedirect = authGuard.redirect(context, state); if (authRedirect != null) return authRedirect; final noAccessRedirect = noAccessGuard.redirect(context, state); if (noAccessRedirect != null) return noAccessRedirect; final roleRedirect = roleRouteGuard.redirect(context, state); return roleRedirect; }. This explicit chaining makes the order clear and maintainable. For refreshListenable, use Listenable.merge([authChangeNotifier, noAccessRouterNotifier]) — do not replace the existing auth notifier, extend it.

Verify that role-route-guard (048) has /no-access in its passthrough paths so it does not try to redirect a global_admin away from the no-access screen. Add a comment above the guard chain explaining the execution order and the reason for each guard's position.

Testing Requirements

Integration tests using flutter_test with full ProviderScope + GoRouter setup matching the production app configuration. Test scenarios: (1) unauthenticated user → lands on /auth/login (auth guard fires, NoAccessRouteGuard skipped), (2) global_admin authenticated → lands on /no-access (NoAccessRouteGuard fires, role guard does not redirect away), (3) coordinator authenticated → lands on /coordinator-home (NoAccessRouteGuard passes, role guard routes correctly), (4) peer_mentor authenticated → lands on /mentor-home, (5) role changes from coordinator to global_admin during session → router refresh → next navigation goes to /no-access. Run existing guard integration tests to confirm no regressions. Use pumpAndSettle and verify router.location after each navigation.

Component
No-Access Route Guard
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.