TenantAtlas/specs/199-global-context-shell-contract/contracts/global-context-shell.logical.openapi.yaml
Ahmed Darrazi b515796839
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 53s
feat: finalize global shell context contract
2026-04-18 15:59:02 +02:00

467 lines
16 KiB
YAML

openapi: 3.1.0
info:
title: Global Context Shell Logical Contract
version: 0.1.0
summary: Logical HTTP contract for workspace and tenant shell context resolution and mutation flows
description: >-
This is a logical contract for Spec 199. The real routes render HTML and redirects,
but the schemas below define the canonical request-scoped shell context and the
expected redirect or recovery outcomes for shared workspace and tenant shell flows.
servers:
- url: /
description: Application root for admin and tenant shell entry surfaces
tags:
- name: shell-context
- name: workspace-switch
- name: tenant-select
- name: tenant-clear
paths:
/admin:
get:
tags: [shell-context]
summary: Resolve workspace-scoped shell entry
description: Resolve the active workspace and optional tenant context for a workspace-scoped admin route, including query-backed tenant hints only where the contract explicitly allows them.
parameters:
- name: tenant
in: query
required: false
schema:
type: string
description: Optional tenant external-ID hint on routes that explicitly allow query-backed shell resolution.
- name: tenant_id
in: query
required: false
schema:
oneOf:
- type: integer
- type: string
description: Optional tenant identifier hint on workspace-scoped routes that explicitly allow query-backed context hints.
responses:
'200':
description: Workspace-scoped shell entry resolved successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/ResolvedShellContextEnvelope'
examples:
tenantless:
value:
resolvedContext:
state: tenantless_workspace
displayMode: tenantless
pageCategory: workspace_scoped
workspaceSource: session_workspace
tenantSource: none
workspace:
id: 42
slug: alpha-workspace
name: Alpha Workspace
tenant: null
recoveryDirective:
action: none
reason: null
destination: null
preserveIntendedUrl: false
rememberedTenant:
value:
resolvedContext:
state: tenant_scoped
displayMode: tenant_scoped
pageCategory: workspace_scoped
workspaceSource: session_workspace
tenantSource: remembered
workspace:
id: 42
slug: alpha-workspace
name: Alpha Workspace
tenant:
id: 7
externalId: tenant-7
name: Tenant Seven
recoveryDirective:
action: none
reason: null
destination: null
preserveIntendedUrl: false
queryHintTenant:
value:
resolvedContext:
state: tenant_scoped
displayMode: tenant_scoped
pageCategory: workspace_scoped
workspaceSource: session_workspace
tenantSource: query_hint
workspace:
id: 42
slug: alpha-workspace
name: Alpha Workspace
tenant:
id: 7
externalId: tenant-7
name: Tenant Seven
requestedContext:
workspaceIdentifier: null
tenantIdentifier: tenant-7
source: query_hint
pageCategory: workspace_scoped
recoveryDirective:
action: none
reason: null
destination: null
preserveIntendedUrl: false
'302':
description: No valid workspace could be resolved and the user must be redirected to a chooser or safe fallback.
'404':
description: The requested context implies inaccessible or invalid workspace-bound data that cannot be widened safely.
/admin/choose-workspace:
get:
tags: [shell-context]
summary: Resolve the explicit workspace chooser exception route
description: Render the explicit workspace chooser exception route used when no workspace truth can be recovered or when the operator must select a workspace directly.
responses:
'200':
description: Workspace chooser rendered as the explicit recovery route.
content:
application/json:
schema:
$ref: '#/components/schemas/ResolvedShellContextEnvelope'
examples:
chooser:
value:
resolvedContext:
state: missing_workspace
displayMode: recovery
pageCategory: workspace_chooser_exception
workspaceSource: none
tenantSource: none
workspace: null
tenant: null
recoveryDirective:
action: none
reason: missing_workspace
destination: /admin/choose-workspace
preserveIntendedUrl: true
/admin/choose-tenant:
get:
tags: [shell-context]
summary: Resolve the explicit choose-tenant route after workspace selection
description: Render the explicit choose-tenant route used when a resolved workspace has multiple selectable tenants.
responses:
'200':
description: Choose-tenant route rendered successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/ResolvedShellContextEnvelope'
examples:
chooseTenant:
value:
resolvedContext:
state: tenantless_workspace
displayMode: tenantless
pageCategory: workspace_scoped
workspaceSource: session_workspace
tenantSource: none
workspace:
id: 42
slug: alpha-workspace
name: Alpha Workspace
tenant: null
recoveryDirective:
action: none
reason: null
destination: /admin/choose-tenant
preserveIntendedUrl: false
/admin/t/{external_id}:
get:
tags: [shell-context]
summary: Resolve tenant-bound shell entry
description: Resolve tenant context for a tenant-bound route where explicit tenant routing is required.
parameters:
- name: external_id
in: path
required: true
schema:
type: string
responses:
'200':
description: Tenant-bound shell entry resolved successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/ResolvedShellContextEnvelope'
examples:
tenantBound:
value:
resolvedContext:
state: tenant_scoped
displayMode: tenant_scoped
pageCategory: tenant_bound
workspaceSource: session_workspace
tenantSource: route
workspace:
id: 42
slug: alpha-workspace
name: Alpha Workspace
tenant:
id: 7
externalId: tenant-7
name: Tenant Seven
recoveryDirective:
action: none
reason: null
destination: null
preserveIntendedUrl: false
'404':
description: The route tenant is invalid, inaccessible, or incompatible with the active workspace.
/admin/switch-workspace:
post:
tags: [workspace-switch]
summary: Switch the active workspace
description: Set the active workspace, re-evaluate tenant compatibility, and redirect to a safe concrete destination such as an intended `/admin...` URL, `admin.workspace.managed-tenants.index`, `/admin/choose-tenant`, or the tenant dashboard.
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [workspace_id]
properties:
workspace_id:
type: integer
responses:
'302':
description: Workspace switch accepted and redirected to intended URL or resolver destination.
headers:
Location:
schema:
type: string
description: Safe destination for the resolved workspace and resulting tenant state, currently an intended `/admin...` URL, `admin.workspace.managed-tenants.index`, `/admin/choose-tenant`, or a tenant dashboard route.
'404':
description: Workspace does not exist, is archived, or is not accessible to the current user.
'422':
description: Request body failed validation.
/admin/select-tenant:
post:
tags: [tenant-select]
summary: Select the active tenant inside the resolved workspace
description: Explicitly activate a tenant that belongs to the current workspace and passes entitlement and operability checks, then redirect to the deterministic tenant entry route for that tenant.
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [tenant_id]
properties:
tenant_id:
type: integer
responses:
'302':
description: Tenant selection accepted and redirected to the deterministic tenant entry route for the selected tenant.
headers:
Location:
schema:
type: string
'404':
description: Tenant is missing, inaccessible, incompatible with the active workspace, or fails operability rules.
'422':
description: Request body failed validation.
/admin/clear-tenant-context:
post:
tags: [tenant-clear]
summary: Clear active tenant context
description: Remove remembered and panel tenant state, then resolve according to page category and route compatibility to either same-route tenantless workspace state or one of the documented concrete destinations: `admin.operations.index`, `admin.evidence.overview`, `admin.workspace.managed-tenants.index`, `admin.operations.view`, or `admin.home`.
responses:
'302':
description: Tenant context cleared and request resolved to tenantless workspace state on the current route or redirected to one of the documented concrete workspace-safe fallbacks.
headers:
Location:
schema:
type: string
'404':
description: The current route cannot recover safely because scope is no longer accessible.
components:
schemas:
RequestedContext:
type: object
properties:
workspaceIdentifier:
oneOf:
- type: integer
- type: string
- type: 'null'
tenantIdentifier:
oneOf:
- type: integer
- type: string
- type: 'null'
source:
$ref: '#/components/schemas/ContextSource'
pageCategory:
$ref: '#/components/schemas/PageCategory'
RememberedContextCandidate:
type: object
properties:
workspaceId:
type: integer
tenantId:
type:
- integer
- 'null'
source:
$ref: '#/components/schemas/ContextSource'
eligible:
type: boolean
invalidReason:
type:
- string
- 'null'
ResolvedShellContextEnvelope:
type: object
required: [resolvedContext]
properties:
resolvedContext:
$ref: '#/components/schemas/ResolvedShellContext'
ResolvedShellContext:
type: object
required:
- state
- displayMode
- pageCategory
- workspaceSource
- tenantSource
- workspace
- tenant
- recoveryDirective
properties:
state:
$ref: '#/components/schemas/ShellState'
displayMode:
type: string
enum:
- tenant_scoped
- tenantless
- recovery
pageCategory:
$ref: '#/components/schemas/PageCategory'
workspaceSource:
$ref: '#/components/schemas/ContextSource'
tenantSource:
$ref: '#/components/schemas/ContextSource'
workspace:
oneOf:
- $ref: '#/components/schemas/WorkspaceReference'
- type: 'null'
tenant:
oneOf:
- $ref: '#/components/schemas/TenantReference'
- type: 'null'
requestedContext:
oneOf:
- $ref: '#/components/schemas/RequestedContext'
- type: 'null'
rememberedContext:
oneOf:
- $ref: '#/components/schemas/RememberedContextCandidate'
- type: 'null'
recoveryDirective:
$ref: '#/components/schemas/RecoveryDirective'
RecoveryDirective:
type: object
required: [action, reason, destination, preserveIntendedUrl]
properties:
action:
$ref: '#/components/schemas/RecoveryAction'
reason:
type:
- string
- 'null'
destination:
type:
- string
- 'null'
preserveIntendedUrl:
type: boolean
WorkspaceReference:
type: object
required: [id, slug, name]
properties:
id:
type: integer
slug:
type: string
name:
type: string
TenantReference:
type: object
required: [id, externalId, name]
properties:
id:
type: integer
externalId:
type: string
name:
type: string
ContextSource:
type: string
enum:
- route
- explicit_switch
- explicit_select
- session_workspace
- filament_tenant
- remembered
- query_hint
- none
PageCategory:
type: string
enum:
- workspace_scoped
- workspace_chooser_exception
- tenant_bound
- tenant_scoped_evidence
- canonical_workspace_record_viewer
ShellState:
type: string
enum:
- tenant_scoped
- tenantless_workspace
- missing_workspace
- invalid_workspace
- missing_tenant
- invalid_tenant
- inaccessible_tenant
- incompatible_tenant
RecoveryAction:
type: string
enum:
- none
- render_tenantless_workspace
- redirect_choose_workspace
- redirect_operations_index
- redirect_evidence_overview
- redirect_workspace_home
- redirect_workspace_managed_tenants
- redirect_workspace_record_fallback
- abort_not_found