{
  "openapi": "3.0.3",
  "info": {
    "title": "Janitor API",
    "description": "REST API for the Janitor automated VCS change management platform",
    "contact": {
      "name": "Jelmer Vernooĳ",
      "url": "https://github.com/jelmer/janitor",
      "email": "jelmer@jelmer.uk"
    },
    "license": {
      "name": "GPL-3.0+",
      "url": "https://www.gnu.org/licenses/gpl-3.0.html"
    },
    "version": "1.0.0"
  },
  "paths": {
    "/active-runs": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "Get all active runs",
        "description": "Port of py handle_list_active_runs — fetch runner `/status` and\nreturn the `processing` field.",
        "operationId": "get_active_runs",
        "responses": {
          "200": {
            "description": "List of currently processing runs"
          },
          "502": {
            "description": "Unable to contact runner"
          }
        }
      }
    },
    "/active-runs/{run_id}": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "Port of py handle_get_active_run — fetch runner `/status`, find entry",
        "description": "with matching `id` in `processing`, return {} / 404 if missing.",
        "operationId": "get_active_run",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run identifier",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Active run entry from runner processing list"
          },
          "404": {
            "description": "Run is not currently processing"
          },
          "502": {
            "description": "Unable to contact runner"
          }
        }
      }
    },
    "/active-runs/{run_id}/kill": {
      "post": {
        "tags": [
          "runs"
        ],
        "summary": "Port of py handle_runner_kill — admin-only POST that forwards to the",
        "description": "runner's `/kill/{run_id}` endpoint and mirrors its JSON response.\n(Python composes `runner_url / \"kill\" / run_id`, not a nested\n`/active-runs/.../kill` path, so match that exact shape.)",
        "operationId": "post_active_run_kill",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run identifier",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Kill request accepted"
          },
          "502": {
            "description": "Unable to contact runner"
          }
        }
      }
    },
    "/active-runs/{run_id}/log": {
      "get": {
        "tags": [
          "logs"
        ],
        "summary": "`GET /active-runs/{run_id}/log/` — proxy the runner's",
        "description": "`/log/{run_id}` log-listing endpoint, which talks to the live\nworker via backchannel and returns its current log filenames.\nFaithful port of `py/janitor/site/api.py::handle_runner_log_index`.",
        "operationId": "get_run_logs",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run identifier",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of current log filenames for the active run"
          },
          "502": {
            "description": "Unable to contact runner"
          }
        }
      }
    },
    "/active-runs/{run_id}/log/{filename}": {
      "get": {
        "tags": [
          "logs"
        ],
        "summary": "Get specific run log file",
        "operationId": "get_run_log_file",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run identifier",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filename",
            "in": "path",
            "description": "Log filename",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Log file content"
          }
        }
      }
    },
    "/api/active-runs/+peek": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "Peek the next queue item from the runner (proxy to runner `/active-runs/+peek`)",
        "operationId": "peek_active_run",
        "responses": {
          "200": {
            "description": "Next queue item (proxied from runner)"
          }
        }
      }
    },
    "/api/c/{codebase}/merge-proposals": {
      "get": {
        "tags": [
          "merge-proposals"
        ],
        "summary": "Get merge proposals for a specific codebase",
        "operationId": "get_codebase_merge_proposals",
        "parameters": [
          {
            "name": "codebase",
            "in": "path",
            "description": "Codebase name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Merge proposals for the codebase"
          }
        }
      }
    },
    "/api/merge-proposal": {
      "post": {
        "tags": [
          "merge-proposals"
        ],
        "summary": "Port of py handle_merge_proposal_change: admin-only POST that forwards",
        "description": "{url, status} to the publisher's /merge-proposal endpoint and mirrors\nthe JSON response (Python empties the body to `{}` on success).",
        "operationId": "post_merge_proposal",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/MergeProposalChangeForm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Publisher accepted the status change"
          },
          "503": {
            "description": "Publisher service not configured"
          }
        }
      }
    },
    "/api/publish/{publish_id}": {
      "get": {
        "tags": [
          "publishing"
        ],
        "summary": "Proxy GET `/publish/:publish_id` to the publisher service.",
        "operationId": "get_publish_details",
        "parameters": [
          {
            "name": "publish_id",
            "in": "path",
            "description": "Publish id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Publish details (proxied from publisher)"
          }
        }
      }
    },
    "/api/refresh-proposal-status": {
      "post": {
        "tags": [
          "merge-proposals"
        ],
        "summary": "Request the publisher to refresh a merge-proposal's status",
        "description": "(proxy to publisher `/refresh-status`).",
        "operationId": "refresh_proposal_status",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/RefreshProposalStatusForm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Proxied publisher response"
          },
          "502": {
            "description": "Publisher unreachable"
          }
        }
      }
    },
    "/api/run/{run_id}": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "`GET /api/run/{run_id}` — return the run row as JSON. Mirrors",
        "description": "Python's `handle_get_run` in `py/janitor/site/api.py`. Returns\n404 if the run id doesn't exist.\n\nThis is the run *metadata* (id, status, timestamps, branches,\netc.). For the JSON the codemod itself produced, see\n`GET /api/run/{run_id}/codemod`.",
        "operationId": "get_run_details",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run details"
          },
          "404": {
            "description": "Unknown run id"
          }
        }
      },
      "post": {
        "tags": [
          "runs"
        ],
        "summary": "Update run information",
        "operationId": "post_run_update",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run status updated"
          }
        }
      }
    },
    "/api/run/{run_id}/codemod": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "`GET /api/run/{run_id}/codemod` — return the codemod's raw JSON",
        "description": "output (the `run.result` column). Distinct from\n`GET /api/run/{run_id}` which returns run metadata; this one\nis the campaign-specific blob the worker's codemod produced\n(e.g. lintian-brush's `applied`/`failed`/`versions`,\nmultiarch-fixes' `applied-hints`).\n\nReturns:\n* `200 {…codemod result JSON…}` for runs whose codemod stored\na result.\n* `204 No Content` for runs that completed without a codemod\nresult blob (failure modes that aborted before the codemod\nran, or campaigns that don't produce one).\n* `404 Not Found` if the run id doesn't exist.",
        "operationId": "get_run_codemod_result",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Codemod result JSON"
          },
          "204": {
            "description": "Run has no codemod result"
          },
          "404": {
            "description": "Unknown run id"
          }
        }
      }
    },
    "/api/run/{run_id}/reschedule": {
      "post": {
        "tags": [
          "runs"
        ],
        "summary": "`POST /run/{run_id}/reschedule` — operator-initiated reschedule",
        "description": "of a run. Faithful proxy of\n`py/janitor/site/api.py::handle_run_reschedule`: builds a JSON\nbody and forwards it to the runner's `/schedule` endpoint, then\nreturns the runner's response verbatim.\n\nThe previous Rust handler invented its own URL\n(`/api/reschedule/{suite}/{codebase}`), wrapped responses in an\n`ApiResponse` envelope Python doesn't use, refused successful\nruns (which Python forwards), and returned hand-written\n\"no_runner_service\" messages when the runner URL was missing.\nReplace with a thin proxy.",
        "operationId": "post_run_reschedule",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "description": "",
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/RunRescheduleForm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Run rescheduled"
          }
        }
      }
    },
    "/api/run/{run_id}/schedule-control": {
      "post": {
        "tags": [
          "runs"
        ],
        "summary": "Control run scheduling",
        "description": "`POST /run/{run_id}/schedule-control` — operator-initiated\ncontrol-campaign schedule for a run. Faithful proxy of\n`py/janitor/site/api.py::handle_schedule_control`: forwards a\nJSON body to the runner's `/schedule-control` endpoint, then\nfetches the queue position from the runner's `/queue/position`\nendpoint and merges it into the response.\n\nThe previous Rust handler invented its own `?action=pause|priority|status`\ndispatcher that talked to non-existent runner URLs (`/api/runs/{}/pause`)\nand reported invented \"not_implemented\" status codes. Replace with\nthe actual two-call proxy Python uses.",
        "operationId": "post_run_schedule_control",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "description": "",
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/RunRescheduleForm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Control-campaign run scheduled"
          }
        }
      }
    },
    "/api/runner/status": {
      "get": {
        "tags": [
          "queue"
        ],
        "summary": "`GET /runner/status` — proxy the runner's `/status` endpoint",
        "description": "verbatim. Faithful port of\n`py/janitor/site/api.py::handle_runner_status`.\n\nPython returns the runner's response body as-is with the\nrunner's status code. On connection failure, returns 502 +\n`{reason: \"unable to contact runner\", details: ...}`. The\nprevious Rust handler wrapped responses in an ApiResponse\nenvelope and replaced unreachable-runner errors with a fake\n\"unhealthy\" status object instead of a 502.",
        "operationId": "get_runner_status",
        "responses": {
          "200": {
            "description": "Runner status (proxied)"
          },
          "502": {
            "description": "Unable to contact runner"
          }
        }
      }
    },
    "/api/{campaign}/c/{codebase}/publish": {
      "post": {
        "tags": [
          "publishing"
        ],
        "summary": "Publish a codebase within a campaign",
        "operationId": "post_codebase_publish",
        "parameters": [
          {
            "name": "campaign",
            "in": "path",
            "description": "Campaign name",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "codebase",
            "in": "path",
            "description": "Codebase name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Publisher accepted the publish request"
          }
        }
      }
    },
    "/api/{campaign}/merge-proposals": {
      "get": {
        "tags": [
          "merge-proposals"
        ],
        "summary": "Get merge proposals for a specific campaign",
        "operationId": "get_campaign_merge_proposals",
        "parameters": [
          {
            "name": "campaign",
            "in": "path",
            "description": "Campaign name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Merge proposals for the campaign"
          }
        }
      }
    },
    "/cupboard/api/mass-reschedule": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Mass reschedule + delete runs (cupboard admin).",
        "description": "Proxies to the runner's `POST /admin/runs/cleanup` (in\n`runner::web::admin_cleanup_runs`), which is the only endpoint\nthat knows how to safely re-queue *and* delete a finished run:\ninserting a fresh queue row from `(codebase, suite, command)`,\nclearing FK references on `last_run` / `run.resume_from`, and\ndeleting the row in one transaction.\n\nThe earlier in-process implementation called\n`bulk_reschedule_queue_items(&run_ids, …)` on UUIDs the function\nexpects to be `queue.id` (i32) — every call returned\n`Invalid queue id 'UUID...': invalid digit found in string`.\n\nForwarded query params: `result_code` (required), `campaign`,\n`description_like` (`%substring%`), `min_finish_time`,\n`max_finish_time`, `dry_run`, `max`.",
        "operationId": "cupboard_mass_reschedule",
        "responses": {
          "200": {
            "description": "Rescheduled and deleted matching runs"
          }
        }
      }
    },
    "/cupboard/api/needs-review": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "GET /cupboard/api/needs-review",
        "operationId": "cupboard_needs_review",
        "responses": {
          "200": {
            "description": "Runs awaiting manual review"
          }
        }
      }
    },
    "/cupboard/api/publish/autopublish": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Trigger autopublish (cupboard)",
        "operationId": "cupboard_autopublish",
        "responses": {
          "200": {
            "description": "Autopublish triggered (proxied to publisher)"
          }
        }
      }
    },
    "/cupboard/api/publish/scan": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Trigger publish scan (cupboard)",
        "operationId": "cupboard_publish_scan",
        "responses": {
          "200": {
            "description": "Publisher scan triggered"
          }
        }
      }
    },
    "/cupboard/api/reprocess-logs": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "POST /cupboard/api/reprocess-logs",
        "description": "Bulk log reprocessing. Matches\n`py/janitor/site/cupboard/api.py::handle_reprocess_logs`: selects failed\nruns (optionally filtered to an explicit list of run IDs), spawns a\nreprocessing task for each, and returns the list of run IDs queued.",
        "operationId": "cupboard_bulk_reprocess_logs",
        "requestBody": {
          "description": "",
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/BulkReprocessForm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Bulk log reprocessing kicked off"
          }
        }
      }
    },
    "/cupboard/api/run/{run_id}/reprocess-logs": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Reprocess logs for a single run (cupboard)",
        "operationId": "cupboard_reprocess_run_logs",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Logs queued for reprocessing"
          }
        }
      }
    },
    "/cupboard/api/{campaign}/needs-review": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "GET /cupboard/api/{campaign}/needs-review",
        "operationId": "cupboard_campaign_needs_review",
        "parameters": [
          {
            "name": "campaign",
            "in": "path",
            "description": "Campaign name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Runs awaiting review for campaign"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Health check endpoint",
        "operationId": "health_check",
        "responses": {
          "200": {
            "description": "Service health status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse"
                }
              }
            }
          },
          "500": {
            "description": "Health check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/merge-proposals": {
      "get": {
        "tags": [
          "merge-proposals"
        ],
        "summary": "Get merge proposals",
        "operationId": "get_merge_proposals",
        "parameters": [
          {
            "name": "pagination",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PaginationParams"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Search query",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Sorting parameters",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/SortOrder"
                }
              ],
              "nullable": true
            }
          },
          {
            "name": "filter",
            "in": "query",
            "description": "Filtering",
            "required": false,
            "schema": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              },
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of merge proposals",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse"
                }
              }
            }
          }
        }
      }
    },
    "/queue": {
      "get": {
        "tags": [
          "queue"
        ],
        "summary": "Port of py handle_queue — proxy runner `/queue?limit=...`.",
        "operationId": "get_queue_status",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of queue entries to return",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int64",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Queue contents from the runner"
          },
          "502": {
            "description": "Unable to contact runner"
          }
        }
      }
    },
    "/run/{run_id}/debdiff": {
      "get": {
        "tags": [
          "diffs"
        ],
        "summary": "Port of py handle_archive_diff for kind=debdiff.",
        "operationId": "get_run_debdiff",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run identifier",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter_boring",
            "in": "query",
            "description": "If set, ask the differ to filter boring/noisy changes",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Debdiff body from the differ service"
          },
          "404": {
            "description": "No matching unchanged build or no artifacts"
          },
          "503": {
            "description": "Unable to contact differ"
          }
        }
      }
    },
    "/run/{run_id}/diff": {
      "get": {
        "tags": [
          "diffs"
        ],
        "summary": "Get VCS diff for run. Port of py handle_diff.",
        "operationId": "get_run_diff",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run identifier",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "role",
            "in": "query",
            "description": "Result branch role to diff (default: \"main\")",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "VCS diff bytes (text/x-diff)"
          },
          "404": {
            "description": "Run not found or branch missing"
          }
        }
      }
    },
    "/run/{run_id}/diffoscope": {
      "get": {
        "tags": [
          "diffs"
        ],
        "summary": "Port of py handle_archive_diff for kind=diffoscope.",
        "operationId": "get_run_diffoscope",
        "parameters": [
          {
            "name": "run_id",
            "in": "path",
            "description": "Run identifier",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter_boring",
            "in": "query",
            "description": "If set, ask the differ to filter boring/noisy changes",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Diffoscope body from the differ service"
          },
          "404": {
            "description": "No matching unchanged build or no artifacts"
          },
          "503": {
            "description": "Unable to contact differ"
          }
        }
      }
    },
    "/status": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "API status and version information",
        "operationId": "api_status",
        "responses": {
          "200": {
            "description": "API status and version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiError": {
        "type": "object",
        "description": "API error type for consistent error handling",
        "required": [
          "error",
          "status"
        ],
        "properties": {
          "details": {
            "nullable": true
          },
          "error": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          }
        }
      },
      "ApiResponse": {
        "type": "object",
        "description": "Standard API response wrapper",
        "properties": {
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/T"
              }
            ],
            "nullable": true
          },
          "details": {
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "pagination": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PaginationInfo"
              }
            ],
            "nullable": true
          },
          "reason": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "BuildInfo": {
        "type": "object",
        "description": "Build information",
        "properties": {
          "architecture": {
            "type": "string",
            "description": "Architecture",
            "nullable": true
          },
          "distribution": {
            "type": "string",
            "description": "Distribution name",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "Build status",
            "nullable": true
          },
          "version": {
            "type": "string",
            "description": "Build version",
            "nullable": true
          }
        }
      },
      "CampaignStatus": {
        "type": "object",
        "description": "Campaign status information",
        "required": [
          "name",
          "total_candidates",
          "pending_candidates",
          "active_runs"
        ],
        "properties": {
          "active_runs": {
            "type": "integer",
            "format": "int64",
            "description": "Active runs"
          },
          "description": {
            "type": "string",
            "description": "Campaign description",
            "nullable": true
          },
          "last_updated": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "name": {
            "type": "string",
            "description": "Campaign name"
          },
          "pending_candidates": {
            "type": "integer",
            "format": "int64",
            "description": "Pending candidates"
          },
          "success_rate": {
            "type": "number",
            "format": "double",
            "description": "Success rate (0.0 to 1.0)",
            "nullable": true
          },
          "total_candidates": {
            "type": "integer",
            "format": "int64",
            "description": "Total candidates"
          }
        }
      },
      "CommonQuery": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PaginationParams"
          },
          {
            "type": "object",
            "properties": {
              "filter": {
                "type": "object",
                "description": "Filtering",
                "additionalProperties": {
                  "type": "string"
                },
                "nullable": true
              },
              "order": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SortOrder"
                  }
                ],
                "nullable": true
              },
              "search": {
                "type": "string",
                "description": "Search query",
                "nullable": true
              },
              "sort": {
                "type": "string",
                "description": "Sorting parameters",
                "nullable": true
              }
            }
          }
        ],
        "description": "Common query parameters for API endpoints"
      },
      "DiffInfo": {
        "type": "object",
        "description": "Diff information",
        "required": [
          "run_id",
          "diff_type",
          "is_binary"
        ],
        "properties": {
          "content": {
            "type": "string",
            "description": "Diff content (if small enough)",
            "nullable": true
          },
          "diff_type": {
            "type": "string",
            "description": "Diff type (vcs, debdiff, diffoscope)"
          },
          "is_binary": {
            "type": "boolean",
            "description": "Whether diff is binary"
          },
          "run_id": {
            "type": "string",
            "description": "Run ID"
          },
          "size": {
            "type": "integer",
            "format": "int64",
            "description": "Size of diff in bytes",
            "nullable": true
          },
          "url": {
            "type": "string",
            "description": "URL to full diff content",
            "nullable": true
          }
        }
      },
      "HealthStatus": {
        "type": "object",
        "description": "System health information",
        "required": [
          "status",
          "timestamp",
          "services"
        ],
        "properties": {
          "services": {
            "type": "object",
            "description": "Individual service statuses",
            "additionalProperties": {
              "$ref": "#/components/schemas/ServiceHealth"
            }
          },
          "status": {
            "type": "string",
            "description": "Overall status"
          },
          "timestamp": {
            "$ref": "#/components/schemas/DateTime"
          },
          "version": {
            "type": "string",
            "description": "System version",
            "nullable": true
          }
        }
      },
      "LogFile": {
        "type": "object",
        "description": "Log file information",
        "required": [
          "name",
          "compressed"
        ],
        "properties": {
          "compressed": {
            "type": "boolean",
            "description": "Whether file is compressed"
          },
          "content_type": {
            "type": "string",
            "description": "Content type",
            "nullable": true
          },
          "modified_time": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "name": {
            "type": "string",
            "description": "Filename"
          },
          "size": {
            "type": "integer",
            "format": "int64",
            "description": "File size in bytes",
            "nullable": true
          }
        }
      },
      "MassRescheduleRequest": {
        "type": "object",
        "description": "Mass reschedule request (admin only)",
        "properties": {
          "campaign": {
            "type": "string",
            "description": "Campaign filter",
            "nullable": true
          },
          "limit": {
            "type": "integer",
            "format": "int64",
            "description": "Maximum number of items to reschedule",
            "nullable": true
          },
          "offset": {
            "type": "integer",
            "format": "int32",
            "description": "Offset for rescheduled items",
            "nullable": true
          },
          "requester": {
            "type": "string",
            "description": "Requester information",
            "nullable": true
          },
          "result_code": {
            "type": "string",
            "description": "Result code filter",
            "nullable": true
          },
          "suite": {
            "type": "string",
            "description": "Suite filter",
            "nullable": true
          }
        }
      },
      "MergeProposal": {
        "type": "object",
        "description": "Merge proposal information",
        "required": [
          "url",
          "status"
        ],
        "properties": {
          "campaign": {
            "type": "string",
            "description": "Associated campaign",
            "nullable": true
          },
          "codebase": {
            "type": "string",
            "description": "Associated codebase",
            "nullable": true
          },
          "created_time": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "Description of the merge proposal",
            "nullable": true
          },
          "merged_at": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "run_id": {
            "type": "string",
            "description": "Associated run ID",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "Status of the merge proposal"
          },
          "updated_time": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "url": {
            "type": "string",
            "description": "Merge proposal URL"
          }
        }
      },
      "MergeProposalChangeForm": {
        "type": "object",
        "description": "Request body for /merge-proposal",
        "required": [
          "url",
          "status"
        ],
        "properties": {
          "status": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "PaginationInfo": {
        "type": "object",
        "description": "Pagination information in response",
        "required": [
          "offset",
          "limit",
          "has_more"
        ],
        "properties": {
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer",
            "format": "int64"
          },
          "offset": {
            "type": "integer",
            "format": "int64"
          },
          "total": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          }
        }
      },
      "PublishMode": {
        "type": "string",
        "description": "Publish modes matching Python implementation",
        "enum": [
          "push-derived",
          "push",
          "propose",
          "attempt-push",
          "bts"
        ]
      },
      "PublishRequest": {
        "type": "object",
        "description": "Publish request with validation",
        "required": [
          "mode"
        ],
        "properties": {
          "branch_name": {
            "type": "string",
            "description": "Specific branch name to publish",
            "nullable": true
          },
          "mode": {
            "$ref": "#/components/schemas/PublishMode"
          },
          "refresh": {
            "type": "boolean",
            "description": "Whether to refresh before publishing",
            "nullable": true
          },
          "requester": {
            "type": "string",
            "description": "Requester information",
            "nullable": true
          }
        }
      },
      "QueueItem": {
        "type": "object",
        "description": "Queue item representation",
        "required": [
          "queue_id",
          "branch_url",
          "context",
          "command",
          "campaign",
          "refresh",
          "codebase"
        ],
        "properties": {
          "branch_url": {
            "type": "string",
            "description": "Branch URL"
          },
          "campaign": {
            "type": "string",
            "description": "Campaign this item belongs to"
          },
          "change_set": {
            "type": "string",
            "description": "Change set identifier",
            "nullable": true
          },
          "codebase": {
            "type": "string",
            "description": "Codebase name"
          },
          "command": {
            "type": "string",
            "description": "Command to execute"
          },
          "context": {
            "description": "Run context"
          },
          "estimated_duration_seconds": {
            "type": "integer",
            "format": "int32",
            "description": "Estimated duration in seconds",
            "nullable": true
          },
          "queue_id": {
            "type": "integer",
            "format": "int64",
            "description": "Queue identifier"
          },
          "refresh": {
            "type": "boolean",
            "description": "Whether this is a refresh run"
          },
          "requester": {
            "type": "string",
            "description": "Who requested this run",
            "nullable": true
          }
        }
      },
      "QueueStatus": {
        "type": "object",
        "description": "Queue status information (for backwards compatibility)",
        "required": [
          "total_candidates",
          "pending_candidates",
          "active_runs",
          "campaigns"
        ],
        "properties": {
          "active_runs": {
            "type": "integer",
            "format": "int64"
          },
          "campaigns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/super.schemas.CampaignStatus"
            }
          },
          "pending_candidates": {
            "type": "integer",
            "format": "int64"
          },
          "total_candidates": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "RefreshProposalStatusForm": {
        "type": "object",
        "description": "Request body for /refresh-proposal-status",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string"
          }
        }
      },
      "RescheduleRequest": {
        "type": "object",
        "description": "Reschedule request with validation",
        "properties": {
          "offset": {
            "type": "integer",
            "format": "int32",
            "description": "Offset from current position (can be negative)",
            "nullable": true
          },
          "refresh": {
            "type": "boolean",
            "description": "Whether to refresh",
            "nullable": true
          },
          "requester": {
            "type": "string",
            "description": "Requester information",
            "nullable": true
          }
        }
      },
      "ResultBranch": {
        "type": "object",
        "description": "Result branch information",
        "required": [
          "name"
        ],
        "properties": {
          "base_revision": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "revision": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ResultTag": {
        "type": "object",
        "description": "Result tag information",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "revision": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Run": {
        "type": "object",
        "description": "Comprehensive run information",
        "required": [
          "run_id",
          "command",
          "logfilenames",
          "result_branches",
          "result_tags",
          "codebase",
          "campaign"
        ],
        "properties": {
          "branch_url": {
            "type": "string",
            "description": "Branch URL",
            "nullable": true
          },
          "build_info": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BuildInfo"
              }
            ],
            "nullable": true
          },
          "campaign": {
            "type": "string",
            "description": "Campaign name"
          },
          "change_set": {
            "type": "string",
            "description": "Change set identifier",
            "nullable": true
          },
          "codebase": {
            "type": "string",
            "description": "Codebase name"
          },
          "command": {
            "type": "string",
            "description": "Command executed"
          },
          "context": {
            "description": "Run context",
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "Build result description",
            "nullable": true
          },
          "failure_stage": {
            "type": "string",
            "description": "Stage where failure occurred",
            "nullable": true
          },
          "failure_transient": {
            "type": "boolean",
            "description": "Whether the failure is transient",
            "nullable": true
          },
          "finish_time": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "logfilenames": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Log filenames"
          },
          "main_branch_revision": {
            "type": "string",
            "description": "Main branch revision",
            "nullable": true
          },
          "result_branches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResultBranch"
            },
            "description": "Result branches"
          },
          "result_code": {
            "type": "string",
            "description": "Result code",
            "nullable": true
          },
          "result_tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResultTag"
            },
            "description": "Result tags"
          },
          "revision": {
            "type": "string",
            "description": "Current revision",
            "nullable": true
          },
          "run_id": {
            "type": "string",
            "description": "Run identifier"
          },
          "start_time": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "subpath": {
            "type": "string",
            "description": "Subpath within the repository",
            "nullable": true
          },
          "suite": {
            "type": "string",
            "description": "Suite name",
            "nullable": true
          },
          "target_branch_url": {
            "type": "string",
            "description": "Target branch URL",
            "nullable": true
          },
          "vcs_type": {
            "type": "string",
            "description": "VCS type (git, bzr, etc.)",
            "nullable": true
          },
          "worker_name": {
            "type": "string",
            "description": "Worker name that executed this run",
            "nullable": true
          }
        }
      },
      "ScheduleResult": {
        "type": "object",
        "description": "Schedule result response",
        "required": [
          "codebase",
          "campaign",
          "queue_position",
          "queue_wait_time"
        ],
        "properties": {
          "campaign": {
            "type": "string",
            "description": "Campaign name"
          },
          "codebase": {
            "type": "string",
            "description": "Codebase name"
          },
          "estimated_duration_seconds": {
            "type": "integer",
            "format": "int32",
            "description": "Estimated duration in seconds",
            "nullable": true
          },
          "offset": {
            "type": "integer",
            "format": "int32",
            "description": "Offset from top of queue",
            "nullable": true
          },
          "queue_position": {
            "type": "integer",
            "format": "int32",
            "description": "New position in the queue"
          },
          "queue_wait_time": {
            "type": "integer",
            "format": "int32",
            "description": "New delay until run, in seconds"
          }
        }
      },
      "ServiceHealth": {
        "type": "object",
        "description": "Individual service health",
        "required": [
          "status"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Optional error message",
            "nullable": true
          },
          "last_check": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "response_time_ms": {
            "type": "integer",
            "format": "int64",
            "description": "Response time in milliseconds",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "Service status"
          }
        }
      },
      "UserInfo": {
        "type": "object",
        "description": "User information for API responses",
        "required": [
          "email",
          "roles",
          "is_admin",
          "is_qa_reviewer"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "User email"
          },
          "is_admin": {
            "type": "boolean",
            "description": "Whether user is admin"
          },
          "is_qa_reviewer": {
            "type": "boolean",
            "description": "Whether user is QA reviewer"
          },
          "name": {
            "type": "string",
            "description": "Display name",
            "nullable": true
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "User roles"
          },
          "username": {
            "type": "string",
            "description": "Username",
            "nullable": true
          }
        }
      },
      "WorkerResult": {
        "type": "object",
        "description": "Worker result information",
        "required": [
          "code",
          "context",
          "branches",
          "tags",
          "remotes",
          "refreshed"
        ],
        "properties": {
          "branch_url": {
            "type": "string",
            "description": "Branch URL",
            "nullable": true
          },
          "branches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResultBranch"
            },
            "description": "Result branches"
          },
          "builder_result": {
            "description": "Builder result",
            "nullable": true
          },
          "code": {
            "type": "string",
            "description": "Result code"
          },
          "codebase": {
            "type": "string",
            "description": "Codebase name",
            "nullable": true
          },
          "codemod": {
            "description": "Codemod information",
            "nullable": true
          },
          "context": {
            "description": "Context information"
          },
          "description": {
            "type": "string",
            "description": "Description of the result",
            "nullable": true
          },
          "details": {
            "description": "Additional details",
            "nullable": true
          },
          "finish_time": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "main_branch_revision": {
            "type": "string",
            "description": "Main branch revision",
            "nullable": true
          },
          "queue_id": {
            "type": "integer",
            "format": "int64",
            "description": "Queue ID",
            "nullable": true
          },
          "refreshed": {
            "type": "boolean",
            "description": "Whether result was refreshed"
          },
          "remotes": {
            "type": "object",
            "description": "Remote information",
            "additionalProperties": {}
          },
          "revision": {
            "type": "string",
            "description": "Current revision",
            "nullable": true
          },
          "stage": {
            "type": "string",
            "description": "Stage information",
            "nullable": true
          },
          "start_time": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DateTime"
              }
            ],
            "nullable": true
          },
          "subpath": {
            "type": "string",
            "description": "Subpath",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResultTag"
            },
            "description": "Result tags"
          },
          "target_branch_url": {
            "type": "string",
            "description": "Target branch URL",
            "nullable": true
          },
          "transient": {
            "type": "boolean",
            "description": "Whether failure is transient",
            "nullable": true
          },
          "value": {
            "type": "integer",
            "format": "int32",
            "description": "Numeric value associated with result",
            "nullable": true
          },
          "vcs_type": {
            "type": "string",
            "description": "VCS type",
            "nullable": true
          },
          "worker_name": {
            "type": "string",
            "description": "Worker name",
            "nullable": true
          }
        }
      }
    },
    "securitySchemes": {
      "cookie_auth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Session-based authentication via cookies"
      }
    }
  },
  "tags": [
    {
      "name": "health",
      "description": "Health check and status endpoints"
    },
    {
      "name": "queue",
      "description": "Queue management operations"
    },
    {
      "name": "runs",
      "description": "Run management and monitoring"
    },
    {
      "name": "logs",
      "description": "Log file access and streaming"
    },
    {
      "name": "diffs",
      "description": "Diff generation and access"
    },
    {
      "name": "merge-proposals",
      "description": "Merge proposal management"
    },
    {
      "name": "publishing",
      "description": "Publishing operations"
    },
    {
      "name": "admin",
      "description": "Administrative operations"
    }
  ]
}