openapi: 3.0.3
info:
  title: Nixstasis E2E API
  version: 1.0.0
  description: Bespoke Phoenix controller API for client-driven end-to-end test runs.
paths:
  /e2e/suites:
    get:
      summary: List configured E2E suites
      responses:
        "200":
          description: Suite list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        journey_ids:
                          type: array
                          items:
                            type: string
  /e2e/runs:
    get:
      summary: List E2E runs
      responses:
        "200":
          description: Run list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/E2ERun"
    post:
      summary: Create or reuse an E2E run
      parameters:
        - in: header
          name: X-E2E-Protocol-Version
          required: true
          schema:
            type: string
            example: "1"
          description: Required E2E protocol version. Legacy client_version/server_version request fields are not accepted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/E2ERunCreateRequest"
      responses:
        "201":
          description: Run created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/E2ERun"
        "400":
          description: Invalid request or action/expect pair
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TypedErrorResponse"
        "409":
          description: Environment is locked by another active run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TypedErrorResponse"
        "422":
          description: Protocol mismatch, seed failure, or persistence failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TypedErrorResponse"
  /e2e/runs/{run_id}:
    get:
      summary: Fetch E2E run details
      parameters:
        - in: path
          name: run_id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Run details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/E2ERun"
        "404":
          description: Run not found
  /e2e/runs/{run_id}/cancel:
    post:
      summary: Cancel an in-progress E2E run
      parameters:
        - in: path
          name: run_id
          required: true
          schema:
            type: string
      responses:
        "202":
          description: Cancellation accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/E2ERun"
        "404":
          description: Run not found
        "422":
          description: Persistence failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TypedErrorResponse"
  /e2e/runs/{run_id}/results:
    get:
      summary: List journey results for a run
      parameters:
        - in: path
          name: run_id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Result list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/E2ERunResult"
        "404":
          description: Run not found
    post:
      summary: Submit journey results for a run
      parameters:
        - in: path
          name: run_id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [results]
              properties:
                results:
                  type: array
                  items:
                    $ref: "#/components/schemas/E2ERunResultSubmit"
      responses:
        "202":
          description: Results accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/E2ERunResult"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TypedErrorResponse"
        "404":
          description: Run not found
        "422":
          description: Persistence failure
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TypedErrorResponse"
  /e2e/runs/{run_id}/results/{journey_id}/log:
    get:
      summary: Fetch retained log content for one journey result
      parameters:
        - in: path
          name: run_id
          required: true
          schema:
            type: string
        - in: path
          name: journey_id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Log content
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      run_id:
                        type: string
                      journey_id:
                        type: string
                      content:
                        type: string
        "404":
          description: Run, result, or log not found
        "410":
          description: Log unavailable because it was pruned or missing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TypedErrorResponse"
components:
  schemas:
    E2ERunCreateRequest:
      type: object
      required: [suite_id, environment_label, trigger_source]
      properties:
        suite_id:
          type: string
        journey_ids:
          type: array
          items:
            type: string
        environment_label:
          type: string
        trigger_source:
          type: string
          enum: [manual, ci]
        idempotency_key:
          type: string
          nullable: true
        metadata:
          type: object
          additionalProperties: true
      additionalProperties: false
      not:
        anyOf:
          - required: [client_version]
          - required: [server_version]
    E2ERun:
      type: object
      properties:
        id:
          type: string
        suite_id:
          type: string
        journey_ids:
          type: array
          items:
            type: string
        environment_label:
          type: string
        trigger_source:
          $ref: "#/components/schemas/TriggerSource"
        protocol_version:
          type: string
          example: "1"
        status:
          $ref: "#/components/schemas/E2ERunStatus"
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
    E2ERunResultSubmit:
      type: object
      required: [journey_id, status]
      properties:
        journey_id:
          type: string
        status:
          type: string
        failure_step:
          type: string
          nullable: true
        failure_reason:
          type: string
          nullable: true
        log_ref:
          type: string
          nullable: true
        duration_ms:
          type: integer
    E2ERunResult:
      type: object
      properties:
        id:
          type: string
        journey_id:
          type: string
        status:
          type: string
          enum: [queued, running, passed, failed, cancelled, blocked, skipped]
        failure_step:
          type: string
          nullable: true
        failure_reason:
          type: string
          nullable: true
        log_ref:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
        finished_at:
          type: string
          format: date-time
        duration_ms:
          type: integer
    E2ERunStatus:
      type: string
      enum: [queued, running, passed, failed, cancelled, blocked]
    TriggerSource:
      type: string
      enum: [manual, ci]
    TypedErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - protocol_mismatch
                - environment_locked
                - invalid_action_expectation
                - invalid_request
                - seed_failed
                - database_error
                - log_unavailable
            message:
              type: string
            details:
              oneOf:
                - type: string
                - type: object
                  additionalProperties: true
