openapi: 3.0.3
info:
  title: Auto Oncall Optimizer API
  version: 0.2.0
paths:
  /healthz:
    get:
      responses:
        "200":
          description: Service is alive
  /readyz:
    get:
      responses:
        "200":
          description: Service is ready
  /api/v1/lark/events:
    post:
      summary: Receive a signed Feishu event callback
      responses:
        "200":
          description: Event accepted
  /api/v1/jobs/{id}:
    get:
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Optimizer job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Job"
        "401":
          description: Missing or invalid admin token
        "404":
          description: Job not found
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  schemas:
    Job:
      type: object
      required: [id, messageId, mode, status, request, createdAt, updatedAt]
      properties:
        id: {type: string}
        messageId: {type: string}
        chatId: {type: string}
        senderOpenId: {type: string}
        request: {type: string}
        issueKey: {type: string}
        trigger: {type: string, enum: [lark, feedback]}
        feedbackId: {type: integer, format: int64}
        sourceJobId: {type: integer, format: int64}
        feedbackAction:
          type: string
          enum: [wrong, revise]
          description: >-
            Version-bound action parsed from a line-anchored Jira /ao command;
            the canonical colon and compatibility punctuation or legacy
            whitespace-only reason separators are accepted upstream.
        answerFingerprint: {type: string}
        mode: {type: string, enum: [analyze, pr, single_ticket]}
        status: {type: string, enum: [queued, running, succeeded, failed]}
        threadId: {type: string}
        worktreePath: {type: string}
        result: {type: string}
        prUrl: {type: string, format: uri}
        investigation:
          $ref: "#/components/schemas/SingleTicketInvestigation"
        error: {type: string}
        notificationStatus: {type: string, enum: [pending, sent]}
        notificationError: {type: string}
        notifiedAt: {type: string, format: date-time}
        createdAt: {type: string, format: date-time}
        updatedAt: {type: string, format: date-time}
    SingleTicketInvestigation:
      oneOf:
        - $ref: "#/components/schemas/SingleTicketInvestigationV1"
        - $ref: "#/components/schemas/SingleTicketInvestigationV2"
      description: Persisted jobs may contain the historical v1 shape; new investigations emit v2.
    SingleTicketInvestigationV2:
      type: object
      description: >-
        Current-main plus fresh-replay investigation with independent findings.
        At most one bounded finding may be selected for automatic repair.
      required: [schemaVersion, issueKey, classification, classificationReason, chain, findings, selectedRepairFindingId, uncertainties]
      properties:
        schemaVersion: {type: string, enum: [auto-oncall-single-ticket-investigation-v2]}
        issueKey: {type: string}
        classification:
          type: string
          enum: [engineering_defect, focus_candidate, no_problem, inconclusive]
          description: >-
            engineering_defect has one selected deterministic finding;
            focus_candidate has no selected finding and retains a diagnostic
            strategy problem; no_problem has no current findings.
        classificationReason: {type: string}
        chain:
          $ref: "#/components/schemas/SingleTicketInvestigationChain"
        findings:
          type: array
          items:
            $ref: "#/components/schemas/SingleTicketFinding"
        selectedRepairFindingId:
          type: string
          nullable: true
          description: ID of the sole admitted deterministic finding, or null.
        uncertainties:
          type: array
          items: {type: string}
    SingleTicketInvestigationV1:
      type: object
      deprecated: true
      description: Historical whole-ticket investigation shape retained for job lookup compatibility.
      required: [schemaVersion, issueKey, classification, classificationReason, chain, firstBrokenBoundary, diagnosticImpact, repairDecision, uncertainties]
      properties:
        schemaVersion: {type: string, enum: [auto-oncall-single-ticket-investigation-v1]}
        issueKey: {type: string}
        classification:
          type: string
          enum: [engineering_defect, focus_candidate, no_problem, inconclusive]
        classificationReason: {type: string}
        chain:
          $ref: "#/components/schemas/SingleTicketInvestigationChain"
        firstBrokenBoundary:
          type: object
          allOf:
            - $ref: "#/components/schemas/SingleTicketBrokenBoundary"
          nullable: true
        diagnosticImpact:
          $ref: "#/components/schemas/SingleTicketDiagnosticImpact"
        repairDecision:
          $ref: "#/components/schemas/SingleTicketRepairDecision"
        uncertainties:
          type: array
          items: {type: string}
    SingleTicketInvestigationChain:
      type: object
      required: [produced, consumable, consumed, completed]
      properties:
        produced: {$ref: "#/components/schemas/SingleTicketBoundaryState"}
        consumable: {$ref: "#/components/schemas/SingleTicketBoundaryState"}
        consumed: {$ref: "#/components/schemas/SingleTicketBoundaryState"}
        completed: {$ref: "#/components/schemas/SingleTicketBoundaryState"}
    SingleTicketBoundaryState:
      type: object
      required: [status, summary, evidenceRefs]
      properties:
        status: {type: string, enum: [established, broken, not_reached, uncertain]}
        summary: {type: string}
        evidenceRefs:
          type: array
          items: {type: string}
    SingleTicketFinding:
      type: object
      required: [id, kind, summary, firstBrokenBoundary, diagnosticImpact, authorityRefs, repairDecision]
      properties:
        id: {type: string}
        kind:
          type: string
          enum: [knowledge_contract, provider_query_contract, orchestration_fallback_contract, diagnostic_strategy, external_transient, historical_only]
        summary: {type: string}
        firstBrokenBoundary:
          type: object
          allOf:
            - $ref: "#/components/schemas/SingleTicketBrokenBoundary"
          nullable: true
        diagnosticImpact:
          $ref: "#/components/schemas/SingleTicketDiagnosticImpact"
        authorityRefs:
          type: array
          description: Exact allowed PingCAP official HTTPS sources required for deterministic knowledge findings.
          items: {type: string, format: uri}
        repairDecision:
          $ref: "#/components/schemas/SingleTicketRepairDecision"
    SingleTicketBrokenBoundary:
      type: object
      required: [stage, layer, producer, consumer, expected, observed, evidenceRefs]
      properties:
        stage:
          type: string
          enum: [input_selection, context_materialization, routing, provider_execution, evidence_handoff, parent_judgment, refinement, final_synthesis, evaluation, runtime_completion]
        layer: {type: string, enum: [produced, consumable, consumed, completed]}
        producer: {type: string}
        consumer: {type: string}
        expected: {type: string}
        observed: {type: string}
        evidenceRefs:
          type: array
          items: {type: string}
    SingleTicketDiagnosticImpact:
      type: object
      required: [kind, summary, evidenceRefs]
      properties:
        kind:
          type: string
          enum: [evidence_lost, evidence_unusable, evidence_not_consumed, unnecessary_refinement, terminal_failure, execution_state_incorrect, latency_or_budget_degradation, analysis_quality_gap, none]
        summary: {type: string}
        evidenceRefs:
          type: array
          items: {type: string}
    SingleTicketRepairDecision:
      type: object
      required: [repairability, reason, violatedContract, reproduction, ownerBoundary, acceptanceCriterion, implementationTargets]
      properties:
        repairability:
          type: string
          enum: [deterministic_engineering, requires_human_judgment, requires_cross_case_evaluation, not_applicable]
        reason: {type: string}
        violatedContract: {type: string, nullable: true}
        reproduction: {type: string, nullable: true}
        ownerBoundary: {type: string, nullable: true}
        acceptanceCriterion: {type: string, nullable: true}
        implementationTargets:
          type: array
          items: {type: string}
