{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/api-evangelist/carbon-arc/main/json-schema/carbon-arc-query-job-response-schema.json", "title": "QueryJobResponse", "x-generated": "2026-10-07", "x-method": "derived", "x-generator": "derive-json-schema.py", "x-source": "openapi/carbon-arc-openapi.json#/components/schemas/QueryJobResponse", "properties": { "job_id": { "type": "string", "title": "Job Id" }, "state": { "$ref": "#/$defs/QueryJobState" }, "position": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Position", "description": "1-indexed place in the queue, while queued." }, "eta_seconds": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "title": "Eta Seconds", "description": "Estimated seconds until this job finishes, from recent completion durations. None while no completions have been recorded yet." }, "queue_wait_ms": { "type": "integer", "title": "Queue Wait Ms", "description": "Time spent queued. Still climbing while queued; frozen at the real wait once the job starts running.", "default": 0 }, "is_heavy_query": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Is Heavy Query", "description": "PRO-3635: whether the query (or, for a multi-query operation, its heaviest sub-query) scored above the 'typical query' band on pyca's structural cost estimator -- a hint that slowness is query-side, not necessarily system-side load. None if no cost estimate has been recorded for this job yet (e.g. still queued)." }, "poll_after_ms": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Poll After Ms", "description": "How long the client should wait before polling again. Absent on a terminal state, which needs no further polling." }, "error": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Error" }, "result": { "anyOf": [ { "$ref": "#/$defs/QueryJobResult" }, { "type": "null" } ] }, "result_json": { "anyOf": [ {}, { "type": "null" } ], "title": "Result Json", "description": "Non-tabular result payload, for jobs whose result doesn't fit QueryJobResult's columns/rows shape (e.g. PRO-3395's batched framework insight queries, each already normalized to plain JSON-safe records). Mutually exclusive with `result` -- a job populates exactly one of the two, based on what its task cached." }, "max_cell_chars": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Max Cell Chars", "description": "Per-cell character cap the worker applied to this result's string cells (see truncate_wide_cells), or null when nothing was shortened. Mirrors SQLQueryResponse.max_cell_chars." }, "truncated": { "type": "boolean", "title": "Truncated", "description": "True if `result`'s rows are a prefix of the real query result -- the worker applied the same byte-payload trim run_query's own sync path uses (see trim_rows_for_payload) before caching, because the result exceeded MAX_PAYLOAD_BYTES. Lives on this envelope rather than QueryJobResult itself: QueryJobResult documents a done job's cached result as always complete, which is no longer true for a trimmed sql_editor job (bugbot finding: 'Async SQL trim hides truncation'), and callers already check SQLQueryResponse.truncated on the sync path -- this mirrors it.", "default": false } }, "type": "object", "required": [ "job_id", "state" ], "$defs": { "QueryJobColumn": { "properties": { "name": { "type": "string", "title": "Name" }, "type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Type" } }, "type": "object", "required": [ "name" ], "title": "QueryJobColumn" }, "QueryJobResult": { "properties": { "columns": { "items": { "$ref": "#/$defs/QueryJobColumn" }, "type": "array", "title": "Columns" }, "rows": { "items": { "items": {}, "type": "array" }, "type": "array", "title": "Rows" }, "row_count": { "type": "integer", "title": "Row Count" } }, "type": "object", "required": [ "columns", "rows", "row_count" ], "title": "QueryJobResult", "description": "A finished job's rows, in the same column/row *structure* as\n``SQLQueryResponse`` — columns plus a row matrix — so PRO-3283's SQL editor\nmigration is a rename rather than a reshape. Note the ``type`` caveat above:\nthe structure translates, the type vocabulary does not.\n\nNo ``applied_limit``/``limit_injected`` here: those describe a caller's\nown SQL rewrite, which happens before submission, not anything the job\nknows about. ``truncated`` also isn't here -- see\n``QueryJobResponse.truncated`` instead: a done job's *cached* result can\nnow be a worker-applied byte-payload trim (not just a caller's own\nrewrite), so that flag belongs on the envelope wrapping this, not on the\nrow/column structure itself." }, "QueryJobState": { "type": "string", "enum": [ "queued", "running", "done", "failed", "cancelled" ], "title": "QueryJobState", "description": "Job lifecycle, mirroring pyca's ``QueryStatusTracker`` states.\n\nOne collapsed ``queued``, with no separate \"waiting for a worker\" vs\n\"waiting for the ConcurrencyGate\" state (PRO-3278 decision): the two are\nindistinguishable to the person waiting.\n\nBe aware of where the worker actually draws that line today. It calls\n``mark_running`` *before* ``async_read_query``, and the gate is acquired\ninside that call, so a job parked on the gate reports ``running`` — with no\nposition, no ETA, and a ``queue_wait_ms`` frozen at pickup — rather than\n``queued``. Under a saturated gate, which is the condition this whole system\nexists to make visible, that understates the wait by exactly the gate wait.\nMoving ``mark_running`` past gate admission is a change to the worker\n(PRO-3281), tracked separately.\n\nAn enum rather than a bare ``str`` so a state pyca adds later surfaces as\na loud, explicit failure in ``QueryJobService`` instead of flowing through\nto clients that have no branch for it." } } }