diff --git a/backend/app/api/endpoints/ai_sessions.py b/backend/app/api/endpoints/ai_sessions.py index 425e6421..4b484e43 100644 --- a/backend/app/api/endpoints/ai_sessions.py +++ b/backend/app/api/endpoints/ai_sessions.py @@ -901,10 +901,21 @@ async def get_session( if not session: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found") - # Allow access if user is owner, escalation target, or picked-up handler + # Allow access if user is owner, escalation target, or picked-up handler. + # Sessions in transit (requesting_escalation / escalated) are also + # readable by any account member — the whole point of escalation is that + # other techs can see the context before claiming. Tenant boundary is + # enforced by RLS on the underlying query, so account-scope is the right + # ceiling for in-transit reads. pkg = session.escalation_package or {} is_handler = pkg.get("picked_up_by") == str(current_user.id) - if session.user_id != current_user.id and session.escalated_to_id != current_user.id and not is_handler: + is_in_transit = session.status in ("requesting_escalation", "escalated") + if ( + session.user_id != current_user.id + and session.escalated_to_id != current_user.id + and not is_handler + and not is_in_transit + ): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found") return _build_session_detail(session) diff --git a/backend/app/services/notification_service.py b/backend/app/services/notification_service.py index 23926b0f..a817b9b6 100644 --- a/backend/app/services/notification_service.py +++ b/backend/app/services/notification_service.py @@ -405,7 +405,12 @@ def _build_notification_body(event: str, payload: dict[str, Any]) -> str: def _build_notification_link(event: str, payload: dict[str, Any]) -> Optional[str]: """In-app link per event type. Returns path (no host).""" links: dict[str, str] = { - "session.escalated": "/pilot/{session_id}", + # ?pickup=true triggers the senior-tech handoff/pickup flow on the + # session page (magic-moment screen for handoff-based escalations, + # legacy SessionBriefing for `requesting_escalation` sessions). + # Without it the senior lands on a session-detail GET they can't + # access pre-pickup, which the user perceives as a dead notification. + "session.escalated": "/pilot/{session_id}?pickup=true", "session.high_priority": "/pilot/{session_id}", "proposal.pending": "/review-queue", "proposal.approved": "/review-queue",