# Multi-Tenant Same-Key End-to-End Flow Sequences Date: 2026-02-22 Source sprint: `SPRINT_20260222_053_DOCS_multi_tenant_same_api_key_contract_baseline.md` Related ADR: `docs/architecture/decisions/ADR-002-multi-tenant-same-api-key-selection.md` ## 1) Sign-in to Tenant Mapping ```mermaid sequenceDiagram autonumber participant User participant Web as Web Console participant Auth as Authority participant Gw as Router/Gateway participant Svc as Tenant-scoped API User->>Web: Sign in Web->>Auth: /connect/authorize + PKCE Auth-->>Web: auth code Web->>Auth: /connect/token (client credentials or password grant, tenant=) Auth->>Auth: Resolve selected tenant from tenant + tenants metadata Auth-->>Web: Access token (stellaops:tenant + optional stellaops:allowed_tenants) Web->>Auth: /console/tenants Auth-->>Web: { tenants[], selectedTenant } Web->>Web: Hydrate ConsoleSessionStore + AuthSessionStore + PlatformContext Web->>Gw: API request + canonical tenant header Gw->>Svc: Forward resolved tenant context Svc-->>Web: Tenant-scoped response ``` Deterministic selection rule: - If `tenant` parameter is present at token request time, it must be in assigned tenant set. - If no parameter and only one assignment/default exists, use that selected tenant. - If ambiguous (multi-assigned and no default/request), reject. ## 2) Header Selector Tenant Switch ```mermaid sequenceDiagram autonumber participant User participant Topbar as Header Tenant Selector participant Session as ConsoleSessionService participant Auth as Authority participant Stores as Session/Context Stores participant APIs as Platform/Scanner/Graph APIs User->>Topbar: Select tenant "tenant-bravo" Topbar->>Session: switchTenant("tenant-bravo") Session->>Stores: Optimistic selectedTenant update Session->>Auth: /console/tenants (tenant header=tenant-bravo) Auth-->>Session: allowed tenants + selectedTenant Session->>Auth: /console/profile + /console/token/introspect Auth-->>Session: profile/token introspection for selected tenant Session->>Stores: Commit tenant to Console/Auth/Platform/TenantActivation stores Session->>APIs: Trigger context reload for tenant-scoped data APIs-->>Topbar: Refreshed tenant-scoped responses ``` Error recovery path: - On switch failure (`403`, `tenant_conflict`, session expiry), restore previous tenant in all stores. - Attempt context reload for previous tenant. - Surface deterministic error in tenant panel with retry action. ## 3) API Request Propagation Through Gateway ```mermaid sequenceDiagram autonumber participant UI as Web API Client participant I as Tenant/Context Interceptors participant Gw as Router/Gateway participant Backend as Platform/Scanner/Graph UI->>I: Outgoing request I->>I: Resolve active tenant from canonical runtime state I-->>UI: Add canonical header X-StellaOps-Tenant (+ compat aliases) UI->>Gw: Request with tenant headers + token Gw->>Gw: Strip caller-supplied identity headers, derive tenant from validated claims, rewrite canonical headers Gw->>Backend: Forward tenant-scoped request Backend->>Backend: Resolve tenant context + enforce tenant ownership Backend-->>UI: Deterministic success/failure payload ``` Cache/store invalidation points after tenant switch: - Console session context cache. - Tenant-scoped page stores (Platform/Scanner/Graph read models). - URL context synchronization where tenant is persisted as global context. ## 4) Failure Sequences ### Missing tenant context - Expected result: deterministic `400`/`401`/`403` based on service policy and auth stage. - UI behavior: keep prior selection if available; show recoverable error panel. ### Tenant mismatch - Trigger: claim tenant != header/request tenant. - Expected result: reject with deterministic conflict error (for example `tenant_conflict` or `tenant_forbidden`). - Audit/telemetry: record attempted tenant override + resolved tenant. ### Insufficient scope - Trigger: token lacks required policy scope for requested endpoint. - Expected result: deterministic `403` with scope policy failure context. - UI behavior: no tenant mutation; show access-denied state.