"""Mobile RoleSwitcher perspective narrowing — single source of truth. The mobile client carries an ``activeRole`` (the RoleSwitcher chip) and sends it as ``?as_role=`` on the surfaces that must collapse to one perspective: the apps catalog AND the action inbox. Both call :func:`resolve_perspective_role_names` so "view as employee" behaves identically on the grid and the action centre — an admin in the employee perspective sees neither admin apps nor admin/auditor action items. This mirrors the desktop ``X-View-Role`` narrowing (:func:`app.core.rbac.apply_view_role_narrow`) but for the query-param channel the mobile webview uses. It only ever NARROWS the role set, never widens — passing a role the caller doesn't hold is ignored (falls back to the full set), so it's not an escalation vector. """ from __future__ import annotations # Perspectives the client may pass as ``?as_role=``. ``manager`` and # ``employee`` are synthetic (never stored DB roles); both collapse to # "no admin role" — the team/self apps + action items only. KNOWN_PERSPECTIVE_ROLES: frozenset[str] = frozenset( { "global_admin", "it_admin", "asset_admin", "cloud_admin", "hr", "finance", "management", "auditor", "manager", "employee", } ) def resolve_perspective_role_names( user_role_names: list[str], as_role: str | None, ) -> list[str]: """Return the role set to feed role-gated mobile surfaces. * ``as_role`` missing / unknown string → full ``user_role_names`` (no perspective requested → pre-perspective behaviour). * ``as_role`` == ``employee`` / ``manager`` → ``[]`` (strip every DB-backed admin role; manager-scope expansion is org-tree-driven downstream, so the empty set collapses to the self/team surfaces). * ``as_role`` == a real role the caller holds → ``[as_role]``. * ``as_role`` is a KNOWN role the caller does NOT hold → ``[]`` (fail CLOSED: the client asked to collapse to a perspective; honour the collapse with the safe empty set rather than falling back to the full role set. Can't widen — the provider gates re-check the returned set — but collapsing is the correct contract for an explicit perspective request). """ if as_role is None or as_role not in KNOWN_PERSPECTIVE_ROLES: return user_role_names if as_role in ("employee", "manager"): return [] if as_role in user_role_names: return [as_role] # Known perspective role the caller doesn't hold → collapse, don't widen. return []