openapi: 3.0.3
info:
  title: System Console Control Tower (Spec 114)
  version: 0.1.0
  description: |
    Planning contract for System Console Control Tower read models.

    NOTE: Filament/Livewire pages render server-side. This OpenAPI file documents
    the intended query surfaces as if they were JSON endpoints to keep fields
    and filtering semantics explicit during implementation.
servers:
  - url: /system
paths:
  /dashboard:
    get:
      summary: Control Tower KPIs
      parameters:
        - in: query
          name: window
          schema:
            type: string
            enum: [1h, 24h, 7d]
          required: false
      responses:
        '200':
          description: KPI + top offenders
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ControlTowerResponse'
  /directory/workspaces:
    get:
      summary: Workspaces directory
      parameters:
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: health
          schema:
            type: string
            enum: [ok, warn, critical, unknown]
      responses:
        '200':
          description: Workspaces list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceListResponse'
  /directory/workspaces/{workspaceId}:
    get:
      summary: Workspace detail
      parameters:
        - in: path
          name: workspaceId
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: Workspace detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceDetailResponse'
  /directory/tenants:
    get:
      summary: Tenants directory
      parameters:
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: workspace_id
          schema: { type: integer }
      responses:
        '200':
          description: Tenants list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantListResponse'
  /directory/tenants/{tenantId}:
    get:
      summary: Tenant detail
      parameters:
        - in: path
          name: tenantId
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: Tenant detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantDetailResponse'
  /ops/runs:
    get:
      summary: Global operation runs
      parameters:
        - in: query
          name: window
          schema:
            type: string
            enum: [1h, 24h, 7d]
        - in: query
          name: status
          schema:
            type: string
            enum: [queued, running, completed]
        - in: query
          name: outcome
          schema:
            type: string
        - in: query
          name: type
          schema:
            type: string
        - in: query
          name: workspace_id
          schema: { type: integer }
        - in: query
          name: tenant_id
          schema: { type: integer }
      responses:
        '200':
          description: Runs list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunListResponse'
  /ops/runs/{runId}:
    get:
      summary: Canonical run detail
      parameters:
        - in: path
          name: runId
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: Run detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunDetailResponse'
  /ops/failures:
    get:
      summary: Failed runs (prefilter)
      parameters:
        - in: query
          name: window
          schema:
            type: string
            enum: [1h, 24h, 7d]
      responses:
        '200':
          description: Failed runs list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunListResponse'
  /ops/stuck:
    get:
      summary: Stuck runs (prefilter)
      parameters:
        - in: query
          name: window
          schema:
            type: string
            enum: [1h, 24h, 7d]
      responses:
        '200':
          description: Stuck runs list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunListResponse'
  /security/access-logs:
    get:
      summary: Access logs
      parameters:
        - in: query
          name: window
          schema:
            type: string
            enum: [1h, 24h, 7d]
        - in: query
          name: actor_id
          schema: { type: integer }
        - in: query
          name: status
          schema:
            type: string
            enum: [success, failure]
      responses:
        '200':
          description: Access logs list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessLogListResponse'
components:
  schemas:
    ControlTowerResponse:
      type: object
      required: [window, kpis, top_offenders]
      properties:
        window: { type: string }
        kpis:
          type: object
          additionalProperties: { type: integer }
        top_offenders:
          type: array
          items:
            type: object
            required: [dimension, id, label, failed_count]
            properties:
              dimension: { type: string, enum: [tenant, workspace, run_type] }
              id: { type: integer }
              label: { type: string }
              failed_count: { type: integer }
    WorkspaceListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WorkspaceSummary'
    WorkspaceDetailResponse:
      type: object
      required: [workspace]
      properties:
        workspace:
          $ref: '#/components/schemas/WorkspaceSummary'
        tenants:
          type: array
          items:
            $ref: '#/components/schemas/TenantSummary'
    TenantListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/TenantSummary'
    TenantDetailResponse:
      type: object
      required: [tenant]
      properties:
        tenant:
          $ref: '#/components/schemas/TenantSummary'
        provider_connections:
          type: array
          items:
            $ref: '#/components/schemas/ProviderConnectionSummary'
        permissions:
          type: array
          items:
            $ref: '#/components/schemas/TenantPermissionSummary'
        recent_runs:
          type: array
          items:
            $ref: '#/components/schemas/RunSummary'
    RunListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RunSummary'
    RunDetailResponse:
      type: object
      required: [run]
      properties:
        run:
          allOf:
            - $ref: '#/components/schemas/RunSummary'
            - type: object
              properties:
                summary_counts:
                  type: object
                  additionalProperties: { type: integer }
                failure_summary:
                  type: array
                  items:
                    $ref: '#/components/schemas/RunFailure'
                context:
                  type: object
                  additionalProperties: true
    AccessLogListResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/AccessLogEntry'
    WorkspaceSummary:
      type: object
      required: [id, name, slug]
      properties:
        id: { type: integer }
        name: { type: string }
        slug: { type: string }
        tenant_count: { type: integer }
        health: { type: string, enum: [ok, warn, critical, unknown] }
        last_activity_at: { type: string, format: date-time, nullable: true }
    TenantSummary:
      type: object
      required: [id, external_id, name, workspace_id]
      properties:
        id: { type: integer }
        external_id: { type: string }
        name: { type: string }
        workspace_id: { type: integer }
        status: { type: string }
        environment: { type: string, nullable: true }
        health: { type: string, enum: [ok, warn, critical, unknown] }
        last_activity_at: { type: string, format: date-time, nullable: true }
    ProviderConnectionSummary:
      type: object
      required: [provider, is_default]
      properties:
        provider: { type: string }
        is_default: { type: boolean }
        last_health_check_at: { type: string, format: date-time, nullable: true }
        health: { type: string, nullable: true }
    TenantPermissionSummary:
      type: object
      required: [key, status]
      properties:
        key: { type: string }
        status: { type: string }
        last_checked_at: { type: string, format: date-time, nullable: true }
    RunSummary:
      type: object
      required: [id, workspace_id, type, status, outcome, created_at]
      properties:
        id: { type: integer }
        workspace_id: { type: integer }
        tenant_id: { type: integer, nullable: true }
        type: { type: string }
        status: { type: string }
        outcome: { type: string }
        initiator_name: { type: string }
        created_at: { type: string, format: date-time }
        started_at: { type: string, format: date-time, nullable: true }
        completed_at: { type: string, format: date-time, nullable: true }
    RunFailure:
      type: object
      required: [code, message]
      properties:
        code: { type: string }
        reason_code: { type: string, nullable: true }
        message: { type: string }
    AccessLogEntry:
      type: object
      required: [id, recorded_at, action, status]
      properties:
        id: { type: integer }
        recorded_at: { type: string, format: date-time }
        action: { type: string }
        status: { type: string }
        actor_id: { type: integer, nullable: true }
        actor_email: { type: string, nullable: true }
        actor_name: { type: string, nullable: true }
        ip: { type: string, nullable: true }
        user_agent: { type: string, nullable: true }
