---
id: STATE-PFM-CSH-002
title: Workspace entry from an address
object: DOM-PFM-CSH-003
product_area: platform
module: console-shell
revision: 1
status: draft
design_intent: confirmed
origin: >-
  react-console:docs/url-architecture.md#6. Tenant from the URL and #11.
  Decisions made during the build, item 24 (decided by Andrew 2026-10-14; final
  acceptance 2026-10-15)
implementation_evidence: observed
source_sha:
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
states:
  - name: resolving
    meaning: >-
      The requested workspace differs from the held context and needs strict
      resolution.
  - name: waiting-after-leave
    meaning: >-
      Resolving presentation waits for the delayed retry after a recent
      workspace departure.
  - name: entered
    meaning: >-
      The requested workspace matches the selected context and content can
      mount.
  - name: not-found
    meaning: No workspace was resolved for the requested identifier.
  - name: denied
    meaning: >-
      The requested workspace was refused for reach; customer users receive the
      denied panel.
  - name: sent-to-hq
    meaning: Denied staff are routed to HQ after leaving any selected workspace.
  - name: unavailable
    meaning: Platform reports the requested workspace out of service.
  - name: failed
    meaning: >-
      The request failed unexpectedly or its successful response named a
      different workspace.
transitions:
  - from: resolving
    to: entered
    trigger: Security returns the matching requested workspace context.
    guard: The answer names the requested workspace and its ticket is current.
    side_effects:
      - Clear SWR answers and remount when the selected workspace changes.
      - Remember the entered workspace for the current person.
    who: 'Platform (Security), console'
    provenance: test
    source: >-
      react-console:e2e/mock/url-architecture.mock.spec.ts#a record deep link
      survives a reload
  - from: resolving
    to: waiting-after-leave
    trigger: The gate detects that this workspace was left less than five seconds ago.
    guard: No additional guard.
    side_effects:
      - Schedule another attempt six seconds later.
    who: console
    provenance: observed
    source: >-
      react-console:components/console/tenant-id-gate.tsx:TenantIdGate
      (wasJustLeft, JUST_LEFT_RETRY_MS)
  - from: waiting-after-leave
    to: resolving
    trigger: The delayed six-second callback requests another attempt.
    guard: The mounted gate still names the requested workspace.
    side_effects: []
    who: console
    provenance: observed
    source: 'react-console:components/console/tenant-id-gate.tsx:TenantIdGate'
  - from: resolving
    to: not-found
    trigger: Security classifies the requested workspace as not found.
    guard: No additional guard.
    side_effects:
      - Retain this refusal in the ten-second module memory.
    who: Platform (Security)
    provenance: test
    source: >-
      react-console:e2e/mock/url-architecture.mock.spec.ts#an id no tenant has
      shows not found
  - from: resolving
    to: denied
    trigger: Security refuses reach over the requested workspace.
    guard: No additional guard.
    side_effects:
      - >-
        Retain denial briefly and offer another reachable workspace when one
        exists.
    who: Platform (Security)
    provenance: test
    source: >-
      react-console:components/console/__tests__/tenant-id-gate.test.tsx#leaves
      anyone else on the access-denied page
  - from: denied
    to: sent-to-hq
    trigger: The refused person is identified as Rhythm staff.
    guard: Staff identity selects HQ handling.
    side_effects:
      - Leave the selected workspace before presenting HQ notice.
      - 'Route to HQ, where emergency access remains a separate action.'
    who: console
    provenance: observed
    source: >-
      react-console:components/console/tenant-id-gate.tsx:deniedDestination,
      TenantIdGate
  - from: resolving
    to: unavailable
    trigger: Security reports workspace unavailability.
    guard: No additional guard.
    side_effects:
      - Offer staff HQ or customer users another reachable workspace.
    who: Platform (Security)
    provenance: test
    source: >-
      react-console:components/console/__tests__/tenant-id-gate.test.tsx#says
      the workspace is unavailable, never no access, with the way back for staff
  - from: resolving
    to: failed
    trigger: >-
      Entry throws an unexpected error or rejects a different workspace’s
      context.
    guard: No additional guard.
    side_effects:
      - Display the failure message and retain it briefly.
    who: console
    provenance: observed
    source: >-
      react-console:lib/security-context/tenant-id.ts:TenantIdUnconfirmedError;
      components/console/tenant-id-gate.tsx
  - from: not-found
    to: resolving
    trigger: A subsequent visit occurs outside the ten-second refusal memory.
    guard: A new visit re-evaluates expired refusal memory.
    side_effects: []
    who: person
    provenance: observed
    source: 'react-console:components/console/tenant-id-gate.tsx:rememberedRefusal'
  - from: denied
    to: resolving
    trigger: A subsequent visit occurs after denied-entry memory expires.
    guard: A new visit re-evaluates expired denial memory.
    side_effects: []
    who: person
    provenance: observed
    source: 'react-console:components/console/tenant-id-gate.tsx:rememberedRefusal'
invalid:
  - from: resolving
    to: entered
    why: >-
      A successful response about another workspace fails strict-entry
      validation.
  - from: denied
    to: entered
    why: >-
      A later authorized context answer is required; the deep link grants no
      emergency access.
  - from: unavailable
    to: denied
    why: >-
      Operational unavailability receives its own classification rather than a
      reach refusal.
concurrency: >-
  Boot supplies one reusable answer for the address workspace. The gate
  remembers one refusal for ten seconds across remounts. A just-left marker
  lasts five seconds and the retry waits six. Ticket checks protect context
  installation against late responses.
invariants:
  - INV-DOM-PFM-CSH-003-01
  - INV-DOM-PFM-CSH-003-02
  - INV-DOM-PFM-CSH-003-09
evidence:
  - >-
    react-console at baf6dae: components/console/tenant-id-gate.tsx;
    lib/security-context/{tenant-id, left-tenant,provider}.ts*;
    components/console/__tests__/tenant-id-gate.test.tsx.
  - >-
    services-platform at ad3f6a6: modules/security/aws/api/access.routes.ts
    (getMySecurityContext, tenant_id).
---
# Workspace entry from an address

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> resolving
  resolving --> entered: matching context
  resolving --> waiting_after_leave: recent departure
  waiting_after_leave --> resolving: delayed attempt
  resolving --> not_found
  resolving --> denied
  denied --> sent_to_hq: staff
  resolving --> unavailable
  resolving --> failed
  not_found --> resolving: memory expired
  denied --> resolving: memory expired
  entered --> [*]
```

## Transitions Explained

Workspace entry has resolving, entered and refusal outcomes. A just-left workspace remains in resolving presentation while a delayed retry waits. A found response installs context only for the latest ticket. Denied staff leave for HQ; customer users receive the denied panel.

## What Cannot Happen

A response naming another workspace does not open requested content. A deep link does not itself open emergency access. An unavailable workspace receives its own explanation rather than an access-denied label.

## Concurrency and Timing

Boot supplies one reusable answer for the address workspace. The gate remembers one refusal for ten seconds across remounts. A just-left marker lasts five seconds and the retry waits six. Ticket checks protect context installation against late responses.
