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