---
id: STATE-PFM-CSH-003
title: Freshness of the application list
object: DOM-PFM-CSH-004
product_area: platform
module: console-shell
revision: 1
status: draft
design_intent: confirmed
origin: >-
  services-platform:docs/proposals/application-access-and-entitlements.md#6.4
  Console integration, item 4 and #5.3 (built and accepted by Andrew 2026-10-15)
implementation_evidence: observed
source_sha:
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
states:
  - name: current
    meaning: >-
      The provider freshness timestamp is less than five minutes old, without a
      guarantee that access remains unchanged.
  - name: aging
    meaning: >-
      The timestamp is at least five minutes old and a visible return can
      request refresh.
  - name: refreshing
    meaning: >-
      An application-refresh promise is outstanding and shared by concurrent
      callers.
transitions:
  - from: current
    to: refreshing
    trigger: A request reports a recognized application-coded 403.
    guard: The console is ready and a workspace is selected.
    side_effects: []
    who: console
    provenance: test
    source: >-
      react-console:e2e/mock/application-access.mock.spec.ts#brings the launcher
      up to date instead of leaving it offering what is gone
  - from: current
    to: aging
    trigger: The timestamp reaches the five-minute threshold.
    guard: No additional guard.
    side_effects: []
    who: console
    provenance: observed
    source: >-
      react-console:lib/security-context/provider.tsx:APPLICATIONS_STALE_AFTER_MS
  - from: aging
    to: refreshing
    trigger: >-
      A visible tab returns with an aged timestamp, or an application refusal
      arrives.
    guard: >-
      The console is ready with a workspace; focus and visibility additionally
      require a visible tab.
    side_effects: []
    who: console
    provenance: observed
    source: >-
      react-console:lib/security-context/provider.tsx:SecurityContextProvider
      (focus and visibility listener)
  - from: refreshing
    to: current
    trigger: The application refresh returns usable data for the held workspace.
    guard: The ticket remains current and the selected workspace matches the request.
    side_effects:
      - Replace applications and features so menu and guard computation follow.
      - 'Retain role, permission-rule and reachable-workspace snapshots.'
    who: 'Platform (Security), console'
    provenance: observed
    source: 'react-console:lib/security-context/provider.tsx:refreshApplications'
  - from: refreshing
    to: aging
    trigger: Refresh fails while the previous timestamp is already aged.
    guard: The previous timestamp has reached the aging threshold.
    side_effects:
      - Keep the held list and timestamp without a visible error.
    who: console
    provenance: observed
    source: >-
      react-console:lib/security-context/provider.tsx:refreshApplications
      (catch)
  - from: refreshing
    to: current
    trigger: >-
      Refresh fails with a recent timestamp or succeeds after its ticket was
      superseded.
    guard: The timestamp is recent after the failure or successful response.
    side_effects:
      - >-
        Keep the existing list; a successful superseded response still advances
        the timestamp.
    who: console
    provenance: observed
    source: 'react-console:lib/security-context/provider.tsx:refreshApplications'
invalid:
  - from: refreshing
    to: current
    why: The response belongs to a workspace change superseded by another ticket.
  - from: current
    to: refreshing
    why: >-
      With no selected workspace, the refresh function returns without
      requesting applications.
concurrency: >-
  One promise is shared across concurrent callers, including callers in
  different workspaces. Successful response arrival advances the timestamp
  before the ticket check; failure leaves it unchanged. Boot and entry do not
  reset it. The service response can still reflect authorizer-cached licensing
  data.
invariants:
  - INV-DOM-PFM-CSH-004-04
  - INV-DOM-PFM-CSH-004-05
evidence:
  - >-
    react-console at baf6dae:
    lib/security-context/{provider.tsx,application-refusal.ts,
    application-refusal.test.ts}; e2e/mock/application-access.mock.spec.ts.
  - >-
    services-platform at ad3f6a6: modules/security/aws/api/access.routes.ts
    (getMyApplications); docs/proposals/application-access-and-entitlements.md
    §5.3, §6.4.
---
# Freshness of the application list

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> current
  current --> refreshing: coded refusal
  current --> aging: timestamp ages
  aging --> refreshing: visible return or coded refusal
  refreshing --> current: applicable success
  refreshing --> aging: failed with old timestamp
  refreshing --> current: failed with recent timestamp or superseded success
```

## Transitions Explained

Current and aging are timing categories derived from the provider timestamp, not persisted states or proof of current authority. Refusal can request refresh immediately. Focus or visibility requests it only when the tab is visible and the timestamp is at least five minutes old.

## What Cannot Happen

A refresh result cannot replace applications after its workspace ticket is superseded. Without a selected workspace, the refresh function returns without a request. Failure does not make an aged timestamp recent.

## Concurrency and Timing

One promise is shared across concurrent callers, including callers in different workspaces. Successful response arrival advances the timestamp before the ticket check; failure leaves it unchanged. Boot and entry do not reset it. The service response can still reflect authorizer-cached licensing data.
