{
  "openapi": "3.0.3",
  "info": {
    "title": "DomeCommand C2 API",
    "description": "The HTTP surface of the DomeCommand counter-UAS command and control server: hydrate the picture once, hold the SSE stream for deltas, and command through the asset's own advertised capabilities.\n\nEvery JSON response is wrapped in the envelope `{ \"ok\": bool, \"data\": ..., \"error\": ..., \"code\": ... }`; the schemas on each operation show the full wrapped body. An error carries both halves: `error` is a sentence naming what failed, for the person reading a log, and `code` is a frozen `ApiErrorCode` for the program deciding what to do next. Branch on `code`; the sentence is free to be reworded.\n\nEvery route is authenticated and workspace-scoped. Present either a short-lived session token (`Authorization: Bearer`) or a workspace API key (`X-Api-Key: dak_...`), and name the tenant with `X-Workspace-Id`. Without a credential you get `401`; with one that does not reach the named workspace, `403`. Four routes are open, and only these: `GET /`, `GET /health`, `GET /api/openapi.json` and `GET /api/stream`. The stream is open because `EventSource` sends no headers, so it authenticates on a single-use ticket from `POST /api/auth/stream-ticket` instead.\n\nThere is one surface. `/v1` was a second prefix justified by authentication, and once both were authenticated what was left was two words for every resource, so it is gone. What it got right stayed: ids are prefixed opaque strings (`ast_`, `tsk_`, `wsp_`, `key_`, `usr_`) meant for reading logs rather than parsing, a create answers `201` with a `Location`, a delete answers `204`, and a documented route is additive-only from then on.",
    "license": {
      "name": ""
    },
    "version": "0.1.0"
  },
  "paths": {
    "/": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Root banner: the server is up and answering.",
        "operationId": "get_root",
        "responses": {
          "200": {
            "description": "the server is up",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "status": "ok"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/assets": {
      "get": {
        "tags": [
          "assets"
        ],
        "summary": "The assets in the current environment, each with what it advertises.",
        "description": "**One resource, read one way.** This used to return the bare registry row while\n`GET /api/fleet` returned the fused view of the same rows, so every reader had\nto know which of two words meant which half of one asset — and the FLEET rail\nonce grouped six assets into an empty panel because a caller picked the wrong\none (`ui/src/lib/apiClient.ts`). `AssetView` is the shape: identity, domain,\nlink state, motion, health and the verbs the asset itself declares.",
        "operationId": "get_api_assets",
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "description": "`vehicle` or `sensor`. Absent means both.",
            "required": false,
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/AssetKind"
                }
              ],
              "nullable": true
            }
          },
          {
            "name": "nearest",
            "in": "query",
            "description": "`lat,lon` — order the answer by distance from a point, nearest first.\n\n**Selection is a query, not a command.** \"Which drone is nearest and free\"\nis a real operator affordance and it was the only thing\n`POST /api/engagement/task/point` still added over the one command path, so\nit is a parameter on the list and the route is gone (spec §3).",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "free",
            "in": "query",
            "description": "Only assets not already committed to a threat.\n\nA merely *proposed* assignment does not count as committed: the drone is\nphysically loitering, so an operator may redirect it.",
            "required": false,
            "schema": {
              "type": "boolean",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "every asset in the workspace's current environment, with its live view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AssetView"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "assets"
        ],
        "summary": "Register an asset. The one way one comes to exist.",
        "description": "Idempotent on the serial: registering the same `connection.address` twice\nreturns the same asset rather than a second row fighting the first for the same\naircraft.",
        "operationId": "post_api_assets",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterAsset"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "the asset with its advertised capabilities. `Location` names it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AssetView"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "no name, or a spec that is not an asset spec",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no catalogue entry by that name, or nothing discovered at that address",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "the catalogue entry is a different domain than the one asked for",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/assets/{id}": {
      "get": {
        "tags": [
          "assets"
        ],
        "summary": "One asset, with what it advertises.",
        "description": "Takes a registry id **or an adoption key**, because the second is what a\nplatform heard on a link is addressed by until somebody saves it, and refusing\nto describe an asset the console can already see is not a distinction an\noperator can act on.",
        "operationId": "get_api_assets_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "registry id, adoption key, or simulated twin id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the asset's live view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AssetView"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no asset with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "assets"
        ],
        "summary": "Edit an asset: callsign, model, performance override, spec, or a sensor placement.",
        "operationId": "post_api_assets_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "asset id, `ast_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "spec"
                ],
                "properties": {
                  "kind": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/AssetKind"
                      }
                    ],
                    "nullable": true
                  },
                  "model": {
                    "type": "string",
                    "description": "**What type this is** — a catalog entry's own name, exactly.\n\nSomething adopted off a link arrives with no model, and until now there was\nno way to give it one: the only door that wrote `spec.model` was\n`from-catalog`, which creates. So a discovered airframe inherited no\nendurance, no speeds and no control protocol, permanently, and the page\nsaid so without offering a fix.\n\nResolved through `canonical_name`, so what lands in the spec is the\ncatalog's spelling rather than the operator's — `catalogFor()` matches\nexactly, and a near-miss inherits nothing while looking correct.",
                    "nullable": true
                  },
                  "name": {
                    "type": "string",
                    "description": "The **callsign** — what humans call this platform (ST2 §2). Editable at any\ntime: the Remote-ID serial is the identity and stays where it is, so a rename\nre-negotiates nothing and leaves fused correlation untouched.",
                    "nullable": true
                  },
                  "performance": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/PlatformPerformance"
                      }
                    ],
                    "nullable": true
                  },
                  "placement": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/Placement"
                      }
                    ],
                    "nullable": true
                  },
                  "spec": {
                    "type": "object"
                  },
                  "switches": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/AssetSwitchesPatch"
                      }
                    ],
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the updated registry row",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Asset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "a placement whose lat/lon is not a point on the earth",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no asset with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "a model the catalogue does not hold, a hand-placed carried sensor, or a spec that is not an object",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "assets"
        ],
        "summary": "Remove an asset from the registry.",
        "description": "The platform stops being ours, which is a fusion decision as much as a\nbookkeeping one: the friendly whitelist is derived from registry membership, so\nthe tracker starts treating its serial as an unknown contact again.\n\n**The guard is worth keeping in view.** Deleting a row by side effect drops it\noff the whitelist, and our own drone starts scoring as an unknown contact\n(invariant 10). Before there was a route at all, a row could only be removed\nwith `psql`, which bypasses the refreshes: the runtime kept the deleted\nvehicle's payload sensors in its declared set, and a phantom\n`<uuid>/eo-turret` sat on the LIVE rail reading NO RETURNS.",
        "operationId": "delete_api_assets_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "asset id, `ast_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "removed; a link issued for it alone is revoked with it"
          },
          "403": {
            "description": "this workspace role is read-only",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no asset with that id in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/assets/{id}/bind": {
      "post": {
        "tags": [
          "assets"
        ],
        "summary": "Pair a heard platform with an existing row.",
        "operationId": "post_api_assets_id_bind",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "asset id, `ast_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Bind a platform heard on a link to a registry row that already exists.\n\nThe other half of adoption. `adopt` is for something heard that we have never\ndescribed; this is for the opposite order — a platform added from the catalog\n(planned, named, with its performance already resolved) that has now turned up\non a link. Without it a catalogued platform could never be paired with anything,\nbecause the only key `fleet::list` binds on is `spec.remote_id_serial`.",
                "required": [
                  "key"
                ],
                "properties": {
                  "key": {
                    "type": "string",
                    "description": "The discovered platform's key — its deterministic adoption serial."
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the row, now carrying the discovered platform's serial",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Asset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no asset with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "the key is already bound to another row, or this row is already paired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/assets/{id}/command": {
      "post": {
        "tags": [
          "assets"
        ],
        "summary": "The one imperative. Tell an asset to do something, and get back the record.",
        "description": "Refuses any verb the asset does not advertise, with the reason. Either way a\ntask is written: a refusal carries the refusing rule's own sentence on `reason`,\nwhich is where an operator can still read it a minute later — the toast it used\nto live in had gone by then.",
        "operationId": "post_api_assets_id_command",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "registry uuid, adoption key, or simulated twin id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssetCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "the task, with its status. `Location` names it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Task"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the verb needs parameters of a different kind",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "unknown asset",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "the verb is not advertised or not available right now; the reason names the verb",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "501": {
            "description": "built without the mavlink-control feature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/assets/{id}/payloads": {
      "post": {
        "tags": [
          "assets"
        ],
        "summary": "Say what a vehicle carries.",
        "description": "Writes ONE key of the spec, read-modify-write, for the same reason the\nperformance override does: a whole-spec PUT from a form is how a serial and a\nmodel get lost. The catalog is untouched — this is a per-vehicle override, so\nremoving a tele lens here removes it from this airframe and from nothing else.",
        "operationId": "post_api_assets_id_payloads",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "asset id, `ast_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "What this vehicle carries, as the operator has just set it (U7 §3).",
                "properties": {
                  "payloads": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/PayloadSpec"
                    },
                    "description": "The full fit, replacing whatever this vehicle carried before. Clearing one\npayload is posting the list without it; `[]` is *carries nothing*, which is\na different state from never having said — and both are different from\ninheriting the model, which is `null`.",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the asset with its new fit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Asset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no asset with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/dev-login": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Log in without a code. Dev deployments only.",
        "description": "Answers 501 with `code: \"feature_absent\"` unless `DEV_MODE` is on, and the\nmessage says so, because the first thing anybody does with a dev route is try\nit against staging and wonder why it is quiet.\n\nIt exists because the alternative is worse: without it, every local console\nsession starts by reading a six-digit code out of a log line, and the shortcut\npeople invent instead is a hardcoded token in the client.",
        "operationId": "post_api_auth_dev_login",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DevLoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "a session, no code required",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Session"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "501": {
            "description": "this deployment is not in dev mode",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/auth/login": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Ask for a login code.",
        "description": "Always answers 200, whether or not the address is one we have seen. Telling a\ncaller which addresses exist is an account enumeration, and the person who\nactually owns the address learns the same thing from their inbox.",
        "operationId": "post_api_auth_login",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "a code is on its way; in dev mode it is in the body",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LoginSent"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "that is not an email address",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "the mail provider refused it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/auth/logout": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "End a session.",
        "description": "The bearer token is not revoked, because it cannot be: it is a signature, and\nnothing we hold can un-sign it. It expires in\n[`SESSION_TTL_MINUTES`](crate::services::auth::SESSION_TTL_MINUTES), and the\nrefresh token that would have renewed it is gone, so the session ends.",
        "operationId": "post_api_auth_logout",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/LogoutRequest"
                  }
                ],
                "nullable": true
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "description": "how many refresh tokens were revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LoggedOut"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "no credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          }
        ]
      }
    },
    "/api/auth/refresh": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Trade a refresh token for the next session.",
        "description": "The presented token is revoked in the same call, so a refresh token is good\nexactly once. A client that retries a failed refresh with the same value gets\na 401, which is the correct answer: the one it should retry with is in the\nresponse it did not read.",
        "operationId": "post_api_auth_refresh",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefreshRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the next session token and the next refresh token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Session"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "unknown, spent or expired refresh token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/auth/stream-ticket": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "A credential an EventSource can carry.",
        "description": "`GET /api/stream` is per workspace like everything else, and the browser API\nthat opens it cannot set a header: `new EventSource(url)` takes a URL and\nnothing more. Putting the session token in the query string instead would put\na thirty-minute credential into every access log and proxy cache between here\nand the browser.\n\nSo this mints a ticket: authenticated, naming one workspace, good for a minute\nand spent the moment the stream picks it up. One in a log is worth a single\nSSE connection, and only if the console has not already used it — which it\ndoes immediately.",
        "operationId": "post_api_auth_stream_ticket",
        "responses": {
          "200": {
            "description": "a single-use ticket for GET /api/stream",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StreamTicket"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "no X-Workspace-Id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "no credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          },
          {
            "api_key": []
          }
        ]
      }
    },
    "/api/auth/verify": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Exchange a code for a session.",
        "operationId": "post_api_auth_verify",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "a session token, its refresh token, and the workspaces this user may open",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Session"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "wrong code, or one that has expired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "too many wrong codes for this login",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/catalog/builtins": {
      "get": {
        "tags": [
          "catalog"
        ],
        "summary": "The built-in sensing catalog (DJI Mavic family + contrast platforms).",
        "description": "Pure / DB-free reference data, so operators can browse coverage before seeding.",
        "operationId": "get_api_catalog_builtins",
        "responses": {
          "200": {
            "description": "the built-in platforms, each with its resolved capability profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "One built-in reference platform, with its resolved capability profile inlined so\nthe frontend can render the emission story without re-deriving it.",
                        "required": [
                          "name",
                          "kind",
                          "spec",
                          "capability"
                        ],
                        "properties": {
                          "capability": {
                            "$ref": "#/components/schemas/CapabilityProfile"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "spec": {
                            "$ref": "#/components/schemas/CatalogSpec"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/catalog/entries": {
      "get": {
        "tags": [
          "catalog"
        ],
        "summary": "Backfill the workspace catalog with any built-in platforms it is missing.",
        "description": "List the workspace's catalog entries.",
        "operationId": "get_api_catalog_entries",
        "responses": {
          "200": {
            "description": "every catalog entry in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CatalogEntryRow"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "catalog"
        ],
        "summary": "Create a catalog entry.",
        "operationId": "post_api_catalog_entries",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Resolve the effective workspace: the caller's if the extractor supplied one,\notherwise the system workspace (dev/tokenless default).",
                "required": [
                  "kind",
                  "name",
                  "spec"
                ],
                "properties": {
                  "kind": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "spec": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored entry, with its server-assigned id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CatalogEntryRow"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the spec does not parse as a catalog spec",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/catalog/entries/{id}": {
      "get": {
        "tags": [
          "catalog"
        ],
        "summary": "Fetch one catalog entry.",
        "operationId": "get_api_catalog_entries_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "catalog entry id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the entry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CatalogEntryRow"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no catalog entry with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "catalog"
        ],
        "summary": "Update a catalog entry; absent fields stay as they are.",
        "operationId": "post_api_catalog_entries_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "catalog entry id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "spec"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "nullable": true
                  },
                  "name": {
                    "type": "string",
                    "nullable": true
                  },
                  "spec": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the updated entry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CatalogEntryRow"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the spec does not parse as a catalog spec",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no catalog entry with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/catalog/products": {
      "get": {
        "tags": [
          "catalog"
        ],
        "summary": "The product catalogue, with each connection recipe's form fields resolved.",
        "operationId": "get_api_catalog_products",
        "responses": {
          "200": {
            "description": "every built-in product, its recipes resolved against what this deployment publishes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "products"
                      ],
                      "properties": {
                        "products": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ProductView"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/catalog/sensor-profiles": {
      "get": {
        "tags": [
          "catalog"
        ],
        "summary": "The sensor profiles a placement can name — what catalog resolves when POST /api/assets registers a sensor.",
        "description": "Everything parametric lives here rather than on the form: detection-probability\nrolloff, measurement sigmas, field of view, maximum range are facts about the\nsensor model, and an operator typing them is an operator inventing them.",
        "operationId": "get_api_catalog_sensor_profiles",
        "responses": {
          "200": {
            "description": "the built-in profiles, one entry per placeable sensor model",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": [
                  {
                    "id": "ground-radar-360",
                    "modality": "radar",
                    "bearing_only": false,
                    "max_range_m": 3000.0,
                    "fov_deg": 360.0
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/api/catalogue/types": {
      "get": {
        "tags": [
          "catalog"
        ],
        "summary": "The catalogue of types (designs/asset-onboarding, section 3b): 105 types with their per-domain specification, the protocol families each speaks, whether this build can carry it, and the schema that says what each domain asks.",
        "description": "Compiled in, so the Add screen cannot come up empty.",
        "operationId": "get_api_catalogue_types",
        "responses": {
          "200": {
            "description": "every type, the protocol families, and the per-domain spec schema",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The whole catalogue as `GET /api/catalogue/types` serves it.",
                      "required": [
                        "generated",
                        "types",
                        "protocol_families",
                        "schema"
                      ],
                      "properties": {
                        "generated": {
                          "type": "string"
                        },
                        "protocol_families": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ProtocolFamily"
                          }
                        },
                        "schema": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/DomainSpecSchema"
                          }
                        },
                        "types": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CatalogueType"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/decision-config": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "Resolve the effective workspace: the caller's, else the system workspace.",
        "description": "`GET /api/decision-config` — the policy in force.\n\nA workspace that has never saved one gets the defaults, which reproduce the\npreviously compiled-in thresholds exactly. There is no \"unconfigured\" state to\nhandle in the UI: the policy always exists.",
        "operationId": "get_api_decision_config",
        "responses": {
          "200": {
            "description": "the policy in force; a workspace that has never saved one gets the defaults",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DecisionConfig"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/decision-config/autonomy": {
      "put": {
        "tags": [
          "settings"
        ],
        "summary": "Move the line.",
        "description": "The body is the [`AutonomyPolicy`] alone. `version` is the server's and is\nbumped on save, so the plan authored next carries a version that says which\ndial it ran under. The deployment's `envelope` is not settable here: it is the\nceiling, and a ceiling an operator can raise is not one.\n\nTakes effect on the next tick — the runtime is refreshed before the response\nreturns, so a saved line is never a line the gate has not got yet.",
        "operationId": "put_api_decision_config_autonomy",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AutonomyPolicy"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the saved policy, carrying the version the server assigned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DecisionConfig"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the policy cannot be stored as sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/discoveries": {
      "get": {
        "tags": [
          "assets"
        ],
        "summary": "What has announced itself and is not registered yet.",
        "description": "Read-only, on purpose. Adopting one of these is `POST /api/assets` with a\n`connection` naming its address, so there is exactly one write path into the\nregistry.",
        "operationId": "get_api_discoveries",
        "responses": {
          "200": {
            "description": "candidates heard on a link, none of them registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DiscoveredAsset"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "no X-Workspace-Id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/distri/config": {
      "get": {
        "tags": [
          "copilot"
        ],
        "summary": "Where distri lives, for a console that has to reach it directly.",
        "description": "Answers without calling distri at all, so it still says where the copilot is\nwhen the copilot is down.",
        "operationId": "get_api_distri_config",
        "responses": {
          "200": {
            "description": "the distri endpoint this deployment is configured against",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "Where distri is, and which workspace to address it as. Served separately from\nthe token so the console can still say *the copilot lives there* when minting\nfails: the two are different failures with different fixes.",
                      "required": [
                        "base_url",
                        "agent_id",
                        "token_auth"
                      ],
                      "properties": {
                        "agent_id": {
                          "type": "string",
                          "description": "Which agent the console opens. Served rather than built in, so a\ndeployment can change it without rebuilding the console."
                        },
                        "base_url": {
                          "type": "string",
                          "description": "The distri API root the BROWSER should call, no trailing slash. Not\nnecessarily the one this server calls: see [`browser_base_url`]."
                        },
                        "token_auth": {
                          "type": "boolean",
                          "description": "Whether the console must mint a token before talking to distri. False\nagainst the OSS server, which authenticates nothing."
                        },
                        "workspace_id": {
                          "type": "string",
                          "description": "The workspace every request is made against, when this deployment names one.",
                          "nullable": true
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "this deployment has no copilot: `code: not_configured`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/distri/token": {
      "post": {
        "tags": [
          "copilot"
        ],
        "summary": "Mint a short-lived distri access token for the calling operator.",
        "description": "The token is workspace-scoped, not per-operator: distri knows this\ndeployment, not the person at the console. What the login buys is the\nrefusal — an anonymous caller gets a 401 rather than a credential.",
        "operationId": "post_api_distri_token",
        "responses": {
          "200": {
            "description": "a short-lived access token and the refresh token that renews it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "A minted browser credential. Short-lived by construction: this is what the\nraw `DISTRI_API_KEY` was standing in for, and the difference is that this one\nexpires.",
                      "required": [
                        "access_token",
                        "refresh_token",
                        "expires_at"
                      ],
                      "properties": {
                        "access_token": {
                          "type": "string",
                          "description": "The bearer the browser calls distri with."
                        },
                        "expires_at": {
                          "type": "integer",
                          "format": "int64",
                          "description": "When the access token dies, Unix seconds."
                        },
                        "refresh_token": {
                          "type": "string",
                          "description": "Renews the access token without another round trip through us."
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "distri refused to mint: `code: upstream`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "this deployment has no copilot: `code: not_configured`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/doctrine": {
      "get": {
        "tags": [
          "doctrine"
        ],
        "summary": "The whole Doctrine section, hydrated once.",
        "operationId": "get_api_doctrine",
        "responses": {
          "200": {
            "description": "the rule catalogue, the settings registry with its default trace, the planning profiles, and the live firing counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "Everything the Doctrine panes need, in one hydration.",
                      "required": [
                        "rule_doctrine",
                        "rules",
                        "settings_registry",
                        "settings_touched_by",
                        "default_trace",
                        "planning_profiles",
                        "telemetry"
                      ],
                      "properties": {
                        "default_trace": {
                          "$ref": "#/components/schemas/ResolutionTrace"
                        },
                        "planning_profiles": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PlanningProfile"
                          }
                        },
                        "rule_doctrine": {
                          "$ref": "#/components/schemas/RuleDoctrine"
                        },
                        "rules": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/RuleRow"
                          }
                        },
                        "settings_registry": {
                          "$ref": "#/components/schemas/SettingsRegistry"
                        },
                        "settings_touched_by": {
                          "type": "object",
                          "description": "Which rules test or set each key, computed from the rule set.\n\nA threshold matters only because something reads it. `engage.raid_threshold`\nis the whole timing of the site's saturation response — `saturation` tests\nit — and the screen could not say so, which made moving it a guess.",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/SettingReader"
                            }
                          }
                        },
                        "telemetry": {
                          "$ref": "#/components/schemas/RuleTelemetry"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/doctrine/export": {
      "get": {
        "tags": [
          "doctrine"
        ],
        "summary": "The workspace's doctrine as a doctrine.v1 YAML document.",
        "description": "The operator's **stored** rules (not the shipped catalogue) plus the constants\nthey reference, framed as one file for version control or to seed another\ndeployment. Served as `application/yaml`, not the JSON envelope, because the\npoint is a file an operator can read and a reviewer can diff.",
        "operationId": "get_api_doctrine_export",
        "responses": {
          "200": {
            "description": "the doctrine.v1 YAML document",
            "content": {
              "application/yaml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "the stored rules cannot be framed as a doctrine.v1 document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/doctrine/import": {
      "post": {
        "tags": [
          "doctrine"
        ],
        "summary": "Replace the workspace's rules from a doctrine.v1 document.",
        "description": "The body is the raw YAML. It is validated **twice**, and both must pass: first\n[`DoctrineDoc::from_yaml`] enforces the three format rules (lossless schema,\nconstants that travel, no forged system rule), then the ordinary rule-set save\nre-runs the author-time checks and the locked-rule protection. An import is not\na privileged path: it may not author anything a hand-written `PUT /api/rules`\ncould not, and it goes through the same `rules::replace_for` to guarantee it.",
        "operationId": "post_api_doctrine_import",
        "requestBody": {
          "content": {
            "application/yaml": {
              "schema": {
                "type": "string"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the imported set, now in force, with its new version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ruleset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "malformed document: not doctrine.v1, a constant that does not travel, a forged system rule, or a rule the author-time checks refuse",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/doctrine/telemetry": {
      "get": {
        "tags": [
          "doctrine"
        ],
        "summary": "The firing counts alone.",
        "description": "The counts are derived from the authored plans' own actions, which are already\nstamped with the rule that produced them. There is no second write path that\ncould disagree with the plan.",
        "operationId": "get_api_doctrine_telemetry",
        "responses": {
          "200": {
            "description": "firing counts for this run, seeded so an unfired rule reads 0",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/RuleTelemetry"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/events": {
      "get": {
        "tags": [
          "events"
        ],
        "summary": "Query the event log: kinds, time window, track or run scope, latest-fold.",
        "description": "The one generic read surface over the store. `?kind=track&fold=latest` yields the\ncurrent picture (latest state per `track_id`); `?kind=alert` reads alerts;\n`?kind=track,alert&since=<iso>&until=<iso>` replays history. Results are wire\nevent bodies in chronological (ascending `t`) order; with no time window, the\nnewest `limit` events per kind.",
        "operationId": "get_api_events",
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "description": "Comma-separated kinds, e.g. `track,alert`. Omit for all kinds.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "Exclusive lower time bound (`t > since`).",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "until",
            "in": "query",
            "description": "Exclusive upper time bound (`t < until`).",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "track_id",
            "in": "query",
            "description": "Scope to a single fused track.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "run_id",
            "in": "query",
            "description": "Scope to a single simulation run (replay reads a run's `track.v1` stream).\nOmit for the live/all-events view.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid",
              "nullable": true
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Newest events kept per kind (default 500).",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "fold",
            "in": "query",
            "description": "`latest` folds Track events to the latest state per `track_id`.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "matching events as wire JSON bodies, ascending by time (or one latest state per track when fold=latest)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "an unknown kind token in the kind list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "the event store query failed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/fusion/explain": {
      "get": {
        "tags": [
          "fusion"
        ],
        "summary": "The newest explained scan, or with ?scans=N the newest N scans as a window.",
        "description": "The newest scan of a coalesced window is the LAST scan the tracker stepped.\nA batch is cut into scans in time order and every positionless return goes\nin the first (`dome-core/src/fusion/scan.rs`), so a sensor reporting below\nthe fastest source's rate is never in the newest scan: on the simulator that\nscan holds the two autopilot telemetry plots and a coast for the intruder,\nevery window. A reader that draws from a hold window asks for one.",
        "operationId": "get_api_fusion_explain",
        "parameters": [
          {
            "name": "scans",
            "in": "query",
            "description": "How many of the newest scans to return, oldest first, as a\n[`FusionExplainWindow`]. Absent: the newest scan alone, as a\n[`FusionExplain`]. Clamped to the ring's depth (900 scans).\n\nAsk for a window when the reader folds one. The newest scan of a\ncoalesced window is the LAST scan the tracker stepped, and a batch's\npositionless returns all go in its first scan, so a source reporting\nbelow the fastest source's rate (a DF bearing beside autopilot\ntelemetry) is never in the newest scan: that scan is a coast for it.",
            "required": false,
            "schema": {
              "type": "integer",
              "nullable": true,
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the tracker's newest explained scan; with `scans=N`, a window of the newest N scans oldest first. The newest scan of a coalesced window is the last scan stepped, which is a coast for any sensor reporting below the fastest source's rate, so a reader folding a hold window asks for `scans`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LatestExplainReply"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no scan has been explained since the pipeline last reset",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/fusion/health": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Liveness plus the degraded flag the console reads.",
        "description": "`GET /api/fusion/health` — the tracker's self-report.\n\nEvery field is computable **without ground truth**, so this is as valid against\nreal sensors as against the simulator. It exists because the identity defect\nthis system shipped was invisible for weeks: the console showed *what* had been\ntracked, never *how well the tracker was tracking*.",
        "operationId": "get_api_fusion_health",
        "responses": {
          "200": {
            "description": "the tracker's current self-report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FusionHealth"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Lightweight liveness probe: whether the fusion pipeline has produced a recent track.",
        "operationId": "get_api_health",
        "responses": {
          "200": {
            "description": "liveness of the fusion pipeline",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "pipeline_alive": true
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/identities": {
      "get": {
        "tags": [
          "identities"
        ],
        "summary": "Every identity claim in the workspace, newest first.",
        "description": "A claim is one sentence: this serial is ours (or theirs), asserted by this\noperator, in this role. It is not a tasking and it commands nothing.",
        "operationId": "get_api_identities",
        "responses": {
          "200": {
            "description": "every identity claim in the workspace, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/IdentityClaim"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "identities"
        ],
        "summary": "Claim an identity for a serial.",
        "description": "The fusion whitelist picks the claim up on the next cycle, so matching tracks reclassify without a second call.",
        "operationId": "post_api_identities",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "asset_id",
                  "affiliation"
                ],
                "properties": {
                  "active": {
                    "type": "boolean"
                  },
                  "affiliation": {
                    "type": "string"
                  },
                  "asset_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "operator": {
                    "type": "string",
                    "nullable": true
                  },
                  "role": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored claim",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IdentityClaim"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "an affiliation that is not friend, hostile, neutral or unknown",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/identities/confirm": {
      "post": {
        "tags": [
          "identities"
        ],
        "summary": "Confirm an affiliation for the platform behind a Remote-ID serial.",
        "operationId": "post_api_identities_confirm",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The operator \"confirm\" action: assert an affiliation for the platform behind a\nRemote-ID serial (e.g. mark friendly). Ensures the asset, supersedes the current\nassignment, and records the confirmed one.",
                "required": [
                  "serial",
                  "affiliation"
                ],
                "properties": {
                  "affiliation": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "nullable": true
                  },
                  "operator": {
                    "type": "string",
                    "nullable": true
                  },
                  "role": {
                    "type": "string",
                    "nullable": true
                  },
                  "serial": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the confirmed claim, superseding any active one for this serial",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IdentityClaim"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "an empty serial, or an affiliation that is not friend, hostile, neutral or unknown",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/identities/{id}": {
      "get": {
        "tags": [
          "identities"
        ],
        "summary": "One identity claim by id.",
        "operationId": "get_api_identities_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "identity claim id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the claim",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IdentityClaim"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no claim with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "identities"
        ],
        "summary": "Update a claim's affiliation, operator, role or active flag.",
        "operationId": "post_api_identities_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "identity claim id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean",
                    "nullable": true
                  },
                  "affiliation": {
                    "type": "string",
                    "nullable": true
                  },
                  "operator": {
                    "type": "string",
                    "nullable": true
                  },
                  "role": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the updated claim",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IdentityClaim"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "an affiliation that is not friend, hostile, neutral or unknown",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no claim with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/influence": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "The influence in force (hydration; changes ride the stream as influence.v1).",
        "responses": {
          "200": {
            "description": "the influence in force: weight overrides, pinned pairings, held assets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "influence"
                      ],
                      "properties": {
                        "influence": {
                          "$ref": "#/components/schemas/PlanInfluence"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "get_api_influence"
      },
      "put": {
        "tags": [
          "settings"
        ],
        "summary": "Accept an influence, or remove it with an empty one.",
        "operationId": "put_api_influence",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "accepted_by"
                ],
                "properties": {
                  "accepted_by": {
                    "type": "string",
                    "description": "The person accepting. Required: an influence with no acceptor is a\nmachine act, and there is no such thing here."
                  },
                  "holds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "uniqueItems": true
                  },
                  "model": {
                    "type": "string",
                    "description": "The model that suggested the accepted values, when a model did.",
                    "nullable": true
                  },
                  "pins": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/PinnedPair"
                    }
                  },
                  "sitrep_id": {
                    "type": "string",
                    "description": "The console sitrep whose suggestion this accepts, when it accepts one.",
                    "nullable": true
                  },
                  "weights": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "number",
                      "format": "double"
                    }
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the accepted influence, recorded and now in force; one replan has been triggered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "influence"
                      ],
                      "properties": {
                        "influence": {
                          "$ref": "#/components/schemas/PlanInfluence"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "no accepted_by, a pin on a threat not on the board or an unknown asset, a pin the geometry has already lost, or a hold on an unknown asset",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/integrations": {
      "get": {
        "tags": [
          "integrations"
        ],
        "summary": "List every protocol and what this environment decided about it.",
        "operationId": "get_api_integrations",
        "responses": {
          "200": {
            "description": "every compiled-in protocol, decorated with live listener state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/IntegrationStatus"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/integrations/{kind}": {
      "post": {
        "tags": [
          "integrations"
        ],
        "summary": "Enable, gate control, or configure one protocol.",
        "operationId": "post_api_integrations_kind",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "description": "protocol kind, e.g. `mavlink`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IntegrationPatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the row after the write, decorated with what actually happened",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IntegrationStatus"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "no such protocol, or the write was refused: every refusal is returned at once, joined in one message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/integrations/{kind}/links/{name}/attach": {
      "post": {
        "tags": [
          "integrations"
        ],
        "summary": "Attach this workspace to one of a protocol's physical links (spec 2026-09-07 §4).",
        "description": "The link is a line under `links:` in `dome.yaml`; a name the file does not declare is a 404 naming what it does, and so is a protocol this build does not have.",
        "operationId": "post_api_integrations_kind_links_name_attach",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "description": "protocol kind, e.g. `mavlink`",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "description": "the link's name in dome.yaml, e.g. `GCS`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the row after the write, with `links` saying what this workspace is attached to",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IntegrationStatus"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no such protocol, or dome.yaml declares no such link for it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/integrations/{kind}/links/{name}/detach": {
      "post": {
        "tags": [
          "integrations"
        ],
        "summary": "Detach this workspace from one of a protocol's physical links.",
        "description": "Its rows on that link read No link and its Discovered loses the socket's devices; the socket stays open for everybody else.",
        "operationId": "post_api_integrations_kind_links_name_detach",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "description": "protocol kind, e.g. `mavlink`",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "description": "the link's name in dome.yaml, e.g. `GCS`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the row after the write",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IntegrationStatus"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no such protocol, or dome.yaml declares no such link for it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/integrations/{kind}/refresh": {
      "post": {
        "tags": [
          "integrations"
        ],
        "summary": "Ask again, because somebody said so.",
        "description": "The one declared exception in `live-data-flow.md`, and it is an operator's act\nevery time. A vendor cloud tells us nothing between requests, so there is no\nstream whose silence means anything — and the honest answer to *is it alive* is\n*this is what it said when we last asked*, with a button to ask again. Putting a\ntimer here instead would be a poll wearing a status chip.\n\nA protocol that streams to us is refused: there is nothing to ask, and pressing\nthis on MAVLink would suggest the row needed help it does not need.",
        "operationId": "post_api_integrations_kind_refresh",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "description": "protocol kind, e.g. `dji`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the row, with `last asked` reset to now",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IntegrationStatus"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "no such protocol, or the protocol streams to us and has nothing to be asked for",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/integrations/{kind}/rotate": {
      "post": {
        "tags": [
          "integrations"
        ],
        "summary": "Issue the shared broker account's password (#305), returned exactly once.",
        "description": "One account covers every device under the vendor workspace, because the vendor\nfixes the topic and the serial inside it is the identity. The username survives\na rotate, so an installer changes one field on each device rather than\nrecommissioning it; the old password stops working the moment this returns.\n\nA protocol whose devices get their own subtree is refused: there each device is\nissued its own credential when it is added, and a shared one would undo that.",
        "operationId": "post_api_integrations_kind_rotate",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "description": "protocol kind, e.g. `dji`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the shared account with its new password, returned exactly once",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LinkIngest"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "no such protocol, the protocol issues per-device credentials, or no secret key is configured to mint one",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/integrations/{kind}/verify": {
      "post": {
        "tags": [
          "integrations"
        ],
        "summary": "Test a credential before it is trusted (#304).",
        "description": "One cheap read against whatever the far end offers, classified into the failure\nan operator can act on: nothing answered, the handshake failed, it answered and\nsaid no, or the set is not complete enough to try. The answer is stored with\nthe moment it ran so the page can say *last verified 2 h ago*.\n\n**It reads.** No config is written, nothing is enabled, and there is no timer\nbehind it. A protocol with no credential is refused by name rather than given a\nbutton that proves nothing.",
        "operationId": "post_api_integrations_kind_verify",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "description": "protocol kind, e.g. `dji`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "what the test proved, stored with the moment it ran",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/VerifyResult"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "no such protocol, or the credential set is not complete enough to try",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/invitations/accept": {
      "post": {
        "tags": [
          "workspaces"
        ],
        "summary": "Accept an invitation.",
        "description": "The token proves the invitation, the session proves the person. Whoever is\nlogged in joins, which is why an invitation mail forwarded to a colleague adds\nthe colleague and not the addressee.",
        "operationId": "post_api_invitations_accept",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AcceptRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the workspace just joined",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Workspace"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no invitation with that token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "already accepted, or expired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          }
        ]
      }
    },
    "/api/keys": {
      "get": {
        "tags": [
          "keys"
        ],
        "summary": "The live keys in this workspace, newest first.",
        "description": "Revoked keys are not listed. A revoked key is not something an operator can\nact on, and a list that grows forever is one nobody reads.",
        "operationId": "get_api_keys",
        "responses": {
          "200": {
            "description": "live keys, newest first, without their secrets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "no X-Workspace-Id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "no credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          },
          {
            "api_key": []
          }
        ]
      },
      "post": {
        "tags": [
          "keys"
        ],
        "summary": "Mint a key. The secret is in this response and nowhere else afterwards.",
        "operationId": "post_api_keys",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MintRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "the key, including the one and only copy of its secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MintedApiKey"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "no name, or a negative lifetime",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "this needs the admin or owner role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          }
        ]
      }
    },
    "/api/keys/{id}": {
      "delete": {
        "tags": [
          "keys"
        ],
        "summary": "Revoke a key. It stops authenticating on the next request.",
        "operationId": "delete_api_keys_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "key id, `key_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "revoked"
          },
          "403": {
            "description": "this needs the admin or owner role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no live key with that id in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          }
        ]
      }
    },
    "/api/links": {
      "get": {
        "tags": [
          "links"
        ],
        "summary": "The deployment's physical links, with this workspace's attachment on each.",
        "operationId": "get_api_links",
        "responses": {
          "200": {
            "description": "every link dome.yaml declares, in the file's order, with whether this workspace is attached, whether the socket is open, and what it carries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PhysicalLink"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/links/discovery": {
      "get": {
        "tags": [
          "links"
        ],
        "summary": "Read the discovery switch and every declared link's effective policy.",
        "operationId": "get_api_links_discovery",
        "responses": {
          "200": {
            "description": "the deployment-wide switch and each declared link's policy as it is in force",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "enabled",
                        "links"
                      ],
                      "properties": {
                        "enabled": {
                          "type": "boolean",
                          "description": "The workspace switch."
                        },
                        "links": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/LinkPolicy"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "links"
        ],
        "summary": "Turn discovery on or off for the whole workspace.",
        "description": "The per-link policy is untouched: turning the switch back on restores exactly\nwhat each link said, rather than requiring every one of them to be re-set.",
        "operationId": "put_api_links_discovery",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the view after the switch, exactly what a subsequent GET would say",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "enabled",
                        "links"
                      ],
                      "properties": {
                        "enabled": {
                          "type": "boolean",
                          "description": "The workspace switch."
                        },
                        "links": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/LinkPolicy"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/links/ingest": {
      "get": {
        "tags": [
          "links"
        ],
        "summary": "What this deployment publishes for something to connect to.",
        "operationId": "get_api_links_ingest",
        "responses": {
          "200": {
            "description": "the addresses this deployment serves for inbound links",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/IngestEndpoint"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/me": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Who the caller is, where they may work, and which workspace this request was made against.",
        "description": "The one call a client makes first. It is also how a console discovers the\nworkspace to put in `X-Workspace-Id`, which is why it does not itself require\none.",
        "operationId": "get_api_me",
        "responses": {
          "200": {
            "description": "the caller, their workspaces, and the current one",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Identity"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "no credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          },
          {
            "api_key": []
          }
        ]
      }
    },
    "/api/meta": {
      "get": {
        "tags": [
          "regions"
        ],
        "summary": "The bootstrap payload: default region, all regions, assets, assignments, catalogue.",
        "operationId": "get_api_meta",
        "responses": {
          "200": {
            "description": "everything the client needs on first load; a running exercise's region wins over the workspace default",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Meta"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/picture": {
      "get": {
        "tags": [
          "picture"
        ],
        "summary": "The whole picture, once.",
        "description": "Thereafter the client applies SSE deltas and never re-fetches this on a timer while the stream is healthy; the degraded-mode fallback poll is the only recurring caller.",
        "operationId": "get_api_picture",
        "responses": {
          "200": {
            "description": "the whole operating picture at one generation: tracks, threats, plan, candidates, estimation, sensors, fleet, region, sim state, autonomy mode, fusion health, integrations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Picture"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/planning-profiles": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "The shipped three, plus whatever the site added.",
        "operationId": "get_api_planning_profiles",
        "responses": {
          "200": {
            "description": "the profile set in force, with the slate that says which plans the site authors",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProfileSet"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "settings"
        ],
        "summary": "Replace the set, and optionally the slate.",
        "operationId": "put_api_planning_profiles",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "What a `PUT` carries. `slate` is optional: editing a profile must not silently\nchange which plans the site authors, and vice versa.",
                "required": [
                  "profiles"
                ],
                "properties": {
                  "profiles": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/PlanningProfile"
                    }
                  },
                  "slate": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/PlanningSlate"
                      }
                    ],
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the saved set, and the slate if one was sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProfileSet"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "a profile or slate the validator refuses",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/plans": {
      "get": {
        "tags": [
          "plans"
        ],
        "summary": "The authored candidate set (0–3 plans), each carrying its strategy label, predicted outcome, and LLM review.",
        "responses": {
          "200": {
            "description": "the authored candidate set, 0 to 3 plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Plan"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "get_api_plans"
      }
    },
    "/api/plans/run": {
      "post": {
        "tags": [
          "plans"
        ],
        "summary": "{ \"variation_count\"?: 1..=3 } — the operator's PLAN button (#61).",
        "description": "Runs EXACTLY the authoring path the autonomous cadence runs, bypassing the cadence floor; the in-flight state rides the engagement picture (`EngagementState.plan_run`), so a clicked run and an autonomous run are indistinguishable to the UI and two clients cannot start overlapping passes.",
        "operationId": "post_api_plans_run",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The optional body of `POST /api/engagement/plan/run`.",
                "properties": {
                  "variation_count": {
                    "type": "integer",
                    "format": "int32",
                    "description": "Run only the first `n` (1..=3) built-in strategies (the plan-run dialog's\nvariation control). Omitted ⇒ the full built-in set.",
                    "nullable": true,
                    "minimum": 0
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "whether the runtime accepted the request; the body is optional and an omitted variation_count runs the full built-in strategy set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "accepted"
                      ],
                      "properties": {
                        "accepted": {
                          "type": "boolean",
                          "description": "`true` once the runtime accepted the request (a pass is queued or already\nrunning — server-side single-flight ensures they never overlap)."
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/plans/{id}/approve": {
      "post": {
        "tags": [
          "plans"
        ],
        "summary": "The operator picks THIS plan: its assignments are adopted as operator-approved tasks and the transport delivers them to the drones.",
        "description": "Unchosen candidates are superseded. `404` when the id is no longer in the candidate set (a replan superseded it) — the client refetches and decides on the fresh set, never on a stale card.\n\n`409` when every assignment sits above this deployment's ceiling. A press is\nconsent, not authority: it answers the ladder's `Asks`, which is doctrine\nasking for exactly this person, and it cannot lift a ceiling — that is\ndeployment authority, and no operator here holds it.",
        "operationId": "post_api_plans_id_approve",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "candidate plan id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "description": "optional; `only` adopts a subset of the plan's assignments",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApproveReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the approved plan; its assignments are now operator-approved tasks in delivery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Plan"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "a partial approval kept nothing: that is a rejection, and /reject is where it belongs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "the plan is not in the current candidate set, a replan superseded it: refetch and decide on the fresh set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "the release table withholds an assignment's effect in the posture in force, or a partial approval named an assignment the plan no longer carries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/plans/{id}/reject": {
      "post": {
        "tags": [
          "plans"
        ],
        "summary": "Decline a candidate.",
        "description": "Rejecting the adopted (executing) plan withdraws approval and recalls the drones.",
        "operationId": "post_api_plans_id_reject",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "candidate plan id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the rejected plan; if it was executing, approval is withdrawn and the drones recalled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Plan"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "the plan is not in the current candidate set, a replan superseded it: refetch and decide on the fresh set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "the posture in force withholds the decision; the response says which effect and which gate",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/regions": {
      "get": {
        "tags": [
          "regions"
        ],
        "summary": "Resolve the effective workspace: the caller's if supplied, else the system workspace (dev/tokenless default).",
        "responses": {
          "200": {
            "description": "every region in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RegionSummary"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "get_api_regions"
      },
      "post": {
        "tags": [
          "regions"
        ],
        "summary": "Create a region.",
        "operationId": "post_api_regions",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegionInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored region, with its server-assigned id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/RegionSummary"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/regions/{id}": {
      "put": {
        "tags": [
          "regions"
        ],
        "summary": "Replace a region's fields.",
        "operationId": "put_api_regions_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "region id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegionInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the updated region",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/RegionSummary"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no region with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "regions"
        ],
        "summary": "Delete a region.",
        "operationId": "delete_api_regions_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "region id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the region is gone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Deleted"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no region with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/rules": {
      "get": {
        "tags": [
          "rules"
        ],
        "summary": "The set in force.",
        "description": "A workspace that has never saved one gets the shipped catalogue at version 0.\nThere is no unconfigured state for the UI to handle.",
        "operationId": "get_api_rules",
        "responses": {
          "200": {
            "description": "the set in force; a workspace that has never saved one gets the shipped catalogue at version 0",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ruleset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "rules"
        ],
        "summary": "Append one rule.",
        "operationId": "post_api_rules",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RuleRow"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the set with the rule appended, one version bump on",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ruleset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "a rule the validator refuses, an id already in the set, or a doctrine-locked id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "rules"
        ],
        "summary": "Replace the whole set.",
        "description": "The bulk save: staging several rules in the authoring page and saving them\ntogether is one call and one version bump. `version` in the body is ignored;\nthe server owns it.",
        "operationId": "put_api_rules",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RuleRow"
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the saved set, one version bump on",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ruleset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "a rule the validator refuses, two rules sharing an id, or the locked rule edited or missing from the set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/rules/library": {
      "get": {
        "tags": [
          "rules"
        ],
        "summary": "The importable rulesets.",
        "description": "Registered before `/rules/{id}` so actix does not read `library` as a rule id.\nStatic: a pack is a starting point, not state, and an operator who imports one\nowns the copy from that moment.",
        "operationId": "get_api_rules_library",
        "responses": {
          "200": {
            "description": "the shipped rule packs, importable as starting points",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RulePack"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/rules/validate": {
      "post": {
        "tags": [
          "rules"
        ],
        "summary": "The verdict, without saving.",
        "description": "Registered before `/rules/{id}` so actix does not read `validate` as a rule id.",
        "operationId": "post_api_rules_validate",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RuleRow"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the verdict: rejections when invalid, overlaps when the rule is new",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Verdict"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/rules/{id}": {
      "delete": {
        "tags": [
          "rules"
        ],
        "summary": "Remove one rule.",
        "operationId": "delete_api_rules_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "rule id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the set without the rule, one version bump on",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ruleset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the doctrine-locked rule may not be removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no rule with this id in the set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "rules"
        ],
        "summary": "Enable, disable, reorder, or replace one rule.",
        "description": "Toggling a rule is a PATCH rather than a full-set save, so the state dot on the\nlist is one call and needs no Save button beside it.",
        "operationId": "patch_api_rules_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "rule id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RulePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the set with the rule changed, one version bump on",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Ruleset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "a change the validator refuses, an edit that changes the rule's id, or the doctrine-locked rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no rule with this id in the set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/scenarios": {
      "get": {
        "tags": [
          "scenarios"
        ],
        "summary": "List the workspace's scenarios.",
        "operationId": "get_api_scenarios",
        "responses": {
          "200": {
            "description": "every scenario in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ScenarioRow"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "scenarios"
        ],
        "summary": "Create a scenario from a name and a spec.",
        "operationId": "post_api_scenarios",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Resolve the effective workspace: the caller's if the extractor supplied one,\notherwise the system workspace (dev/tokenless default).",
                "required": [
                  "name",
                  "spec"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "spec": {
                    "type": "object",
                    "description": "The raw [`crate::Scenario`] JSON. Validated on create."
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored scenario, with its server-assigned id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ScenarioRow"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the spec does not deserialise as a scenario",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/scenarios/{id}": {
      "get": {
        "tags": [
          "scenarios"
        ],
        "summary": "Fetch one scenario by id.",
        "operationId": "get_api_scenarios_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "scenario id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the scenario row, spec included",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ScenarioRow"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no scenario with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "scenarios"
        ],
        "summary": "Update a scenario's name, spec, or both.",
        "operationId": "put_api_scenarios_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "scenario id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScenarioChangeset"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the updated scenario",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ScenarioRow"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the new spec does not deserialise as a scenario",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no scenario with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "scenarios"
        ],
        "summary": "Delete a scenario.",
        "operationId": "delete_api_scenarios_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "scenario id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the scenario is gone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "404": {
            "description": "no scenario with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/secrets": {
      "get": {
        "tags": [
          "secrets"
        ],
        "summary": "Which secrets exist. Names only.",
        "operationId": "get_api_secrets",
        "responses": {
          "200": {
            "description": "every stored secret's name, hint and timestamp, plus whether writes will work",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "secrets",
                        "configured"
                      ],
                      "properties": {
                        "configured": {
                          "type": "boolean",
                          "description": "Whether writes will work at all, so the console can say why a field is\ndisabled instead of failing when the operator presses save."
                        },
                        "secrets": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/SecretInfo"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/secrets/{name}": {
      "put": {
        "tags": [
          "secrets"
        ],
        "summary": "Store a value under a name, replacing any existing one.",
        "operationId": "put_api_secrets_name",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "description": "the secret's name, referenced as `secret://<name>`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "value"
                ],
                "properties": {
                  "hint": {
                    "type": "string"
                  },
                  "value": {
                    "type": "string",
                    "description": "The value. The only place it appears in this file."
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored secret's name, hint and reference; never its value",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SecretInfo"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the value is empty",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "no encryption key is configured: set DOME_SECRET_KEY to a base64 32-byte key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "secrets"
        ],
        "summary": "Forget a secret.",
        "operationId": "delete_api_secrets_name",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "description": "the secret's name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the secret is gone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Deleted"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no secret with this name in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/settings/export": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "Export everything this workspace is configured with, as one document.",
        "operationId": "get_api_settings_export",
        "responses": {
          "200": {
            "description": "the workspace's assets and links as one settings document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AssetManifest"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/settings/import": {
      "post": {
        "tags": [
          "settings"
        ],
        "summary": "Import a settings document: validate it, and apply it unless dry_run.",
        "operationId": "post_api_settings_import",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AssetManifest"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "dry_run": {
                        "type": "boolean",
                        "description": "Check it and change nothing. What the console's import preview calls."
                      }
                    }
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "every validation problem at once; applied only when the document is clean and dry_run is false",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "ok",
                        "errors",
                        "missing_secrets",
                        "assets",
                        "applied"
                      ],
                      "properties": {
                        "applied": {
                          "type": "boolean",
                          "description": "Whether anything was written."
                        },
                        "assets": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Assets that would be created. Empty on a failed validation."
                        },
                        "errors": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Every problem, not just the first."
                        },
                        "issued": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/LinkIngest"
                          },
                          "description": "**Credentials issued by this apply, returned exactly once.**\n\nA link's ingest password exists so a sensor can authenticate as that link\nand nothing else. It is shown here, at the moment it is minted, and never\nagain — a password a route will hand back on demand is one that ends up in a\nbrowser cache, a proxy log and a screenshot."
                        },
                        "missing_secrets": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "`secret://` names the document needs that the store does not hold. Separate\nfrom `errors` because it is fixable without touching the file."
                        },
                        "ok": {
                          "type": "boolean"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/signals": {
      "post": {
        "tags": [
          "signals"
        ],
        "summary": "Ingest one tick of observations into the caller's workspace.",
        "operationId": "post_api_signals",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignalBatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "how many observations were accepted for fusion",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Accepted"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "an empty batch, or one over the size limit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "no credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "this workspace role is read-only",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session": []
          }
        ]
      }
    },
    "/api/sim/pause": {
      "post": {
        "tags": [
          "simulation"
        ],
        "summary": "Hold or release the running exercise without ending its run.",
        "operationId": "post_api_sim_pause",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "paused"
                ],
                "properties": {
                  "paused": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the workspace state, now Paused or Running",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkspaceSimState"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "nothing is running",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sim/runs": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "GET /api/simulation/runs — a workspace's runs, newest-first.",
        "operationId": "get_api_sim_runs",
        "parameters": [
          {
            "name": "scenario_id",
            "in": "query",
            "description": "Filter to runs of one scenario.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid",
              "nullable": true
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the workspace's runs, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SimulationRun"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sim/runs/{id}": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "GET /api/simulation/runs/{id} — one run record.",
        "operationId": "get_api_sim_runs_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "run id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the run record",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SimulationRun"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no run with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "runs"
        ],
        "summary": "DELETE /api/simulation/runs/{id} — remove the run record and its events.",
        "operationId": "delete_api_sim_runs_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "run id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the run and its events are gone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "404": {
            "description": "no run with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sim/runs/{id}/events": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "GET /api/simulation/runs/{id}/events — the run's persisted event stream, chronological.",
        "description": "Drives replay (default `kind=track`) and overlays.",
        "operationId": "get_api_sim_runs_id_events",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "run id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "description": "Comma-separated kinds, e.g. `track,alert`. Defaults to `track` (replay).",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the run's events as wire JSON bodies, ascending by time",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "an unknown kind token in the kind list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no run with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "the event store query failed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sim/simulators": {
      "get": {
        "tags": [
          "simulation"
        ],
        "summary": "What this deployment has, and whether it answers.",
        "description": "Both kinds are always returned. An unconfigured simulator is shown and greyed\nby the console, never hidden: hiding it turns \"nobody set up the GPU box\" into\n\"that does not exist\".",
        "operationId": "get_api_sim_simulators",
        "responses": {
          "200": {
            "description": "both simulator kinds, configured or not, with reachability",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SimulatorStatus"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sim/start": {
      "post": {
        "tags": [
          "simulation"
        ],
        "summary": "Run a scenario in the attached environment.",
        "description": "**One press.** This used to be `load` (spawn and hold) followed by `play`\n(start the clock), preserving a gap in which the geometry could be checked\nbefore anything moved. That gap moved up a level: attaching an ENVIRONMENT is\nnow separate, so by the time this is called the engine is connected and the\nworld is up — and the scenario's geometry is inspected on a map in the console\n*before* anything connects, which is a better place for it than a frozen live\nworld.",
        "operationId": "post_api_sim_start",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scenario_id"
                ],
                "properties": {
                  "region_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "**Run it here instead.** Anchors the simulated world on an operator-chosen\nregion rather than the scenario's stored one, so \"run this over Munich\"\ngenerates tracks over Munich. Carried over from the legacy\n`POST /api/simulation/start`, which this replaced: it was the one thing\nthat path could express and this could not.",
                    "nullable": true
                  },
                  "scenario_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the run is open and the workspace state says Running",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkspaceSimState"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the environment is Live, the stored scenario spec is invalid, or the world refused to bind or start",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no scenario with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sim/state": {
      "get": {
        "tags": [
          "simulation"
        ],
        "summary": "What this workspace is attached to.",
        "description": "Workspace state, not component state. That is the fix for a selection that\nvanished when a modal closed, and it is why two operators on one workspace see\nthe same thing.",
        "operationId": "get_api_sim_state",
        "responses": {
          "200": {
            "description": "the workspace's mode, simulator, loaded scenario and run state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkspaceSimState"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "simulation"
        ],
        "summary": "Switch mode, simulator or map.",
        "description": "**Refuses while something is loaded.** Silently discarding a loaded exercise\nbecause a dropdown moved is worse than a locked control that says why —\nsomebody has a drill set up and a hand on the console.",
        "operationId": "put_api_sim_state",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "mode"
                ],
                "properties": {
                  "mode": {
                    "$ref": "#/components/schemas/Mode"
                  },
                  "simulator": {
                    "$ref": "#/components/schemas/SimulatorKind"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the attached state after the switch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkspaceSimState"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "a run is live and the requested change would swap the environment underneath it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sim/stop": {
      "post": {
        "tags": [
          "simulation"
        ],
        "summary": "End the run and return the environment to how it was.",
        "description": "**The environment stays attached.** Stopping reverts it to its earliest state —\nthe scenario's objects, sensors and tracks go, the pipeline returns to live —\nbut the engine stays connected. Detaching is what changing environment does,\nand doing it here would make every run pay an Unreal reconnection.\n\n**Undoes every part of [`start`].** It shipped undoing one of four, and the\nthree missing ones read as separate bugs: tracks that never left the map after\nSTOP, the previous run's sensors on the rail of the next one, and a stopped\nconsole still refusing to command a real aircraft in the name of an exercise\nthat was over.",
        "operationId": "post_api_sim_stop",
        "responses": {
          "200": {
            "description": "the run is finalised and the environment is back to Idle, still attached",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkspaceSimState"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/strategies": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "List the compiled-in behaviour and solver strategy catalogues, with the generated policies.",
        "operationId": "get_api_strategies",
        "responses": {
          "200": {
            "description": "base behaviour and solver strategies with their declared params, plus the generated policy lists",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The full strategy catalog payload for the UI.",
                      "required": [
                        "behavior",
                        "solver",
                        "behavior_policies",
                        "solver_policies"
                      ],
                      "properties": {
                        "behavior": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/StrategyInfo"
                          },
                          "description": "Base behavior strategies (with declared params) — the drone `behavior` picker."
                        },
                        "behavior_policies": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/GeneratedPolicy"
                          },
                          "description": "Auto-generated behavior policies (base + curated variants) for the dropdown."
                        },
                        "solver": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/StrategyInfo"
                          },
                          "description": "Base solver strategies (with declared params) — the solver-config picker."
                        },
                        "solver_policies": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/GeneratedPolicy"
                          },
                          "description": "Auto-generated solver policies for the dropdown."
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/stream": {
      "get": {
        "tags": [
          "stream"
        ],
        "summary": "The live server-sent-events feed: one snapshot, then generation-stamped deltas.",
        "description": "On connect the stream emits one `snapshot` event carrying the current track\npicture (`tracks` plus the `generation` it reflects), then pushes events as they\nhappen. Every delta carries the stream generation (`stream_gen` on domain events,\n`generation` on the snapshot and `resync`); a gap in the sequence tells the\nclient to re-hydrate from `GET /api/picture`.\n\nStream-control events, never filtered by `kinds`:\n- `snapshot`: the initial track picture, `{ tracks, generation }`.\n- `resync`: this connection lagged and deltas were dropped, `{ generation, dropped }`;\nre-hydrate from `GET /api/picture`.\n\nPicture-slice events, pushed when the underlying state changes, never filtered:\n- `live_state`: the autonomy ceiling (`engagement_mode`) or the tracker's\nself-report (`fusion_health`), whichever changed.\n- `engagement_slice`: `{ threats, plan, candidate_plans, sim }`, the engagement\npicture that replaces wholesale rather than merging.\n- `fleet_slice`: `{ assets }`, our forces.\n- `sensor_slice`: `{ sensors }`, one view per placed or observed sensor.\n- `integration_slice`: `{ integrations, candidates }`, per-protocol status and\nthe unclaimed-source candidate list.\n\nOpt-in state, sent only to a client whose `kinds` names it:\n- `fusion_explain`: `{ explain }`, the tracker's explanation of one scan\n(`fusion-explain.v1`): every measurement as the sensor produced it, the\ncovariance before and after, what the tracker did with each. One event\nper scan, in tick order, including every scan of a coalesced window.\n\nDomain events (subject to the `kinds` filter), each a wire JSON body named by its\nfrozen SSE event name: `track_update`, `track_removed`, `obs`, `alert`, `coa`,\n`engagement`, `bda`, `swarm_command`, `tasking`, `tasking_status`, `threat`,\n`plan`, `posture`, `influence`.\n\nA comment ping keeps the connection alive every 15 seconds.",
        "operationId": "get_api_stream",
        "parameters": [
          {
            "name": "kinds",
            "in": "query",
            "description": "Comma-separated domain event names to receive, e.g. `track_update,alert`.\nOmitted means every domain event except `obs` (the raw observation\nfirehose; opt in explicitly, and only useful with `DOME_OBS_RETENTION=all`).\nThe picture deltas (`snapshot`, `resync`, `live_state`, `engagement_slice`,\n`fleet_slice`, `sensor_slice`, `integration_slice`) are never filtered.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "ticket",
            "in": "query",
            "description": "**Which workspace's picture**, as a single-use ticket from\n`POST /v1/auth/stream-ticket`. Here rather than in a header because the\nbrowser API that opens this connection cannot set one, and a session token\nin a query string is a session token in an access log.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the SSE stream: a snapshot event, then generation-stamped deltas until the client disconnects",
            "content": {
              "text/event-stream": {}
            }
          }
        },
        "security": []
      }
    },
    "/api/tasks": {
      "get": {
        "tags": [
          "tasking"
        ],
        "summary": "The ledger: who ordered what, when, under which line, and what came of it.",
        "operationId": "get_api_tasks",
        "parameters": [
          {
            "name": "actor",
            "in": "query",
            "description": "The asset as the surface names it.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "One of `proposed`, `issued`, `executing`, `complete`, `refused`,\n`superseded`, `reverted`.",
            "required": false,
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/TaskStatus"
                }
              ],
              "nullable": true
            }
          },
          {
            "name": "origin",
            "in": "query",
            "description": "`operator`, `plan` or `autonomy` — the `source` tag of the origin.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "plan",
            "in": "query",
            "description": "Tasks this plan minted.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "object",
            "in": "query",
            "description": "A track id the task is addressed at.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "Issued at or after this instant.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time",
              "nullable": true
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "At most this many, newest first. Clamped by the service.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int64",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the workspace's tasks, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Task"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tasks/{id}": {
      "get": {
        "tags": [
          "tasking"
        ],
        "summary": "One task, its status, and its refusal reason verbatim.",
        "operationId": "get_api_tasks_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "a `tsk_…` id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the record",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Task"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no task with that id in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tasks/{id}/seen": {
      "post": {
        "tags": [
          "tasking"
        ],
        "summary": "A console is watching this window.",
        "description": "Load-bearing, not telemetry. A window that lapses unwatched does NOT fire: the\ndelegation is \"you may act, and I can take it back\", and nobody who never saw it\ncould. So the console saying it has the countdown on screen is what makes the\nrelease legitimate, and its absence is what makes the fail-safe hold.",
        "operationId": "post_api_tasks_id_seen",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the task in `proposed` with a window: `tsk_…`, or the release id it was minted from",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "recorded; a release this console watched may fire when its window lapses"
          },
          "409": {
            "description": "the act is no longer open",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tasks/{id}/stop": {
      "post": {
        "tags": [
          "tasking"
        ],
        "summary": "Take back a scheduled release before its window lapses.",
        "description": "A stop window is delegation with a catch: the act runs on the runtime's clock\nunless a human takes it back. Until this route existed there was no way to take\nanything back, so the catch was decorative and every window was an automatic\nrelease the operator could not reach.\n\n`409` when the act is no longer open — it already fired, or somebody else already\nstopped it. That is not a success and must not be reported as one: the operator\nneeds to know the thing they tried to stop is already gone.",
        "operationId": "post_api_tasks_id_stop",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the task in `proposed` with a window: `tsk_…`, or the release id it was minted from",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the release was taken back and will not fire"
          },
          "409": {
            "description": "the act is no longer open: it already fired, or was already stopped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/threats": {
      "get": {
        "tags": [
          "threats"
        ],
        "summary": "The current live threats (one per hostile object), each with its rolled-up alert timeline and kill-chain stage.",
        "description": "Carries the authored candidate plans too (the same snapshot the console's ONE poll feeds on, so the PLAN count updates as candidates land — no second poll).",
        "operationId": "get_api_threats",
        "responses": {
          "200": {
            "description": "the live threat board: one threat per hostile object, the executing plan, and the authored candidate set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EngagementState"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/threats/designate": {
      "post": {
        "tags": [
          "threats"
        ],
        "summary": "A HUMAN's call on an object.",
        "description": "This is an ROE-relevant act, not a UI toggle. Our doctrine says kinematics\nalone may never reach *hostile* (a fast, inbound, unidentified contact maxes\nout at *suspect*); **operator designation is the sanctioned path there**. The\nruntime installs it on the ThreatManager on the next tick, and the resulting\nstate change is persisted as `threat.v1` (the change-signature includes\n`designation` + `affiliation`, so a declaration on an already-confirmed threat\nis recorded rather than silently vanishing — it previously did).\n\n# `by` is a claim, not a proven identity\n\nThe route is authenticated and workspace-scoped like everything else — this\nnote used to say the whole `/api` scope was open, which stopped being true\nwith PR #325. What is still true is narrower and worth keeping in view: `by`\nis **supplied in the body and never checked against the credential**, so the\naudit trail records who the caller *said* decided. The caller's own identity\nis on the session; `by` is a label beside it.\n\nReconciling the two is its own piece of work. Until it lands, read `by` as\nannotation rather than attribution.",
        "operationId": "post_api_threats_designate",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The operator's declaration about an object.",
                "required": [
                  "id",
                  "verdict"
                ],
                "properties": {
                  "by": {
                    "type": "string",
                    "description": "Who declared it (placeholder until auth lands).",
                    "nullable": true
                  },
                  "id": {
                    "type": "string",
                    "description": "Threat id or fused track id — whichever the operator was looking at."
                  },
                  "verdict": {
                    "$ref": "#/components/schemas/OperatorVerdict"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the recorded declaration; attribution is the caller's unverified claim",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "id": "trk-7",
                  "verdict": "hostile",
                  "claimed_by": "operator",
                  "attribution_verified": false
                }
              }
            }
          },
          "400": {
            "description": "no object id supplied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/threats/designations": {
      "get": {
        "tags": [
          "threats"
        ],
        "summary": "The calls currently in force (audit read).",
        "operationId": "get_api_threats_designations",
        "responses": {
          "200": {
            "description": "object id to verdict, for every designation currently in force",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "trk-7": "hostile",
                  "trk-12": "benign"
                }
              }
            }
          }
        }
      }
    },
    "/api/thresholds": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "Every setting resolved, with the layer that set it.",
        "operationId": "get_api_thresholds",
        "responses": {
          "200": {
            "description": "every key with its value, layer, source and any clamp, plus the keys the baseline config cannot yet hold",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ThresholdsView"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "settings"
        ],
        "summary": "Write keys, not a blob.",
        "description": "A key with nowhere to go is **refused, naming the key**. Accepting it and\ndropping it is how a settings screen comes to show values the system ignores.",
        "operationId": "put_api_thresholds",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "key",
                    "value"
                  ],
                  "properties": {
                    "key": {
                      "$ref": "#/components/schemas/SettingKey"
                    },
                    "value": {
                      "$ref": "#/components/schemas/SettingValue"
                    }
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the resolved settings after the write",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ThresholdsView"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "a key with nowhere to go in the baseline config, named in the refusal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tracks": {
      "get": {
        "tags": [
          "picture"
        ],
        "summary": "The live track set, for anyone who is not hydrating a console.",
        "description": "`GET /api/picture` is the heaviest thing this server serves and it is for\nhydration: one client behaviour, sized for it, and the rule beside it is hydrate\nonce and hold the stream. Somebody integrating wants tracks, and reading the\nwhole picture to get at part of it is the thing that rule exists to stop\n(CLAUDE.md rules 1 and 2, spec §7).\n\nObservations go in at `POST /api/signals`. Until this route, nothing read a\ntrack back out except the hydration payload.",
        "operationId": "get_api_tracks",
        "responses": {
          "200": {
            "description": "every live track in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Track"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/tracks/{id}/explain": {
      "get": {
        "tags": [
          "fusion"
        ],
        "summary": "Every recent scan that holds the track, with the scans just before it was born.",
        "responses": {
          "200": {
            "description": "the track's explained scans, oldest first; empty when the ring holds none",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TrackExplainHistory"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "get_api_tracks_id_explain",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "track id, e.g. T-00012",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_s",
            "in": "query",
            "description": "Seconds of scans before the track's first appearance to include, so the\ndetections that led to the birth are in the reply. Default 30, at most 120.",
            "required": false,
            "schema": {
              "type": "number",
              "format": "double",
              "nullable": true
            }
          }
        ]
      }
    },
    "/api/workspace/default-region": {
      "put": {
        "tags": [
          "regions"
        ],
        "summary": "Set the workspace default region.",
        "operationId": "put_api_workspace_default_region",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Body for setting the workspace default region.",
                "required": [
                  "region_id"
                ],
                "properties": {
                  "region_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the region now set as the workspace default",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/RegionSummary"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no region with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/workspaces": {
      "get": {
        "tags": [
          "workspaces"
        ],
        "summary": "Every workspace the caller belongs to, with their role in each.",
        "operationId": "get_api_workspaces",
        "responses": {
          "200": {
            "description": "the caller's workspaces, oldest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Workspace"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "no credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          },
          {
            "api_key": []
          }
        ]
      },
      "post": {
        "tags": [
          "workspaces"
        ],
        "summary": "Create a workspace. The caller owns it and is its first member.",
        "operationId": "post_api_workspaces",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWorkspace"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "the new workspace, with the caller as owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Workspace"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "no name",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "no credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          }
        ]
      }
    },
    "/api/workspaces/{id}": {
      "get": {
        "tags": [
          "workspaces"
        ],
        "summary": "One workspace, if the caller is in it.",
        "operationId": "get_api_workspaces_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "workspace id, `wsp_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the workspace and the caller's role in it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Workspace"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no workspace with that id, or the caller is not in it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          },
          {
            "api_key": []
          }
        ]
      }
    },
    "/api/workspaces/{id}/invitations": {
      "get": {
        "tags": [
          "workspaces"
        ],
        "summary": "Outstanding and accepted invitations.",
        "operationId": "get_api_workspaces_id_invitations",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "workspace id, `wsp_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "invitations, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invitation"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "this needs the admin or owner role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no workspace with that id, or the caller is not in it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          }
        ]
      },
      "post": {
        "tags": [
          "workspaces"
        ],
        "summary": "Invite an address into a workspace.",
        "description": "The mail carries the accept link. When this deployment cannot send mail the\ninvitation is still created and the call answers 201 with the row, so an\noperator on an air-gapped site can pass the link on by hand rather than being\nblocked by a mail provider they were never going to have.",
        "operationId": "post_api_workspaces_id_invitations",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "workspace id, `wsp_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "the invitation; the token went to the address",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invitation"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "not an address, or an attempt to invite a second owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "this needs the admin or owner role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "already a member",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          }
        ]
      }
    },
    "/api/workspaces/{id}/members": {
      "get": {
        "tags": [
          "workspaces"
        ],
        "summary": "Who is in a workspace.",
        "operationId": "get_api_workspaces_id_members",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "workspace id, `wsp_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "members with their roles, in join order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Member"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no workspace with that id, or the caller is not in it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          },
          {
            "api_key": []
          }
        ]
      }
    },
    "/api/workspaces/{id}/members/{user_id}": {
      "delete": {
        "tags": [
          "workspaces"
        ],
        "summary": "Remove a member.",
        "description": "The owner cannot be removed by anyone, including themselves: a workspace with\nno owner is one nobody can administer, and the way to be rid of it is to\ndelete it rather than to leave it orphaned.",
        "operationId": "delete_api_workspaces_id_members_user_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "workspace id, `wsp_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user_id",
            "in": "path",
            "description": "user id, `usr_…`",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "removed"
          },
          "403": {
            "description": "this needs the admin or owner role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "not a member of this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "that member owns this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "session": []
          }
        ]
      }
    },
    "/api/zones": {
      "get": {
        "tags": [
          "zones"
        ],
        "summary": "Resolve the effective workspace: the caller's, else the system workspace.",
        "description": "List zones in the current environment.",
        "operationId": "get_api_zones",
        "responses": {
          "200": {
            "description": "every zone in the workspace's current environment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Zone"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "zones"
        ],
        "summary": "Create a zone.",
        "operationId": "post_api_zones",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ZoneInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored zone, with its server-assigned id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Zone"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid input: empty name, non-positive radius, degenerate polygon, or an inverted altitude band",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/zones/{id}": {
      "put": {
        "tags": [
          "zones"
        ],
        "summary": "Replace a zone's fields.",
        "operationId": "put_api_zones_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "zone id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ZoneInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the updated zone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Zone"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no zone with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "zones"
        ],
        "summary": "Delete a zone.",
        "operationId": "delete_api_zones_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "zone id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the zone is gone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Deleted"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no zone with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Root-scope health check: the server is up and answering.",
        "operationId": "get_health",
        "responses": {
          "200": {
            "description": "the server is up",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "status": "ok"
                }
              }
            }
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "schemas": {
      "AcceptRequest": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The `dinv_…` token from the invitation mail."
          }
        }
      },
      "Accepted": {
        "type": "object",
        "description": "What the pipeline took.",
        "required": [
          "accepted"
        ],
        "properties": {
          "accepted": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "AcousticBearing": {
        "type": "object",
        "description": "Acoustic bearing detection (MQTT `acoustic`, SAPIENT ACOUSTIC node).",
        "properties": {
          "band": {
            "type": "string",
            "nullable": true
          },
          "bearing_deg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "bearing_sigma_deg": {
            "type": "number",
            "format": "double",
            "description": "**The 1σ on this bearing**, degrees, as the node states it.\n\nA $400 single node at 10° and a 128-element array at 0.5° are the difference\nbetween a fix worth handing an operator and one that is not; resolving σ from\nthe observation first is what stops them reading identically.",
            "nullable": true
          },
          "harmonics_hz": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "double"
            },
            "description": "The harmonic comb above the fundamental, hertz. The identity cue: the spacing\nseparates multirotor from fixed-wing from piston without a learned model."
          },
          "noise_floor_db": {
            "type": "number",
            "format": "double",
            "description": "**The node's measured ambient for this window**, dB.\n\nAcoustic range is a property of the *site*, not the sensor: the same node\nreaches ~2 km on a still rural night and ~150 m beside a road at noon. A\nprofile constant is therefore wrong by more than an order of magnitude at one\nend, and wrong in the direction that makes the console imply coverage which is\nnot there. `None` means the node did not measure one — never zero.",
            "nullable": true
          },
          "rotor_bpf_hz": {
            "type": "number",
            "format": "double",
            "description": "Rotor blade-pass frequency, hertz.",
            "nullable": true
          },
          "snr_db": {
            "type": "number",
            "format": "double",
            "description": "What the detector actually cleared over that floor, dB.",
            "nullable": true
          },
          "spl_db": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        }
      },
      "Action": {
        "type": "string",
        "description": "Every action the system can be asked to take, in one vocabulary.\n\nThis replaces three overlapping enums. `CommandVerb` (17 variants) was what the\nconsole sent, `CommandVerb` (11) was what the policy listed and `Effect` (18) was\nwhat the release table was keyed by, so `Arm`, `Disarm`, `Takeoff`, `Land`,\n`Stop`, `FollowRoute` and `Orbit` appeared in the first only and doctrine could not\nexpress \"no launching here\" at all. Every one of them is on the ladder now.",
        "enum": [
          "alert",
          "cue_sensor",
          "assign_search_sector",
          "arm",
          "disarm",
          "takeoff",
          "land",
          "rtb",
          "hold",
          "stop",
          "move_to",
          "follow_route",
          "orbit",
          "vacate_for_manned_aircraft",
          "climb_above_smoke",
          "surveil",
          "follow",
          "warn",
          "designate_friend",
          "designate_suspect",
          "roster_enrolment",
          "jam",
          "spoof",
          "intercept",
          "designate_hostile",
          "payload_release_near_people"
        ]
      },
      "ActionOrigin": {
        "oneOf": [
          {
            "type": "object",
            "description": "A deterministic tier-1 rule (`id` == the rule's stable id).",
            "required": [
              "id",
              "kind"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "rule"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "solver"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "operator"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "reviewer"
                ]
              }
            }
          }
        ],
        "description": "Where a [`PlannedAction`] originated — provenance for audit and the combined\nloop. Internally tagged on `kind`:\n`{\"kind\":\"rule\",\"id\":\"keep_out_breach\"}` · `{\"kind\":\"solver\"}` ·\n`{\"kind\":\"operator\"}` · `{\"kind\":\"reviewer\"}`.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "ActionSet": {
        "type": "object",
        "description": "**What a platform can be asked to do** — declared, not inferred.\n\nAbsent ⇒ the empty set: an observed platform is never taskable, which is the same\nsafe default [`ControlLink::None`](crate::catalog::ControlLink::None) already takes. See\n[`for_control`](Self::for_control) for the class default, and\n[`AssetSpec::resolved_actions`](crate::AssetSpec::resolved_actions) for the full\nresolution order (class default → catalog entry → asset override).",
        "properties": {
          "effects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EffectKind"
            },
            "description": "The rungs this platform can perform. Discriminants — the parameters come from\nthe order. **No class default puts anything in here**: a rung is declared on a\ncatalog entry or on the unit, or it does not exist.",
            "uniqueItems": true
          },
          "verbs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommandVerb"
            },
            "description": "The verbs it accepts — lifecycle, navigation and tasking, as today.",
            "uniqueItems": true
          }
        }
      },
      "AdapterId": {
        "type": "string",
        "description": "**What a link speaks.** Closed on purpose: adding a protocol adds a variant here\nand an adapter behind it, which is what makes \"is this supported?\" answerable.",
        "enum": [
          "mavlink",
          "asterix",
          "cot",
          "sapient",
          "mqtt",
          "remoteid",
          "adsb",
          "dji",
          "dji_cloud",
          "klv",
          "inturai",
          "simlink"
        ]
      },
      "AdsbReport": {
        "type": "object",
        "description": "ADS-B cooperative surveillance report (dump1090, MAVLink ADSB_VEHICLE).",
        "properties": {
          "baro_alt_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "callsign": {
            "type": "string",
            "nullable": true
          },
          "emitter_category": {
            "type": "string",
            "nullable": true
          },
          "icao": {
            "type": "string",
            "nullable": true
          },
          "squawk": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Affiliation": {
        "type": "string",
        "description": "The APP-6 / MIL-STD-2525 standard-identity ladder — the affiliation vocabulary\nshared by an operator-confirmed [`Assignment`] and a fused track's inferred\naffiliation channel (research Part D3). Ordered by ladder position\n(`Pending < Unknown < AssumedFriend < Friend < Neutral < Suspect < Hostile`).\n\nThe original four confirmed-side values (`friend`/`hostile`/`neutral`/`unknown`)\nkeep their wire spelling (`snake_case` == `lowercase` for those), so stored\nassignments parse unchanged; `pending`/`assumed_friend`/`suspect` are additive.",
        "enum": [
          "pending",
          "unknown",
          "assumed_friend",
          "friend",
          "neutral",
          "suspect",
          "hostile"
        ]
      },
      "AffiliationSource": {
        "type": "string",
        "description": "Provenance of a track's affiliation: machine-inferred by fusion vs operator-\ndeclared. An operator override is tagged `Operator` and is not silently\noverwritten by the next machine pass (research Part D3).",
        "enum": [
          "machine",
          "operator"
        ]
      },
      "AlertSeverity": {
        "type": "string",
        "description": "Severity, ordered so `>` means \"more severe\" (drives escalation).",
        "enum": [
          "medium",
          "high",
          "critical"
        ]
      },
      "AlertingPolicy": {
        "type": "object",
        "description": "What is worth interrupting a human for.",
        "required": [
          "closure",
          "loss_of_custody_steps",
          "geofence_breach_m",
          "swarm_forming"
        ],
        "properties": {
          "closure": {
            "$ref": "#/components/schemas/ClosureThresholds"
          },
          "geofence_breach_m": {
            "type": "number",
            "format": "double",
            "description": "Geofence breach radius in metres (was `r < 450.0`)."
          },
          "loss_of_custody_steps": {
            "type": "integer",
            "format": "int32",
            "description": "Alert after this many consecutive coasting steps (was `coast >= 3`).",
            "minimum": 0
          },
          "notify_grace_s": {
            "type": "number",
            "format": "double",
            "description": "Extra seconds a `Gate::Notify` window waits when no console has reported\nseeing it, so a window nobody could have watched is not counted as one\nthey declined. Bounded on purpose: never *wait for a client*, because a\ncomms failure must not disable air defence."
          },
          "swarm_forming": {
            "type": "boolean",
            "description": "Alert when contacts cluster into a swarm."
          }
        }
      },
      "Alias": {
        "type": "object",
        "description": "**One handle a thing is known by elsewhere.** Never an identity on its own.",
        "required": [
          "scheme",
          "value"
        ],
        "properties": {
          "scheme": {
            "$ref": "#/components/schemas/AliasScheme"
          },
          "value": {
            "type": "string"
          }
        }
      },
      "AliasScheme": {
        "type": "string",
        "description": "Which naming system minted a handle.\n\nCarried rather than inferred: the same string can be a callsign in one system and a\nserial in another, and a resolver that guessed would join two different aircraft.",
        "enum": [
          "remote_id",
          "mavlink",
          "callsign",
          "icao",
          "adapter",
          "platform"
        ]
      },
      "AltitudeBand": {
        "type": "object",
        "description": "A zone's vertical extent, in metres **above ground level**. AGL is measured\nfrom the site's `elevation_m` (metres AMSL), which is why the site carries one:\nwithout it a bare \"120 m\" is ambiguous between the ground and the sea.\n\nBoth ends are optional and a missing end is open, so `{}` is every altitude,\na ceiling alone is surface-up-to, and a floor alone is above-and-clear. That\nis also what makes the field additive: a zone stored before bands existed\ndeserializes to the open band, which is exactly what it always meant.\n\nArduPilot's fence grammar carries polygon inclusion/exclusion *and* altitude\nfences (min/max), selected by the `FENCE_TYPE` bitmask. A C2 that cannot say a\nfloor and a ceiling cannot mirror the onboard fence it supervises, so the band\nbelongs to the zone rather than to a rule that reads it.",
        "properties": {
          "ceiling_m": {
            "type": "number",
            "format": "double",
            "description": "Highest altitude the zone reaches, metres AGL. `None` is no ceiling.",
            "nullable": true
          },
          "floor_m": {
            "type": "number",
            "format": "double",
            "description": "Lowest altitude the zone reaches, metres AGL. `None` is the surface.",
            "nullable": true
          }
        }
      },
      "Anchor": {
        "oneOf": [
          {
            "type": "string",
            "enum": [
              "subject"
            ]
          },
          {
            "type": "object",
            "required": [
              "protected_asset"
            ],
            "properties": {
              "protected_asset": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "zone"
            ],
            "properties": {
              "zone": {
                "type": "string"
              }
            }
          }
        ],
        "description": "What a distance is measured from."
      },
      "ApiErrorCode": {
        "type": "string",
        "description": "**What went wrong, in a word a program can match on.**\n\nFrozen. Add a variant when a client needs to tell a new case apart from the\nones here; never rename one, and never repoint an existing one at a different\ncondition, because a deployed integration is matching on the string.",
        "enum": [
          "invalid_input",
          "unauthenticated",
          "not_a_member",
          "forbidden",
          "unknown_resource",
          "verb_not_advertised",
          "verb_unavailable",
          "withheld",
          "commanding_off",
          "conflict",
          "gone",
          "feature_absent",
          "not_configured",
          "rate_limited",
          "upstream",
          "internal"
        ]
      },
      "ApiKey": {
        "type": "object",
        "description": "An API key row. Never carries the secret: see [`MintedApiKey`].",
        "required": [
          "id",
          "name",
          "prefix",
          "created_at"
        ],
        "properties": {
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "type": "string",
            "description": "Email of whoever minted it.",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "nullable": true
          },
          "id": {
            "type": "string",
            "description": "`key_…`"
          },
          "last_used_at": {
            "type": "string",
            "description": "When something last authenticated with it. `None` means it has never been\nused, which is the useful thing to see beside a key nobody can account\nfor.",
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string",
            "description": "The first characters of the secret, so an operator can tell two keys\napart without holding either."
          }
        }
      },
      "ApprovalVerdict": {
        "type": "object",
        "description": "Why an action did or did not need approval — carried on the action so the\ndecision is inspectable. Silent automation is indistinguishable from a bug.",
        "required": [
          "needs_approval",
          "reason"
        ],
        "properties": {
          "needs_approval": {
            "type": "boolean"
          },
          "reason": {
            "type": "string",
            "description": "The setting that decided it, in the operator's own vocabulary."
          }
        }
      },
      "ApproveReq": {
        "type": "object",
        "description": "The optional body of `POST /api/engagement/plans/{id}/approve`.",
        "properties": {
          "only": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Adopt only these assignment ids and leave the rest of the plan unflown —\nan operator who agreed with part of what was offered. Omitted ⇒ the plan\nis approved as the solver authored it.\n\nA subset asks the planner nothing new: every kept leg is one it already\nsized, so a subset of a feasible plan is feasible. The release gate is\nstill resolved over what remains, so dropping legs cannot release an\neffect the deployment's ceiling withholds.",
            "nullable": true
          }
        }
      },
      "Area": {
        "type": "object",
        "description": "An area (loiter/observe): a point + radius.",
        "required": [
          "center",
          "radius_m"
        ],
        "properties": {
          "center": {
            "$ref": "#/components/schemas/GeoPoint"
          },
          "radius_m": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Asset": {
        "type": "object",
        "description": "A workspace-scoped fixed platform (the drone itself). Neither friendly nor\nhostile intrinsically — its side is an [`crate::assignment::IdentityClaim`].",
        "required": [
          "id",
          "workspace_id",
          "kind",
          "name",
          "spec",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "created_at": {
            "type": "string",
            "description": "Audit timestamps."
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique asset id."
          },
          "kind": {
            "$ref": "#/components/schemas/AssetKind"
          },
          "name": {
            "type": "string",
            "description": "Callsign / display name (e.g. `\"BLUE-01\"`)."
          },
          "roles": {
            "$ref": "#/components/schemas/RoleSet"
          },
          "spec": {
            "$ref": "#/components/schemas/AssetSpec"
          },
          "updated_at": {
            "type": "string"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "Workspace this asset belongs to."
          }
        }
      },
      "AssetCommand": {
        "type": "object",
        "description": "One command, addressed to an asset by **asset id** — never by a protocol identifier.",
        "required": [
          "verb"
        ],
        "properties": {
          "origin": {
            "$ref": "#/components/schemas/CommandOrigin"
          },
          "params": {
            "$ref": "#/components/schemas/CommandParams"
          },
          "verb": {
            "$ref": "#/components/schemas/CommandVerb"
          }
        }
      },
      "AssetDomain": {
        "type": "string",
        "description": "The operating **domain** (vector) of a platform — the coarse category, distinct\nfrom the finer airframe `class` in the catalogue (multirotor/fixedwing/…). We\nare **aerial-focused now**, but this is an enum (not a hardcoded \"drone\") so\nground / maritime / space / EW slot in later with no rework. Solver problems are\nnamespaced by domain (an *aerial* WTA today). One variant on purpose; extensible.",
        "enum": [
          "aerial",
          "ground",
          "maritime",
          "ew"
        ]
      },
      "AssetHealth": {
        "type": "object",
        "description": "Everything an operator triages on before committing an asset to a task.",
        "properties": {
          "alt_rel_m": {
            "type": "number",
            "format": "float",
            "description": "Altitude above the launch point, metres — what an operator flies by.",
            "nullable": true
          },
          "armed": {
            "type": "boolean",
            "description": "Motors energised."
          },
          "attitude": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "float"
            },
            "description": "Attitude — roll/pitch/yaw, radians.",
            "nullable": true
          },
          "climb_mps": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "energy": {
            "$ref": "#/components/schemas/Energy"
          },
          "groundspeed_mps": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "in_air": {
            "type": "boolean",
            "description": "Under way (airborne, or moving for a ground platform)."
          },
          "message": {
            "type": "string",
            "description": "Most recent message from the platform (pre-arm failures and the like).",
            "nullable": true
          },
          "mode": {
            "$ref": "#/components/schemas/FlightMode"
          },
          "nav": {
            "$ref": "#/components/schemas/NavQuality"
          },
          "sensors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SensorStatus"
            },
            "description": "The sensors the platform declares it has, with health. Empty until it reports any."
          }
        }
      },
      "AssetKind": {
        "type": "string",
        "description": "Whether a registry row is a thing that **moves and carries**, a thing that\n**sees**, or a thing we **defend** (U7 §1, §5).\n\nOne registry table, one wire type — a sensor is registered exactly the way a\nvehicle is, and splitting the table would break the one-list rule the OUR\nFORCES rail exists to hold. What changes is that the distinction the free-text\n`kind` string was already carrying (`\"drone\"` vs `\"sensor\"`) is now checkable.\n\n**Domain answers *what kind of vehicle*; `kind` answers *vehicle or sensor*.**\n\n`Protected` is the third value the string was carrying and is not a force at\nall: it is what the deployment defends (#62), stored in the same table so that\none place answers \"what is at this site\". Folding it into `Vehicle` would file\na terminal building in OUR FORCES.\n\nDeserialization is **lenient on purpose**: every row written before this type\nexisted says `\"drone\"` or `\"friendly_drone\"`, and a registry that refuses to\nload is worse than one that knows what those meant. Serialization is always\ncanonical, so no free text survives a round trip.",
        "enum": [
          "vehicle",
          "sensor",
          "protected"
        ]
      },
      "AssetManifest": {
        "type": "object",
        "required": [
          "apiVersion",
          "kind"
        ],
        "properties": {
          "apiVersion": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "metadata": {
            "$ref": "#/components/schemas/ManifestMeta"
          },
          "spec": {
            "$ref": "#/components/schemas/ManifestSpec"
          }
        }
      },
      "AssetRole": {
        "type": "string",
        "description": "**What a thing is** — the other half of the axis [`AssetKind`] could only ever\nanswer with one word.\n\n`AssetKind` is `Vehicle | Sensor | Protected`, so it cannot say that a Dedrone box\nboth sees and jams, or that an interceptor is a platform that is also an effector.\nRoles are added **alongside** `kind`, never in place of it: no frozen string moves\nand nothing migrates.\n\nOrdered by declaration, and that order is the wire order — two writers of the same\nset produce the same JSON.",
        "enum": [
          "sensor",
          "effector",
          "platform",
          "protected"
        ]
      },
      "AssetSelector": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "select",
              "value"
            ],
            "properties": {
              "select": {
                "type": "string",
                "enum": [
                  "id"
                ]
              },
              "value": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "select",
              "value"
            ],
            "properties": {
              "select": {
                "type": "string",
                "enum": [
                  "kind"
                ]
              },
              "value": {
                "type": "string"
              }
            }
          }
        ],
        "description": "Which asset a `prioritize` speaks to. A **selector, not a global** — the\ndefended asset list is a priority order, not a set, and a commander names what\nis protected *and in what order*.",
        "discriminator": {
          "propertyName": "select"
        }
      },
      "AssetSpec": {
        "allOf": [
          {
            "description": "Forward-compatible free-form metadata."
          },
          {
            "type": "object",
            "properties": {
              "actions": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ActionSet"
                  }
                ],
                "nullable": true
              },
              "aliases": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Alias"
                },
                "description": "**Every other handle this asset is known by elsewhere** — a MAVLink system id on\none of our links, a vendor serial, an ICAO address.\n\nDeclared rather than derived. Before this, adoption invented a Remote-ID serial\nfor an aircraft that had never broadcast one (`mav-udp-14550-1`), which is a\n\"given to us\" id we made up — and it still matched nothing, because the track\ncarried the scenario's callsign instead."
              },
              "control": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ControlProfile"
                  }
                ],
                "nullable": true
              },
              "domain": {
                "$ref": "#/components/schemas/AssetDomain"
              },
              "model": {
                "type": "string",
                "description": "Catalog entry name → the asset's emission / signature / capability profile."
              },
              "payloads": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PayloadSpec"
                },
                "description": "**What this vehicle carries** — the per-unit override (U7 §3).\n\n`None` ⇒ inherit the model's catalog payloads. `Some([])` ⇒ it carries\nnothing, deliberately: the pod came off. Those are different states and the\n`Option` is what keeps them apart, exactly as it does for `performance`.",
                "nullable": true
              },
              "performance": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/PlatformPerformance"
                  }
                ],
                "nullable": true
              },
              "provenance": {
                "$ref": "#/components/schemas/Provenance"
              },
              "remote_id_serial": {
                "type": "string",
                "description": "The unique cooperative identifier the asset's signals carry (the Remote-ID\nserial). Detections matching a registered asset's serial are correlated to it.\n\n**One of the handles this asset answers to, not its identity.** Read through\n[`aliases_with_name`](Self::aliases_with_name) rather than compared directly;\nthe identity is the row's own id. See\n`docs/decisions/2026-09-01-identity-and-the-silent-plan-overlay.md` §7 rule 1.",
                "nullable": true
              },
              "roles": {
                "$ref": "#/components/schemas/RoleSet"
              },
              "sensors": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PlacedSensorSpec"
                },
                "description": "**Where a sensor row stands** (#54). A registry row whose `kind` is\n[`AssetKind::Sensor`] carries exactly one\n[`PlacedSensorSpec`](crate::catalog::sensor_profile::PlacedSensorSpec) here,\nwith a [`Fixed`](crate::catalog::sensor_profile::SensorPlacement::Fixed)\nmount: that is what makes an emplaced RF-DF an asset like everything else\nrather than a second kind of thing with its own list.\n\nWhat a **vehicle** carries is `payloads` below — a fit is declared, and the\nmount is derived, because a carried sensor has no position of its own."
              },
              "switches": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AssetSwitches"
                  }
                ],
                "nullable": true
              }
            }
          }
        ],
        "description": "A platform's *fixed identity* — what never changes about the drone itself: its\nunique signal identifier and its `model`. Operational context that changes over\ntime (operator, role, and even which **side** it is on) is NOT here — that is a\ndynamic [`crate::assignment::IdentityClaim`] layered on top of the asset. Concrete\nfields (no bare `Value` in domain code); a flattened `extra` carries\nforward-compat metadata at the boundary."
      },
      "AssetSwitches": {
        "type": "object",
        "description": "**The switches, resolved.** What the row stores is [`AssetSwitchesPatch`]\nshaped (every field optional); this is what a reader gets, with the defaults\napplied: receiving on, commanding off, video off, automation by site rules.",
        "required": [
          "receiving",
          "commanding",
          "video",
          "automation"
        ],
        "properties": {
          "automation": {
            "$ref": "#/components/schemas/Automation"
          },
          "commanding": {
            "type": "boolean",
            "description": "The machine may task it: the plan and the autonomy loop may deliver to\nthis asset. **Off by default**: a device that was just heard is not the\nruntime's to fly until somebody says so. An operator's own command is not\ngated by this."
          },
          "receiving": {
            "type": "boolean",
            "description": "What it reports reaches the picture and fusion. Off, the link stays open\nand the asset's page still shows it alive, but it contributes no track,\nno telemetry and no health."
          },
          "video": {
            "type": "boolean",
            "description": "Its feed may be played through the media gateway, on demand."
          }
        }
      },
      "AssetSwitchesPatch": {
        "type": "object",
        "description": "`POST /api/assets/{id}` with a `switches` field: one or more of the four.\nMirrors the console's `AssetSwitchesPatch`.",
        "properties": {
          "automation": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Automation"
              }
            ],
            "nullable": true
          },
          "commanding": {
            "type": "boolean",
            "nullable": true
          },
          "receiving": {
            "type": "boolean",
            "nullable": true
          },
          "video": {
            "type": "boolean",
            "nullable": true
          }
        }
      },
      "AssetView": {
        "type": "object",
        "description": "One asset, as the console reads it.",
        "required": [
          "id",
          "name",
          "domain",
          "kind",
          "link"
        ],
        "properties": {
          "affiliation": {
            "$ref": "#/components/schemas/Affiliation"
          },
          "autopilot": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Autopilot"
              }
            ],
            "nullable": true
          },
          "capabilities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ControlCapability"
            },
            "description": "What this asset can be told to do. Empty ⇒ monitor-only."
          },
          "domain": {
            "$ref": "#/components/schemas/AssetDomain"
          },
          "frame": {
            "$ref": "#/components/schemas/FrameType"
          },
          "health": {
            "$ref": "#/components/schemas/AssetHealth"
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/AssetKind"
          },
          "kinematics": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Kinematics"
              }
            ],
            "nullable": true
          },
          "link": {
            "$ref": "#/components/schemas/LinkState"
          },
          "mission": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MissionProgress"
              }
            ],
            "nullable": true
          },
          "name": {
            "type": "string",
            "description": "Callsign."
          },
          "payloads": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResolvedPayload"
            },
            "description": "**What it carries**, resolved per-vehicle override → the model's catalog\npayloads → nothing (U7 §3). Each entry is a sensor with an `on_asset`\nmount — the same entity U3 renders, not a second model of one. An entry\nwhose profile does not resolve is carried here as **unresolved** and must\nnot be drawn anywhere as a working sensor."
          },
          "performance": {
            "$ref": "#/components/schemas/PlatformPerformance"
          },
          "placement": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Placement"
              }
            ],
            "nullable": true
          },
          "profile": {
            "type": "string",
            "description": "The sensor profile this row's model resolves — `rf-df`, `ground-radar-360`,\n`acoustic-array`, `eo-turret`. Absent on a vehicle.",
            "nullable": true
          },
          "provenance": {
            "$ref": "#/components/schemas/Provenance"
          },
          "saved": {
            "type": "boolean",
            "description": "**Whether this is a registry row, or something we are merely hearing.**\n\nOUR FORCES is `saved ∪ heard`. A platform talking to us is in the list from\nthe moment it speaks — it just is not *ours* until somebody saves it, and this\nis the flag the console hangs that affordance on. `true` for everything that\ncame out of the registry.\n\nDefaults to `true` so a reader that predates the union is not told that every\nasset it knows about is unsaved."
          },
          "sensor_id": {
            "type": "string",
            "description": "The id observations from this sensor carry, which is what the picture's\nsensor slice is keyed by. A command is still addressed by the asset id; this\nis what the dispatcher resolves it to.",
            "nullable": true
          },
          "switches": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AssetSwitches"
              }
            ],
            "nullable": true
          },
          "track_id": {
            "type": "string",
            "description": "The fused track this asset is showing up as, when correlated.",
            "nullable": true
          }
        }
      },
      "AssetsByKind": {
        "type": "object",
        "required": [
          "sensor",
          "vehicle"
        ],
        "properties": {
          "sensor": {
            "type": "integer",
            "minimum": 0
          },
          "vehicle": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "Assignment": {
        "type": "object",
        "description": "An assignment = one instruction bound to one [`Subject`] (a drone, a group, or\nthe swarm) — the unit the operator sees under the asset/threat and approves,\nand what the main loop emits as `tasking.v1`. Targets key on ObjectId (a single\nobject or a threat-group id), never raw position.",
        "required": [
          "id",
          "subject",
          "verb",
          "status"
        ],
        "properties": {
          "approval": {
            "$ref": "#/components/schemas/ApprovalVerdict"
          },
          "confidence": {
            "type": "number",
            "format": "double"
          },
          "guidance": {
            "$ref": "#/components/schemas/Guidance"
          },
          "id": {
            "type": "string"
          },
          "origin": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CommandOrigin"
              }
            ],
            "nullable": true
          },
          "params": {
            "$ref": "#/components/schemas/CommandParams"
          },
          "status": {
            "$ref": "#/components/schemas/TaskStatus"
          },
          "subject": {
            "$ref": "#/components/schemas/Subject"
          },
          "tti_s": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "verb": {
            "$ref": "#/components/schemas/CommandVerb"
          }
        }
      },
      "Att": {
        "type": "object",
        "description": "Emitting platform attitude (sim sensor platforms only).",
        "required": [
          "yaw_deg",
          "pitch_deg",
          "roll_deg"
        ],
        "properties": {
          "pitch_deg": {
            "type": "number",
            "format": "double"
          },
          "roll_deg": {
            "type": "number",
            "format": "double"
          },
          "yaw_deg": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Automation": {
        "type": "string",
        "description": "**How far the machine may go on this asset without a person.** Three words\nan operator reads on the asset's page: site rules decide, ask me first, never.",
        "enum": [
          "site",
          "ask",
          "never"
        ]
      },
      "AutonomyPolicy": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "line",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "linear"
                ]
              },
              "line": {
                "$ref": "#/components/schemas/Level"
              },
              "stop_window": {
                "type": "integer",
                "format": "int32",
                "description": "Seconds the operator has to stop an action that ran on its own.",
                "nullable": true,
                "minimum": 0
              }
            }
          },
          {
            "type": "object",
            "required": [
              "auto",
              "kind"
            ],
            "properties": {
              "auto": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Action"
                },
                "uniqueItems": true
              },
              "kind": {
                "type": "string",
                "enum": [
                  "custom"
                ]
              },
              "stop_window": {
                "type": "integer",
                "format": "int32",
                "nullable": true,
                "minimum": 0
              }
            }
          }
        ],
        "description": "What runs without asking.\n\n`Linear` is the default and the shape the slider edits. `Custom` is the escape\nhatch for a site whose needs are not a prefix of the ladder: \"auto-intercept what I\nhave declared hostile, but never declare hostile for me\" is `Linear` at `Deny` plus\n`Intercept`, and it cannot be said with one number.\n\nCustom is deliberately not the default. A per-action matrix is what the release\ntable was, and nobody could hold it in their head.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "Autopilot": {
        "type": "string",
        "description": "Which autopilot firmware is on the other end (from `HEARTBEAT.autopilot`).",
        "enum": [
          "px4",
          "ardu_pilot",
          "generic"
        ]
      },
      "Binding": {
        "type": "string",
        "description": "Which clock decided `fires_at`. The card prints a different sentence for each.",
        "enum": [
          "doctrine",
          "tactical"
        ]
      },
      "Bound": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "bound"
            ],
            "properties": {
              "bound": {
                "type": "string",
                "enum": [
                  "none"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "min",
              "max",
              "bound"
            ],
            "properties": {
              "bound": {
                "type": "string",
                "enum": [
                  "range"
                ]
              },
              "max": {
                "type": "number",
                "format": "double"
              },
              "min": {
                "type": "number",
                "format": "double"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "min",
              "bound"
            ],
            "properties": {
              "bound": {
                "type": "string",
                "enum": [
                  "at_least"
                ]
              },
              "min": {
                "type": "number",
                "format": "double"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "max",
              "bound"
            ],
            "properties": {
              "bound": {
                "type": "string",
                "enum": [
                  "at_most"
                ]
              },
              "max": {
                "type": "number",
                "format": "double"
              }
            }
          }
        ],
        "description": "Layer 0: the bound every other layer is clamped to. **It is why a rule can be\nauthored carelessly without being able to author something unsafe.**",
        "discriminator": {
          "propertyName": "bound"
        }
      },
      "BrokerAccount": {
        "type": "object",
        "description": "**The broker account a whole integration shares.**\n\nOne account covers every device under one vendor workspace, because the vendor\nfixes the topic and the identity is inside it: a DJI Dock publishes on\n`thing/product/{sn}/osd` and the `sn` is the aircraft. Minting one credential\nper aircraft there is work with no security benefit, and it could not be scoped\nto one aircraft anyway, because the broker enforces no topic authority\n(`docs/specs/2026-08-20-mqtt-ingest.md`).\n\nThe username is also the secret-store name the password lives under\n([`credential_name`]), so revoking the account and revoking its password are\none act rather than two that can diverge.",
        "required": [
          "username",
          "issued_at"
        ],
        "properties": {
          "issued_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the password was last issued. A rotate is an act with a date on it,\nand an installer holding a password from before that date is holding one\nthat no longer works."
          },
          "username": {
            "type": "string"
          }
        }
      },
      "CapabilityProfile": {
        "type": "object",
        "description": "What a platform emits (detectability) and, later, senses. Resolved from a\n[`super::CatalogSpec`] via [`super::CatalogSpec::capability_profile`]; the sim's\nemission layer reads it to decide which per-modality `obs.v1` records a given\ndrone produces, so **signals follow capability, not a hardcoded list**.",
        "properties": {
          "actions": {
            "$ref": "#/components/schemas/ActionSet"
          },
          "autonomous": {
            "type": "boolean",
            "description": "`true` ⇒ **radio-silent**: a preprogrammed / INS platform with no live RF\ncontrol link and no Remote ID broadcast. It is seen on radar/EO/acoustic but\nnever on RF or RemoteID — the operator's key discriminator for OWA munitions."
          },
          "camera_range_m": {
            "type": "number",
            "format": "double",
            "description": "EO/IR camera useful range (m), when the platform carries an imager.",
            "nullable": true
          },
          "control": {
            "$ref": "#/components/schemas/ControlProfile"
          },
          "gnss": {
            "type": "boolean",
            "description": "Carries a GNSS receiver (navigation; not itself an emission)."
          },
          "ins": {
            "type": "boolean",
            "description": "Carries an inertial navigation system (dead-reckoning when GNSS is denied)."
          },
          "remote_id": {
            "type": "boolean",
            "description": "Broadcasts ASTM F3411 / FAA Remote ID ⇒ a RemoteID receiver reports its\nserial/operator (suppressed while [`autonomous`](Self::autonomous))."
          },
          "rf_control": {
            "type": "boolean",
            "description": "A live RF control link is present ⇒ RF/DF detectable (suppressed while\n[`autonomous`](Self::autonomous))."
          },
          "signature": {
            "$ref": "#/components/schemas/EmissionSignature"
          }
        }
      },
      "CapabilitySpec": {
        "type": "object",
        "description": "The authorable capability flags stored on a catalog entry, kept separate from the\nphysical [`EmissionSignature`]. Every field is optional so an unspecified flag\nfalls back to a class-derived default in\n[`super::CatalogSpec::capability_profile`] — pre-capability entries still resolve\nto an honest profile. Additive / back-compatible.",
        "properties": {
          "actions": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ActionSet"
              }
            ],
            "nullable": true
          },
          "autonomous": {
            "type": "boolean",
            "nullable": true
          },
          "camera_range_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "control_link": {
            "type": "string",
            "description": "Control-link designation (e.g. `\"OcuSync 4\"`, `\"INS/preprogrammed\"`). Operator\nreference; not used for gating.",
            "nullable": true
          },
          "control_protocol": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ControlLink"
              }
            ],
            "nullable": true
          },
          "gnss": {
            "type": "boolean",
            "nullable": true
          },
          "ins": {
            "type": "boolean",
            "nullable": true
          },
          "onboard_autonomy": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OnboardAutonomy"
              }
            ],
            "nullable": true
          },
          "remote_id": {
            "type": "boolean",
            "nullable": true
          },
          "rf_control": {
            "type": "boolean",
            "nullable": true
          }
        }
      },
      "CatalogEntry": {
        "type": "object",
        "description": "Workspace-scoped catalog entry for threat library.",
        "required": [
          "id",
          "workspace_id",
          "kind",
          "name",
          "spec",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "created_at": {
            "type": "string",
            "description": "Audit timestamps."
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique entry ID."
          },
          "kind": {
            "type": "string",
            "description": "Entry kind (e.g., \"drone_type\")."
          },
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "spec": {
            "$ref": "#/components/schemas/CatalogSpec"
          },
          "updated_at": {
            "type": "string"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "Workspace this entry belongs to."
          }
        }
      },
      "CatalogEntryRow": {
        "type": "object",
        "description": "Row from catalog_entries table (workspace-scoped threat library entry).",
        "required": [
          "id",
          "workspace_id",
          "kind",
          "name",
          "spec",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "spec": {
            "type": "object"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "CatalogSpec": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Figures"
          },
          {
            "description": "Additional free-form metadata."
          },
          {
            "type": "object",
            "properties": {
              "capabilities": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Notable capabilities."
              },
              "capability": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/CapabilitySpec"
                  }
                ],
                "nullable": true
              },
              "class": {
                "type": "string",
                "description": "Airframe classification (multirotor, fixedwing, vtol, loitering_munition, etc.)."
              },
              "command": {
                "type": "boolean",
                "nullable": true
              },
              "dimensions": {
                "$ref": "#/components/schemas/DimensionsSpec"
              },
              "domain": {
                "$ref": "#/components/schemas/AssetDomain"
              },
              "notes": {
                "type": "string"
              },
              "payloads": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PayloadSpec"
                },
                "description": "**What a platform of this model carries** (U7 §3).\n\nThis was `sensors: Vec<String>` — `\"RGB camera\"`, `\"video downlink\"` —\nwhich no [`SensorProfile`](sensor_profile::SensorProfile) resolved against,\nso the knowledge was present and unusable. Naming a real profile is what\nturns *carries a camera* into a reach, a field of view and a measurement\nkind, and it is the join that makes the catalog worth maintaining: change a\nmodel's payloads and you change what every vehicle that is one contributes\nto the picture.\n\nFree text that names no sensing modality (a video downlink, an RTK module)\nbelongs in `capabilities` — it is something the platform *has*, not\nsomething that sees."
              },
              "product": {
                "type": "string",
                "description": "The product whose recipes carry a platform of this type into\nConnection, when the product catalogue has one. Explicit, never a slug\nprefix.",
                "nullable": true
              },
              "profile": {
                "type": "string",
                "description": "The sensor profile a sensor of this type resolves to, when one exists.",
                "nullable": true
              },
              "protocols": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Protocol family ids it speaks (`mavlink`, `dji_cloud_api`, `rtsp`), from\nthe catalogue's own list. Empty for a row that never said."
              },
              "receive": {
                "type": "boolean",
                "description": "Whether it reports, whether it can be told, whether it carries video,\nas the catalogue records them. `None` where the row never said.",
                "nullable": true
              },
              "segment": {
                "type": "string",
                "description": "`defence`, `enterprise`, `consumer`, `research`; `threat` for a reference\nplatform that is only ever observed; `generic` for a placeholder."
              },
              "signature": {
                "$ref": "#/components/schemas/SignatureSpec"
              },
              "source": {
                "type": "string",
                "description": "Where the figures came from, as a URL, and what the vendor said in words."
              },
              "vendor": {
                "type": "string",
                "description": "Who makes it: `DJI`, `Quantum Systems`. The name a type is filed under."
              },
              "video": {
                "type": "boolean",
                "nullable": true
              }
            }
          }
        ],
        "description": "Detailed threat specifications."
      },
      "CatalogueDomain": {
        "type": "string",
        "description": "Where the catalogue files a type. Finer than [`AssetDomain`], and derived\nfrom it: a kinetic interceptor is an aerial vehicle whose class makes it an\neffector, an AUV is a maritime vehicle that dives, a radar is a sensor\nwhatever ground it stands on. The registry keeps the four domains; the\nAdd screen groups by these seven.",
        "enum": [
          "aerial",
          "ground",
          "maritime",
          "underwater",
          "effector",
          "ew",
          "sensor"
        ]
      },
      "CatalogueType": {
        "type": "object",
        "description": "One type as the API serves it: a platform of the one catalogue, with its id\nand what this build can do with it.",
        "required": [
          "id",
          "vendor",
          "model",
          "domain",
          "class",
          "segment",
          "spec",
          "protocols",
          "receive",
          "command",
          "video",
          "notes",
          "source",
          "enabled",
          "integrations"
        ],
        "properties": {
          "class": {
            "type": "string"
          },
          "command": {
            "type": "boolean"
          },
          "domain": {
            "$ref": "#/components/schemas/CatalogueDomain"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether an integration in this build speaks one of its protocols."
          },
          "id": {
            "type": "string",
            "description": "`dji/matrice-400`; see [`type_id`]."
          },
          "integrations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdapterId"
            },
            "description": "The adapters that can carry it. Empty when `enabled` is false."
          },
          "model": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "product": {
            "type": "string",
            "nullable": true
          },
          "profile": {
            "type": "string",
            "nullable": true
          },
          "protocols": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "receive": {
            "type": "boolean"
          },
          "segment": {
            "type": "string",
            "description": "`defence`, `enterprise`, `consumer`, `research`; `threat` for a reference\nplatform that is only ever observed, `generic` for a placeholder."
          },
          "source": {
            "type": "string"
          },
          "spec": {
            "$ref": "#/components/schemas/Figures"
          },
          "vendor": {
            "type": "string"
          },
          "video": {
            "type": "boolean"
          }
        }
      },
      "Clamp": {
        "type": "object",
        "description": "What a clamp did, so the trace can say it rather than the value simply being\ndifferent from what was written.",
        "required": [
          "from",
          "to",
          "bound"
        ],
        "properties": {
          "bound": {
            "$ref": "#/components/schemas/Bound"
          },
          "from": {
            "$ref": "#/components/schemas/SettingValue"
          },
          "to": {
            "$ref": "#/components/schemas/SettingValue"
          }
        }
      },
      "ClassHypothesis": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/ObjectClass"
        },
        "description": "A focal element: a subset of the frame Θ (a singleton like `uav_multirotor`, a\nunion like \"commercial quad,\" or Θ itself = ignorance). Normalized (sorted, unique)\nso set equality is structural. Serializes transparently as its class list."
      },
      "Classification": {
        "type": "object",
        "description": "Fused classification of a track: the voted object class, corroborated\nconfidence, disposition affiliation, and (for air hostiles) a MIL-STD-2525\nsymbol code. `affiliation` is an open vocabulary\n(`hostile`/`suspect`/`neutral`/`friend`/`assumed_friend`), so it stays a\n`String` rather than the narrower [`crate::Affiliation`] enum.",
        "required": [
          "type",
          "confidence",
          "affiliation"
        ],
        "properties": {
          "affiliation": {
            "type": "string"
          },
          "confidence": {
            "type": "number",
            "format": "double"
          },
          "std2525": {
            "type": "string",
            "description": "MIL-STD-2525 symbol id for air hostiles/suspects, else `null`. Always\nemitted (as `null` when absent) to match the original wire.",
            "nullable": true
          },
          "type": {
            "type": "string",
            "description": "Fused object class, e.g. `\"uav_multirotor\"`. Wire key is `type`."
          }
        }
      },
      "ClosureThresholds": {
        "type": "object",
        "description": "Bounds for the closure rule. Named `…Thresholds`, not `…Rule`: `ClosureRule` is\nthe rule *type* in `dome-core::rules`; this holds its numbers.",
        "required": [
          "tti_s",
          "cpa_m",
          "min_speed_mps"
        ],
        "properties": {
          "cpa_m": {
            "type": "number",
            "format": "double",
            "description": "…and within this closest point of approach."
          },
          "min_speed_mps": {
            "type": "number",
            "format": "double",
            "description": "…and at least this fast. `0.0` disables the speed gate."
          },
          "tti_s": {
            "type": "number",
            "format": "double",
            "description": "A contact within this time-to-impact is treated as actively closing."
          }
        }
      },
      "ClusterBy": {
        "type": "string",
        "description": "How the fleet is partitioned into groups.",
        "enum": [
          "axis",
          "proximity"
        ]
      },
      "CommandOrigin": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "source"
            ],
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "operator"
                ]
              },
              "who": {
                "type": "string",
                "nullable": true
              }
            }
          },
          {
            "type": "object",
            "required": [
              "plan_id",
              "source"
            ],
            "properties": {
              "plan_id": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "enum": [
                  "plan"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "source"
            ],
            "properties": {
              "line": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Level"
                  }
                ],
                "nullable": true
              },
              "source": {
                "type": "string",
                "enum": [
                  "autonomy"
                ]
              }
            }
          }
        ],
        "description": "Who issued a command — kept for the audit trail and to distinguish operator intent\nfrom autonomy.",
        "discriminator": {
          "propertyName": "source"
        }
      },
      "CommandParams": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "none"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "alt_m",
              "kind"
            ],
            "properties": {
              "alt_m": {
                "type": "number",
                "format": "float"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "altitude"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "lat",
              "lon",
              "kind"
            ],
            "properties": {
              "alt_m": {
                "type": "number",
                "format": "float",
                "nullable": true
              },
              "kind": {
                "type": "string",
                "enum": [
                  "point"
                ]
              },
              "lat": {
                "type": "number",
                "format": "double"
              },
              "lon": {
                "type": "number",
                "format": "double"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "points",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "route"
                ]
              },
              "on_complete": {
                "$ref": "#/components/schemas/OnComplete"
              },
              "points": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RoutePoint"
                }
              }
            }
          },
          {
            "type": "object",
            "required": [
              "lat",
              "lon",
              "radius_m",
              "kind"
            ],
            "properties": {
              "alt_m": {
                "type": "number",
                "format": "float",
                "nullable": true
              },
              "kind": {
                "type": "string",
                "enum": [
                  "area"
                ]
              },
              "lat": {
                "type": "number",
                "format": "double"
              },
              "lon": {
                "type": "number",
                "format": "double"
              },
              "radius_m": {
                "type": "number",
                "format": "double"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "track_id",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "target"
                ]
              },
              "track_id": {
                "type": "string"
              }
            }
          }
        ],
        "description": "The parameter supplied with a command, tagged so it cannot be confused with another\nverb's parameter.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "CommandSpec": {
        "type": "object",
        "description": "**One thing an integration can be told**, in the words of the protocol.\n\nA table rather than prose: `arm, take off, go there, come home` reads as a\nsentence and answers nothing an operator is asking. What they want to know is\nwhich verbs exist, what each one takes, and where the protocol documents it.\n\nThis is the INTEGRATION's vocabulary, not an asset's. What a given asset can be\ntold is still what that asset advertises — the frozen contract in\n`docs/specs/2026-07-30-asset-control-contract.md` — and this list is the\nsuperset the protocol makes possible.",
        "required": [
          "verb",
          "takes",
          "summary"
        ],
        "properties": {
          "summary": {
            "type": "string",
            "description": "One line. What it does, not how it is encoded."
          },
          "takes": {
            "type": "string",
            "description": "What it takes. `nothing` is a real answer and is said, not left blank."
          },
          "verb": {
            "type": "string",
            "description": "The verb as the protocol names it."
          }
        }
      },
      "CommandVerb": {
        "type": "string",
        "description": "What an asset can be told to do.\n\nGrouped into three [classes](CommandVerbClass) because they are **governed\ndifferently**: driving our own airframe is not the same decision as prosecuting\nsomething else, and collapsing them would either over-gate arming or under-gate a\nstrike.\n\n`Ord` is derived so a verb can sit in the ordered set an\n[`ActionSet`](crate::action::ActionSet) declares — the order is declaration order\n(lifecycle, navigation, tasking) and carries no meaning beyond a stable iteration.",
        "enum": [
          "arm",
          "disarm",
          "takeoff",
          "land",
          "return_to_base",
          "hold",
          "stop",
          "move_to",
          "follow_route",
          "orbit",
          "surveil",
          "follow",
          "intercept",
          "cue_sensor",
          "cue_release",
          "jam",
          "spoof",
          "designate",
          "alert",
          "transfer_commanding"
        ]
      },
      "CommandVerbClass": {
        "type": "string",
        "description": "How a verb is governed. `Effect` verbs pass through the decision loop's ROE gate;\n`Lifecycle` and `Navigation` are direct control of a platform that is already ours,\nand `Advisory` reaches no platform at all.\n\nThe gated class is named for what it does, not for the general word: lifecycle and\nnavigation act on the asset itself, an effect acts on **something else**, a track or\nan area, which is exactly why it is gated. `tasking` is accepted on the wire because\nthat is what this class was called before.",
        "enum": [
          "lifecycle",
          "navigation",
          "effect",
          "advisory"
        ]
      },
      "Commissioning": {
        "type": "object",
        "description": "**What an installer types into the device, and nothing they choose.**\n\nEvery value here is a fact about this deployment: the address the listener\nactually bound, the account we issued, the topic the vendor fixes. It is\nrendered as copy panels, never as form fields, because a field for any of them\nwould be a second source for one fact and the one that cannot change anything.\n\n**The password is not here.** It is returned exactly once, by the rotate that\nmints it; a route that would hand it back on demand is one that ends up in a\nbrowser cache and a screenshot.",
        "required": [
          "broker",
          "topic"
        ],
        "properties": {
          "account": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BrokerAccount"
              }
            ],
            "nullable": true
          },
          "broker": {
            "type": "string",
            "description": "`mqtt://dome.site:1883`."
          },
          "topic": {
            "type": "string",
            "description": "The topic the vendor publishes on and will not be told otherwise."
          },
          "workspace_id": {
            "type": "string",
            "description": "The workspace an operator binds the device to, when one is configured.",
            "nullable": true
          }
        }
      },
      "Condition": {
        "type": "object",
        "description": "One clause of a rule's `when`.",
        "required": [
          "fact",
          "op",
          "rhs"
        ],
        "properties": {
          "arg": {
            "type": "string",
            "description": "The zone kind or name a membership fact is asking about.",
            "nullable": true
          },
          "fact": {
            "$ref": "#/components/schemas/Fact"
          },
          "negated": {
            "type": "boolean",
            "description": "`true` inverts the whole clause. `and`/`or`/`not` beyond this is Tier 3."
          },
          "op": {
            "$ref": "#/components/schemas/Op"
          },
          "rhs": {
            "$ref": "#/components/schemas/Operand"
          }
        }
      },
      "Connection": {
        "type": "object",
        "description": "How an asset is reached, when it is reachable at all.\n\nThe address is a **protocol address** and not an identity: it never appears in\na path, and the asset-control contract is what says so. A sensor mast that is\ncommanded by nothing has no connection and that is an ordinary state.",
        "required": [
          "integration",
          "address"
        ],
        "properties": {
          "address": {
            "type": "string",
            "description": "The address as the link advertised it, for example `mav-udp0-3`. Taken\nfrom `GET /v1/discoveries`, never invented: a caller that guesses an\naddress is guessing which aircraft it is talking to."
          },
          "integration": {
            "type": "string",
            "description": "The protocol: `mavlink`, `mqtt`, `sapient`, `dji_cloud`."
          }
        }
      },
      "ConnectionRecipe": {
        "type": "object",
        "description": "One way a product can be connected.",
        "required": [
          "id",
          "label",
          "adapter",
          "transport",
          "provides"
        ],
        "properties": {
          "adapter": {
            "$ref": "#/components/schemas/AdapterId"
          },
          "direction": {
            "$ref": "#/components/schemas/LinkDirection"
          },
          "extra": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParamSpec"
            },
            "description": "**What this product needs that its adapter and transport cannot know.**\n\nAn emplaced acoustic node must say where it stands, or its detections\nlocalise nothing; a DJI aircraft on the same MQTT transport must not be\nasked. Neither the adapter nor the transport can tell those apart — the\n*product* is the only thing that knows it is bolted to a mast. Putting\n`lat`/`lon` on `AdapterId::Mqtt` would ask every drone for its permanent\nposition."
          },
          "id": {
            "type": "string",
            "description": "Unique within its product. Named in a manifest, so it is frozen once used."
          },
          "label": {
            "type": "string"
          },
          "note": {
            "type": "string",
            "description": "One line. Why you would pick this over its siblings.",
            "nullable": true
          },
          "provides": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Provides"
            }
          },
          "simulated": {
            "type": "boolean",
            "description": "**A simulated recipe is the same product reached through the simulator.**\nSame product id, same name, nothing to fill in — which is what makes a\nscenario and a live deployment the same document with one field changed."
          },
          "topic": {
            "type": "string",
            "description": "**The topic the vendor publishes on and will not be told otherwise.**\n\n`None` — the usual case — means the topic is **ours**, and it is the link's\nown subtree: `ingest/{token}/#`. A device we can configure gets pointed at\nthat, and then the topic names the link, the link names the adapter, and\nnothing is guessed from the topic's shape.\n\n`Some` is for a protocol that fixes it: a DJI Dock speaks\n`thing/product/{sn}/osd` because the Cloud API says so, and no setting on\nour side changes that. It costs nothing, because the topic was never what\nidentified the link — the **username is the token** either way.",
            "nullable": true
          },
          "transport": {
            "$ref": "#/components/schemas/TransportKind"
          },
          "unavailable": {
            "type": "string",
            "description": "Set when the recipe is real and we cannot serve it yet. Listed rather than\nhidden: somebody who owns the hardware needs to know the half exists.",
            "nullable": true
          }
        }
      },
      "ControlCapability": {
        "type": "object",
        "description": "A verb an asset declares it supports, and whether it can be used *right now*.\n\n`available: false` with a `reason` is how the console greys a button and explains it\n(take-off while already flying, arm with a failed pre-arm check) without inventing rules\nof its own.",
        "required": [
          "verb",
          "params",
          "class",
          "available"
        ],
        "properties": {
          "available": {
            "type": "boolean"
          },
          "class": {
            "$ref": "#/components/schemas/CommandVerbClass"
          },
          "params": {
            "$ref": "#/components/schemas/ParamKind"
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "verb": {
            "$ref": "#/components/schemas/CommandVerb"
          }
        }
      },
      "ControlKind": {
        "type": "string",
        "description": "**What an integration can be told to do**, if anything.\n\n`None` is a first-class answer and is rendered as *reports only* rather than a\ndisabled switch — an inert toggle invites the question of why it does nothing.",
        "enum": [
          "none",
          "vehicle",
          "task",
          "pointing"
        ]
      },
      "ControlLink": {
        "type": "string",
        "description": "The command protocol DomeCommand speaks to actuate a platform — a property of the\nairframe/autopilot, independent of whether a given instance is friend or foe.",
        "enum": [
          "mavlink",
          "psdk",
          "crtp",
          "sim",
          "mount",
          "none"
        ]
      },
      "ControlProfile": {
        "type": "object",
        "description": "How DomeCommand commands a platform: the command protocol + where its Execute loop\nruns. Additive to [`CapabilityProfile`]; a platform we only *observe* is\n`None`/`Offboard` and is never tasked.",
        "properties": {
          "autonomy": {
            "$ref": "#/components/schemas/OnboardAutonomy"
          },
          "link": {
            "$ref": "#/components/schemas/ControlLink"
          }
        }
      },
      "CooperativeId": {
        "type": "object",
        "description": "A cooperative identification record (ADS-B, Mode-S, Remote-ID, own-telemetry,\nfiled flight plan, RF library). Only [`source`](Self::source) is required; a\nproducer populates the fields its source carries and the rest are omitted.",
        "properties": {
          "asset_id": {
            "type": "string",
            "description": "Own-asset identifier (authenticated own-telemetry).",
            "nullable": true
          },
          "callsign": {
            "type": "string",
            "description": "Flight callsign / flight number.",
            "nullable": true
          },
          "confidence": {
            "type": "number",
            "format": "double",
            "description": "Confidence of the identification, `0.0..=1.0`."
          },
          "emitter_category": {
            "type": "string",
            "description": "ADS-B emitter category (used to recognise crewed aircraft).",
            "nullable": true
          },
          "icao_hex": {
            "type": "string",
            "description": "ICAO 24-bit address (ADS-B / Mode-S), hex string. Accepts the legacy `icao`\nspelling on the wire as well.",
            "nullable": true
          },
          "matched_flight_plan": {
            "type": "string",
            "description": "The filed flight plan this id correlated to, e.g. `\"SQ321 WSSS-EGLL\"`.",
            "nullable": true
          },
          "operator": {
            "type": "string",
            "description": "Operator registration string.",
            "nullable": true
          },
          "registered": {
            "type": "boolean",
            "description": "Whether the broadcast is from a registered / authorized source.",
            "nullable": true
          },
          "rf_signature_match": {
            "type": "string",
            "description": "Matched entry from the hostile/friendly RF library.",
            "nullable": true
          },
          "serial": {
            "type": "string",
            "description": "Remote-ID / UAS serial (CTA-2063-A).",
            "nullable": true
          },
          "source": {
            "$ref": "#/components/schemas/IdSource"
          },
          "squawk": {
            "type": "string",
            "description": "Transponder squawk code.",
            "nullable": true
          }
        }
      },
      "Coordination": {
        "type": "object",
        "description": "The mesh-coordination block for a group's tasking (bites only when group > 1).\nNo `comms_denied` — the collision-avoid floor is intrinsic to the Execute loop.",
        "required": [
          "leader",
          "stigmergy_ns",
          "min_sep_m"
        ],
        "properties": {
          "lane": {
            "type": "integer",
            "format": "int32",
            "nullable": true,
            "minimum": 0
          },
          "leader": {
            "$ref": "#/components/schemas/Leader"
          },
          "min_sep_m": {
            "type": "number",
            "format": "double"
          },
          "stigmergy_ns": {
            "type": "string"
          }
        }
      },
      "Corroboration": {
        "type": "object",
        "description": "Per-track corroboration summary — \"which independent modalities agree, how\nrecently\" — sized by **tracks, not by raw returns** (#64). Published on the\ntrack so the console reads the corroboration structure from the one picture\ninstead of subscribing to the raw `obs` stream to reconstruct it.",
        "required": [
          "independent_modalities"
        ],
        "properties": {
          "contributors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SensorContribution"
            },
            "description": "Every sensor that has contributed, with its modality and last-seen time."
          },
          "independent_modalities": {
            "type": "integer",
            "format": "int32",
            "description": "The count of **distinct modalities** — three radars agreeing is `1`. This\nis the number that actually drives confidence, precomputed so the console,\nsolver, and LLM do not each re-derive it.",
            "minimum": 0
          }
        }
      },
      "CotSignal": {
        "type": "object",
        "description": "Cursor-on-Target / TAK external track (already-fused, from a partner system).",
        "properties": {
          "affiliation": {
            "type": "string",
            "nullable": true
          },
          "callsign": {
            "type": "string",
            "nullable": true
          },
          "cot_type": {
            "type": "string",
            "nullable": true
          },
          "uid": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "CreateWorkspace": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "description": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "URL-safe short name. Derived from `name` when omitted, and disambiguated\nwith a numeric suffix if that is taken.",
            "nullable": true
          }
        }
      },
      "CredentialKind": {
        "type": "string",
        "description": "Which of the two credentials a request arrived with.",
        "enum": [
          "session",
          "api_key"
        ]
      },
      "CredentialScope": {
        "type": "string",
        "description": "**What a credential on this connection covers.**\n\nDerived, never declared per product: a product that gains a way to connect must\nnot also have to remember what that implies about secrets.\n\nThe one rule this encodes is *a per-device credential is only right when WE\nchoose the topic*. See `docs/specs/2026-08-20-the-integration-is-the-unit.md`.",
        "enum": [
          "none",
          "integration",
          "per_asset"
        ]
      },
      "Custody": {
        "type": "object",
        "description": "Track custody / lifecycle bookkeeping: update count, age, contributing\nsensors, corroboration, and coast (missed-update) steps.",
        "required": [
          "n_updates",
          "age_s",
          "last_update_t",
          "contributors",
          "corroboration_count",
          "coast_steps"
        ],
        "properties": {
          "age_s": {
            "type": "number",
            "format": "double"
          },
          "coast_steps": {
            "type": "integer",
            "format": "int64"
          },
          "contributors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "corroboration": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Corroboration"
              }
            ],
            "nullable": true
          },
          "corroboration_count": {
            "type": "integer",
            "minimum": 0
          },
          "end": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CustodyEnd"
              }
            ],
            "nullable": true
          },
          "last_update_t": {
            "type": "string"
          },
          "n_updates": {
            "type": "integer",
            "format": "int64"
          },
          "seen_modalities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Modality"
            },
            "description": "Distinct sensing modalities that have contributed to this track — the\n≥2-modality corroboration signal and the UI provenance chip. Additive\n(`#[serde(default)]`, omitted when empty), so legacy `track.v1` records\nparse to an empty list. Populated by fusion (W1); read by W6."
          }
        }
      },
      "CustodyCause": {
        "type": "string",
        "description": "Why fusion ended custody of a track (#281 phase 4). Emitted once, on the\nfinal frame of a track that leaves the picture, so a downstream consumer can\ntell a drone that went down from a drone that flew behind a building. The\nverdict is fusion's honest reading of its own evidence, never a kill claim:\nwhat to conclude from it belongs to the consumer.",
        "enum": [
          "occlusion_coast",
          "fell",
          "staleness_expired",
          "merged",
          "clutter_expired"
        ]
      },
      "CustodyEnd": {
        "type": "object",
        "description": "The evidence behind a [`CustodyCause`]: the last observed kinematics and\nhow long the track had gone unseen when the verdict fired. Carried so a\nconsumer can audit the verdict rather than trust it.",
        "required": [
          "cause",
          "vertical_rate_mps",
          "altitude_m",
          "unseen_s"
        ],
        "properties": {
          "altitude_m": {
            "type": "number",
            "format": "double",
            "description": "Altitude (m, ENU z) at the last observation."
          },
          "cause": {
            "$ref": "#/components/schemas/CustodyCause"
          },
          "unseen_s": {
            "type": "number",
            "format": "double",
            "description": "Seconds unobserved when the verdict fired."
          },
          "vertical_rate_mps": {
            "type": "number",
            "format": "double",
            "description": "Vertical rate (m/s, ENU +up) at the last observation. Negative = descent."
          }
        }
      },
      "DecisionConfig": {
        "type": "object",
        "description": "The operator-owned policy. One object, versioned, region-scoped, read by every\ntier of the decision loop.",
        "required": [
          "id"
        ],
        "properties": {
          "alerting": {
            "$ref": "#/components/schemas/AlertingPolicy"
          },
          "autonomy": {
            "$ref": "#/components/schemas/AutonomyPolicy"
          },
          "engagement": {
            "$ref": "#/components/schemas/EngagementPolicy"
          },
          "envelope": {
            "$ref": "#/components/schemas/Envelope"
          },
          "geography": {
            "$ref": "#/components/schemas/GeographyPolicy"
          },
          "id": {
            "type": "string"
          },
          "identification": {
            "$ref": "#/components/schemas/IdentificationPolicy"
          },
          "objective": {
            "$ref": "#/components/schemas/ObjectivePolicy"
          },
          "region_id": {
            "type": "string",
            "description": "Scoped to a region, or global (`None`).",
            "nullable": true
          },
          "version": {
            "type": "integer",
            "format": "int32",
            "description": "Bumped on every save and stamped onto every plan. A plan whose provenance\ncannot be reconstructed is not auditable, and an unauditable C-UAS decision\nis worthless after the fact.",
            "minimum": 0
          }
        }
      },
      "Deleted": {
        "type": "object",
        "description": "**What a delete answers.** One shape, because four copies of `{ deleted: true }`\nis four places for a client to learn a different key.",
        "required": [
          "deleted"
        ],
        "properties": {
          "deleted": {
            "type": "boolean"
          }
        }
      },
      "Detection": {
        "type": "object",
        "description": "A single sensor detection: the common cross-sensor estimate (class, position,\nvelocity, cooperative id) plus the modality-specific raw [`Signal`].",
        "required": [
          "signal"
        ],
        "properties": {
          "class": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ObjectClass"
              }
            ],
            "nullable": true
          },
          "class_conf": {
            "type": "number",
            "format": "float"
          },
          "cooperative_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CooperativeId"
              }
            ],
            "nullable": true
          },
          "enu": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Enu"
              }
            ],
            "nullable": true
          },
          "geo": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Geo"
              }
            ],
            "nullable": true
          },
          "kind": {
            "type": "string",
            "description": "Free-form detection kind label carried for continuity (`\"adsb_track\"`,\n`\"radar_plot\"`, `\"remote_id_broadcast\"`, …).",
            "nullable": true
          },
          "rel": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RelGeom"
              }
            ],
            "nullable": true
          },
          "signal": {
            "$ref": "#/components/schemas/Signal"
          },
          "vel_mps": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Vel"
              }
            ],
            "nullable": true
          }
        }
      },
      "DevLoginRequest": {
        "type": "object",
        "description": "`POST /v1/auth/dev-login`",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "Any address. It is created on first use, exactly as a real login would."
          }
        }
      },
      "DeviceToken": {
        "type": "object",
        "description": "**A device token this workspace issued on an integration**, where the\nprotocol has them: an MQTT sensor's username, and the topic subtree it\npublishes into. The password is in the secret store under\n`credential_name(token)` and is returned once, when it is minted.",
        "required": [
          "slug",
          "name",
          "token"
        ],
        "properties": {
          "direction": {
            "$ref": "#/components/schemas/LinkDirection"
          },
          "endpoint": {
            "type": "string",
            "description": "The address stored for it: our broker and its subtree, or the vendor's\nfixed topic."
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "The id an asset names in `spec.link`. Operator-chosen and stable."
          },
          "token": {
            "type": "string",
            "description": "Unguessable, minted once, and the whole of the device's identity on\nthe wire."
          }
        }
      },
      "DimensionsSpec": {
        "type": "object",
        "properties": {
          "height_m": {
            "type": "number",
            "format": "double"
          },
          "length_m": {
            "type": "number",
            "format": "double"
          },
          "weight_kg": {
            "type": "number",
            "format": "double"
          },
          "width_m": {
            "type": "number",
            "format": "double"
          },
          "wingspan_m": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "DiscoveredAsset": {
        "type": "object",
        "description": "A platform heard on a link that is not yet bound to a registry asset — offered to the\noperator to **adopt** in one click, instead of asking them to type an identifier for\nsomething that is already announcing itself.",
        "required": [
          "key",
          "link_id",
          "protocol",
          "name",
          "domain",
          "age_s"
        ],
        "properties": {
          "age_s": {
            "type": "number",
            "format": "double",
            "description": "Seconds since it was last heard."
          },
          "autopilot": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Autopilot"
              }
            ],
            "nullable": true
          },
          "domain": {
            "$ref": "#/components/schemas/AssetDomain"
          },
          "frame": {
            "$ref": "#/components/schemas/FrameType"
          },
          "key": {
            "type": "string",
            "description": "Stable identity derived from the link and the platform's own id, so adopting twice\nbinds the same asset rather than duplicating it."
          },
          "link_id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Suggested callsign."
          },
          "protocol": {
            "type": "string"
          }
        }
      },
      "DiscoveryPolicy": {
        "type": "string",
        "description": "**What may happen to something heard on a link.**\n\nDiscovery used to be an *emergent* property rather than a decision: whatever a\nMAVLink hub heard was offered, because `fleet::heard` read MAVLink hubs and\nnothing else could be discovered at all. Neither half was chosen by anybody, and\nthe second half is the reason a WESCAM or an acoustic node could never arrive\nthis way.\n\nThe variants are ordered by how much they let a stranger do:\n\n**`Adopt` on a link an adversary can transmit on is a way to register a hostile\nas one of ours.** A registry row *is* the friendly whitelist — that is the whole\ndesign, and there is no separate \"mark friendly\" step to catch it. So `Off` is\nthe default, `Adopt` is documented as bench-only, and neither is reachable\nwithout somebody having said so on the link itself.",
        "enum": [
          "off",
          "offer",
          "adopt"
        ]
      },
      "DomainSpecSchema": {
        "type": "object",
        "required": [
          "domain",
          "fields"
        ],
        "properties": {
          "domain": {
            "$ref": "#/components/schemas/CatalogueDomain"
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SpecField"
            }
          }
        }
      },
      "DroppedObservations": {
        "type": "object",
        "description": "Observations turned away at the pipeline's door because they came from a source\nthe current binding does not admit.\n\n**Counted, not just dropped.** Silently discarding data is how a real detection\ngoes missing during an exercise and nobody knows: an operator who can see \"412\nlive observations were turned away while this exercise ran\" knows the radar was\nworking; one who sees nothing has no way to tell that apart from a dead sensor.",
        "required": [
          "live",
          "simulated"
        ],
        "properties": {
          "live": {
            "type": "integer",
            "format": "int64",
            "description": "Live-source observations turned away because a run owns the picture.",
            "minimum": 0
          },
          "simulated": {
            "type": "integer",
            "format": "int64",
            "description": "Simulated observations turned away — no run is bound, or the bound run does\nnot admit them. A non-zero count here means a scenario is generating into a\npipeline that is not listening, which is a wiring fault worth seeing.",
            "minimum": 0
          }
        }
      },
      "Effect": {
        "oneOf": [
          {
            "type": "object",
            "description": "Identification — writes a track's affiliation. Never above the ceiling, and\nresolved by the lattice rather than by order.",
            "required": [
              "affiliation",
              "reason",
              "effect"
            ],
            "properties": {
              "affiliation": {
                "$ref": "#/components/schemas/Affiliation"
              },
              "effect": {
                "type": "string",
                "enum": [
                  "designate"
                ]
              },
              "reason": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "description": "A fire-control order / **battle drill** — produces a `PlannedAction`, and is\ngated by `authority.*`.\n\nCarries **what it is about** and **who does it**. The two hardcoded\nstrategies this replaces (`nearest_free_effector`, `the subject`) could not\nsay *the EO turret covering the tank farm*, and an action with no performer\nis not an action.",
            "required": [
              "verb",
              "effect"
            ],
            "properties": {
              "by": {
                "$ref": "#/components/schemas/Performer"
              },
              "count": {
                "type": "integer",
                "format": "int32",
                "description": "How many performers. Clamped by `engage.max_effectors_per_target`.",
                "minimum": 0
              },
              "effect": {
                "type": "string",
                "enum": [
                  "emit"
                ]
              },
              "priority": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/AlertSeverity"
                  }
                ],
                "nullable": true
              },
              "reason": {
                "type": "string"
              },
              "target": {
                "$ref": "#/components/schemas/TargetRef"
              },
              "verb": {
                "$ref": "#/components/schemas/CommandVerb"
              }
            }
          },
          {
            "type": "object",
            "description": "**Commander's guidance to the plan.** The solver chooses *who*; the rule\nstates *what must be true*.",
            "required": [
              "requirement",
              "effect"
            ],
            "properties": {
              "effect": {
                "type": "string",
                "enum": [
                  "require"
                ]
              },
              "requirement": {
                "$ref": "#/components/schemas/Requirement"
              }
            }
          },
          {
            "type": "object",
            "description": "Derive a different value for a constant, **for this pass only**. The\nconstant does not move — the derived value lives in the decision and dies\nwith the pass. Clamped by the doctrine bound, exactly as a `set` was.\n\nThis replaced `Effect::Set`-into-a-cascade-layer: a constant a rule can\nchange is not a constant, and the screen reading 85 while the engine used\n120 was the defect. Same authoring shape, different meaning — the alias\nkeeps rule sets stored before the rename readable.",
            "required": [
              "key",
              "value",
              "effect"
            ],
            "properties": {
              "effect": {
                "type": "string",
                "enum": [
                  "derive"
                ]
              },
              "key": {
                "$ref": "#/components/schemas/SettingKey"
              },
              "value": {
                "$ref": "#/components/schemas/SettingValue"
              }
            }
          },
          {
            "type": "object",
            "description": "The defended asset list.",
            "required": [
              "selector",
              "priority",
              "effect"
            ],
            "properties": {
              "effect": {
                "type": "string",
                "enum": [
                  "prioritize"
                ]
              },
              "priority": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "selector": {
                "$ref": "#/components/schemas/AssetSelector"
              }
            }
          },
          {
            "type": "object",
            "description": "ROE permission — **narrowing only**.",
            "required": [
              "effects",
              "effect"
            ],
            "properties": {
              "effect": {
                "type": "string",
                "enum": [
                  "deny"
                ]
              },
              "effects": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Action"
                }
              },
              "scope": {
                "$ref": "#/components/schemas/Scope"
              }
            }
          }
        ],
        "description": "What a rule may do. Four of these are the four behaviours a commander asked for\n— auto-assign · plan conditionally · raise an asset's importance · change the\nposture — and they are four different C2 acts that must not collapse into one.\n\nNote what is **absent**: there is no `Permit`. Permitting is a\n[`Grant`](crate::authority::Grant), not a rule effect. A rule may only take\nauthority away.",
        "discriminator": {
          "propertyName": "effect"
        }
      },
      "EffectKind": {
        "type": "string",
        "description": "**The five rungs** — what an order authorises the aircraft to *do* when it gets\nthere, as a discriminant.\n\nOrdered: each rung strictly contains the *observation rights* of the ones below it\n([`observation_rights_include`](Self::observation_rights_include)), and **none\nauthorises the ones above it** ([`authorises`](Self::authorises)). Those are two\ndifferent questions and they have two different names on purpose — conflating them\nis how *\"you may close and look\"* silently becomes *\"you may strike\"*.\n\nThis is the discriminant only. The parameterised form an order carries —\n`Identify { close_to_m, payloads }`, `Emit { profile }` — is the `Effect` the\nenvelope lands with in [#230](https://github.com/domecommand/platform/issues/230); a\n**capability** set names rungs, and the parameters come from the order.",
        "enum": [
          "observe",
          "identify",
          "emit",
          "capture",
          "terminal"
        ]
      },
      "EmissionSignature": {
        "type": "object",
        "description": "The physical emission signature of a platform — the raw signal levels each\nsensor modality keys off. Mirrors [`super::SignatureSpec`] but is the resolved,\nemission-facing shape carried on a [`CapabilityProfile`].",
        "properties": {
          "acoustic_db": {
            "type": "number",
            "format": "double",
            "description": "Acoustic sound-pressure level, dB at 100 m. Present ⇒ acoustically detectable\n(short range only).",
            "nullable": true
          },
          "ir": {
            "type": "string",
            "description": "IR / thermal signature level (`\"low\"` / `\"medium\"` / `\"high\"`). Present ⇒\nan IR/thermal sensor can detect it.",
            "nullable": true
          },
          "rcs_dbsm": {
            "type": "number",
            "format": "double",
            "description": "Radar cross-section, dBsm. Drives radar detectability / range. Absent ⇒ the\nplatform cannot be modeled as a radar contact.",
            "nullable": true
          },
          "rf_bands": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "RF emission bands (e.g. `\"2.4 GHz\"`, `\"5.8 GHz\"`). The bands an RF/DF sensor\nwould report when the platform is not radio-silent."
          }
        }
      },
      "Energy": {
        "type": "object",
        "description": "Energy remaining, however the platform measures it.",
        "properties": {
          "amps": {
            "type": "number",
            "format": "float",
            "description": "Current draw, amps.",
            "nullable": true
          },
          "consumed_mah": {
            "type": "integer",
            "format": "int32",
            "description": "Charge drawn this sortie, mAh.",
            "nullable": true
          },
          "pct": {
            "type": "number",
            "format": "float",
            "description": "Remaining charge, percent.",
            "nullable": true
          },
          "volts": {
            "type": "number",
            "format": "float",
            "description": "Pack voltage, volts.",
            "nullable": true
          }
        }
      },
      "EngagementPolicy": {
        "type": "object",
        "description": "When the machine may act, and what needs a human.",
        "required": [
          "assign_below_tti_s",
          "closure",
          "max_effectors_per_target"
        ],
        "properties": {
          "assign_below_tti_s": {
            "type": "number",
            "format": "double",
            "description": "Assign a defender when time-to-impact drops below this."
          },
          "closure": {
            "$ref": "#/components/schemas/ClosureThresholds"
          },
          "max_effectors_per_target": {
            "type": "integer",
            "format": "int32",
            "description": "Do not commit more than this many effectors to one target.",
            "minimum": 0
          }
        }
      },
      "EngagementState": {
        "type": "object",
        "description": "The current engagement/mission state — a queryable snapshot served at\n`GET /api/threats`. Durable across ticks (maintained by the `ThreatManager`),\nunlike the ephemeral picture snapshot.",
        "required": [
          "t",
          "generation",
          "threats"
        ],
        "properties": {
          "candidate_plans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Plan"
            },
            "description": "Candidate plans authored asynchronously by the planning service (solver +\nLLM review) — 1..3 alternatives the operator chooses between. Filled in by\nthe API layer from the runtime's authored set; empty until authoring runs."
          },
          "gate_refusals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GateRefusal"
            },
            "description": "Pairings the release table WITHHELD this tick, each naming the effect,\nthe gate and its source. A pairing that silently did not happen is\nindistinguishable from a bug. `Confirm` and `TwoPerson` are NOT here:\ndoctrine delegated those to the operator rather than refusing them."
          },
          "generation": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "pending_releases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PendingRelease"
            },
            "description": "Pairings the table released under `Gate::Notify`, each carrying its own\nwindow. The runtime's clock advances these; nothing here fires."
          },
          "plan": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Plan"
              }
            ],
            "nullable": true
          },
          "plan_run": {
            "$ref": "#/components/schemas/PlanRunState"
          },
          "t": {
            "type": "number",
            "format": "double"
          },
          "threats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Threat"
            },
            "description": "Active + recently-resolved threats (resolved kept briefly for the log)."
          }
        }
      },
      "EntityFilter": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "named"
            ],
            "properties": {
              "named": {
                "type": "string",
                "description": "This exact one, by id or callsign."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "role"
            ],
            "properties": {
              "role": {
                "type": "string",
                "description": "`interceptor` · `jammer` · `observer` · `multi`."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "modality"
            ],
            "properties": {
              "modality": {
                "type": "string",
                "description": "`eo` · `ir` · `radar` · `rf` · `acoustic` — sensors."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "in_zone"
            ],
            "properties": {
              "in_zone": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "covering_asset"
            ],
            "properties": {
              "covering_asset": {
                "type": "string",
                "description": "Within reach of a protected asset — the coverage question, asked directly."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "within_m"
            ],
            "properties": {
              "within_m": {
                "type": "object",
                "required": [
                  "of",
                  "m"
                ],
                "properties": {
                  "m": {
                    "type": "number",
                    "format": "double"
                  },
                  "of": {
                    "$ref": "#/components/schemas/Anchor"
                  }
                }
              }
            }
          },
          {
            "type": "string",
            "description": "Not already committed to something else.",
            "enum": [
              "idle"
            ]
          },
          {
            "type": "object",
            "required": [
              "min_endurance"
            ],
            "properties": {
              "min_endurance": {
                "type": "number",
                "format": "double"
              }
            }
          }
        ],
        "description": "How to narrow a pool. **The four ways a site names things**, plus availability."
      },
      "EntityPool": {
        "type": "string",
        "description": "Which population to draw a performer from.",
        "enum": [
          "effectors",
          "sensors",
          "fleet"
        ]
      },
      "EntityQuery": {
        "type": "object",
        "description": "A performer query — the dynamic case.",
        "required": [
          "pool"
        ],
        "properties": {
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EntityFilter"
            }
          },
          "pick": {
            "$ref": "#/components/schemas/Pick"
          },
          "pool": {
            "$ref": "#/components/schemas/EntityPool"
          }
        }
      },
      "EntityRef": {
        "oneOf": [
          {
            "type": "object",
            "description": "A maintained track.",
            "required": [
              "track_id",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "track"
                ]
              },
              "track_id": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "description": "A place on the ground: a point, or a circle when the verb takes one.",
            "required": [
              "lat",
              "lon",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "place"
                ]
              },
              "lat": {
                "type": "number",
                "format": "double"
              },
              "lon": {
                "type": "number",
                "format": "double"
              },
              "radius_m": {
                "type": "number",
                "format": "double",
                "nullable": true
              }
            }
          }
        ],
        "description": "What a task is addressed *at*, as distinct from the asset it is addressed *to*.\n\nDerived from [`CommandParams`] rather than supplied beside it, so the two cannot\ndisagree about which track an intercept is against. It exists as its own field\nbecause the ledger's common question — \"what is tasked against T-00481\" — should not\nrequire decoding a parameter union.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "Enu": {
        "type": "object",
        "description": "Local East-North-Up position, metres, about the deployment origin.",
        "required": [
          "x",
          "y",
          "z"
        ],
        "properties": {
          "x": {
            "type": "number",
            "format": "double"
          },
          "y": {
            "type": "number",
            "format": "double"
          },
          "z": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Envelope": {
        "oneOf": [
          {
            "type": "string",
            "description": "Observe, manoeuvre, watch and shadow. No denial, no kinetic.",
            "enum": [
              "civil"
            ]
          },
          {
            "type": "string",
            "description": "The whole ladder.",
            "enum": [
              "military"
            ]
          },
          {
            "type": "object",
            "required": [
              "custom"
            ],
            "properties": {
              "custom": {
                "type": "object",
                "description": "The awkward real case, such as a civil site holding an RF-denial licence.",
                "required": [
                  "ceiling"
                ],
                "properties": {
                  "ceiling": {
                    "$ref": "#/components/schemas/Level"
                  }
                }
              }
            }
          }
        ],
        "description": "What exists at this deployment at all, as a package rather than a number.\n\nAn action above the ceiling is **not gated, it is absent**: no card, no button, no\ndraft, and the capability list says why. Set by deployment authority and changed\nrarely; it is not the operator's dial."
      },
      "Environment": {
        "type": "object",
        "description": "One environment, as an operator reads it.",
        "required": [
          "id",
          "kind",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "description": "`live` | `lite` | `airsim`."
          },
          "name": {
            "type": "string"
          },
          "simulated": {
            "type": "boolean",
            "description": "**Whether anything here is real.**\n\nA fact on the row rather than a comparison against `kind`, because it is\nthe safety gate: enabling a real protocol inside a simulated environment\nis refused, and a rule that depends on string matching in three places is\na rule that eventually disagrees with itself."
          }
        }
      },
      "EoDetection": {
        "type": "object",
        "description": "Electro-optical detection with a pixel bounding box (EO camera).",
        "properties": {
          "bbox_px": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "description": "Pixel bounding box `[x, y, w, h]`.",
            "nullable": true
          },
          "detector": {
            "type": "string",
            "nullable": true
          },
          "pixel_conf": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "track_px": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "description": "In-frame pixel track centroid `[x, y]`.",
            "nullable": true
          }
        }
      },
      "EstimationFeature": {
        "type": "object",
        "description": "Per-track estimation overlay: distance from base, closing rate, and ETA.\n\nWire shape matches the frontend `WireEstimationOverlay`. Derived per tick from\nthe track's ENU position + velocity — one number, shared by the map overlay,\nthe solver, and the LLM command context (no independent recomputation).",
        "required": [
          "track_id",
          "distance_from_base_m",
          "closing_rate_mps"
        ],
        "properties": {
          "closing_rate_mps": {
            "type": "number",
            "format": "double",
            "description": "Negative = closing on the protected asset (per the frozen overlay contract)."
          },
          "distance_from_base_m": {
            "type": "number",
            "format": "double"
          },
          "eta_s": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "nearest_protected_asset_id": {
            "type": "string",
            "nullable": true
          },
          "track_id": {
            "type": "string"
          }
        }
      },
      "EventProvenance": {
        "type": "object",
        "description": "The reasons an event happened, carried **on the event** — not in a sibling\nstream (`06` §6, A5). Additive to every frozen schema: absent on events from\nbefore the migration, present on everything a decision causes.",
        "required": [
          "ruleset_version",
          "constants_version",
          "autonomy",
          "envelope"
        ],
        "properties": {
          "autonomy": {
            "$ref": "#/components/schemas/AutonomyPolicy"
          },
          "because": {
            "type": "string",
            "description": "The facts that matched, in the words the rules tested."
          },
          "constants_version": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "decided_by": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The rules that caused it."
          },
          "envelope": {
            "$ref": "#/components/schemas/Envelope"
          },
          "gate": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Gate"
              }
            ],
            "nullable": true
          },
          "released_by": {
            "type": "string",
            "description": "Who released it — an operator id, or absent for an AUTO act.",
            "nullable": true
          },
          "ruleset_version": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          }
        }
      },
      "Existence": {
        "type": "object",
        "description": "Channel-1 existence evidence on a track: the running LLR `score`, its historical\npeak `max_score` (deletion is relative to the peak), and the derived `status`.",
        "required": [
          "score",
          "max_score",
          "status"
        ],
        "properties": {
          "max_score": {
            "type": "number",
            "format": "double",
            "description": "Highest score this track has reached (for relative deletion)."
          },
          "score": {
            "type": "number",
            "format": "double",
            "description": "Accumulated log-likelihood-ratio track score."
          },
          "status": {
            "$ref": "#/components/schemas/TrackStatus"
          }
        }
      },
      "Fact": {
        "type": "string",
        "description": "A fact a rule may test. **Closed**: not arbitrary expressions, a fixed set of\ntyped predicates. That is what keeps the purity contract enforceable rather\nthan aspirational — there is no fact for \"the current time of day\", so no rule\ncan be written that reads a wall clock.",
        "enum": [
          "track_affiliation",
          "track_confidence",
          "track_modalities",
          "track_range_m",
          "track_closure_ms",
          "track_tti_s",
          "track_alt_m",
          "track_speed_ms",
          "track_spoof_risk",
          "track_coop_id",
          "track_registered_as",
          "track_do_not_engage",
          "track_visual_id",
          "track_inside_zone",
          "track_inside_named_zone",
          "track_dwell_s",
          "track_class",
          "track_sustained_speed_ms",
          "track_wandering",
          "track_rf_silent",
          "track_manned_aircraft",
          "track_matched_flight_plan",
          "asset_endurance_frac",
          "asset_link_age_s",
          "asset_committed",
          "asset_under_attack",
          "asset_capability",
          "picture_hostile_count",
          "picture_track_count",
          "picture_generation",
          "mission_elapsed_s"
        ]
      },
      "FeedRenderer": {
        "type": "string",
        "description": "How a sensing thing's feed is **rendered**.\n\nU4's feed dock selects its renderer by this value and by nothing else. The\numbrella's decision 4 — *\"every sensing thing has a feed and the renderer is\nchosen by modality\"* — stops being prose here and becomes a type.",
        "enum": [
          "video",
          "ppi",
          "bearing",
          "report",
          "none"
        ]
      },
      "Figure": {
        "oneOf": [
          {
            "type": "number",
            "format": "double"
          },
          {
            "type": "string"
          }
        ],
        "description": "A figure that is a number for most vendors and a string for some. A radar's\nfield of view is `120 x 80`; a camera's thermal resolution is `640x512`."
      },
      "Figures": {
        "type": "object",
        "description": "Every specification key any domain uses, typed. A platform carries only its\nown; the rest are `None`, and `skip_serializing_if` keeps them off the wire.\nFlattened onto [`CatalogSpec`](super::CatalogSpec), so `endurance_min` on a\nhand-written row, on a catalogue entry and on a workspace's own entry is the\nsame key read by the same code.",
        "properties": {
          "bands": {
            "type": "string",
            "nullable": true
          },
          "battery_h": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "bearing_accuracy_deg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "ceiling_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "classification": {
            "type": "string",
            "nullable": true
          },
          "color": {
            "type": "string",
            "nullable": true
          },
          "cruise_kn": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "cruise_mps": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "depth_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "detection_range_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "dynamic_range_db": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "effective_range_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "endurance_h": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "endurance_min": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "fov_deg": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Figure"
              }
            ],
            "nullable": true
          },
          "ip_rating": {
            "type": "string",
            "nullable": true
          },
          "length_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "link_range_km": {
            "type": "number",
            "format": "double",
            "description": "How far its control link reaches. Vendors quote this as \"range\" for ground\nvehicles, and it must not answer the performance question `Range`.",
            "nullable": true
          },
          "max_mps": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "max_speed_kn": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "method": {
            "type": "string",
            "nullable": true
          },
          "mtow_kg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "net_range_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "operating_time_h": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "outputs": {
            "type": "string",
            "nullable": true
          },
          "payload_kg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "range_detail": {
            "type": "string",
            "nullable": true
          },
          "range_km": {
            "type": "number",
            "format": "double",
            "description": "How far it goes and returns. Never a radio range: that is `link_range_km`.",
            "nullable": true
          },
          "range_nm": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "reach_detail": {
            "type": "string",
            "nullable": true
          },
          "reach_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "speed_kn": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "thermal": {
            "type": "string",
            "nullable": true
          },
          "tow_capacity_kg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "tracks": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "tx_power_w": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "update_hz": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "update_s": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "weight_kg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "wind_ms": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        }
      },
      "FilterVerdict": {
        "type": "string",
        "description": "The tracker's consistency verdict — deliberately a small enum, not a score, so\nthe UI can render a state rather than a number needing interpretation.",
        "enum": [
          "insufficient",
          "consistent",
          "over_confident",
          "under_confident"
        ]
      },
      "FlightEnvelope": {
        "type": "object",
        "description": "Whole-life kinematic envelope verdicts (#281 phase 5, research\n`2026-08-25-what-each-sensor-tells-you.md` §6). Fusion accumulates a track's\nobserved path (never coasted extrapolation) and emits this block **only when\nat least one verdict holds**, so a track inside every envelope serialises\nbyte-identically to before this field existed. Legacy `track.v1` records\nparse to `None`.\n\nThe verdicts are cheap discriminations, not classifications:\n\n- `sustained_fast`: ground speed never observed below the Class I ceiling\nplus margin (35 m/s = ~27 m/s ceiling x 1.3) across at least 10 s: the\ntrack cannot be a small UAS;\n- `wandering`: sub-envelope speed (ceiling under 20 m/s, the measured bird\ncruise band) with a bent path (straightness under 0.5) over at least 20 s\nand real ground covered, the corroboration the bird rule needs;\n- `static_over_life`: net displacement under 25 m over at least 30 s at\nnear-zero speed, the clutter signature.\n\nThe thresholds live in `dome-core::fusion::constants` beside the code that\napplies them.",
        "required": [
          "sustained_fast",
          "wandering",
          "static_over_life",
          "floor_speed_mps",
          "ceiling_speed_mps",
          "net_displacement_m",
          "straightness",
          "observed_s"
        ],
        "properties": {
          "ceiling_speed_mps": {
            "type": "number",
            "format": "double",
            "description": "Fastest observed windowed ground speed (m/s)."
          },
          "floor_speed_mps": {
            "type": "number",
            "format": "double",
            "description": "Slowest observed windowed ground speed (m/s) since the second update."
          },
          "net_displacement_m": {
            "type": "number",
            "format": "double",
            "description": "Net displacement (m) from the first to the last observed position."
          },
          "observed_s": {
            "type": "number",
            "format": "double",
            "description": "Observed seconds the verdicts cover (first to last observation)."
          },
          "static_over_life": {
            "type": "boolean",
            "description": "Went nowhere across the whole observed life."
          },
          "straightness": {
            "type": "number",
            "format": "double",
            "description": "Net displacement over path length, 0..1. 1.0 is a straight run."
          },
          "sustained_fast": {
            "type": "boolean",
            "description": "Ground speed never observed below the small-UAS ceiling with margin."
          },
          "wandering": {
            "type": "boolean",
            "description": "Slow and bent: sub-envelope speed with no straight-line ingress."
          }
        }
      },
      "FlightMode": {
        "type": "string",
        "description": "The vehicle's active flight mode, normalized across autopilots (PX4/ArduPilot modes\nand DJI/sim states collapse into this shared vocabulary).",
        "enum": [
          "unknown",
          "manual",
          "guided",
          "auto",
          "loiter",
          "rtl",
          "land"
        ]
      },
      "ForceSource": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "synthetic"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Take a COPY of the named registry assets — their models, payloads and\nperformance — and fly the copies. The originals are untouched and their\nlive telemetry does not enter the run.",
            "required": [
              "asset_ids",
              "kind"
            ],
            "properties": {
              "asset_ids": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "kind": {
                "type": "string",
                "enum": [
                  "copy_of"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Model against the real assets themselves: their live telemetry enters the\nrun and commands reach them. A rehearsal with real aircraft in the air.\n\nDeliberately last, deliberately explicit, and deliberately named for what it\ndoes. A run must **ask** for this — it is never arrived at by leaving a field\nblank, which is the whole reason [`Synthetic`](ForceSource::Synthetic) is the\ndefault.\n\nTwo consequences worth stating rather than discovering. It is the only\nconfiguration in which a command reaches a transport, so the banner renders\nit loud. And it is **not reproducible**: half its input is the actual world,\nso the same scenario run twice does not fuse the same way. That is a property\nof what it models, not a defect in it.",
            "required": [
              "asset_ids",
              "kind"
            ],
            "properties": {
              "asset_ids": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "kind": {
                "type": "string",
                "enum": [
                  "live"
                ]
              }
            }
          }
        ],
        "description": "What a scenario models its own force from. Part of\n[`Scenario`](crate::scenario::Scenario), defaulting to [`Synthetic`].\n\n[`Synthetic`]: ForceSource::Synthetic",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "FrameType": {
        "type": "string",
        "description": "The airframe class (from `HEARTBEAT.mavtype`), used for the map glyph and to decide\nwhich commands make sense.",
        "enum": [
          "quadrotor",
          "hexarotor",
          "octorotor",
          "fixed_wing",
          "vtol",
          "helicopter",
          "ground",
          "marine",
          "other"
        ]
      },
      "FusionExplain": {
        "type": "object",
        "description": "One scan of the tracker, explained: every track's before-and-after, every\nmeasurement, and the measurements no track took.",
        "required": [
          "schema",
          "t",
          "tick",
          "algorithm",
          "tracks"
        ],
        "properties": {
          "algorithm": {
            "type": "string",
            "description": "The tracker in force, as `GET /api/fusion/health` names it."
          },
          "held": {
            "type": "boolean",
            "description": "`true` when the world was HELD (paused) for this scan. A paused simulator\nkeeps re-emitting its last batch so custody holds, and the tracker keeps\nticking on it, so the stream carries scans the archive refuses. The flag\nis the archive's own guard written on the frame: a replay ring on the\nclient refuses a held scan the same way, and the equal-time heuristic it\nonce used (which threw away distinct scans of one instant) is not needed.\nAbsent on the wire when `false`."
          },
          "schema": {
            "type": "string",
            "description": "`fusion-explain.v1`."
          },
          "t": {
            "type": "string",
            "description": "The window clock at this scan, ISO 8601 on the observations' own axis."
          },
          "tick": {
            "type": "integer",
            "format": "int64",
            "description": "The tracker's monotonic scan counter, so two explanations order without\ncomparing float time.",
            "minimum": 0
          },
          "tracks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TrackExplain"
            },
            "description": "Every track the tracker holds after this scan, emitted or withheld."
          },
          "unassociated": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MeasurementContribution"
            },
            "description": "Measurements no track took: a lone ray with no crossing partner, a ray\noutside every gate, a plot suppressed as a duplicate. Each carries the\nreason. These are the sensor detections that arrive before there is a\ntrack to attach to, which is the part of the story the picture cannot show."
          }
        }
      },
      "FusionExplainWindow": {
        "type": "object",
        "description": "The reply to `GET /api/fusion/explain?scans=N`: the newest `N` scans the\nring holds, oldest first, so a reader that folds a hold window has one to\nfold from its first read.",
        "required": [
          "scans",
          "ticks"
        ],
        "properties": {
          "scans": {
            "type": "integer",
            "description": "The number asked for, after clamping to the ring's depth. `ticks` is\nshorter only when the ring holds fewer.",
            "minimum": 0
          },
          "ticks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FusionExplain"
            }
          }
        }
      },
      "FusionHealth": {
        "type": "object",
        "description": "Live fusion health. Served at `GET /api/fusion/health` and rendered in the\nconsole so a degrading tracker is visible *while* it degrades.",
        "required": [
          "algorithm",
          "tracks",
          "tracks_born",
          "tracks_born_per_object",
          "nis_mean",
          "nis_over_confident_frac",
          "nis_samples",
          "verdict",
          "bearings_withheld"
        ],
        "properties": {
          "algorithm": {
            "type": "string",
            "description": "The association algorithm actually running (`imm-kf-gnn` / `greedy-gnn-alphabeta` / `mahalanobis-hungarian-swarm`)."
          },
          "bearings_withheld": {
            "type": "integer",
            "format": "int64",
            "description": "Bearing-only observations withheld because they had no triangulation partner\nthis window — they are honestly dropped rather than ghosted at the origin.",
            "minimum": 0
          },
          "duplicate_pairs": {
            "type": "integer",
            "format": "int64",
            "description": "Confirmed non-cooperative track pairs holding a STANDING duplicate right\nnow: co-located, co-moving and class-compatible this instant, with no\nregard for how they got there or how long they have sat like this.\n\n`tracks_born_per_object` is a birth RATE, so it goes quiet the moment a\nduplicate stops minting fresh ids — a pair that folded from an old\nassociation failure and then just sits there, two tracks on one object,\nis invisible to it. This field re-checks the whole confirmed picture\nevery call, so a duplicate the tracker is quietly holding shows up here\neven when nothing new is being born. `#[serde(default)]` so an older\nclient reading the frozen wire before this field existed still parses.",
            "minimum": 0
          },
          "duplicate_pairs_per_track": {
            "type": "number",
            "format": "double",
            "description": "`duplicate_pairs` normalised by the current track count, so the number\nreads the same whether the picture holds 3 tracks or 300. `0.0` on an\nempty picture. `#[serde(default)]` for the same wire-compatibility\nreason as `duplicate_pairs`."
          },
          "nis_mean": {
            "type": "number",
            "format": "double",
            "description": "Mean NIS. Near 2.0 = honest covariance (2-D measurement ⇒ 2 dof)."
          },
          "nis_over_confident_frac": {
            "type": "number",
            "format": "double",
            "description": "Fraction of NIS samples above the χ² band."
          },
          "nis_samples": {
            "type": "integer",
            "format": "int64",
            "description": "NIS samples taken (updates). Below ~20 the verdict is `Insufficient`.",
            "minimum": 0
          },
          "tracks": {
            "type": "integer",
            "format": "int64",
            "description": "Confirmed tracks in the current picture.",
            "minimum": 0
          },
          "tracks_born": {
            "type": "integer",
            "format": "int64",
            "description": "Tracks minted since the run began (lifetime total).",
            "minimum": 0
          },
          "tracks_born_per_object": {
            "type": "number",
            "format": "double",
            "description": "**Recent** tracks started per object held — a RATE over the last ~minute, not\na lifetime total.\n\nA tracker holding steady custody of a stable picture starts nothing new, so\nthis sits near **0**. It rises toward and past **1** when the tracker keeps\nminting fresh ids for objects it is *already holding* — the duplicate-track\ndefect. (An earlier cut divided LIFETIME births by the CURRENT track count and\nread 70× on a healthy tracker the moment a wave ended; a rate keeps the\nquestion well-posed.)"
          },
          "verdict": {
            "$ref": "#/components/schemas/FilterVerdict"
          }
        }
      },
      "Gate": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "gate"
            ],
            "properties": {
              "gate": {
                "type": "string",
                "enum": [
                  "not_permitted"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "gate"
            ],
            "properties": {
              "gate": {
                "type": "string",
                "enum": [
                  "asks"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "It happens now. `stop_within` seconds to take it back, `0` for none.",
            "required": [
              "stop_within",
              "gate"
            ],
            "properties": {
              "gate": {
                "type": "string",
                "enum": [
                  "runs"
                ]
              },
              "stop_within": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              }
            }
          }
        ],
        "description": "What the gate answers. Three cases and no others.",
        "discriminator": {
          "propertyName": "gate"
        }
      },
      "GateRefusal": {
        "type": "object",
        "description": "A pairing the release table refused.\n\nTyped rather than a `serde_json::Value`, because [`EngagementState`] is\nserved by `GET /api/threats` and broadcast on a watch channel, which makes it\ndomain code by the rule in CLAUDE.md.\n\nOnly [`Gate::Withheld`] produces one. `Confirm` and `TwoPerson` are doctrine\ndelegating the decision to the operator, which is the ordinary case and is\nalready visible as a proposed assignment; filing that as a refusal would make\nthe run record say doctrine refused what doctrine delegated.\n\n[`EngagementState`]: crate::engagement::EngagementState\n[`Gate::Withheld`]: crate::authority::Gate::Withheld",
        "required": [
          "effect",
          "gate",
          "source",
          "target_track_id",
          "asset_id",
          "independent_sensor_types"
        ],
        "properties": {
          "asset_id": {
            "type": "string"
          },
          "effect": {
            "type": "string"
          },
          "gate": {
            "type": "string"
          },
          "independent_sensor_types": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "source": {
            "type": "string"
          },
          "target_track_id": {
            "type": "string"
          }
        }
      },
      "GeneratedPolicy": {
        "type": "object",
        "description": "A named, ready-to-assign policy = a base strategy id + concrete param overrides\n+ presentation. Materializes to a [`StrategyRef`] for storage, or resolves to a\ntree / solver config via the catalogs.",
        "required": [
          "id",
          "label",
          "description",
          "kind",
          "base",
          "params"
        ],
        "properties": {
          "base": {
            "type": "string",
            "description": "The base strategy this specializes."
          },
          "description": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/PolicyKind"
          },
          "label": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "additionalProperties": {
              "type": "number",
              "format": "double"
            }
          }
        }
      },
      "Geo": {
        "type": "object",
        "description": "WGS84 geodetic position.",
        "required": [
          "lat",
          "lon",
          "alt_m"
        ],
        "properties": {
          "alt_m": {
            "type": "number",
            "format": "double"
          },
          "lat": {
            "type": "number",
            "format": "double"
          },
          "lon": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "GeoPoint": {
        "type": "object",
        "description": "A point target for a reposition/surveil instruction.",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "alt_m": {
            "type": "number",
            "format": "double"
          },
          "lat": {
            "type": "number",
            "format": "double"
          },
          "lon": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "GeographyPolicy": {
        "type": "object",
        "description": "Where the rules apply. The zones themselves come from the zones table (#62);\nthis says what each **category means**, which is the operator's decision.",
        "required": [
          "isr_priority_weight"
        ],
        "properties": {
          "isr_priority_weight": {
            "type": "number",
            "format": "double",
            "description": "Coverage weight applied inside ISR-priority zones."
          }
        }
      },
      "GpsFix": {
        "type": "string",
        "description": "GNSS fix quality, collapsed from MAVLink `GPS_FIX_TYPE` to the states we act on.",
        "enum": [
          "no_fix",
          "fix2d",
          "fix3d",
          "dgps",
          "rtk"
        ]
      },
      "Group": {
        "type": "object",
        "description": "A named element with an explicit roster (Alpha, Bravo, …). 1..N drones — a\ngroup of 1 is a single drone (there is no separate per-drone concept).",
        "required": [
          "callsign",
          "members"
        ],
        "properties": {
          "callsign": {
            "type": "string"
          },
          "members": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Grouping": {
        "type": "object",
        "description": "How the fleet was partitioned — a planner knob carried on the Plan/Mission.",
        "required": [
          "cluster_by",
          "max_group_size",
          "min_groups"
        ],
        "properties": {
          "cluster_by": {
            "$ref": "#/components/schemas/ClusterBy"
          },
          "max_group_size": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "min_groups": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          }
        }
      },
      "Guidance": {
        "type": "object",
        "description": "The lifecycle of one assignment is [`TaskStatus`](crate::task::TaskStatus), and\nthere is no second one.\n\n`TaskStatus` was that second one: `proposed | approved | en_route |\non_station | complete | aborted`, a ladder beside the task's own. Two of its\nrungs had no writer in the pipeline at all — nothing ever set `en_route` or\n`on_station` outside a test — so the console rendered a distinction the server\ncould not make, and `aborted` could not tell \"refused\" from \"taken back\". The\nmapping, for anything reading a stored plan:\n\n| was | is |\n|---|---|\n| `proposed` | `proposed` |\n| `approved` | `issued` |\n| `en_route`, `on_station` | `executing` |\n| `complete` | `complete` |\n| `aborted` | `reverted`, or `refused` where the gate is what stopped it |\n\nLikewise `CommandOrigin` — who put this on the wire — is\n[`CommandOrigin`](crate::command::CommandOrigin), which says the same thing for\nan operator's click and now carries the autonomy line the way\n`CommandOrigin::Autonomy` did.\nGuidance for an assignment — the computed aim recomputed each replan cycle.\nThe plan biases *which* target; guidance is *how* to get there. Kept minimal\nfor now (predicted intercept point + ETA); lead/PN detail lives in the sim.",
        "properties": {
          "cost_s": {
            "type": "number",
            "format": "double",
            "description": "What the objective was charged for this pairing, in seconds of equivalent\nETA: the cost-matrix cell the assignment solver actually chose\n(`dome-solvers/src/intercept.rs`). The one cost in the system with a\ndefinition, so it is the one a card may show.",
            "nullable": true
          },
          "eta_s": {
            "type": "number",
            "format": "double",
            "description": "Estimated time to reach/intercept (s).",
            "nullable": true
          },
          "intercept_enu": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Enu"
              }
            ],
            "nullable": true
          },
          "pk": {
            "type": "number",
            "format": "double",
            "description": "The effector's probability of defeating this target, from its profile.\nAbsent where nothing priced the shot (the greedy pairing, an operator's pin).",
            "nullable": true
          }
        }
      },
      "IdSource": {
        "oneOf": [
          {
            "type": "string",
            "enum": [
              "Adsb"
            ]
          },
          {
            "type": "string",
            "enum": [
              "AsterixModeS"
            ]
          },
          {
            "type": "string",
            "enum": [
              "RemoteId"
            ]
          },
          {
            "type": "string",
            "enum": [
              "OwnTelemetry"
            ]
          },
          {
            "type": "string",
            "enum": [
              "FlightPlan"
            ]
          },
          {
            "type": "string",
            "enum": [
              "RfLibrary"
            ]
          },
          {
            "type": "string",
            "description": "Friendly-subtraction correlation (fusion's own-asset self-report match).",
            "enum": [
              "Correlation"
            ]
          },
          {
            "type": "object",
            "required": [
              "Other"
            ],
            "properties": {
              "Other": {
                "type": "string",
                "description": "Any source without a dedicated variant, preserved verbatim."
              }
            }
          }
        ],
        "description": "Where a cooperative identification came from. Known sources carry their frozen\nwire label; anything else round-trips losslessly through [`IdSource::Other`], so\nno wire value can fail to parse. Serialized as the plain string."
      },
      "Identification": {
        "type": "object",
        "description": "Layer-2.5 cooperative identification (IFF). A heterogeneous block: cooperative\nallowlist hit, friendly-subtraction correlation, or a seen-but-unidentified\nRemote-ID serial. Every key is optional and omitted when absent (no arm emits\nan explicit `null`), so a flat all-`Option` struct reproduces every arm.",
        "properties": {
          "affiliation": {
            "type": "string",
            "nullable": true
          },
          "airframe": {
            "type": "string",
            "nullable": true
          },
          "cooperative": {
            "type": "boolean",
            "nullable": true
          },
          "correlated_with": {
            "type": "string",
            "nullable": true
          },
          "corroborated": {
            "type": "boolean",
            "nullable": true
          },
          "do_not_engage": {
            "type": "boolean",
            "nullable": true
          },
          "envelope": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FlightEnvelope"
              }
            ],
            "nullable": true
          },
          "identified": {
            "type": "boolean",
            "nullable": true
          },
          "manned_aircraft": {
            "type": "boolean",
            "nullable": true
          },
          "matched_flight_plan": {
            "type": "string",
            "description": "The filed flight plan an ADS-B report matched (#281 phase 5), carried so\nthe filed-flight Orient rule reads a positive fact rather than inferring\none from `source == \"adsb\"`, which an unfiled transponder also produces.\nAdditive: absent on legacy records and on every track without a match.",
            "nullable": true
          },
          "operator": {
            "type": "string",
            "nullable": true
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "registered_as": {
            "type": "string",
            "nullable": true
          },
          "seen_serial": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "allOf": [
              {
                "$ref": "#/components/schemas/IdSource"
              }
            ],
            "nullable": true
          },
          "source_trust": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "spoof_risk": {
            "type": "boolean",
            "nullable": true
          },
          "whitelisted": {
            "type": "boolean",
            "nullable": true
          }
        }
      },
      "IdentificationPolicy": {
        "type": "object",
        "description": "When the machine may decide what something *is*.",
        "required": [
          "auto_threat_confidence",
          "min_independent_modalities",
          "never_below_confidence"
        ],
        "properties": {
          "auto_threat_confidence": {
            "type": "number",
            "format": "double",
            "description": "Auto-designate as a threat at or above this confidence."
          },
          "min_independent_modalities": {
            "type": "integer",
            "format": "int32",
            "description": "…but only with this many **independent modalities** agreeing. One radar\nreporting 3,000 times is one modality; radar + EO + RF is three. This\ndistinction is the whole basis of trust in the picture, so raising\n`auto_threat_confidence` alone can never buy a designation on repetition.",
            "minimum": 0
          },
          "never_below_confidence": {
            "type": "number",
            "format": "double",
            "description": "Never auto-designate below this, whatever else is true. A floor that\noutranks every other setting here."
          }
        }
      },
      "Identity": {
        "type": "object",
        "description": "The answer to `GET /v1/me`: who the caller is, where they may work, and which\nworkspace this particular request was made against.",
        "required": [
          "user",
          "workspaces",
          "credential"
        ],
        "properties": {
          "credential": {
            "$ref": "#/components/schemas/CredentialKind"
          },
          "current_workspace": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Workspace"
              }
            ],
            "nullable": true
          },
          "user": {
            "$ref": "#/components/schemas/PublicUser"
          },
          "workspaces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Workspace"
            }
          }
        }
      },
      "IdentityBelief": {
        "type": "object",
        "description": "A Dempster-Shafer basic probability assignment (mass function) over the class\nframe — the identity channel of a fused track. A vacuous belief (all mass on Θ) is\ntotal ignorance.",
        "properties": {
          "masses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MassEntry"
            }
          }
        }
      },
      "IdentityClaim": {
        "type": "object",
        "description": "**Who an asset belongs to, as a human claimed it.** IFF, not tasking.\n\nThis was called `Assignment`, and `crate::plan::Assignment` — an\ninterceptor paired to a threat, with guidance and a time to intercept — was\nalso called `Assignment`. Both were on the wire, `lib.rs` carried a comment\nabout the clash, and the two mean nothing like each other: this one assigns\nnobody to anything. It records that a Remote ID serial is ours, whose it is\nand what role it flies, and every write of it refreshes the fusion friendly\nwhitelist so matching tracks reclassify on the next cycle.\n\nThe plan's `Assignment` kept the name, because that is the word a plan speaks.",
        "required": [
          "id",
          "workspace_id",
          "asset_id",
          "affiliation",
          "active",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "active": {
            "type": "boolean",
            "description": "Whether this is the currently-in-force claim for the asset. Superseded\nclaims are retained (`active = false`) so the change history is kept."
          },
          "affiliation": {
            "$ref": "#/components/schemas/Affiliation"
          },
          "asset_id": {
            "type": "string",
            "format": "uuid",
            "description": "The fixed asset this claim applies to."
          },
          "created_at": {
            "type": "string",
            "description": "Audit timestamps."
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique claim id."
          },
          "operator": {
            "type": "string",
            "description": "Operating unit (e.g. `\"BLUE-OPS\"`), if known.",
            "nullable": true
          },
          "role": {
            "type": "string",
            "description": "Mission role (e.g. `\"interceptor\"`, `\"loiter\"`, `\"sensor\"`), if assigned.",
            "nullable": true
          },
          "updated_at": {
            "type": "string"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "Workspace this claim belongs to."
          }
        }
      },
      "IngestCounters": {
        "type": "object",
        "description": "**Why a sensor that is publishing never reaches the picture** — the two counters\nthat answer it, and nothing else.\n\nBoth already had a cause and neither had a face. A topic matching no declared\nlink is dropped and counted at the router; a connection with the wrong password\nis refused at CONNECT. An operator with a mistyped token sees one of these rise\nand knows which half to fix; without them the device simply does not appear.",
        "properties": {
          "dropped": {
            "type": "integer",
            "format": "int64",
            "description": "Arrived on a topic no declared link owns.",
            "minimum": 0
          },
          "refused": {
            "type": "integer",
            "format": "int64",
            "description": "Turned away at CONNECT: no such link, or the wrong password.",
            "minimum": 0
          },
          "refused_last": {
            "type": "string",
            "format": "date-time",
            "description": "When the last refusal was, so a count of 2 from last Tuesday is not read as\na device failing right now.",
            "nullable": true
          }
        }
      },
      "IngestEndpoint": {
        "type": "object",
        "description": "Everything this deployment publishes for something to connect *to*.",
        "properties": {
          "http_base": {
            "type": "string",
            "description": "Base URL for webhooks and polled-in pushes: `https://dome.site.example`.",
            "nullable": true
          },
          "mqtt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MqttIngest"
              }
            ],
            "nullable": true
          }
        }
      },
      "IngestSource": {
        "type": "string",
        "enum": [
          "live",
          "simulated"
        ]
      },
      "IntegrationLive": {
        "type": "object",
        "description": "**One protocol's live state, as the picture carries it.**\n\nDeliberately NOT the spec. The spec is compiled into the binary and fetched once\n(`GET /api/integrations`); putting it on the picture would send every command\ntable and every parameter list to every client on every re-hydration, to say\nsomething that cannot change while the process runs. What rides the picture is\nonly what moves: the rung, the count, the age of the last thing heard.\n\nJoined to the spec by [`Self::kind`] on the client.",
        "properties": {
          "asked_at": {
            "type": "string",
            "format": "date-time",
            "description": "When we last asked, for an integration we ask rather than one that tells us\n([`Learns::Ask`]). `None` on everything that streams.",
            "default": null,
            "nullable": true
          },
          "control_enabled": {
            "type": "boolean",
            "default": false
          },
          "counters": {
            "allOf": [
              {
                "$ref": "#/components/schemas/IngestCounters"
              }
            ],
            "default": {
              "dropped": 0,
              "refused": 0
            }
          },
          "enabled": {
            "type": "boolean",
            "default": false
          },
          "error": {
            "type": "string",
            "description": "What is wrong, in the OS's or the protocol's own words. `None` while all is\nwell.",
            "default": null,
            "nullable": true
          },
          "kind": {
            "type": "string",
            "default": ""
          },
          "last_heard_s": {
            "type": "number",
            "format": "double",
            "description": "Seconds since anything was heard. `None` when nothing ever has, which is not\nthe same as quiet and must not render as an age.",
            "default": null,
            "nullable": true
          },
          "listening": {
            "type": "boolean",
            "description": "Enabled **and** something is genuinely bound or connected. Intent and state\nare different questions, and this is the second one.",
            "default": false
          },
          "msgs_per_min": {
            "type": "number",
            "format": "double",
            "description": "Messages a minute, **where the transport counts them**. `None` where it does\nnot; a zero would read as silence.",
            "default": null,
            "nullable": true
          },
          "nodes": {
            "type": "integer",
            "description": "How many of the integration's noun are being **heard** right now — nodes on\nthe wire, not rows in the registry. `0` with `state: listening` is the\nordinary fresh-deployment answer.",
            "default": 0,
            "minimum": 0
          },
          "state": {
            "allOf": [
              {
                "$ref": "#/components/schemas/IntegrationState"
              }
            ],
            "default": "off"
          },
          "transport_state": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TransportState"
            },
            "description": "Each configured wire and whether that one is open. Empty on every protocol\nreached over a form.",
            "default": []
          }
        }
      },
      "IntegrationPatch": {
        "type": "object",
        "description": "What an operator is asking to write.",
        "properties": {
          "config": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true
          },
          "control_enabled": {
            "type": "boolean",
            "nullable": true
          },
          "enabled": {
            "type": "boolean",
            "nullable": true
          },
          "transports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transport"
            },
            "description": "**The whole list, or nothing.**\n\nParams merge, because a console sending `{enabled: true}` must not have to\nresend a config it never touched. A list cannot: merging by index has no\nmeaning, and there would be no way to say *remove the second one*, which is\nthe edit an operator makes when they unplug a radio for good.",
            "nullable": true
          }
        }
      },
      "IntegrationSpec": {
        "type": "object",
        "description": "One protocol this build can speak.",
        "required": [
          "kind",
          "label",
          "what",
          "noun",
          "noun_plural",
          "telemetry",
          "control",
          "self_registers",
          "params"
        ],
        "properties": {
          "commands": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CommandSpec"
            },
            "description": "**What it can be told**, one row per verb. Empty ⇒ reports only."
          },
          "control": {
            "$ref": "#/components/schemas/ControlKind"
          },
          "default_transports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transport"
            },
            "description": "**What it holds open when nothing has been configured.** The conventional\nGCS port, so a fresh deployment hears a radio without anybody typing\nanything. Only meaningful alongside `transport_modes`."
          },
          "doc_url": {
            "type": "string",
            "description": "**Where the protocol is documented** — the vendor's page or the standard's,\nnever ours. An operator wiring a device needs the source of truth, and\nparaphrasing a protocol spec into our own docs is how the two drift.",
            "nullable": true
          },
          "kind": {
            "$ref": "#/components/schemas/AdapterId"
          },
          "label": {
            "type": "string",
            "description": "What an operator calls it."
          },
          "learns": {
            "$ref": "#/components/schemas/Learns"
          },
          "liveness": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Liveness"
              }
            ],
            "nullable": true
          },
          "noun": {
            "type": "string",
            "description": "The noun for the things on it. A MAVLink integration carries *vehicles*; an\nMQTT one carries *sensors*. Getting this right is most of why a row reads."
          },
          "noun_plural": {
            "type": "string",
            "description": "Its plural, carried rather than derived. English does not pluralise by\nappending `s` — a DJI integration carries *aircraft*, not *aircrafts* —\nand a count is the most-read text on the row."
          },
          "params": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParamSpec"
            },
            "description": "The fields this integration's config asks for. Same machinery that\ngenerates the add-asset form, so a `secret` field lands in the secret store\nas a reference and never rides the wire as a value."
          },
          "self_registers": {
            "type": "boolean",
            "description": "Whether devices announce themselves. Mirrors `AdapterCaps::self_registers`,\nand it decides how a device is set up: a self-registering protocol has a\ndiscover→adopt flow, everything else is declared with a credential."
          },
          "telemetry": {
            "type": "string",
            "description": "What arrives."
          },
          "transport_modes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TransportMode"
            },
            "description": "**The modes an operator may open a link in**, when this protocol is reached\nover a *list* of links rather than a form.\n\nEmpty for everything whose connection is one set of fields: an MQTT broker\nwe run, a vendor cloud with one base URL. MAVLink is the case that forced\nit: a site with a telemetry radio and a bench SITL link is one deployment\nspeaking one protocol over two wires, and every vehicle on either is told\napart by system id, not by which socket carried it."
          },
          "unavailable": {
            "type": "string",
            "description": "Set when the protocol has no transport in this build yet. Listed rather\nthan hidden: somebody who owns the hardware needs to know the half exists.",
            "nullable": true
          },
          "what": {
            "type": "string",
            "description": "One line: what it is, in the words somebody would use out loud."
          }
        }
      },
      "IntegrationState": {
        "type": "string",
        "description": "**The state ladder, ordered by how much attention a row deserves.**\n\nSix rungs, each with a label the row always shows beside its colour — colour is\nnever the only signal, and `off` is grey rather than red so red keeps meaning\nsomething. The order is the sort order of the index: what is arriving first,\nwhat is switched off next, what this build cannot speak at all last.",
        "enum": [
          "live",
          "listening",
          "quiet",
          "down",
          "off",
          "not_here",
          "not_enabled"
        ]
      },
      "IntegrationStatus": {
        "allOf": [
          {
            "$ref": "#/components/schemas/IntegrationView"
          },
          {
            "type": "object",
            "required": [
              "listening"
            ],
            "properties": {
              "commissioning": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Commissioning"
                  }
                ],
                "nullable": true
              },
              "listening": {
                "type": "boolean",
                "description": "Enabled **and** something is genuinely bound or connected."
              },
              "problem": {
                "type": "string",
                "description": "Why it is not, when it is enabled and is not. `None` while all is well.",
                "nullable": true
              },
              "serves": {
                "type": "string",
                "description": "**The address this deployment publishes for devices to point at**, e.g.\n`mqtt://dome.site:1883`.\n\nReported, never asked. It is decided at boot by `DOME_PUBLIC_HOST` and\n`DOME_MQTT_PORT` and is what the listener actually binds — so a form field\nfor it was a second source for one fact, and the one that could not change\nanything. `None` when this protocol publishes no such service.",
                "nullable": true
              },
              "transport_state": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TransportState"
                },
                "description": "**Each configured link, and whether that one is open.** Empty on every\nprotocol reached over a form.\n\nThe integration's own `listening` cannot answer this once there is more than\none wire: a deployment with a bench link up and a telemetry radio unplugged\nis neither listening nor not, and the useful sentence is per row: *live, 5\nvehicles* beside *down, no such device*."
              }
            }
          }
        ],
        "description": "An integration, plus **whether it is actually running** — which is not the same\nquestion as whether it is enabled.\n\nA listener whose port was busy leaves a row saying `enabled` with nothing bound.\nReporting intent as if it were state is the lie this whole page exists to end,\nso the socket is asked rather than the database."
      },
      "IntegrationView": {
        "allOf": [
          {
            "$ref": "#/components/schemas/IntegrationSpec"
          },
          {
            "type": "object",
            "required": [
              "enabled",
              "control_enabled",
              "config",
              "transports",
              "assets",
              "assets_by_kind",
              "links",
              "tokens",
              "discovered",
              "discovery"
            ],
            "properties": {
              "assets": {
                "type": "integer",
                "description": "**How many things are on it**, counted as ASSETS rather than links.\n\nA link means different things per protocol — one per sensor on MQTT, one\nshared socket for a whole fleet on MAVLink — so a link count says something\ndifferent on every row and is read as the same thing. An asset count is the\nnumber an operator is actually looking for, and its noun comes from the\nspec: *3 sensors*, *2 vehicles*, *3 nodes*.",
                "minimum": 0
              },
              "assets_by_kind": {
                "$ref": "#/components/schemas/AssetsByKind"
              },
              "config": {
                "type": "object",
                "description": "Non-secret values only. A `secret://` reference is a name, not a value, and\nis safe to show — it is what an operator sees to know a secret is set.",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "control_enabled": {
                "type": "boolean"
              },
              "discovered": {
                "type": "integer",
                "description": "Devices heard on the attached links that this workspace holds no row for.\nFilled by [`with_discovered`]; `0` straight off the row.",
                "minimum": 0
              },
              "discovery": {
                "$ref": "#/components/schemas/DiscoveryPolicy"
              },
              "enabled": {
                "type": "boolean"
              },
              "links": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/LinkAttachment"
                },
                "description": "**The deployment's physical links of this protocol**, each with whether\nthis workspace is attached (spec 2026-09-07 §4). Empty on a protocol\nreached over a credential."
              },
              "managed_by_deployment": {
                "type": "string",
                "description": "**Set by the deployment, not by this workspace.**\n\nA protocol that binds a port on this machine is declared under `links:`\nin `dome.yaml`: two workspaces on one host cannot both bind\n14550, and a tenant who could pick the number could take a port out from\nunder another one. `Some(_)` means the row shows what the file says and no\nswitch, with this sentence beside it; `None` means the form it always had.\n\nA protocol reached over somebody else's cloud with somebody's credential\nis never this — that is an account, and it belongs to whoever holds it.",
                "nullable": true
              },
              "tokens": {
                "type": "integer",
                "description": "How many device tokens this workspace issued on it.",
                "minimum": 0
              },
              "transports": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Transport"
                },
                "description": "**The links this protocol holds open**, resolved: a row stored before\ntransports existed reads as the one endpoint it had, and one never written\nreads as the protocol's own default. Empty on everything reached over a form."
              },
              "unavailable_here": {
                "type": "string",
                "description": "**Why this environment cannot offer it at all.** Derived from the\nenvironment's `simulated` flag, so the console can disable the row and say\nwhy rather than offering a switch the write will refuse.",
                "nullable": true
              },
              "verified": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/VerifyResult"
                  }
                ],
                "nullable": true
              }
            }
          }
        ],
        "description": "**One protocol, and what this deployment decided about it.**\n\nThe spec and the row are returned together because neither is useful alone: the\nspec says what the protocol IS, the row says whether it is on. A console that\nhad to join them itself would be a second place the rules live."
      },
      "Invitation": {
        "type": "object",
        "description": "An outstanding invitation. The token is not on it: it goes to the invited\naddress and nowhere else.",
        "required": [
          "id",
          "email",
          "role",
          "expires_at",
          "accepted",
          "created_at"
        ],
        "properties": {
          "accepted": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "expires_at": {
            "type": "string"
          },
          "id": {
            "type": "string",
            "description": "`inv_…`"
          },
          "role": {
            "$ref": "#/components/schemas/WorkspaceRole"
          }
        }
      },
      "InviteRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string"
          },
          "role": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WorkspaceRole"
              }
            ],
            "nullable": true
          }
        }
      },
      "IrDetection": {
        "type": "object",
        "description": "Infra-red / thermal detection (MQTT `thermal`).",
        "properties": {
          "bbox_px": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "nullable": true
          },
          "dt_k": {
            "type": "number",
            "format": "double",
            "description": "Thermal contrast, kelvin.",
            "nullable": true
          }
        }
      },
      "Kinematics": {
        "type": "object",
        "description": "Track kinematic state: position (geodetic + local ENU), velocity, and derived\nspeed/heading. Reuses the shared [`Geo`]/[`Enu`]/[`Vel`] geometry types.",
        "required": [
          "geo",
          "enu",
          "vel_mps",
          "speed_mps",
          "heading_deg"
        ],
        "properties": {
          "enu": {
            "$ref": "#/components/schemas/Enu"
          },
          "geo": {
            "$ref": "#/components/schemas/Geo"
          },
          "heading_deg": {
            "type": "number",
            "format": "double"
          },
          "speed_mps": {
            "type": "number",
            "format": "double"
          },
          "vel_mps": {
            "$ref": "#/components/schemas/Vel"
          }
        }
      },
      "LatestExplainReply": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/FusionExplain"
          },
          {
            "$ref": "#/components/schemas/FusionExplainWindow"
          }
        ],
        "description": "What `GET /api/fusion/explain` answers with: one scan, or a window of them\nwhen `scans` was asked for. Untagged on the wire: a window has `ticks`, a\nscan has `schema`."
      },
      "Layer": {
        "type": "string",
        "description": "The cascade's layers, lowest first. A layer's number *is* its precedence.\n\nPosture and rules used to sit between region and the operator override. They\nare gone from the cascade entirely: posture gates and rules derive, and a\nderivation ([`crate::rules::Derivation`]) lives in the decision rather than in\na shared mutable layer. *\"Which rule wins when two set the same key\"* was a\nquestion only that shared layer could ask.",
        "enum": [
          "doctrine_bounds",
          "defaults",
          "baseline",
          "region",
          "operator_override"
        ]
      },
      "Leader": {
        "type": "string",
        "description": "Group leadership for mesh coordination.",
        "enum": [
          "static",
          "elected"
        ]
      },
      "Learns": {
        "type": "string",
        "description": "**How this deployment finds out what is on an integration.**\n\nThe distinction the no-poll rule turns on. Everything that streams to us has a\nliveness we can read off the stream itself; a vendor cloud we *ask* has none,\nand the honest thing is to say when we last asked rather than to invent a timer\nthat asks again so a page can look live.",
        "enum": [
          "stream",
          "ask"
        ]
      },
      "Level": {
        "type": "string",
        "description": "How far an action reaches, and how hard it is to take back.\n\nThe ordering is the whole model, so it is `Ord` and the numbers are stable: they\nare persisted in an [`AutonomyPolicy`] and rendered on a slider.",
        "enum": [
          "observe",
          "manoeuvre",
          "watch",
          "shadow",
          "deny",
          "destroy"
        ]
      },
      "LinkAttachment": {
        "type": "object",
        "description": "**One physical link, as an integration row lists it**: the file's name,\nits endpoint, and whether this workspace is attached.",
        "required": [
          "name",
          "endpoint",
          "attached"
        ],
        "properties": {
          "attached": {
            "type": "boolean"
          },
          "endpoint": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "LinkDirection": {
        "type": "string",
        "description": "**Which end dials.**\n\nThe question every integration answers and none of them were asked. `SocketRole`\nasked it for UDP — `udpin` binds, `udpout` dials — and nothing generalised it, so\nevery MQTT integration was modelled as though we were the client.\n\nWe are not. A Batear node publishes **to us**. An Inturai pod publishes **to us**\n— its recipe was even labelled *\"Our broker\"* while asking the operator to type a\nbroker address. A DJI Dock connects **to our** broker; that is what the Cloud API\nrequires. In every one of those cases the address is ours to *publish* and the\ncredential is ours to *issue*, and asking an operator to supply either is asking\nthem for something only we know.",
        "enum": [
          "inbound",
          "outbound"
        ]
      },
      "LinkIngest": {
        "type": "object",
        "description": "**One link's ingest**: where to publish, and who to publish as.",
        "required": [
          "url",
          "topic",
          "username"
        ],
        "properties": {
          "password": {
            "type": "string",
            "description": "**Returned exactly once, when the credential is issued.** Never on a read —\na password a route will hand back is one that ends up in a browser cache and\na screenshot.",
            "nullable": true
          },
          "topic": {
            "type": "string",
            "description": "`ingest/{token}/#` — this link's subtree and no other's."
          },
          "url": {
            "type": "string",
            "description": "`mqtt://dome.site:1883`."
          },
          "username": {
            "type": "string",
            "description": "The MQTT username. The token itself: unguessable, and it is what the broker\nACL is written against, so a compromised sensor cannot publish as another."
          }
        }
      },
      "LinkPolicy": {
        "type": "object",
        "description": "One physical link, with what this workspace's discovery word means on it.",
        "required": [
          "protocol",
          "name",
          "endpoint",
          "attached",
          "can_discover",
          "effective"
        ],
        "properties": {
          "attached": {
            "type": "boolean"
          },
          "can_discover": {
            "type": "boolean",
            "description": "**Can this adapter discover at all?** A KLV feed carries video and never\nsays *I am MAST-EO-1*, so `offer` on it would be inert. Surfaced so the\nconsole can say why the control is disabled rather than leaving it looking\nbroken."
          },
          "effective": {
            "type": "string",
            "description": "What is in force: `off` when detached, when the switch is off, or when\nthe adapter cannot announce."
          },
          "endpoint": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "protocol": {
            "$ref": "#/components/schemas/AdapterId"
          }
        }
      },
      "LinkSnapshot": {
        "type": "object",
        "description": "A saved transport link and its live state. Plumbing — the console shows this on the\nsettings page, not on the map.\n\nIt deliberately reads like [`SensorActivity`](crate::sensor::SensorActivity): an\noperator triages a dead link the way they triage a silent sensor, so the two\nshould look the same.",
        "required": [
          "id",
          "name",
          "transport",
          "adapter",
          "enabled",
          "connected",
          "auto_register",
          "affiliation"
        ],
        "properties": {
          "adapter": {
            "$ref": "#/components/schemas/AdapterId"
          },
          "affiliation": {
            "type": "string",
            "description": "Affiliation a node discovered here is assumed to have."
          },
          "auto_register": {
            "type": "boolean",
            "description": "**Superseded by [`Self::discovery`]** and kept for links stored before it."
          },
          "config": {
            "type": "object",
            "description": "**The non-secret half of the link's config.** Populated from\n[`crate::link::Link::public_config`] and never from `config` directly — this\ntype rides the picture, so a secret here is a secret in every SSE delta.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "connected": {
            "type": "boolean",
            "description": "**The socket is currently open.** Not the same question as [`Self::state`]:\na bound socket is not evidence of a vehicle."
          },
          "declared": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Node ids this link **declares** — what the operator said was on the other end.\nDistinct from [`Self::nodes`], which is what has actually been heard: a\ndeclared node that never speaks is the failure an operator needs to see."
          },
          "discovery": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DiscoveryPolicy"
              }
            ],
            "nullable": true
          },
          "enabled": {
            "type": "boolean",
            "description": "The operator wants it up (persisted; reconnected on boot)."
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "id": {
            "type": "string"
          },
          "last_heard_s": {
            "type": "number",
            "format": "double",
            "description": "Seconds since *any* node was last heard on this link. `None` when nothing has\never been heard — which is not the same as quiet.",
            "nullable": true
          },
          "msgs_per_min": {
            "type": "number",
            "format": "double",
            "description": "Messages per minute over the rolling window."
          },
          "msgs_total": {
            "type": "integer",
            "format": "int64",
            "description": "Messages decoded on this link since it came up.",
            "minimum": 0
          },
          "name": {
            "type": "string"
          },
          "nodes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Which node ids arrived here. `len()` is the old `asset_count`."
          },
          "state": {
            "$ref": "#/components/schemas/LinkState"
          },
          "transport": {
            "$ref": "#/components/schemas/Transport"
          }
        }
      },
      "LinkState": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "state"
            ],
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "unlinked"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Talking to us now.",
            "required": [
              "protocol",
              "link_id",
              "state"
            ],
            "properties": {
              "link_id": {
                "type": "string",
                "description": "The link this asset is reachable on."
              },
              "protocol": {
                "type": "string",
                "description": "Wire protocol, e.g. `\"mavlink\"`."
              },
              "state": {
                "type": "string",
                "enum": [
                  "connected"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Was talking, has gone quiet.",
            "required": [
              "protocol",
              "link_id",
              "age_s",
              "state"
            ],
            "properties": {
              "age_s": {
                "type": "number",
                "format": "double"
              },
              "link_id": {
                "type": "string"
              },
              "protocol": {
                "type": "string"
              },
              "state": {
                "type": "string",
                "enum": [
                  "stale"
                ]
              }
            }
          }
        ],
        "description": "Whether we can talk to an asset right now, and over what.",
        "discriminator": {
          "propertyName": "state"
        }
      },
      "Liveness": {
        "type": "object",
        "description": "**How often this protocol says something unprompted**, and how long silence has\nto last before it means anything.\n\nThe numbers are the protocol's, not ours. MAVLink sends `HEARTBEAT` at 1 Hz and\na GCS is expected to notice within a few beats; BSI Flex 335 makes `StatusReport`\nan obligation of every SAPIENT node, detections or not; an MQTT client's keepalive\nis what the broker itself times out on. One global \"quiet after 15 s\" applied to\nall three would call a healthy sensor array dead and a dead radio fine.\n\nIt rides the **spec**, which the console fetches once, rather than the status,\nwhich rides the picture: the cadence of a protocol does not change while a\ndeployment runs. The client never holds these numbers — it renders the state the\nserver derived from them.",
        "required": [
          "source",
          "heartbeat_s",
          "quiet_after_s",
          "down_after_s"
        ],
        "properties": {
          "down_after_s": {
            "type": "number",
            "format": "double",
            "description": "Silence past this is `down`."
          },
          "heartbeat_s": {
            "type": "number",
            "format": "double",
            "description": "The interval that source runs at, in seconds."
          },
          "quiet_after_s": {
            "type": "number",
            "format": "double",
            "description": "Silence past this is `quiet` — worth showing, not yet worth alarming."
          },
          "source": {
            "type": "string",
            "description": "What sets the pace, in the protocol's own words: `HEARTBEAT at 1 Hz`."
          }
        }
      },
      "LogKind": {
        "type": "string",
        "description": "The kind of a mission-log entry. The log IS the audit trail (no decisions table):\n`Raised` (the triggering sitrep), `Bda` (an outcome that drives status), etc.",
        "enum": [
          "raised",
          "tasked",
          "tasking_status",
          "bda",
          "retasked",
          "override",
          "closed"
        ]
      },
      "LoggedOut": {
        "type": "object",
        "description": "How many sessions this ended.",
        "required": [
          "revoked"
        ],
        "properties": {
          "revoked": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "description": "`POST /v1/auth/login`",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "Where the code goes. An address we have never seen is a signup."
          }
        }
      },
      "LoginSent": {
        "type": "object",
        "description": "What a login attempt tells the client.",
        "required": [
          "sent_to",
          "expires_in_minutes"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "**The code itself, in dev mode only.**\n\nA local deployment has no mail provider, and a login flow that cannot be\ncompleted without one is a login flow nobody can develop against. Absent\non any deployment where `DEV_MODE` is off.",
            "nullable": true
          },
          "expires_in_minutes": {
            "type": "integer",
            "format": "int64"
          },
          "sent_to": {
            "type": "string",
            "description": "How the code was delivered, in words a person can act on."
          }
        }
      },
      "LogoutRequest": {
        "type": "object",
        "description": "`POST /v1/auth/logout`",
        "properties": {
          "refresh": {
            "type": "string",
            "description": "Revoke this one. Omit to revoke every session this user holds, which is\nwhat \"sign out everywhere\" means.",
            "nullable": true
          }
        }
      },
      "ManifestAsset": {
        "type": "object",
        "description": "One thing we hold, and the link it is on.",
        "required": [
          "name",
          "product",
          "recipe"
        ],
        "properties": {
          "config": {
            "type": "object",
            "description": "**This thing's own settings** — where a mast stands, how far it hears.\nNever the connection's: two nodes on one broker differ here and must.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "link": {
            "type": "string",
            "description": "A [`ManifestLink::id`]. Absent only for a simulated recipe, which opens no\nsocket and therefore attaches to nothing.",
            "nullable": true
          },
          "name": {
            "type": "string",
            "description": "The callsign. Unique within a manifest."
          },
          "product": {
            "type": "string",
            "description": "A [`Product::id`](crate::catalog::product::Product::id)."
          },
          "recipe": {
            "type": "string",
            "description": "A [`ConnectionRecipe::id`](crate::catalog::product::ConnectionRecipe::id)\non that product."
          }
        }
      },
      "ManifestLink": {
        "type": "object",
        "description": "**A declared connection.** Configured first; assets attach to it.\n\nHoisted out of the asset because a connection is *shared*: ten acoustic nodes on\none broker have one address and one credential between them. When each asset\ncarried its own, the tenth ended up pointing at a broker the other nine had\nstopped using, and nothing could tell you that had happened.\n\nIt is also the only place [`DiscoveryPolicy`](crate::link::DiscoveryPolicy) can\nlive. Discovery used to be an emergent property of which adapter happened to\nhave a hub; here it is a decision, written down, per link.",
        "required": [
          "id",
          "adapter"
        ],
        "properties": {
          "adapter": {
            "$ref": "#/components/schemas/AdapterId"
          },
          "config": {
            "type": "object",
            "description": "The transport's and adapter's parameters. Secrets hold a `secret://`\nreference. **Never per-asset settings** — those live on the asset.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "direction": {
            "$ref": "#/components/schemas/LinkDirection"
          },
          "discovery": {
            "$ref": "#/components/schemas/DiscoveryPolicy"
          },
          "endpoint": {
            "type": "string",
            "description": "[`Transport`](crate::link::Transport)'s canonical form —\n`udpin:0.0.0.0:14550`, `mqtt:broker:1883/topic`.\n\nFor an **inbound** link this is where *we* listen, not where a vendor's\nservice lives. The distinction matters because the two used to be the same\nfield with the same prompt, and an operator adding an acoustic node was asked\nfor the address of a broker this deployment runs.\n\n**Empty is legal for an inbound MQTT link**, and is the normal case: the\nbroker is ours, so its address is not the operator's to write down. It is\nfilled in on apply from this deployment's own ingest configuration, which is\nalso what lets a manifest exported from one site import at another and point\nat *that* site's broker rather than the first one's."
          },
          "id": {
            "type": "string",
            "description": "Referenced by [`ManifestAsset::link`]. Unique within the document."
          },
          "name": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ManifestMeta": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          }
        }
      },
      "ManifestSpec": {
        "type": "object",
        "properties": {
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ManifestAsset"
            }
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ManifestLink"
            },
            "description": "**Declared first.** An asset can only attach to a link that is here."
          }
        }
      },
      "MassEntry": {
        "type": "object",
        "description": "One `(focal element, mass)` pair of a mass function.",
        "required": [
          "classes",
          "mass"
        ],
        "properties": {
          "classes": {
            "$ref": "#/components/schemas/ClassHypothesis"
          },
          "mass": {
            "type": "number",
            "format": "double",
            "description": "Basic probability mass assigned to it, `0.0..=1.0`."
          }
        }
      },
      "MeasurementContribution": {
        "type": "object",
        "description": "One measurement's fate: its geometry as the sensor produced it, the noise the\nfilter applied, and what it did to the estimate.",
        "required": [
          "obs_id",
          "sensor_id",
          "modality",
          "kind",
          "locus",
          "nis",
          "nis_dof",
          "trace_reduction_m2",
          "delta_l",
          "accepted"
        ],
        "properties": {
          "accepted": {
            "type": "boolean"
          },
          "delta_l": {
            "type": "number",
            "format": "double",
            "description": "The existence log-likelihood-ratio this measurement added (`score::apply_hit`).\nA bearing scores too, priced against an angular clutter density; `0` means\nthe measurement earned no credit, and `rejected_reason` says why."
          },
          "kind": {
            "$ref": "#/components/schemas/MeasurementKind"
          },
          "locus": {
            "$ref": "#/components/schemas/MeasurementLocus"
          },
          "modality": {
            "$ref": "#/components/schemas/Modality"
          },
          "nis": {
            "type": "number",
            "format": "double",
            "description": "Normalised innovation squared against the track's own `S`, and its degrees\nof freedom (2 for a plot or an az/el ray, 1 for an azimuth-only ray), so a\nconsumer can draw the χ² bound for that update. `0` for a birth."
          },
          "nis_dof": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "obs_id": {
            "type": "string"
          },
          "rejected_reason": {
            "type": "string",
            "description": "Why this measurement did not count in full: the gate, no declared sigma,\nno track in reach, a duplicate, an ambiguous crossing. On a row with\n`accepted: false` nothing was applied at all. On an accepted row it says\nwhy the measurement was fused into the filter and still scored `delta_l`\nof zero.",
            "nullable": true
          },
          "sensor_id": {
            "type": "string"
          },
          "trace_reduction_m2": {
            "type": "number",
            "format": "double",
            "description": "Covariance-trace reduction attributable to this measurement, m². Order\ndependent and only approximately additive; a birth reports `0`."
          },
          "track_id": {
            "type": "string",
            "description": "The track this measurement landed on, or `None` when no track took it.",
            "nullable": true
          }
        }
      },
      "MeasurementKind": {
        "type": "string",
        "description": "What a sensor's detections fundamentally measure — the key the fusion\npipeline auto-selects per-observation handling on.",
        "enum": [
          "position",
          "bearing_only",
          "range_only",
          "zone_only",
          "cooperative_id"
        ]
      },
      "MeasurementLocus": {
        "oneOf": [
          {
            "type": "object",
            "description": "A geolocated plot (radar, lidar, a triangulated crossing, a cooperative\nreport): the position measured and the 3×3 `R` the filter applied to it.",
            "required": [
              "enu",
              "r",
              "kind"
            ],
            "properties": {
              "enu": {
                "$ref": "#/components/schemas/Enu"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "position"
                ]
              },
              "r": {
                "type": "array",
                "items": {
                  "type": "number",
                  "format": "double"
                }
              }
            }
          },
          {
            "type": "object",
            "description": "A ray from the sensor (EO/IR, RF/DF, acoustic, ELINT). `elevation_deg` is\n`None` when the sensor measured no elevation; a consumer that draws one\nanyway is inventing it. `max_range_m` is the sensor's declared reach, so the\nray can be drawn to where the sensor could have heard it, never further.",
            "required": [
              "sensor_enu",
              "bearing_deg",
              "bearing_sigma_deg",
              "max_range_m",
              "kind"
            ],
            "properties": {
              "bearing_deg": {
                "type": "number",
                "format": "double"
              },
              "bearing_sigma_deg": {
                "type": "number",
                "format": "double"
              },
              "elevation_deg": {
                "type": "number",
                "format": "double",
                "nullable": true
              },
              "elevation_sigma_deg": {
                "type": "number",
                "format": "double",
                "nullable": true
              },
              "kind": {
                "type": "string",
                "enum": [
                  "bearing"
                ]
              },
              "max_range_m": {
                "type": "number",
                "format": "double"
              },
              "sensor_enu": {
                "$ref": "#/components/schemas/Enu"
              }
            }
          }
        ],
        "description": "The measurement's own geometry, in the form the sensor produced it.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "Member": {
        "type": "object",
        "description": "A member of a workspace, for the membership list.",
        "required": [
          "user_id",
          "email",
          "role",
          "joined_at"
        ],
        "properties": {
          "email": {
            "type": "string"
          },
          "joined_at": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "$ref": "#/components/schemas/WorkspaceRole"
          },
          "user_id": {
            "type": "string",
            "description": "`usr_…`"
          }
        }
      },
      "Meta": {
        "type": "object",
        "description": "**The bootstrap payload.** Everything the console needs on first load: where to\ncentre, the regions it may switch to, the asset registry, the claims made\nagainst it, and the catalogue.\n\nOne call rather than five, because five means five loading states and four\nchances to draw a map before it knows where it is pointed. New fields are\nadditive; `region` is preserved for existing clients.",
        "required": [
          "regions",
          "assets",
          "assignments",
          "catalog"
        ],
        "properties": {
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Asset"
            },
            "description": "The asset registry. Affiliation-agnostic: what is deployed, not whose."
          },
          "assignments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IdentityClaim"
            },
            "description": "Operator-confirmed affiliation, operator and role per asset. The client\njoins these to `assets` by `asset_id`."
          },
          "catalog": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogEntry"
            },
            "description": "The drone-type library."
          },
          "region": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RegionSummary"
              }
            ],
            "nullable": true
          },
          "regions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RegionSummary"
            },
            "description": "Every region in the workspace."
          }
        }
      },
      "MintRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "expires_in_days": {
            "type": "integer",
            "format": "int64",
            "description": "Days until it expires. Omit for a key that does not expire, which is what\na deployment's own integration usually wants.",
            "nullable": true
          },
          "name": {
            "type": "string",
            "description": "What this key is for, in words. It is the only thing distinguishing two\nkeys on the page, so `ground station` beats `key 2`."
          }
        }
      },
      "MintedApiKey": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "required": [
              "secret"
            ],
            "properties": {
              "secret": {
                "type": "string",
                "description": "`dak_…`. Shown once."
              }
            }
          }
        ],
        "description": "**The one response that carries a secret.**\n\nReturned by `POST /v1/keys` and never again: the server keeps a peppered hash\nand cannot reproduce `secret` afterwards. A client that does not save it here\nhas to mint another key."
      },
      "Mission": {
        "type": "object",
        "description": "The committed, executing operation. Several run concurrently (a standing patrol\non Alpha while Bravo prosecutes a reactive intercept); per group a Response\nsupersedes the Posture until it closes.",
        "required": [
          "id",
          "name",
          "trigger",
          "kind",
          "objective",
          "roe_posture",
          "groups",
          "taskings",
          "status",
          "origin"
        ],
        "properties": {
          "from_plan": {
            "type": "string",
            "description": "The approved Plan this mission was raised from; `None` for a Standing posture.",
            "nullable": true
          },
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Group"
            }
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/MissionKind"
          },
          "log": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MissionLogEntry"
            },
            "description": "The mission's own timeline — the audit trail (was the \"decision log\")."
          },
          "name": {
            "type": "string"
          },
          "objective": {
            "$ref": "#/components/schemas/Objective"
          },
          "origin": {
            "$ref": "#/components/schemas/PlanOrigin"
          },
          "roe_posture": {
            "$ref": "#/components/schemas/Level"
          },
          "status": {
            "$ref": "#/components/schemas/MissionStatus"
          },
          "taskings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tasking"
            }
          },
          "trigger": {
            "$ref": "#/components/schemas/MissionTrigger"
          }
        }
      },
      "MissionKind": {
        "type": "string",
        "description": "Posture = open-ended (patrol/CAP); Response = threat/time-bounded (intercept)\nand reverts to the group's Posture on close.",
        "enum": [
          "posture",
          "response"
        ]
      },
      "MissionLogEntry": {
        "type": "object",
        "description": "One entry in a mission's timeline.",
        "required": [
          "t",
          "kind",
          "summary"
        ],
        "properties": {
          "detail": {
            "type": "string",
            "nullable": true
          },
          "kind": {
            "$ref": "#/components/schemas/LogKind"
          },
          "summary": {
            "type": "string"
          },
          "t": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "MissionProgress": {
        "type": "object",
        "description": "Progress through an uploaded mission (`MISSION_CURRENT` + the uploaded count).",
        "required": [
          "seq",
          "count"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "format": "int32",
            "description": "Total waypoints in the loaded mission.",
            "minimum": 0
          },
          "seq": {
            "type": "integer",
            "format": "int32",
            "description": "The waypoint sequence the vehicle is currently flying to.",
            "minimum": 0
          }
        }
      },
      "MissionStatus": {
        "type": "string",
        "description": "Mission status — a rollup the log drives. `Proposed`/`Approved` live at the\nPlan stage; `Executing`→`Assessing`→`Complete` as taskings close.",
        "enum": [
          "proposed",
          "approved",
          "executing",
          "assessing",
          "complete",
          "aborted",
          "superseded"
        ]
      },
      "MissionTrigger": {
        "type": "string",
        "description": "How a mission was raised. Solver = reactive/auto; Operator = deliberate op;\nStanding = a posture (patrol/CAP) that runs until changed.",
        "enum": [
          "standing",
          "operator",
          "solver"
        ]
      },
      "Modality": {
        "oneOf": [
          {
            "type": "string",
            "enum": [
              "Radar"
            ]
          },
          {
            "type": "string",
            "enum": [
              "Rf"
            ]
          },
          {
            "type": "string",
            "enum": [
              "Eo"
            ]
          },
          {
            "type": "string",
            "enum": [
              "Ir"
            ]
          },
          {
            "type": "string",
            "enum": [
              "Acoustic"
            ]
          },
          {
            "type": "string",
            "enum": [
              "RemoteId"
            ]
          },
          {
            "type": "string",
            "enum": [
              "Adsb"
            ]
          },
          {
            "type": "string",
            "enum": [
              "Telemetry"
            ]
          },
          {
            "type": "string",
            "enum": [
              "Cot"
            ]
          },
          {
            "type": "object",
            "required": [
              "Other"
            ],
            "properties": {
              "Other": {
                "type": "string",
                "description": "Any label without a dedicated variant (LIDAR, SAPIENT nodes, aliases such\nas `rf-doa`/`thermal-ir`), preserved verbatim."
              }
            }
          }
        ],
        "description": "A sensing modality. The known variants carry the frozen wire labels; any other\nlabel (aliases like `rf-doa`, or a new modality) round-trips losslessly through\n[`Modality::Other`], so no wire value can ever fail to parse. Serialized as the\nplain string (via `from`/`into` `String`), not a tagged object."
      },
      "Mode": {
        "type": "string",
        "description": "Live, or simulated.",
        "enum": [
          "live",
          "sim"
        ]
      },
      "MqttIngest": {
        "type": "object",
        "description": "The MQTT ingest this deployment publishes.",
        "required": [
          "host",
          "port",
          "tls"
        ],
        "properties": {
          "host": {
            "type": "string",
            "description": "The host a sensor on the site can reach us at. Not `localhost`: this string\nis copied into a device on the other side of a switch."
          },
          "port": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "tls": {
            "type": "boolean",
            "description": "Whether the listener is TLS. Reported rather than requested — a sensor\npointed at the wrong scheme fails in a way nobody traces back to here."
          }
        }
      },
      "NavQuality": {
        "type": "object",
        "description": "Navigation-solution quality, as the platform reports it.",
        "properties": {
          "fix": {
            "$ref": "#/components/schemas/GpsFix"
          },
          "hdop": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "satellites": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "vdop": {
            "type": "number",
            "format": "float",
            "nullable": true
          }
        }
      },
      "ObjectClass": {
        "type": "string",
        "description": "A detected/tracked object's class. Shared by [`Detection::class`] and (later)\n`Track.classification.type`. `as_str` returns the frozen wire string.",
        "enum": [
          "uav_multirotor",
          "uav_fixedwing",
          "uav_vtol",
          "bird",
          "aircraft_manned",
          "helicopter",
          "person",
          "ground_vehicle",
          "vessel",
          "rf_emitter",
          "unknown"
        ]
      },
      "Objective": {
        "type": "string",
        "description": "The optimization objective a Plan was solved for.",
        "enum": [
          "protect",
          "balanced",
          "min_leakers"
        ]
      },
      "ObjectivePolicy": {
        "type": "object",
        "description": "What a good plan optimises for.",
        "required": [
          "objective",
          "weights",
          "variation_count",
          "interceptor_speed_mps"
        ],
        "properties": {
          "interceptor_speed_mps": {
            "type": "number",
            "format": "double",
            "description": "Interceptor cruise used for ETA / guidance (m/s)."
          },
          "objective": {
            "$ref": "#/components/schemas/Objective"
          },
          "replan_cadence_s": {
            "type": "integer",
            "format": "int32",
            "description": "Seconds between automatic replans; `None` plans on demand only.",
            "nullable": true,
            "minimum": 0
          },
          "stability": {
            "$ref": "#/components/schemas/PlanStabilityPolicy"
          },
          "variation_count": {
            "type": "integer",
            "format": "int32",
            "description": "How many candidate plans to author. Capped at 1..=3 by design (#58).",
            "minimum": 0
          },
          "weights": {
            "$ref": "#/components/schemas/SolverWeights"
          }
        }
      },
      "Observation": {
        "type": "object",
        "description": "Layer 0 — raw observation, per drone per sensor, high-rate.\n\nThe `schema` field carries the frozen wire string `\"obs.v1\"` at runtime.\n`detection` is the typed per-sensor [`Detection`] (issue 5.4);\n`platform`/`sensor`/`quality` are concrete types (issue 37). `provenance`\nremains a [`Value`] on purpose: it is the **Layer-0 ingestion boundary** — open,\nper-adapter detector/source metadata (`provenance_extra` is config-driven, so\neach protocol adapter injects its own keys) and is never read by domain logic.\nThis is the convention's sanctioned \"Value only at true boundaries\" case.",
        "required": [
          "schema",
          "obs_id",
          "t",
          "platform",
          "sensor",
          "detection",
          "provenance",
          "quality"
        ],
        "properties": {
          "detection": {
            "$ref": "#/components/schemas/Detection"
          },
          "obs_id": {
            "type": "string"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "provenance": {},
          "quality": {
            "$ref": "#/components/schemas/Quality"
          },
          "schema": {
            "type": "string"
          },
          "sensor": {
            "$ref": "#/components/schemas/Sensor"
          },
          "t": {
            "type": "string"
          }
        }
      },
      "Offer": {
        "type": "object",
        "description": "One layer's offer for one key, and who made it.",
        "required": [
          "layer",
          "key",
          "value",
          "source_id"
        ],
        "properties": {
          "key": {
            "$ref": "#/components/schemas/SettingKey"
          },
          "layer": {
            "$ref": "#/components/schemas/Layer"
          },
          "source_id": {
            "type": "string",
            "description": "The region id or operator that wrote it. `region:harbour`, `operator:duty`."
          },
          "value": {
            "$ref": "#/components/schemas/SettingValue"
          }
        }
      },
      "OnComplete": {
        "type": "string",
        "description": "What the vehicle does after the last point of a route.",
        "enum": [
          "hold",
          "return_to_base",
          "land",
          "loop"
        ]
      },
      "OnboardAutonomy": {
        "type": "string",
        "description": "Where the Execute loop runs for a platform — the switch deciding whether the drone\nthinks for itself or `dome-runtime` thinks for it. A property of the *unit*, not\njust the model (the same airframe is `Offboard` stock, `OnDevice` once flashed with\ndome firmware / fitted with a companion computer), so it is asset-overridable.",
        "enum": [
          "on_device",
          "offboard"
        ]
      },
      "OodaStage": {
        "type": "string",
        "description": "Boyd's four stages, which are also Parasuraman's four stages of information\nprocessing. Every rule declares which one it acts in, and the posture decides\nwhether it fires automatically or proposes.",
        "enum": [
          "observe",
          "orient",
          "decide",
          "act"
        ]
      },
      "Op": {
        "type": "string",
        "description": "The comparison a condition makes.",
        "enum": [
          "eq",
          "ne",
          "lt",
          "lte",
          "gt",
          "gte",
          "in",
          "not_in"
        ]
      },
      "Operand": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "operand",
              "value"
            ],
            "properties": {
              "operand": {
                "type": "string",
                "enum": [
                  "number"
                ]
              },
              "value": {
                "type": "number",
                "format": "double"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "operand",
              "value"
            ],
            "properties": {
              "operand": {
                "type": "string",
                "enum": [
                  "text"
                ]
              },
              "value": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "operand",
              "value"
            ],
            "properties": {
              "operand": {
                "type": "string",
                "enum": [
                  "bool"
                ]
              },
              "value": {
                "type": "boolean"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "operand",
              "value"
            ],
            "properties": {
              "operand": {
                "type": "string",
                "enum": [
                  "list"
                ]
              },
              "value": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          {
            "type": "object",
            "required": [
              "operand",
              "value"
            ],
            "properties": {
              "operand": {
                "type": "string",
                "enum": [
                  "setting"
                ]
              },
              "value": {
                "$ref": "#/components/schemas/SettingKey"
              }
            }
          }
        ],
        "description": "A condition's right-hand side. **A literal or a setting key** — that is what\nlets a rule be written against a threshold the operator also owns, so tuning\nthe number does not mean re-authoring the rule.",
        "discriminator": {
          "propertyName": "operand"
        }
      },
      "OperatorVerdict": {
        "type": "string",
        "description": "A HUMAN's declaration about an object, overriding the machine's call.\n\nThis is an ROE-relevant act, not a UI preference. Our own doctrine (research\nPart D3) says kinematics alone may never reach *hostile* — a fast, inbound,\nunidentified contact maxes out at *suspect*. **Operator designation is the\nsanctioned path to hostile**, which is exactly why it must be recorded with\nprovenance (who, when) rather than flipped silently in the client.",
        "enum": [
          "hostile",
          "benign"
        ]
      },
      "OtherSignal": {
        "type": "object",
        "description": "A modality without a dedicated variant yet (LIDAR, generic SAPIENT/IoT nodes).\nCarries the reporting sensor's modality label for provenance.",
        "required": [
          "sensor_modality"
        ],
        "properties": {
          "range_m": {
            "type": "number",
            "format": "double",
            "description": "**A measured range with no bearing.** An annulus centred on the sensor —\nmmWave proximity, an RSSI-derived distance. Additive and optional on the typed\nrepresentation, which is what keeps `obs.v1` frozen while letting a range-only\nsensor say what it actually measured instead of borrowing a position.",
            "nullable": true
          },
          "range_sigma_m": {
            "type": "number",
            "format": "double",
            "description": "1σ on that range. `None` means the sensor did not state one — never zero,\nwhich would read as perfect.",
            "nullable": true
          },
          "sensor_modality": {
            "type": "string",
            "description": "The reporting sensor's modality label. Serialized as `sensor_modality` so it\nnever collides with the `modality` tag [`Signal`] uses to discriminate."
          },
          "zone_radius_m": {
            "type": "number",
            "format": "double",
            "description": "**The radius of the volume a presence detection covers.** Set by a sensor that\ncan only say *something is within my reach*: a single-microphone acoustic node,\na CSI link. It describes the sensor's coverage, not the target's distance.",
            "nullable": true
          }
        }
      },
      "ParamInfo": {
        "type": "object",
        "description": "Serializable declared parameter (name + defaults + bounds) for the UI.",
        "required": [
          "name",
          "default",
          "min",
          "max",
          "description"
        ],
        "properties": {
          "default": {
            "type": "number",
            "format": "double"
          },
          "description": {
            "type": "string"
          },
          "max": {
            "type": "number",
            "format": "double"
          },
          "min": {
            "type": "number",
            "format": "double"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "ParamKind": {
        "type": "string",
        "description": "The shape of the parameter a verb needs. The console uses this to decide whether a\nbutton fires immediately, opens an altitude prompt, or arms a map tool.",
        "enum": [
          "none",
          "altitude",
          "point",
          "route",
          "area",
          "target"
        ]
      },
      "ParamSpec": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ParamType"
          },
          {
            "type": "object",
            "required": [
              "key",
              "label"
            ],
            "properties": {
              "default": {
                "type": "string",
                "nullable": true
              },
              "key": {
                "type": "string"
              },
              "label": {
                "type": "string",
                "description": "What the form calls it."
              },
              "required": {
                "type": "boolean"
              },
              "secret": {
                "type": "boolean",
                "description": "**One flag, two jobs.** It decides storage — the value lives in the secret\nstore and [`Link::config`] holds only a `secret://` reference — and it decides\nredaction, because [`crate::fleet::LinkSnapshot`] rides the picture and a\nsecret on it is a secret in every SSE delta."
              }
            }
          }
        ],
        "description": "**One thing a link needs that its [`Transport`] address cannot carry.** A broker\npassword, a tenant id, a poll interval, a vendor app key.\n\nThis is the half [`AdapterCaps`] was missing: `GET /api/adapters` could say which\ntransports were legal and could therefore generate a form with exactly one field\nin it — an endpoint. Every authenticated integration needs more than that."
      },
      "ParamType": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "text"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "min",
              "max",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "integer"
                ]
              },
              "max": {
                "type": "integer",
                "format": "int64"
              },
              "min": {
                "type": "integer",
                "format": "int64"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "options",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "enum"
                ]
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "url"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "bool"
                ]
              }
            }
          }
        ],
        "description": "What kind of value a parameter takes, so the add form renders the right control\nand the route can refuse a bad one before a socket is opened.\n\nNamed `ParamType` rather than `ParamKind` on purpose: [`crate::command::ParamKind`]\nalready exists and means something else entirely — what an *operator* must supply\nfor a verb. Two types with one name, one crate-root re-export away from an\nambiguity, is a trap worth not setting.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "PayloadSensing": {
        "type": "object",
        "description": "What a resolved payload can actually see. `None` on the payload means the\nprofile named does not exist here — see [`ResolvedPayload`].",
        "required": [
          "modality",
          "measurement",
          "max_range_m",
          "fov_deg",
          "bearing_only"
        ],
        "properties": {
          "bearing_only": {
            "type": "boolean",
            "description": "Reported for the same reason the console renders a wedge and never a blip:\na bearing-only sensor cannot produce a range, and pretending otherwise\nfabricates a measurement."
          },
          "fov_deg": {
            "type": "number",
            "format": "double"
          },
          "max_range_m": {
            "type": "number",
            "format": "double"
          },
          "measurement": {
            "$ref": "#/components/schemas/MeasurementKind"
          },
          "modality": {
            "$ref": "#/components/schemas/Modality"
          }
        }
      },
      "PayloadSource": {
        "type": "string",
        "description": "Which branch of the resolution order produced a payload — the same three-step\norder as [`resolved_performance`](crate::asset::AssetSpec::resolved_performance):\n*this vehicle's own list → the model's catalog payloads → nothing*.",
        "enum": [
          "vehicle",
          "catalog"
        ]
      },
      "PayloadSpec": {
        "type": "object",
        "description": "**What a platform carries** — one payload, as a catalog entry or a vehicle\ndeclares it (U7 §3).\n\nA payload is a *sensor, mounted*. The catalog used to list what a model\ncarries as free text (`\"RGB camera\"`, `\"video downlink\"`) which no\n[`SensorProfile`] resolved against, so the knowledge was present and\nunusable: \"carries a camera\" is a noun, while `eo-turret` is a reach, a field\nof view and a measurement kind. This is the same declaration with the profile\nnamed, so it resolves.\n\n`label` is what a person calls this particular fit (`Hasselblad 100MP main`).\nIt is optional because the profile id is already an honest answer; a payload\nis never blocked on someone having written a nicer name for it.",
        "required": [
          "profile"
        ],
        "properties": {
          "boresight_deg": {
            "type": "number",
            "format": "double",
            "description": "Where it looks, relative to the platform's nose. Ignored by an omni profile."
          },
          "label": {
            "type": "string",
            "description": "What the operator calls it. Absent ⇒ the profile speaks for itself.",
            "nullable": true
          },
          "profile": {
            "type": "string",
            "description": "A [`SensorProfile`] id — `eo-turret`, `ir-turret`, `rf-df`, …"
          }
        }
      },
      "PendingRelease": {
        "type": "object",
        "description": "One scheduled release, waiting out its window.",
        "required": [
          "id",
          "assignment_id",
          "effect",
          "target_track_id",
          "asset_id",
          "window_s",
          "decided_t",
          "fires_at",
          "binding"
        ],
        "properties": {
          "asset_id": {
            "type": "string"
          },
          "assignment_id": {
            "type": "string"
          },
          "binding": {
            "$ref": "#/components/schemas/Binding"
          },
          "decided_t": {
            "type": "number",
            "format": "double"
          },
          "effect": {
            "type": "string"
          },
          "fires_at": {
            "type": "number",
            "format": "double"
          },
          "id": {
            "type": "string"
          },
          "seen_t": {
            "type": "number",
            "format": "double",
            "description": "When a console first reported this on screen. `None` means nobody has.",
            "nullable": true
          },
          "target_track_id": {
            "type": "string"
          },
          "window_s": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Performer": {
        "oneOf": [
          {
            "type": "string",
            "description": "Nobody is tasked — the system raises the alert, writes the designation, or\nmoves the setting.",
            "enum": [
              "system"
            ]
          },
          {
            "type": "string",
            "description": "The asset the rule fired on. `bingo_rtb` sends **that** aircraft home.",
            "enum": [
              "subject"
            ]
          },
          {
            "type": "object",
            "required": [
              "query"
            ],
            "properties": {
              "query": {
                "$ref": "#/components/schemas/EntityQuery"
              }
            }
          }
        ],
        "description": "Who performs an action."
      },
      "PhysicalLink": {
        "type": "object",
        "description": "**One physical link of the deployment, as `GET /api/links` answers it**\n(`docs/specs/2026-09-07-one-configuration-links-on-the-integration.md` §4).\n\nA line under `links:` in `dome.yaml`: the deployment's, opened at boot,\nnever a row. `attached` is this workspace's answer; `state`, `heard` and\n`assets` are facts about the socket and about this workspace's rows on it.",
        "required": [
          "protocol",
          "name",
          "endpoint",
          "state",
          "attached",
          "heard",
          "assets"
        ],
        "properties": {
          "assets": {
            "type": "integer",
            "format": "int32",
            "description": "This workspace's registry rows for devices heard on it.",
            "minimum": 0
          },
          "attached": {
            "type": "boolean",
            "description": "Whether this workspace listens to it."
          },
          "endpoint": {
            "type": "string",
            "description": "`Transport`'s canonical form."
          },
          "heard": {
            "type": "integer",
            "format": "int32",
            "description": "Devices heard on it, whoever they belong to.",
            "minimum": 0
          },
          "name": {
            "type": "string",
            "description": "The name the file gives it: `GCS`, `Radio`, `Broker`."
          },
          "protocol": {
            "$ref": "#/components/schemas/AdapterId"
          },
          "state": {
            "$ref": "#/components/schemas/TransportState"
          }
        }
      },
      "Pick": {
        "type": "string",
        "description": "Which of the survivors to take.",
        "enum": [
          "nearest",
          "idlest",
          "first",
          "all"
        ]
      },
      "Picture": {
        "type": "object",
        "description": "The one operating picture: hydrate once, then apply generation-stamped deltas.\n\nEvery field is a slice of a single world at a single instant, so the status\nbar, the map, the fleet panel and the threat rail can all read the same object\nand cannot disagree. `generation` is the monotonic stamp every delta carries;\na gap between the last applied generation and an incoming delta means the\nclient re-hydrates rather than trusting a picture it knows is stale.",
        "required": [
          "generation",
          "t",
          "tracks",
          "threats"
        ],
        "properties": {
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AssetView"
            },
            "description": "**OUR FORCES** — every asset that is ours, saved or merely heard, in the\none shape the console reads. Changes arrive as the `fleet_slice` delta.\n\nThis rode nothing for a long time, and the console hydrated it once at\nmount and then POLLED `GET /api/fleet/{id}` every two seconds for whichever\nasset was selected. Two consequences, both reported as separate faults:\nthe rail stayed empty when a scenario put three aircraft on a link after\nthe page had loaded, and pressing ARM changed nothing on screen because a\ncard's armed state had no way to arrive — the poll was the only thing that\never moved it, and it was answering 404.\n\n`live-data-flow.md`: if a panel needs live state, put it on the picture and\ngive it a delta. This is that."
          },
          "candidate_plans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Plan"
            }
          },
          "candidates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiscoveredAsset"
            },
            "description": "**Heard, not yet added** (#300) — platforms announcing themselves on a link\nthat no registry row claims, and that the link's own discovery policy is\nwilling to offer.\n\nThe most time-sensitive thing on the Assets page and the cheapest to act on,\nwhich is exactly why it must not depend on a page happening to re-ask: a\ncandidate appearing bumps the generation and reaches the page as a delta."
          },
          "default_region": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RegionSummary"
              }
            ],
            "nullable": true
          },
          "engagement_mode": {
            "$ref": "#/components/schemas/Level"
          },
          "estimation": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EstimationFeature"
            }
          },
          "fusion_health": {
            "$ref": "#/components/schemas/FusionHealth"
          },
          "generation": {
            "type": "integer",
            "format": "int64",
            "description": "Monotonic; every delta carries the generation it produced.",
            "minimum": 0
          },
          "integrations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IntegrationLive"
            },
            "description": "**What each protocol is doing** (#300) — the rung, the count heard, the age\nof the last thing that arrived. Changes arrive as the `integration_slice`\ndelta, beside [`Self::candidates`], which moves for the same reasons.\n\nThis replaced a two-second `setInterval` on the settings Assets page that\nfetched `/api/links` and the discovery endpoint — the last poll in Settings,\nand the thing that made a console sitting idle on a configuration screen\ngenerate continuous traffic. Liveness is producer-side truth: the server\nreads it off the stream and pushes the answer, exactly as Lattice derives\n`connection_status` and SAPIENT makes `StatusReport` an obligation.\n\nOnly what MOVES rides here. The specs (labels, command tables, parameters)\nare compiled in and fetched once; sending them on every re-hydration would\npay for a protocol's whole vocabulary to report that a radio is still up."
          },
          "pending_releases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PendingRelease"
            },
            "description": "**The releases counting down**, and the reason a console can show a stop.\n\nAn act the line delegated with a stop window runs on the runtime's clock\nunless a human takes it back. Until this rode the picture there was nothing\nfor a console to draw a countdown from and nothing to offer a stop on, so the\nwindow was delegation with a catch nobody could reach.\n\nLoad-bearing rather than informational: a window that lapses with no console\nwatching does not fire, so this is what makes a delegated release legitimate\nas well as visible."
          },
          "plan": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Plan"
              }
            ],
            "nullable": true
          },
          "plan_run": {
            "$ref": "#/components/schemas/PlanRunState"
          },
          "protected": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProtectedAsset"
            },
            "description": "What this deployment is defending (#62). Empty means *nothing is\nconfigured* — the UI must render nothing, not an unnamed marker at the\nmap centre labelled `ASSET-1`."
          },
          "region": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RegionSummary"
              }
            ],
            "nullable": true
          },
          "sensors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SensorView"
            },
            "description": "**Sensors as entities** (U3 #80): one [`SensorView`] per placed or observed\nsensor, so a ground sensor has a card, a quick view and a FOCUS panel like\nanything else that is ours. Changes arrive as the `sensor_slice` delta —\nnothing polls this.\n\nThis field previously carried `Vec<SensorRangeFeature>` and was `[]` in\nevery build (no placed-sensor source was wired into the runtime), so the\nre-typing cannot regress a client. The overlay envelope is now derived via\n[`SensorRangeFeature::from_view`] rather than transported twice."
          },
          "sim": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SimPictureState"
              }
            ],
            "nullable": true
          },
          "t": {
            "type": "number",
            "format": "double",
            "description": "Sim-time (seconds) the picture was assembled at."
          },
          "threats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Threat"
            }
          },
          "tracks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Track"
            }
          }
        }
      },
      "PinnedPair": {
        "type": "object",
        "description": "One accepted pairing: this asset stays on this threat. The solver emits it\nverbatim and removes both ends from the matrix — absence, never expense\n(the `mask_by_action` discipline).",
        "required": [
          "asset_id",
          "target_id"
        ],
        "properties": {
          "asset_id": {
            "type": "string"
          },
          "target_id": {
            "type": "string"
          }
        }
      },
      "PlacedSensorSpec": {
        "type": "object",
        "description": "One placed sensor in a scenario (or carried by an [`crate::asset::AssetSpec`]):\nwhich [`SensorProfile`] it runs, where it sits (fixed or asset-mounted), and\nwhere it points. Replaces the old fixed-position-only ground-sensor spec — a\nsensor is now a first-class asset that can be emplaced or flown.",
        "required": [
          "id",
          "profile",
          "placement"
        ],
        "properties": {
          "boresight_deg": {
            "type": "number",
            "format": "double"
          },
          "id": {
            "type": "string"
          },
          "placement": {
            "$ref": "#/components/schemas/SensorPlacement"
          },
          "profile": {
            "type": "string",
            "description": "A [`SensorProfile`] id (`ground-radar-360`, `rf-df`, `acoustic-array`, …)."
          }
        }
      },
      "Placement": {
        "type": "object",
        "description": "**Where a sensor stands, corrected after it was installed** (#306).\n\nA mast that was re-guyed five degrees off was wrong forever: position and\nboresight were written once by the add flow and no route would take them\nagain. This is that route, and it writes the fact to **both** places the fact\nlives, because they are read by different things:\n\n- `spec.sensors[0]` — a [`PlacedSensorSpec`] with a `Fixed` mount in ENU\nmetres. This is the copy that matters: `declared_from_registry` hands it to\nthe runtime, `GET /api/sensors` builds its view from it, and the console's\ncoverage is drawn from that view. A sensor with nothing here contributes\nnothing to fusion, whatever its page says.\n- `spec.config` — the **manifest's** copy of what an operator typed at the add\nscreen. A sensor added through a product recipe carries `lat`/`lon` there\nand nothing in `spec.sensors`, which is exactly how one ends up showing a\nposition on its page while `picture.sensors` stays empty.\n\nWriting one and leaving the other is how the two disagree, so a placement\nwrite touches both.",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "boresight_deg": {
            "type": "number",
            "format": "double",
            "description": "Degrees true. Absent ⇒ keep the stored one; an omni sensor never sends\none, because a boresight on an omni sensor is a number with no meaning.",
            "nullable": true
          },
          "height_m": {
            "type": "number",
            "format": "double",
            "description": "Height above ground, metres — the ENU `z`, which is what the simulator's\nsensor geometry ranges from and what `enu_to_geo` reports as `alt_m`.\nAbsent ⇒ keep the stored height.",
            "nullable": true
          },
          "lat": {
            "type": "number",
            "format": "double"
          },
          "lon": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Plan": {
        "type": "object",
        "description": "A Plan — how our system will command the swarm against the current picture.\nCommander's intent + concrete assignments, with one approval lifecycle.",
        "required": [
          "id",
          "generation",
          "created_t",
          "created_by",
          "input_t",
          "status",
          "objective",
          "assignments"
        ],
        "properties": {
          "accepts": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlanAccepts"
              }
            ],
            "nullable": true
          },
          "actions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlannedAction"
            },
            "description": "The composed action set (issue #61) — rules + solver output in one ordered\nlist, each [`PlannedAction`] stamped with its origin. Additive to `plan.v1`:\nempty on legacy plans and on the projection until the combined loop fills it;\n`assignments` above stays the back-compat view of the committing actions."
          },
          "assignments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Assignment"
            }
          },
          "authored_under": {
            "$ref": "#/components/schemas/AutonomyPolicy"
          },
          "created_by": {
            "$ref": "#/components/schemas/PlanOrigin"
          },
          "created_t": {
            "type": "number",
            "format": "double"
          },
          "decision_config_version": {
            "type": "integer",
            "format": "int32",
            "description": "The [`DecisionConfig`](crate::DecisionConfig) version this plan was authored\nunder (#63). A plan whose provenance cannot be reconstructed is not\nauditable, and an unauditable C-UAS decision is worthless after the fact —\n\"why did it propose that?\" is answerable only if the policy in force at the\ntime is identifiable. `0` on plans authored before the policy existed.",
            "minimum": 0
          },
          "generation": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "id": {
            "type": "string"
          },
          "influence_id": {
            "type": "string",
            "description": "The accepted influence this plan was authored under (spec 2026-08-25 §6),\nwhen one was in force — beside `decision_config_version`, the audit\ntrail's answer to \"why did economy weigh 3.0 here\".",
            "nullable": true
          },
          "input_t": {
            "type": "number",
            "format": "double"
          },
          "label": {
            "type": "string",
            "description": "The authoring strategy's display label (e.g. \"PROTECT — maximum\ncoverage\"), for candidate plans authored by the planning service.",
            "nullable": true
          },
          "objective": {
            "$ref": "#/components/schemas/Objective"
          },
          "parent_plan_id": {
            "type": "string",
            "nullable": true
          },
          "predicted": {
            "$ref": "#/components/schemas/PredictedOutcome"
          },
          "provenance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EventProvenance"
              }
            ],
            "nullable": true
          },
          "rationale": {
            "type": "string",
            "nullable": true
          },
          "reproposed": {
            "type": "boolean",
            "description": "**This exact set was already offered, and the operator has not acted on\nit** (#195 §4). The planner re-authored on its cadence and derived the\nsame committing actions; the geometry is fresher, the decision is not new.\n\nA surface reads this to render the card without re-alerting: no sound, no\nre-entry at the top of the queue, no second entry in the interrupt budget.\nIt is on the wire rather than kept in the backend because the *record*\nmust still say what was offered and when — suppressing the event instead\nwould buy quiet by deleting the audit trail."
          },
          "review": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlanReview"
              }
            ],
            "nullable": true
          },
          "review_error": {
            "type": "string",
            "description": "Why there is no [`review`](Self::review) — the reviewer was unreachable,\nunconfigured, or answered with something unusable.\n\nA missing review is REPORTED, never substituted. The system used to fall\nback to a deterministic if/else over the plan's own predicted outcome and\npresent it as the model's verdict, which manufactured confidence out of\nnothing (and, when a real provider failed, attributed the fabrication to\nthat provider's name). An operator approving an engagement must be able to\ntell \"the reviewer endorsed this\" from \"no reviewer answered\".",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/PlanStatus"
          }
        }
      },
      "PlanAccepts": {
        "type": "object",
        "description": "What a Plan **accepts** — the resolved diff the plan leaves behind, computed at\nauthoring time from the engageable set minus the committed set (and the\nprotected-asset set minus the defended set).\n\nThis is the *identity* companion to [`PredictedOutcome`]'s counts: where\n`expected_leakers` says \"one engageable threat goes unengaged\", this says\n*which* one. It exists so the map can draw an unengaged threat **as unengaged**\nand the plan surface can name what it gives up — **without the UI re-resolving\nthe plan against the live picture**, which is how two surfaces come to disagree.\nThe plan carries its own overlay; the resolution is computed once, here.",
        "properties": {
          "uncovered_asset_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Protected assets (#62) left without a defending assignment — asset ids."
          },
          "unengaged_target_ids": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ObjectId"
            },
            "description": "Engageable threats (hostile/suspect, unresolved) this plan commits **no**\neffector against — object ids matching the assignment target ids."
          },
          "zones_touched": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The doctrine zones (`KeepOut` / `FreeFire`) this plan's geometry enters, by\nzone **id**, in first-touched order, deduped (#69). Computed once at\nauthoring time — where the zone set and the authored geometry meet — so the\noverlay can outline and name them **without the UI re-resolving membership\nagainst the live zone set**. Identity is the plan's; the shape/name are\nlooked up for rendering (the same split as `unengaged_target_ids`). Empty\nwhen the plan touches none; additive to `plan.v1`, absent on legacy plans."
          }
        }
      },
      "PlanCandidate": {
        "type": "object",
        "description": "A CANDIDATE course of action — the solver's *proposal*, compared before approval.\nSeveral coexist (the Plan Viewer's parallel alternatives). Shares the `Tasking`\nbody with `Mission`; approving one yields the committed `Mission`.",
        "required": [
          "id",
          "strategy",
          "objective",
          "roe_posture",
          "taskings",
          "status",
          "origin"
        ],
        "properties": {
          "grouping": {
            "$ref": "#/components/schemas/Grouping"
          },
          "id": {
            "type": "string"
          },
          "objective": {
            "$ref": "#/components/schemas/Objective"
          },
          "origin": {
            "$ref": "#/components/schemas/PlanOrigin"
          },
          "predicted": {
            "$ref": "#/components/schemas/PredictedOutcome"
          },
          "rationale": {
            "type": "string",
            "nullable": true
          },
          "roe_posture": {
            "$ref": "#/components/schemas/Level"
          },
          "status": {
            "$ref": "#/components/schemas/MissionStatus"
          },
          "strategy": {
            "type": "string",
            "description": "The solver strategy that produced it (\"protect\", \"min_leakers\", …)."
          },
          "taskings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tasking"
            }
          }
        }
      },
      "PlanInfluence": {
        "type": "object",
        "description": "The operator-accepted solve inputs in force for the current run.",
        "properties": {
          "holds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Assets kept out of the matrix entirely.",
            "uniqueItems": true
          },
          "influence_id": {
            "type": "string",
            "description": "Identity of the accept that produced this influence (`INF-<n>`), stamped\nonto every plan authored under it. Empty for the empty influence."
          },
          "pins": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PinnedPair"
            },
            "description": "Pairings the solve must keep."
          },
          "weights": {
            "type": "object",
            "description": "Per-metric overrides applied over the saved doctrine weights. Partial:\nan absent metric keeps its configured value. Clamped to the registry\nbound 0..10 on receipt.",
            "additionalProperties": {
              "type": "number",
              "format": "double"
            }
          }
        }
      },
      "PlanOrigin": {
        "type": "string",
        "description": "Who created a Plan (provenance for audit + replan lineage).",
        "enum": [
          "solver",
          "operator",
          "agent"
        ]
      },
      "PlanReview": {
        "type": "object",
        "description": "An LLM's review of a candidate Plan (issue: PLAN authoring pipeline). The\nsolver authors the plan; the reviewer critiques it. Advisory only — the\napproval authority stays with the operator (and the review says so on the\ncard, with provenance: which model, how confident).",
        "required": [
          "reviewer",
          "verdict",
          "confidence",
          "summary"
        ],
        "properties": {
          "confidence": {
            "type": "number",
            "format": "double",
            "description": "Reviewer confidence in its own verdict, 0..1."
          },
          "reviewer": {
            "type": "string",
            "description": "The reviewing provider (e.g. `mock-llm-v1`, `distri:<model>`)."
          },
          "summary": {
            "type": "string",
            "description": "One-paragraph critique shown on the plan card."
          },
          "verdict": {
            "$ref": "#/components/schemas/ReviewVerdict"
          }
        }
      },
      "PlanRunState": {
        "type": "object",
        "description": "The **backend-owned** state of the plan-authoring loop (#61), published through\nthe picture so an autonomous run and a clicked run are indistinguishable to the\nUI, and two clients cannot start overlapping passes (the single-flight guard is\nserver-side). Additive to the engagement snapshot.",
        "required": [
          "status",
          "seq",
          "trigger"
        ],
        "properties": {
          "last_error": {
            "type": "string",
            "description": "The reason the last pass could not author, if any.",
            "nullable": true
          },
          "seq": {
            "type": "integer",
            "format": "int64",
            "description": "Monotonic — the client's auto-summary keys on this so an unchanged decision\nre-published with new plan ids does not re-fire.",
            "minimum": 0
          },
          "started_at": {
            "type": "number",
            "format": "double",
            "description": "Sim-time the current pass started, while `status == Authoring`.",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/PlanRunStatus"
          },
          "trigger": {
            "$ref": "#/components/schemas/PlanRunTrigger"
          }
        }
      },
      "PlanRunStatus": {
        "type": "string",
        "description": "Whether the plan-authoring loop is idle or running a pass (#61).",
        "enum": [
          "idle",
          "authoring"
        ]
      },
      "PlanRunTrigger": {
        "type": "string",
        "description": "What triggered the current/last authoring pass — an operator click or the\nautonomous cadence. The two run the SAME code path; this only records which.",
        "enum": [
          "auto",
          "manual"
        ]
      },
      "PlanStabilityPolicy": {
        "type": "object",
        "description": "**The plan surface's churn budget.**\n\nA slow contact wandering near the threat threshold produced 13 plan\nproposals and 15 operator interrupts in two minutes, measured. Each plan was\nthe correct answer to the picture at the instant it was solved; the fault was\nthat the picture was re-asked sixty times a minute and every different answer\nbecame a card demanding a decision. Below ~70% reliability an alert class is\nworse than nothing (`docs/references/supervisory-control-hmi.md`), so this is\na safety property, not a comfort one.\n\n**Authored, not compiled.** These were `const`s in `dome-core`, which meant a\ndeployment whose contacts sit differently against the threshold had no way to\nsay so and no way to see what the numbers were. They are settings for the\nsame reason every other threshold is: `SolverConfig`'s own contract is \"no\nhardcoded solver consts\".",
        "required": [
          "enter_score",
          "exit_score",
          "smoothing_s",
          "stand_down_s"
        ],
        "properties": {
          "enter_score": {
            "type": "number",
            "format": "double",
            "description": "Threat score (0..1) at or above which a contact **enters** the plannable\nset."
          },
          "exit_score": {
            "type": "number",
            "format": "double",
            "description": "Score below which a contact already in the set **leaves** it. Strictly\nunder `enter_score`: the gap between them IS the hysteresis, and a gap of\nzero is no gate at all — a score sitting on one line flaps across it, and\nevery flap is a re-plan."
          },
          "smoothing_s": {
            "type": "number",
            "format": "double",
            "description": "The smoothing window, in seconds. The planner reasons over a score\nsmoothed across this span rather than the instantaneous one, which\njitters every tick as the estimate moves. `0` disables smoothing."
          },
          "stand_down_s": {
            "type": "number",
            "format": "double",
            "description": "How long the plannable set must stay **empty** before a standing plan is\nwithdrawn. Without it, one tick of lost custody tears down an engagement\nthat the next tick rebuilds."
          }
        }
      },
      "PlanStatus": {
        "type": "string",
        "description": "The Plan's approval lifecycle (single active plan; approve supersedes prior).",
        "enum": [
          "proposed",
          "approved",
          "executing",
          "completed",
          "superseded",
          "rejected"
        ]
      },
      "PlannedAction": {
        "type": "object",
        "description": "One action with provenance, the authority it needs, its execution status, and a\none-line rationale — the unit the operator sees and (when gated) approves. The\ncomposed [`Plan`] is an ordered set of these, from rules AND the solver.",
        "required": [
          "action",
          "origin",
          "approval",
          "status",
          "rationale"
        ],
        "properties": {
          "action": {
            "$ref": "#/components/schemas/Action"
          },
          "approval": {
            "$ref": "#/components/schemas/ApprovalVerdict"
          },
          "origin": {
            "$ref": "#/components/schemas/ActionOrigin"
          },
          "rationale": {
            "type": "string",
            "description": "Why THIS action, in one line."
          },
          "status": {
            "$ref": "#/components/schemas/TaskStatus"
          }
        }
      },
      "PlanningProfile": {
        "type": "object",
        "description": "One named set of planning weights.",
        "required": [
          "id",
          "name",
          "summary",
          "version",
          "shipped",
          "objective",
          "weights",
          "leakage_weight",
          "max_effectors_per_target",
          "assign_below_tti_s",
          "interceptor_speed_mps"
        ],
        "properties": {
          "assign_below_tti_s": {
            "type": "number",
            "format": "double",
            "description": "Commit a defender when time-to-impact drops below this."
          },
          "copied_from": {
            "type": "string",
            "description": "The profile a plan uses when none is named. Exactly one is the site default.\nThe id this was copied from, where it was. Provenance for a tuned profile:\n*\"this started as PROTECT\"* is the first thing anyone asks.",
            "nullable": true
          },
          "id": {
            "type": "string",
            "description": "Stable id — `protect`, `attrit`, or whatever a site names its copy."
          },
          "interceptor_speed_mps": {
            "type": "number",
            "format": "double",
            "description": "How many candidate plans to author. Capped at 1..=3 by design — more\ncandidates is not more insight."
          },
          "leakage_weight": {
            "type": "number",
            "format": "double",
            "description": "How much an expected leaker costs the plan. The number an operator reaches\nfor first, and today it is not addressable at all."
          },
          "max_effectors_per_target": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "name": {
            "type": "string"
          },
          "objective": {
            "$ref": "#/components/schemas/Objective"
          },
          "shipped": {
            "type": "boolean",
            "description": "`true` for the three that ship. A shipped profile may be **copied but not\nedited**: an operator who tunes \"PROTECT\" itself and then cannot get back to\nstock has lost the reference point the other profiles are read against."
          },
          "summary": {
            "type": "string",
            "description": "One line the list shows under the name."
          },
          "version": {
            "type": "integer",
            "format": "int32",
            "description": "Bumped on every save, exactly like `DecisionConfig.version`, so a plan can\nstamp the profile *version* it ran under and a debrief can reconstruct it.",
            "minimum": 0
          },
          "weights": {
            "$ref": "#/components/schemas/SolverWeights"
          }
        }
      },
      "PlanningSlate": {
        "type": "object",
        "description": "**What the planner authors each pass.**\n\nThe model this replaced was one profile marked `site_default` plus a\n`variation_count` on it — which is backwards twice over. The point of\nauthoring more than one plan is to put *genuinely different answers* in front\nof an operator, and different answers come from different profiles: PROTECT\nwill cover the asset, PRESERVE will hold the magazine, and the choice between\nthem is the decision. Asking one profile for three variations asks the same\nopinion three times.\n\nSo the configuration is what an operator would actually say: **how many plans,\nfrom which profiles** — and those are one thing, not two, because a count and\na list can disagree and a list alone cannot.",
        "required": [
          "profiles"
        ],
        "properties": {
          "profiles": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The profiles authored in parallel, in the order they are presented."
          }
        }
      },
      "Platform": {
        "type": "object",
        "description": "The sensing platform for an observation. `id` is always present; the rest are\nproducer-dependent (attitude/velocity/zone/mode are sim-only; `kind`/`geo` are\nabsent on some pass-through adapter frames), so they are omitted when unset.",
        "required": [
          "id"
        ],
        "properties": {
          "att": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Att"
              }
            ],
            "nullable": true
          },
          "geo": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlatformGeo"
              }
            ],
            "nullable": true
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "nullable": true
          },
          "mode": {
            "type": "string",
            "nullable": true
          },
          "vel_mps": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Vel"
              }
            ],
            "nullable": true
          },
          "zone_id": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "PlatformGeo": {
        "type": "object",
        "description": "Emitting platform geodetic position. Uses `alt_m_agl` (height above ground),\ndistinct from a track's `alt_m` (see [`crate::Geo`]).",
        "required": [
          "lat",
          "lon",
          "alt_m_agl"
        ],
        "properties": {
          "alt_m_agl": {
            "type": "number",
            "format": "double"
          },
          "lat": {
            "type": "number",
            "format": "double"
          },
          "lon": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "PlatformIdentity": {
        "type": "object",
        "description": "Make and model, so a card can say `Echodyne EchoGuard` instead of\n`ground-radar-360` (design sheet 03 D2).\n\n**Every field is optional and defaulted.** A card must never be blocked on\ncatalog metadata: a sensor whose make nobody recorded still renders, using what\nthe profile knows. That is why this is not a required struct on `SensorProfile`.",
        "properties": {
          "make": {
            "type": "string",
            "nullable": true
          },
          "model": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "PlatformPerformance": {
        "type": "object",
        "description": "What this individual platform can do (ST2 §1).\n\nEvery field is optional, and the reason is the whole point: **absent means\n\"inherit the catalog\", and absent-with-no-catalog means the console says\n`assumed` rather than guessing.** These three numbers cannot be measured off a\nlink — a hovering platform reads 0.2 m/s, which is not a transit speed, and a\npercentage is not a duration — so they are facts about the platform that\nsomebody has to declare. Declaring them here is how a prototype, an aftermarket\nbattery, or a simulated airframe gets honest estimates without a catalog entry.",
        "properties": {
          "battery_capacity_mah": {
            "type": "number",
            "format": "double",
            "description": "Pack capacity, so a percentage can become a duration on a platform whose\nendurance was never characterised.",
            "nullable": true
          },
          "ceiling_m": {
            "type": "number",
            "format": "double",
            "description": "Service ceiling, metres. Aerial only; meaningless everywhere else.",
            "nullable": true
          },
          "cruise_mps": {
            "type": "number",
            "format": "double",
            "description": "What a leg is actually flown at — the input to every ETA on the console.",
            "nullable": true
          },
          "endurance_min": {
            "type": "number",
            "format": "double",
            "description": "`14 min of flight left` instead of `51% charge`, and every reachability\nverdict. An **aerial or maritime** figure: a ground platform has a range,\nnot a flight time, and a mast has neither (U7 §2).",
            "nullable": true
          },
          "hover_draw_a": {
            "type": "number",
            "format": "double",
            "description": "Optional measured discharge, for the same reason.",
            "nullable": true
          },
          "max_mps": {
            "type": "number",
            "format": "double",
            "description": "*Can this interceptor catch that contact* — never observed, because the\nplatform never flies at max unless told to.",
            "nullable": true
          },
          "range_km": {
            "type": "number",
            "format": "double",
            "description": "How far a ground platform can go and return, km. The ground answer to the\nquestion endurance answers in the air — asked of nothing else.",
            "nullable": true
          }
        }
      },
      "PointingState": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "idle"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Running its own pattern: rotation for a scanning radar, a revisited\nsector for anything else that scans.",
            "required": [
              "sector",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "searching"
                ]
              },
              "sector": {
                "$ref": "#/components/schemas/SearchSector"
              }
            }
          },
          {
            "type": "object",
            "description": "Commanded onto a track and slewing/dwelling toward it, not yet holding\nit. A dwelling radar in this state **is not searching elsewhere**, and\nthat cost is real.",
            "required": [
              "track_id",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "cued"
                ]
              },
              "track_id": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "description": "Holding the target inside its field of view. An imager in this state is\naccruing dwell toward a visual identification.",
            "required": [
              "track_id",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "tracking"
                ]
              },
              "track_id": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "description": "**A human pointed it.** Automation must not re-arbitrate it away until\nthe cue is released; automatic cueing resumes the moment it is.",
            "required": [
              "user",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "operator"
                ]
              },
              "track_id": {
                "type": "string",
                "nullable": true
              },
              "user": {
                "type": "string",
                "description": "Who pointed it. Never blank — an unattributed manual override is\nindistinguishable from a bug."
              }
            }
          }
        ],
        "description": "**What a sensor is being pointed at, and by whom.**\n\nFive states, and each one carries a distinct *behaviour* (Move 5 of\n`docs/specs/2026-08-15-the-console-earns-its-silence.md`) — which is why this\nis not [`SensorState`], whose three values describe *liveness* (is it\nreturning at all) rather than *aim*. A sensor can be `Searching` and `Silent`\nat once, and collapsing the two would make that unsayable.\n\nInternally tagged on `kind`, so a consumer switches on one string and reads\nthe payload it implies. The variant order is the arbitration order the\nexecutor honours: an `Operator` cue outranks everything and is never\nre-arbitrated away until it is released.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "PolicyKind": {
        "type": "string",
        "description": "Which DSL a policy targets.",
        "enum": [
          "behavior",
          "solver"
        ]
      },
      "Predicate": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "all"
            ],
            "properties": {
              "all": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Predicate"
                },
                "description": "Every child must hold. Short-circuits on the first that does not."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "any"
            ],
            "properties": {
              "any": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Predicate"
                },
                "description": "At least one child must hold. Short-circuits on the first that does."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "not"
            ],
            "properties": {
              "not": {
                "$ref": "#/components/schemas/Predicate"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "test"
            ],
            "properties": {
              "test": {
                "$ref": "#/components/schemas/Condition"
              }
            }
          },
          {
            "type": "string",
            "description": "Unconditional. Legal in a subtree; **refused as a whole rule**, because a\nrule that fires on everything every pass is not a rule.",
            "enum": [
              "always"
            ]
          }
        ],
        "description": "A rule's `when`, as a boolean tree over the closed fact grammar.\n\nThe flat `Vec<Condition>` this replaces was implicitly ANDed, which cannot say\n*\"A and (B or C)\"*. In practice that shape got written as two near-duplicate\nrules, and two rules that mean one thing drift apart the first time somebody\ntunes one of them.\n\nExternally tagged, so the JSON an operator reads, an LLM writes and the store\nkeeps are the same document:\n\n```json\n{\"all\": [\n{\"test\": {\"fact\": \"track_affiliation\", \"op\": \"ne\",\n\"rhs\": {\"operand\": \"text\", \"value\": \"friend\"}}},\n{\"any\": [\n{\"test\": {\"fact\": \"track_inside_zone\", \"arg\": \"keep_out\",\n\"op\": \"eq\", \"rhs\": {\"operand\": \"bool\", \"value\": true}}},\n{\"test\": {\"fact\": \"track_dwell_s\", \"op\": \"gt\",\n\"rhs\": {\"operand\": \"number\", \"value\": 120}}}\n]}\n]}\n```\n\n## What nesting does not change\n\n**Still no chaining.** A tree is more expressive *within* one rule; it creates\nno dependency between rules, so the single-pass guarantee is untouched.\n**Still pure and total.** `All([])` is `true` and `Any([])` is `false` — the\nidentities — and evaluation reads only [`Facts`], which has no clock in it."
      },
      "PredictedOutcome": {
        "type": "object",
        "description": "The predicted outcome of a Plan — self-describing, for the card + the copilot.",
        "required": [
          "expected_leakers",
          "coverage_pct",
          "assigned"
        ],
        "properties": {
          "assigned": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "coverage_pct": {
            "type": "number",
            "format": "double"
          },
          "expected_leakers": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          }
        }
      },
      "Product": {
        "type": "object",
        "description": "One catalogued product.",
        "required": [
          "id",
          "make",
          "model",
          "kind",
          "domain",
          "summary",
          "connections"
        ],
        "properties": {
          "connections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConnectionRecipe"
            }
          },
          "domain": {
            "$ref": "#/components/schemas/AssetDomain"
          },
          "glyph": {
            "type": "string",
            "description": "Its silhouette. Absent ⇒ the console falls back to its category default.",
            "nullable": true
          },
          "id": {
            "type": "string",
            "description": "`dji/mavic-3-enterprise`. Namespaced, stable, and written into manifests —\nso it is frozen the moment one is exported."
          },
          "kind": {
            "$ref": "#/components/schemas/ProductKind"
          },
          "make": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "profile": {
            "type": "string",
            "description": "A sensor's [`SensorProfile`](super::sensor_profile::SensorProfile) id, or a\nvehicle's platform-catalog name — the exact string `catalogFor()` matches on.",
            "nullable": true
          },
          "summary": {
            "type": "string",
            "description": "One line, pre-formatted, in the units its kind is measured in."
          },
          "thumbnail": {
            "type": "string",
            "description": "A licensed image under `ui/public/products/`. Absent everywhere today.",
            "nullable": true
          },
          "vendor_url": {
            "type": "string",
            "description": "**The vendor's own page.** An operator deciding what to buy or how to wire\nit needs the source of truth, and paraphrasing a datasheet into our catalog\nis how the two drift.",
            "nullable": true
          }
        }
      },
      "ProductKind": {
        "type": "string",
        "description": "What a product is. Mirrors [`crate::asset::AssetKind`] minus `Protected`,\nwhich is not something you *add from a catalog* — it is a place you defend.",
        "enum": [
          "vehicle",
          "sensor"
        ]
      },
      "ProductView": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Product"
          },
          {
            "type": "object",
            "required": [
              "connections"
            ],
            "properties": {
              "connections": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RecipeView"
                }
              },
              "sensing": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Sensing"
                  }
                ],
                "nullable": true
              }
            }
          }
        ]
      },
      "ProfileSet": {
        "type": "object",
        "required": [
          "version",
          "profiles",
          "slate"
        ],
        "properties": {
          "profiles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanningProfile"
            }
          },
          "slate": {
            "$ref": "#/components/schemas/PlanningSlate"
          },
          "version": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          }
        }
      },
      "ProposeReq": {
        "type": "object",
        "description": "A request to propose candidate plans: which groups, against what target, and\n(optionally) which solver strategies to generate — defaults to the 3-way set.",
        "required": [
          "groups",
          "target"
        ],
        "properties": {
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Group"
            }
          },
          "policy": {
            "type": "string",
            "nullable": true
          },
          "strategies": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "target": {
            "$ref": "#/components/schemas/TargetRef"
          }
        }
      },
      "ProtectedAsset": {
        "type": "object",
        "description": "What we are defending — the thing ranges, CPA and TTI are measured against.\n\nFirst-class and named: an unnamed origin is not an asset, and the UI renders\n[`ProtectedAsset::name`] verbatim rather than inventing an `ASSET-1` label.",
        "required": [
          "id",
          "name",
          "enu"
        ],
        "properties": {
          "enu": {
            "$ref": "#/components/schemas/Enu"
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/ProtectedAssetKind"
          },
          "name": {
            "type": "string",
            "description": "Operator-authored, shown verbatim — \"Terminal 1\", \"Fuel Farm\"."
          },
          "priority": {
            "type": "integer",
            "format": "int32",
            "description": "Relative worth when several are threatened at once — feeds the solver's\ncoverage objective. Equal by default, so it can be ignored until it matters.",
            "minimum": 0
          },
          "radius_m": {
            "type": "number",
            "format": "double",
            "description": "Radius treated as \"at the asset\" for impact and CPA purposes."
          },
          "region_id": {
            "type": "string",
            "description": "Scoped to a region, or global (`None`).",
            "nullable": true
          }
        }
      },
      "ProtectedAssetKind": {
        "type": "string",
        "description": "What kind of thing is being defended. Semantic only — the solver reads\n[`ProtectedAsset::priority`], not this.",
        "enum": [
          "site",
          "runway",
          "building",
          "crowd",
          "vessel",
          "other"
        ]
      },
      "ProtocolFamily": {
        "type": "object",
        "description": "One protocol family the catalogue names, and what it is.",
        "required": [
          "id",
          "summary",
          "used_by"
        ],
        "properties": {
          "adapters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdapterId"
            },
            "description": "The adapters in this build that carry it. Empty where none does, and\nthe client colours a recipe pill by this rather than by its own list."
          },
          "id": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "used_by": {
            "type": "string"
          }
        }
      },
      "Provenance": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "real"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Generated by a provider (`dome-simulator`, an AirSim host, …).",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "simulated"
                ]
              },
              "provider": {
                "type": "string",
                "description": "Who runs it — named, because \"simulated\" alone does not say by what."
              }
            }
          }
        ],
        "description": "Where this platform comes from — a real machine, or one a provider is\nsimulating (ST2 §4).\n\nThis is the field the OUR FORCES corner flash carries, and it replaced\naffiliation there for one reason: **everything in the registry we operate is\nours, so affiliation never varies and a mark that never varies carries no\ninformation.** Provenance does vary, and confusing a simulated platform for a\nreal one is the worst mistake this product allows.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "Provides": {
        "type": "string",
        "description": "What a connection gives the operator, in their terms rather than ours.",
        "enum": [
          "telemetry",
          "detections",
          "video",
          "commands",
          "pointing"
        ]
      },
      "PublicUser": {
        "type": "object",
        "description": "A user, as anything outside the database sees them. No password, because\nthere is none: a login is a code sent to this address.",
        "required": [
          "id",
          "email",
          "user_name",
          "is_admin",
          "created_at"
        ],
        "properties": {
          "created_at": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "id": {
            "type": "string",
            "description": "`usr_…`"
          },
          "is_admin": {
            "type": "boolean",
            "description": "Deployment-wide administrator. Not a workspace role."
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "user_name": {
            "type": "string",
            "description": "Unique handle, derived from the email on first sight."
          }
        }
      },
      "Quality": {
        "type": "object",
        "description": "Per-observation quality metrics. All producer-dependent, all optional; fusion\nreads `geoloc_sigma_m` (with a default) for measurement noise.",
        "properties": {
          "age_s": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "bearing_sigma_deg": {
            "type": "number",
            "format": "double",
            "description": "1σ bearing (azimuth) uncertainty for **this** detection, degrees.\n\nA bearing sensor's angular accuracy is a property of the detection, not\nonly of the sensor: a 6-px bounding box localizes worse than a 60-px one,\nand a low-SNR acoustic bearing worse than a loud one. Fusion's bearing\nupdate reads this first and falls back to the sensor's\n[`SensorProfile::meas`](crate::catalog::sensor_profile::SensorProfile)\nonly when it is absent — it never substitutes a global constant.",
            "nullable": true
          },
          "ce_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "confidence": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "detector_conf": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "elevation_sigma_deg": {
            "type": "number",
            "format": "double",
            "description": "1σ elevation uncertainty for this detection, degrees. Absent on a sensor\nthat measures azimuth only; fusion then performs a 1-DOF update rather\nthan inventing an elevation constraint.",
            "nullable": true
          },
          "geoloc_sigma_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "le_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "range_nm": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "rssi_dbm": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        }
      },
      "RadarPlot": {
        "type": "object",
        "description": "Radar plot detection (ASTERIX CAT048, SAPIENT RADAR node).",
        "properties": {
          "azimuth_deg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "doppler_mps": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "elevation_deg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "range_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "rcs_m2": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "snr_db": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "track_number": {
            "type": "integer",
            "format": "int32",
            "nullable": true,
            "minimum": 0
          }
        }
      },
      "RecipeView": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ConnectionRecipe"
          },
          {
            "type": "object",
            "required": [
              "params",
              "link_params",
              "asset_params",
              "needs_endpoint",
              "servable",
              "endpoint_is_ours",
              "credential_scope"
            ],
            "properties": {
              "asset_params": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ParamSpec"
                },
                "description": "**The thing's own** — where a mast stands, how far it hears. Two nodes on\none broker differ here and must."
              },
              "credential_scope": {
                "$ref": "#/components/schemas/CredentialScope"
              },
              "endpoint_is_ours": {
                "type": "boolean",
                "description": "**Whose address is it.** True for an inbound link, where the endpoint the\noperator sets is *which of our ports we bind*, not a remote service — the\ndifference between \"their address\" and \"we will listen on\"."
              },
              "ingest_url": {
                "type": "string",
                "description": "For an inbound recipe: **the address this deployment publishes**, e.g.\n`mqtt://dome.site:1883`. `None` when it publishes no such service — and then\nthe recipe cannot be used, which is said rather than shown as an empty form.\n\nOnly the address. The *topic* and the *credential* belong to a link that\ndoes not exist yet, and are issued when the asset is added — showing a topic\nhere would be showing one the sensor must not actually publish on.",
                "nullable": true
              },
              "link_params": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ParamSpec"
                },
                "description": "**The connection's own** — the broker, its credentials, a vendor tenant.\nShared by everything on the same link, and therefore asked once per link\nrather than once per asset."
              },
              "needs_endpoint": {
                "type": "boolean",
                "description": "**Whether the OPERATOR supplies the address.** False for an inbound link:\nwe are the server there, so the address is ours to publish. It was true for\neverything, which is how an operator ended up typing the address of a broker\nthis deployment runs."
              },
              "params": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ParamSpec"
                },
                "description": "Everything, link-level and asset-level together."
              },
              "servable": {
                "type": "boolean",
                "description": "Whether this deployment can serve an inbound link of this kind at all."
              },
              "vendor_topic": {
                "type": "string",
                "description": "**The topic a vendor fixes**, where one does — a DJI Dock publishes on\n`thing/product/{sn}/osd` whatever we would prefer. `None` means the topic is\nours, and this link gets its own subtree when it is created.",
                "nullable": true
              }
            }
          }
        ],
        "description": "A recipe with its fields already resolved. The console never derives them."
      },
      "RefreshRequest": {
        "type": "object",
        "description": "`POST /v1/auth/refresh`",
        "required": [
          "refresh"
        ],
        "properties": {
          "refresh": {
            "type": "string",
            "description": "The `dref_…` value from the previous session."
          }
        }
      },
      "RegionInput": {
        "type": "object",
        "description": "Operator-supplied fields to create or update a region — the write shape for\n`POST`/`PUT /api/regions`. Geographic only (a monitored location), no scenario\nconcepts. Validated by [`RegionInput::validate`] before it reaches the store.",
        "required": [
          "name",
          "lat",
          "lon",
          "geofence_radius_m"
        ],
        "properties": {
          "elevation_m": {
            "type": "number",
            "format": "double",
            "description": "Ground elevation, metres AMSL: the anchor for every AGL altitude here.",
            "nullable": true
          },
          "geofence_radius_m": {
            "type": "number",
            "format": "double"
          },
          "lat": {
            "type": "number",
            "format": "double"
          },
          "lon": {
            "type": "number",
            "format": "double"
          },
          "name": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "RegionSummary": {
        "type": "object",
        "description": "Lightweight region summary served by `GET /api/regions` and `GET /api/meta`.",
        "required": [
          "id",
          "name",
          "lat",
          "lon",
          "geofence_radius_m"
        ],
        "properties": {
          "elevation_m": {
            "type": "number",
            "format": "double",
            "description": "Ground elevation at the site, metres AMSL. This is what every \"metres AGL\"\nin the system is measured from, a zone's altitude band above all. Optional\nbecause a site that has not been surveyed should say so rather than claim a\nzero it does not have.",
            "nullable": true
          },
          "geofence_radius_m": {
            "type": "number",
            "format": "double"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lat": {
            "type": "number",
            "format": "double"
          },
          "lon": {
            "type": "number",
            "format": "double"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "RegisterAsset": {
        "type": "object",
        "description": "`POST /v1/assets`",
        "required": [
          "name"
        ],
        "properties": {
          "catalog": {
            "type": "string",
            "description": "A catalogue slug, for example `dji-m30t`. Omit for a bare spec.",
            "nullable": true
          },
          "connection": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Connection"
              }
            ],
            "nullable": true
          },
          "kind": {
            "$ref": "#/components/schemas/AssetKind"
          },
          "name": {
            "type": "string",
            "description": "The callsign. What a person says out loud."
          },
          "placement": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Placement"
              }
            ],
            "nullable": true
          },
          "spec": {
            "type": "object",
            "description": "Spec fields. Merged over whatever the catalogue supplied, so a caller can\nname a model and still override its serial."
          }
        }
      },
      "RelGeom": {
        "type": "object",
        "description": "A sensor-relative geometry (range/bearing/elevation vs the reporting sensor).",
        "required": [
          "range_m",
          "bearing_deg",
          "elevation_deg"
        ],
        "properties": {
          "bearing_deg": {
            "type": "number",
            "format": "double"
          },
          "elevation_deg": {
            "type": "number",
            "format": "double"
          },
          "range_m": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "RemoteIdReport": {
        "type": "object",
        "description": "Cooperative Remote-ID self-report (ASTM F3411 / FAA Remote ID).",
        "properties": {
          "home_geo": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Geo"
              }
            ],
            "nullable": true
          },
          "operator_id": {
            "type": "string",
            "nullable": true
          },
          "serial": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "ua_type": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Requirement": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "asset",
              "effectors",
              "require"
            ],
            "properties": {
              "asset": {
                "type": "string"
              },
              "effectors": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "require": {
                "type": "string",
                "enum": [
                  "coverage"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "effectors",
              "require"
            ],
            "properties": {
              "effectors": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "require": {
                "type": "string",
                "enum": [
                  "reserve"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "A relay, a replacement on station — one of these must exist before the\ncurrent holder may leave.",
            "required": [
              "role",
              "count",
              "require"
            ],
            "properties": {
              "count": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "require": {
                "type": "string",
                "enum": [
                  "on_station"
                ]
              },
              "role": {
                "type": "string"
              }
            }
          }
        ],
        "description": "A constraint the solver must satisfy. **`require` is intent** — *\"the fuel farm\ngets two effectors' worth of coverage during the window; you work out who\"* —\nas against `emit`, which is a battle drill.",
        "discriminator": {
          "propertyName": "require"
        }
      },
      "Resolution": {
        "type": "string",
        "description": "How a threat ended, once [`ThreatStage::Resolved`].",
        "enum": [
          "neutralized",
          "leaked",
          "departed",
          "lost",
          "dismissed"
        ]
      },
      "ResolutionTrace": {
        "type": "object",
        "description": "Every key's resolution, from one pass. Emitted every pass, and it is the\ndebrief artefact.",
        "required": [
          "entries"
        ],
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Resolved"
            }
          }
        }
      },
      "Resolved": {
        "type": "object",
        "description": "A key's resolved value, and every part of why. This tuple **is** the provenance:\nit is what the settings surface renders and what a task record stamps.",
        "required": [
          "key",
          "value",
          "layer",
          "source_id"
        ],
        "properties": {
          "clamped": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Clamp"
              }
            ],
            "nullable": true
          },
          "key": {
            "$ref": "#/components/schemas/SettingKey"
          },
          "layer": {
            "$ref": "#/components/schemas/Layer"
          },
          "overridden": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Offer"
            },
            "description": "Every offer that lost, in precedence order. `WHY` renders this."
          },
          "source_id": {
            "type": "string"
          },
          "value": {
            "$ref": "#/components/schemas/SettingValue"
          }
        }
      },
      "ResolvedPayload": {
        "type": "object",
        "description": "One payload a vehicle carries, resolved.\n\nThis is the shape a U3 `SensorView` is built from — a carried sensor is that\nentity with a [`SensorPlacement::OnAsset`] mount, **not** a second entity\nmodel. It rides the vehicle's live position, appears on the OUR FORCES rail in\nthe vehicle's domain group, and feeds the dock.\n\n`sensing: None` is the load-bearing case (U7 V5): a payload naming a profile we\ndo not hold is **unresolved**. It is still listed — the fit is a fact somebody\ndeclared — but nothing draws it as a working sensor, because a sensor whose\nmodel we do not know has no reach we can honestly claim.",
        "required": [
          "id",
          "asset_id",
          "profile",
          "source"
        ],
        "properties": {
          "asset_id": {
            "type": "string",
            "description": "The vehicle carrying it."
          },
          "boresight_deg": {
            "type": "number",
            "format": "double"
          },
          "id": {
            "type": "string",
            "description": "Stable per vehicle: `BLUE-03/eo-turret`. This is what a `sensor:` route\naddresses, so it must not change between reads or collide between fits."
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "profile": {
            "type": "string",
            "description": "The [`SensorProfile`] id this payload names."
          },
          "sensing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PayloadSensing"
              }
            ],
            "nullable": true
          },
          "source": {
            "$ref": "#/components/schemas/PayloadSource"
          }
        }
      },
      "ReviewVerdict": {
        "type": "string",
        "description": "The reviewing model's verdict on a candidate Plan — advisory, never a gate.\nA human approves plans; the review is decision support shown on the card.",
        "enum": [
          "endorsed",
          "caution",
          "rejected"
        ]
      },
      "RfBearing": {
        "type": "object",
        "description": "RF direction-finding bearing (MQTT `rf`, SAPIENT PASSIVE_RF).",
        "properties": {
          "band": {
            "type": "string",
            "nullable": true
          },
          "bandwidth_mhz": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "bearing_deg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "emitter_id": {
            "type": "string",
            "nullable": true
          },
          "freq_mhz": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "protocol_hint": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Roe": {
        "type": "object",
        "description": "ROE posture — the release stance inside a mission (or a FreeFire zone).\n\nOne group's ROE — the approval gate + the posture.\n\n#63: `authority: Authority` becomes an explicit approval verdict carrying the\nsetting that produced it, so a group's ROE states its own reason.",
        "required": [
          "approval",
          "posture"
        ],
        "properties": {
          "approval": {
            "$ref": "#/components/schemas/ApprovalVerdict"
          },
          "posture": {
            "$ref": "#/components/schemas/Level"
          }
        }
      },
      "Role": {
        "type": "string",
        "description": "The tactical role a group plays in the mission.",
        "enum": [
          "screen",
          "intercept",
          "reserve",
          "escort",
          "strike"
        ]
      },
      "RoleSet": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/AssetRole"
        },
        "description": "**A set, because the real world does not partition.** A Dedrone box is\n`{Sensor, Effector}`, an interceptor `{Platform, Effector}`, a mast `{Sensor}`, a\nhospital `{Protected}`. A drone carrying an EO turret stays `{Platform}` with a\nsensor *payload* — that mechanism already exists and is not disturbed.\n\nSerialises as an array of strings in [`AssetRole`] order, so it is stable\nregardless of how it was built.",
        "uniqueItems": true
      },
      "RoutePoint": {
        "type": "object",
        "description": "A point on a route.\n\nThe optional fields are what an autopilot understands beyond a coordinate, and\nthey are roughly the common denominator of MAVLink mission items. Vendor extras\nride the namespaced `params` object; nothing here interprets it.",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "action": {
            "$ref": "#/components/schemas/RoutePointAction"
          },
          "alt_m": {
            "type": "number",
            "format": "float",
            "description": "Altitude above launch. `None` ⇒ hold the current working altitude.",
            "nullable": true
          },
          "hold_s": {
            "type": "integer",
            "format": "int32",
            "description": "Seconds to stay here.",
            "minimum": 0
          },
          "lat": {
            "type": "number",
            "format": "double"
          },
          "lon": {
            "type": "number",
            "format": "double"
          },
          "params": {
            "type": "object",
            "description": "Vendor extras, namespaced by vendor. Never interpreted here."
          },
          "speed_mps": {
            "type": "number",
            "format": "float",
            "description": "Metres per second on the leg into this point. `None` ⇒ the vehicle's own\ncruise.",
            "nullable": true
          }
        }
      },
      "RoutePointAction": {
        "type": "string",
        "description": "What a vehicle does **at** a route point, beyond passing through it.",
        "enum": [
          "pass",
          "loiter",
          "land"
        ]
      },
      "RuleAuthority": {
        "type": "object",
        "description": "What doctrine says about **one rule**: whether the operator may change it, and\nwhether it may act when the posture would otherwise hold it.",
        "required": [
          "rule_id",
          "locked",
          "may_override_posture",
          "why"
        ],
        "properties": {
          "locked": {
            "type": "boolean",
            "description": "Cannot be edited, reordered, disabled or deleted."
          },
          "may_override_posture": {
            "type": "boolean",
            "description": "Fires even where the control status would withhold it."
          },
          "rule_id": {
            "type": "string"
          },
          "why": {
            "type": "string",
            "description": "**Why**, in the words a commander would use. Rendered beside the padlock.\n\nA lock with no reason on screen is indistinguishable from a bug, and the\nfirst thing anyone does with an unexplained restriction is look for the\nway around it."
          }
        }
      },
      "RuleCategory": {
        "type": "string",
        "description": "The category a rule falls into, derived from the verbs it emits.\n\n**GEOGRAPHY is gone as a category.** It classified the *condition* while every\nother category classified the *effect*, so it comes back as a facet —\n[`RuleRow::touches_a_zone`] — rather than a peer.",
        "enum": [
          "identification",
          "engagement",
          "alerting",
          "planning",
          "posture"
        ]
      },
      "RuleDoctrine": {
        "type": "object",
        "description": "**Doctrine's standing statement about rules** — the parallel of\n[`crate::authority::ReleaseTable`], which is doctrine's statement about\neffects.\n\nThis used to be `if row.id != SELF_DEFENCE_ID`, a string compared against a\nconstant in the middle of a validator. That made a real doctrinal question —\n*which rules may act outside the posture?* — unanswerable without reading\nRust, and unchangeable without shipping a binary. A deployment whose doctrine\nprotects a second act (a medevac corridor, a mandated vacate-for-manned) had\nno way to say so.\n\nThe invariant the hardcoded check was really protecting still holds, and it is\nthe important one: **a rule cannot grant itself an override.** Doctrine has to\nname it here. An author writing `overrides_posture: true` on their own rule is\nrefused exactly as before — the difference is that the answer is now data a\ndeployment owns rather than a literal in a function.",
        "required": [
          "entries"
        ],
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RuleAuthority"
            }
          }
        }
      },
      "RuleFiring": {
        "type": "object",
        "description": "One rule's firing record for the current run.\n\n**\"Fired N× this run\" is not decoration.** A rule nobody can see firing is a\nrule nobody will trust, and trust is the entire reason anyone raises the\nposture. A rule at 0× across many runs is either dead configuration or a threat\nwe have never faced — and those look identical without the count.",
        "required": [
          "rule_id",
          "count"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "last_t": {
            "type": "string",
            "description": "Mission-relative, as `MM:SS` or `HH:MM:SS` — the form the surface prints.",
            "nullable": true
          },
          "last_target": {
            "type": "string",
            "nullable": true
          },
          "rule_id": {
            "type": "string"
          }
        }
      },
      "RulePack": {
        "type": "object",
        "description": "One importable ruleset.",
        "required": [
          "id",
          "name",
          "situation",
          "the_decision",
          "rules"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RuleRow"
            }
          },
          "situation": {
            "type": "string",
            "description": "The site this was written for, in one line."
          },
          "the_decision": {
            "type": "string",
            "description": "**The rule that is the actual decision.** Every pack has one or two, and a\nlist that does not point at them is a list nobody can read."
          }
        }
      },
      "RulePatch": {
        "type": "object",
        "description": "What a `PATCH` may change. Every field is optional; absent means unchanged.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "nullable": true
          },
          "order": {
            "type": "integer",
            "format": "int32",
            "nullable": true,
            "minimum": 0
          },
          "rule": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RuleRow"
              }
            ],
            "nullable": true
          },
          "sentence": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "RuleRow": {
        "type": "object",
        "description": "One stored rule. The typed row is the single representation; the sentence and\nthe DSL are both **projections** of it, and both round-trip.",
        "required": [
          "id",
          "template",
          "order",
          "enabled",
          "category",
          "stage",
          "when",
          "then",
          "status",
          "sentence"
        ],
        "properties": {
          "category": {
            "$ref": "#/components/schemas/RuleCategory"
          },
          "enabled": {
            "type": "boolean"
          },
          "id": {
            "type": "string"
          },
          "locked": {
            "type": "boolean",
            "description": "Doctrine, not configuration. `self_defence` is the only one."
          },
          "order": {
            "type": "integer",
            "format": "int32",
            "description": "Evaluation precedence. Resolves `set`/`prioritize` conflicts and nothing\nelse.",
            "minimum": 0
          },
          "overrides_posture": {
            "type": "boolean",
            "description": "The single exception to the posture's authority, and the only rule\npermitted to carry it."
          },
          "sentence": {
            "type": "string",
            "description": "The sentence the list renders, with `[n]` marking each editable parameter."
          },
          "stage": {
            "$ref": "#/components/schemas/OodaStage"
          },
          "status": {
            "$ref": "#/components/schemas/RuleStatus"
          },
          "template": {
            "$ref": "#/components/schemas/RuleTemplate"
          },
          "then": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Effect"
            }
          },
          "when": {
            "$ref": "#/components/schemas/Predicate"
          }
        }
      },
      "RuleStatus": {
        "type": "string",
        "description": "Whether a rule is real code today, or a thing the catalogue is honest about.\n\nThe catalogue is explicit about what is **shipped**, what merely **behaves\nlike** a rule, and what is **dead** — and a dead rule is revived or deleted,\nnever left in between.",
        "enum": [
          "shipped",
          "was_an_if_block",
          "new"
        ]
      },
      "RuleTelemetry": {
        "type": "object",
        "description": "Every rule's firing record, for one run.\n\n## What counts as a firing\n\nA rule that matches four tracks fired **four** times — rounding that to one\nwould hide exactly the behaviour an operator is watching for. But a rule that\nkeeps matching *the same* track on every pass has not fired again: the\nauthoring pass runs every few seconds, so counting per pass would turn one\nkeep-out breach into forty and the number would stop meaning anything.\n\nSo a firing is one **(rule, subject)** pair, once per run. `fired 4x this run`\nthen reads as *four things tripped this rule*, which is what an operator takes\nit to mean.",
        "required": [
          "firings"
        ],
        "properties": {
          "firings": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/RuleFiring"
            }
          }
        }
      },
      "RuleTemplate": {
        "type": "string",
        "description": "The templates a rule may be authored from — a typed slot list. Adding a rule is\npicking one from a palette grouped by response rung, then filling chips.",
        "enum": [
          "assign_on_threat",
          "designate_in_zone",
          "designate_on_id",
          "prioritize_asset",
          "escalate_posture",
          "require_coverage",
          "alert_on",
          "deny_verb",
          "custom"
        ]
      },
      "Ruleset": {
        "type": "object",
        "description": "The rule set in force, plus the version it is at.",
        "required": [
          "version",
          "rules"
        ],
        "properties": {
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RuleRow"
            }
          },
          "version": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          }
        }
      },
      "RunState": {
        "type": "string",
        "description": "Whether a simulation is running in the attached environment.\n\n**Three states, not four.** There was a `Loaded` — spawned and held, waiting\nfor a second press — which preserved a gap in which the geometry could be\nchecked before anything moved. That gap moved up a level once attaching an\nENVIRONMENT became separate: by the time a scenario starts, the engine is\nconnected and the world is up, and the geometry is inspected on a map in the\nconsole *before* anything connects. Staging a live world to look at it was\nsolving a problem that had moved.",
        "enum": [
          "idle",
          "running",
          "paused"
        ]
      },
      "RunStatus": {
        "oneOf": [
          {
            "type": "string",
            "description": "The run is in progress — events are still being appended.",
            "enum": [
              "Running"
            ]
          },
          {
            "type": "string",
            "description": "The run ended cleanly via `POST /api/simulation/stop`.",
            "enum": [
              "Complete"
            ]
          },
          {
            "type": "string",
            "description": "The run was superseded by a new run before it was stopped.",
            "enum": [
              "Aborted"
            ]
          },
          {
            "type": "object",
            "required": [
              "Other"
            ],
            "properties": {
              "Other": {
                "type": "string",
                "description": "Any other label, preserved verbatim."
              }
            }
          }
        ],
        "description": "Lifecycle of a run. Serialized as a plain lowercase string; unknown labels\nround-trip losslessly through [`RunStatus::Other`] (same pattern as\n[`Modality`](crate::Modality)/[`IdSource`](crate::IdSource), #37b) so no\nstored value can ever fail to parse."
      },
      "RunSummary": {
        "type": "object",
        "description": "Headline metrics for a run, computed on finalize by querying the run's events.\n\nPhase 1 fills duration/ticks/tracks/threats/alerts. The outcome + cost\nfields are labeled from the closed kill chain (2026-07-14): `neutralized`/\n`leaked` count terminal `threat.v1` resolutions (BDA kill verdicts and\nasset-reach), `interceptor_cost_usd` prices `engagement.v1` launches.",
        "required": [
          "duration_s",
          "tick_count",
          "tracks_seen",
          "threats_total",
          "threats_peak_active",
          "alerts_total",
          "alerts_by_kind",
          "neutralized",
          "leaked",
          "interceptor_cost_usd"
        ],
        "properties": {
          "alerts_by_kind": {
            "type": "object",
            "description": "Alert counts keyed by [`AlertKind`](crate::AlertKind) slug.",
            "additionalProperties": {
              "type": "integer",
              "format": "int64",
              "minimum": 0
            }
          },
          "alerts_total": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "duration_s": {
            "type": "number",
            "format": "double"
          },
          "interceptor_cost_usd": {
            "type": "number",
            "format": "double",
            "description": "Interceptor launches × unit cost — the engagement's economy label."
          },
          "leaked": {
            "type": "integer",
            "format": "int64",
            "description": "Threats that reached the defended asset (terminal `Resolution::Leaked`).",
            "minimum": 0
          },
          "neutralized": {
            "type": "integer",
            "format": "int64",
            "description": "Threats assessed killed (terminal `Resolution::Neutralized`).",
            "minimum": 0
          },
          "threats_peak_active": {
            "type": "integer",
            "format": "int64",
            "description": "Peak number of hostile tracks active in any single tick.",
            "minimum": 0
          },
          "threats_total": {
            "type": "integer",
            "format": "int64",
            "description": "Distinct hostile track objects (the operator's threats).",
            "minimum": 0
          },
          "tick_count": {
            "type": "integer",
            "format": "int64",
            "description": "Distinct event timestamps observed — a proxy for OODA ticks.",
            "minimum": 0
          },
          "tracks_seen": {
            "type": "integer",
            "format": "int64",
            "description": "Distinct `track_id`s seen across the run.",
            "minimum": 0
          }
        }
      },
      "ScenarioChangeset": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "spec": {
            "type": "object",
            "nullable": true
          }
        }
      },
      "ScenarioRow": {
        "type": "object",
        "required": [
          "id",
          "workspace_id",
          "name",
          "spec",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "spec": {
            "type": "object"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "Scope": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "scope"
            ],
            "properties": {
              "scope": {
                "type": "string",
                "enum": [
                  "global"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "scope"
            ],
            "properties": {
              "scope": {
                "type": "string",
                "enum": [
                  "subject"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "scope",
              "id"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "scope": {
                "type": "string",
                "enum": [
                  "zone"
                ]
              }
            }
          }
        ],
        "description": "Where an effect applies. Named so `Scope` is not designed to exclude\nper-echelon delegation later.",
        "discriminator": {
          "propertyName": "scope"
        }
      },
      "SearchSector": {
        "type": "object",
        "description": "The arc a searching sensor sweeps when nothing has cued it.\n\n`width_deg >= 360` is a full rotation (a scanning radar); anything narrower is\na sector the sensor revisits. Carried rather than derived, because the console\ndraws the *pattern* and not just the instantaneous wedge — a radar that is\ndwelling a 52° sector and one that is turning through 360° both report a\nboresight, and only the sector tells them apart.",
        "required": [
          "center_deg",
          "width_deg"
        ],
        "properties": {
          "center_deg": {
            "type": "number",
            "format": "double",
            "description": "Sector centre, compass degrees (0 = N, clockwise)."
          },
          "width_deg": {
            "type": "number",
            "format": "double",
            "description": "Sector width in degrees. `>= 360.0` ⇒ a full rotation."
          }
        }
      },
      "SecretInfo": {
        "type": "object",
        "description": "**What a client may learn about a secret: that it exists.**\n\nThere is deliberately no field here that could hold the value, and no route\nthat returns one. A secret that can be read back is a secret that appears in a\nbrowser cache, a proxy log and a screenshot.",
        "required": [
          "name",
          "hint",
          "reference",
          "updated_at"
        ],
        "properties": {
          "hint": {
            "type": "string",
            "description": "Operator-supplied, e.g. `prod broker, rotated quarterly`."
          },
          "name": {
            "type": "string"
          },
          "reference": {
            "type": "string",
            "description": "The reference to paste into a config: `secret://<name>`."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Sensing": {
        "type": "object",
        "description": "**What a sensor IS**, resolved from its profile.\n\nA sensor's catalog page used to say `\"batear-node\" is not in the catalog`,\nbecause the catalog it consulted holds airframes: endurance, cruise, ceiling.\nNone of those describe a mast. What describes one is what it measures, how far\nit reaches, and how well it resolves a bearing — and all of that already exists\nin the sensor profile the product names.",
        "required": [
          "modality",
          "measures"
        ],
        "properties": {
          "bearing_sigma_deg": {
            "type": "number",
            "format": "double",
            "description": "How well it resolves a bearing. Absent when it reports none — which is a\nfact about the sensor, not a missing number.",
            "nullable": true
          },
          "fov_deg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "measures": {
            "type": "string",
            "description": "What it constrains: a zone, a bearing, a range, a position. The difference\ndecides whether a detection can localise anything at all."
          },
          "modality": {
            "type": "string"
          },
          "reach_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        }
      },
      "Sensor": {
        "type": "object",
        "description": "The sensor that produced an observation. `id` + `modality` are always present;\n`fov_deg`/`range_m` are sim-only characteristics.",
        "required": [
          "id",
          "modality"
        ],
        "properties": {
          "fov_deg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "id": {
            "type": "string"
          },
          "modality": {
            "$ref": "#/components/schemas/Modality"
          },
          "pointing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SensorPointing"
              }
            ],
            "nullable": true
          },
          "range_m": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        }
      },
      "SensorActivity": {
        "type": "object",
        "description": "How much this sensor is producing, over a rolling window.\n\n# The clock\n\nEvery instant here is the **pipeline's window clock** — a tick counter in\nunits of `FusionService::WINDOW_S`, the same value `Picture.t` carries and the\nsame one `crate::time::iso_from_sim` renders into `Track.t`,\n`custody.last_update_t` and each `SensorContribution.last_seen_t`. So\n`iso_from_sim(last_return_t)` is directly comparable with the custody record\nof that same return — pinned by a test in `dome-runtime/tests/sensor_slice.rs`.\n\nIt is deliberately **not** wall clock and deliberately **not**\n`Observation.t`:\n\n- wall clock would break byte-identical replay on the first re-run;\n- `Observation.t` is not a shared time base at all. A live MAVLink link\nstamps real UTC (`iso_now`), while the simulator and every file-replay\nadapter stamp `iso_from_sim` — a synthetic epoch. Two sources feeding one\npipeline therefore disagree about what time it is, and the tick counter is\nthe only clock they *do* share. Reading `Observation.t` here would make a\nsensor's silence depend on which kind of source it happened to be.\n\n**The known limit.** The pipeline only ticks when observations arrive, so a\ntotal sensing outage freezes this clock and no sensor is ever marked\n[`SensorState::Silent`] by it. That case — *everything* stopped — is covered by\nthe console's wall-clock staleness banner instead; this field covers *this*\nsensor stopped while others continue. Introducing a wall clock here to close\nthe gap would trade a determinism guarantee for a signal that already exists.",
        "required": [
          "returns_total",
          "returns_per_min"
        ],
        "properties": {
          "last_return_t": {
            "type": "number",
            "format": "double",
            "description": "Window-clock instant (seconds) of the most recent return. `None` ⇒ never\nreported, which is not the same as silent — see [`SensorState`].",
            "nullable": true
          },
          "returns_per_min": {
            "type": "number",
            "format": "double",
            "description": "Returns per minute over the rolling window."
          },
          "returns_total": {
            "type": "integer",
            "format": "int64",
            "description": "Returns since the run started.",
            "minimum": 0
          },
          "silent_for_s": {
            "type": "number",
            "format": "double",
            "description": "How long it has been quiet, in window-clock seconds. `None` ⇒ never reported.",
            "nullable": true
          }
        }
      },
      "SensorContribution": {
        "type": "object",
        "description": "One contributor's corroboration of a track: which sensor, on which modality,\nand when it last contributed. This is the per-contributor `(modality,\nlast_seen)` pairing fusion computes at association and — before #64 —\ndiscarded when it flattened into `Custody::contributors` +\n`Custody::seen_modalities`. Deliberately carries **no strength/weight**:\nfusion records none, and the console must not invent one (the recency chip is\nderived from `last_seen_t`).",
        "required": [
          "sensor_id",
          "modality",
          "last_seen_t"
        ],
        "properties": {
          "last_seen_t": {
            "type": "string",
            "description": "ISO timestamp of this contributor's most recent contribution. The console\nderives its LIVE/RECENT/QUIET recency chip from this — never a fabricated\nsignal-strength number."
          },
          "modality": {
            "$ref": "#/components/schemas/Modality"
          },
          "sensor_id": {
            "type": "string",
            "description": "The contributing sensor/platform id (e.g. `radar-north`), not a placeholder."
          }
        }
      },
      "SensorMount": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "fixed"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Carried by `asset_id` — the route the owning asset's FOCUS panel follows\ninto this sensor (sheet 03 D1's `WHAT IT CARRIES` rows).",
            "required": [
              "asset_id",
              "kind"
            ],
            "properties": {
              "asset_id": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "on_asset"
                ]
              }
            }
          }
        ],
        "description": "Where a sensor sits. The wire mirror of [`SensorPlacement`], flattened to what\nthe console needs: a fixed emplacement, or the asset that carries it.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "SensorPlacement": {
        "oneOf": [
          {
            "type": "object",
            "description": "Emplaced at a static point in ENU metres about the deployment origin.",
            "required": [
              "pos_enu",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "fixed"
                ]
              },
              "pos_enu": {
                "$ref": "#/components/schemas/Enu"
              }
            }
          },
          {
            "type": "object",
            "description": "Carried by the asset named `asset_id`; its live position each tick is the\nasset's ENU position plus `mount_offset_enu`.",
            "required": [
              "asset_id",
              "mount_offset_enu",
              "kind"
            ],
            "properties": {
              "asset_id": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "on_asset"
                ]
              },
              "mount_offset_enu": {
                "$ref": "#/components/schemas/Enu"
              }
            }
          }
        ],
        "description": "Where a [`PlacedSensorSpec`] physically sits. Internally tagged on `kind`\n(`\"fixed\"` | `\"on_asset\"`). A `Fixed` sensor is emplaced at a static ENU\npoint; an `OnAsset` sensor rides a live asset (a drone) and is resolved to\nthat asset's **current** position + `mount_offset_enu` each tick, so a sensor\nflown closer to a contact firms up its detection exactly like a fixed one.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "SensorPointing": {
        "type": "object",
        "description": "**A sensor's live aim**, on the picture as a delta.\n\n`boresight_deg` duplicates [`SensorView::boresight_deg`] deliberately: the\nview's field is the one the coverage wedge has always been drawn from, and\nkeeping the pointing block self-describing means a consumer that reads only\n`pointing` (the live-pointing layer) never has to reach back up for the angle\nit is about to draw. Both are written from the same value; they cannot\ndisagree.\n\n`since` is the **window-clock instant** the current state was entered — the\nsame clock [`SensorActivity`] documents, and never a wall clock, so a replay\nstays byte-identical.",
        "required": [
          "state",
          "boresight_deg",
          "since"
        ],
        "properties": {
          "boresight_deg": {
            "type": "number",
            "format": "double"
          },
          "elevation_deg": {
            "type": "number",
            "format": "double",
            "description": "**Where the head is looking in the vertical**, degrees, positive up.\n\n`None` means *not measured*, and stays `None`. A guessed nadir or a fixed\ndepression angle is a fabricated claim about where we are looking — and the\nonly elevation this system otherwise holds is the airframe's own pitch, which\nis the camera's pitch for a fixed mount and nothing at all for a gimbal.\n\nPopulated from MISB ST 0601 tag 19 for a compliant turret. Until this existed\nthere was no gimbal elevation anywhere in `dome-types`, which is what the\nconsole's ST 0601 ground-footprint work has been waiting on.",
            "nullable": true
          },
          "hfov_deg": {
            "type": "number",
            "format": "double",
            "description": "Current horizontal field of view, degrees.\n\n**Per observation, not per profile**, because a continuous-zoom head changes it\nmid-mission — and zoom is not optional on a turret expected to identify at\nkilometre ranges. From ST 0601 tag 16.",
            "nullable": true
          },
          "since": {
            "type": "number",
            "format": "double"
          },
          "state": {
            "$ref": "#/components/schemas/PointingState"
          }
        }
      },
      "SensorState": {
        "type": "string",
        "description": "What a sensor is doing, in one word.\n\nThree states, not five, because a state word an operator cannot act on is a\nrow that teaches them to stop reading rows (ISA-101).",
        "enum": [
          "searching",
          "silent",
          "offline"
        ]
      },
      "SensorStatus": {
        "type": "object",
        "description": "One sensor the vehicle declares, with its reported state. Decoded from the three\n`SYS_STATUS` bitmasks (`onboard_control_sensors_present` / `_enabled` / `_health`).\n\nOnly sensors the vehicle actually declares **present** are listed — an absent sensor\nis omitted rather than reported as healthy, so the panel never invents hardware.",
        "required": [
          "id",
          "label",
          "present",
          "enabled",
          "healthy"
        ],
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "The vehicle reports it is enabled/in use."
          },
          "healthy": {
            "type": "boolean",
            "description": "The vehicle reports it is operating correctly."
          },
          "id": {
            "type": "string",
            "description": "Stable key, e.g. `\"3d_gyro\"`."
          },
          "label": {
            "type": "string",
            "description": "Operator-facing label, e.g. `\"Gyroscope\"`."
          },
          "present": {
            "type": "boolean",
            "description": "The vehicle reports this sensor is fitted."
          }
        }
      },
      "SensorView": {
        "type": "object",
        "description": "**A sensor, as an entity.** One per placed or observed sensor, on the picture.",
        "required": [
          "sensor_id",
          "name",
          "modality",
          "measurement",
          "feed",
          "mount",
          "max_range_m",
          "fov_deg",
          "boresight_deg",
          "range_sigma_m",
          "bearing_sigma_deg",
          "state",
          "activity"
        ],
        "properties": {
          "activity": {
            "$ref": "#/components/schemas/SensorActivity"
          },
          "bearing_sigma_deg": {
            "type": "number",
            "format": "double"
          },
          "boresight_deg": {
            "type": "number",
            "format": "double"
          },
          "enu": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Enu"
              }
            ],
            "nullable": true
          },
          "feed": {
            "$ref": "#/components/schemas/FeedRenderer"
          },
          "fov_deg": {
            "type": "number",
            "format": "double"
          },
          "frame_url": {
            "type": "string",
            "description": "Where this sensor's frames can be watched, when something serves them.\n\n**Advertised by the backend, never derived in the client.** The provider\nmay be on another machine, behind a different port, or not serving frames\nat all, and only the backend knows — the same property that lets a provider\non one host drive an engine on another without the console learning\nanything about topology. A URL assembled in the browser from a convention\nwould throw that away.\n\nAlways a `/stream` URL. One request that never ends is a push, which\n`live-data-flow.md` permits; a component re-fetching `/frame` on a timer is\na poll, which it does not.\n\n**`None` means nothing is serving frames**, and the console draws no tile\nrather than one showing a broken image. Absent, not black.",
            "nullable": true
          },
          "geo": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Geo"
              }
            ],
            "nullable": true
          },
          "identity": {
            "$ref": "#/components/schemas/PlatformIdentity"
          },
          "max_range_m": {
            "type": "number",
            "format": "double",
            "description": "Coverage reach. `0.0` when nobody told us — the console then says so\nrather than drawing a reach it invented."
          },
          "measurement": {
            "$ref": "#/components/schemas/MeasurementKind"
          },
          "modality": {
            "$ref": "#/components/schemas/Modality"
          },
          "mount": {
            "$ref": "#/components/schemas/SensorMount"
          },
          "name": {
            "type": "string",
            "description": "What to call it. Defaults to the id — never blank, never a UUID we minted."
          },
          "pointing": {
            "$ref": "#/components/schemas/SensorPointing"
          },
          "profile": {
            "type": "string",
            "description": "The [`SensorProfile`] id it runs, when it was declared with one.",
            "nullable": true
          },
          "provenance": {
            "$ref": "#/components/schemas/Provenance"
          },
          "range_sigma_m": {
            "type": "number",
            "format": "double",
            "description": "1σ range accuracy. **`0.0` means this sensor reports no range at all.**"
          },
          "sensor_id": {
            "type": "string",
            "description": "The id observations carry (`Observation.sensor.id`) and custody records."
          },
          "state": {
            "$ref": "#/components/schemas/SensorState"
          }
        }
      },
      "Session": {
        "type": "object",
        "description": "**A browser session.**\n\n`token` is the short-lived bearer every request carries. `refresh` is the\nlong-lived half, and the only one that can be taken away, which is why the\nsession token is short: revoking cannot reach a token already issued, so the\nwindow is kept small instead.",
        "required": [
          "token",
          "expires_in",
          "refresh",
          "user",
          "workspaces"
        ],
        "properties": {
          "expires_in": {
            "type": "integer",
            "format": "int64",
            "description": "Seconds until `token` expires. The client refreshes before this, not\nafter a 401."
          },
          "refresh": {
            "type": "string",
            "description": "Presented to `POST /v1/auth/refresh` for the next session token. Rotated\non every use: a refresh token is good exactly once."
          },
          "token": {
            "type": "string",
            "description": "The bearer token. Send it as `Authorization: Bearer <token>`."
          },
          "user": {
            "$ref": "#/components/schemas/PublicUser"
          },
          "workspaces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Workspace"
            },
            "description": "Every workspace this user belongs to, with their role in each. The client\npicks one and sends it as `X-Workspace-Id` from then on."
          }
        }
      },
      "SettingKey": {
        "type": "string",
        "description": "A namespaced setting key — `fusion.cluster_gate_m`, `identify.never_below_confidence`.\n\nA newtype rather than a bare `String` so a key cannot be confused with a value,\na rule id, or a zone name at a call site where all four are in scope."
      },
      "SettingOwner": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "owner"
            ],
            "properties": {
              "owner": {
                "type": "string",
                "enum": [
                  "threshold"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "A rule wearing a checkbox. `rule_id` is where it belongs.",
            "required": [
              "rule_id",
              "owner"
            ],
            "properties": {
              "owner": {
                "type": "string",
                "enum": [
                  "rule"
                ]
              },
              "rule_id": {
                "type": "string"
              }
            }
          }
        ],
        "description": "Which surface a key belongs to. See [`SettingSpec::owned_by`].",
        "discriminator": {
          "propertyName": "owner"
        }
      },
      "SettingReader": {
        "type": "object",
        "description": "One rule's use of one key.",
        "required": [
          "rule_id",
          "how"
        ],
        "properties": {
          "how": {
            "$ref": "#/components/schemas/SettingUse"
          },
          "rule_id": {
            "type": "string"
          }
        }
      },
      "SettingSpec": {
        "type": "object",
        "description": "One row of the registry. **The registry is the single source of truth;\n`DecisionConfig` becomes a view over it, not a parallel structure.**",
        "required": [
          "key",
          "default",
          "bound",
          "rule_overridable",
          "owning_stage",
          "describes"
        ],
        "properties": {
          "bound": {
            "$ref": "#/components/schemas/Bound"
          },
          "default": {
            "$ref": "#/components/schemas/SettingValue"
          },
          "describes": {
            "type": "string",
            "description": "One line, in the operator's words. The registry is also the help text; a\nnumber nobody can read the meaning of is a number nobody will tune."
          },
          "key": {
            "$ref": "#/components/schemas/SettingKey"
          },
          "owned_by": {
            "$ref": "#/components/schemas/SettingOwner"
          },
          "owning_stage": {
            "$ref": "#/components/schemas/OodaStage"
          },
          "rule_overridable": {
            "type": "boolean",
            "description": "Whether a rule may derive a per-pass value for this key at all.\n`identify.never_below_confidence` is **rule-locked**: it is the floor that\noutranks the threshold, and a rule that could move it would be a rule that\ncould remove it."
          }
        }
      },
      "SettingUse": {
        "type": "string",
        "description": "How a rule touches a setting key.",
        "enum": [
          "tests",
          "derives"
        ]
      },
      "SettingValue": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind",
              "value"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "number"
                ]
              },
              "value": {
                "type": "number",
                "format": "double"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind",
              "value"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "bool"
                ]
              },
              "value": {
                "type": "boolean"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind",
              "value"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "text"
                ]
              },
              "value": {
                "type": "string"
              }
            }
          }
        ],
        "description": "A setting's value. A closed sum, not a `serde_json::Value`: the registry is\ndomain code, and an untyped bag here would put every clamp and every comparison\none `unwrap` away from being wrong.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "SettingsRegistry": {
        "type": "object",
        "description": "Every tunable, addressable by key.",
        "required": [
          "specs"
        ],
        "properties": {
          "specs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SettingSpec"
            }
          }
        }
      },
      "Signal": {
        "oneOf": [
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/RadarPlot"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "radar"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/RfBearing"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "rf"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/EoDetection"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "eo"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/IrDetection"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "ir"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/AcousticBearing"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "acoustic"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/RemoteIdReport"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "remote_id"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsbReport"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "adsb"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/TelemetryReport"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "telemetry"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/CotSignal"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "cot"
                    ]
                  }
                }
              }
            ]
          },
          {
            "allOf": [
              {
                "$ref": "#/components/schemas/OtherSignal"
              },
              {
                "type": "object",
                "required": [
                  "modality"
                ],
                "properties": {
                  "modality": {
                    "type": "string",
                    "enum": [
                      "other"
                    ]
                  }
                }
              }
            ]
          }
        ],
        "description": "The raw, per-modality signal exactly as the sensor reports it — internally\ntagged on `modality`. Producers (decoders) populate the variant that matches\ntheir sensor; fusion reads typed fields instead of string-digging a `Value`.",
        "discriminator": {
          "propertyName": "modality"
        }
      },
      "SignalBatch": {
        "type": "object",
        "description": "`POST /v1/signals`",
        "required": [
          "observations"
        ],
        "properties": {
          "observations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Observation"
            },
            "description": "One tick's worth of observations. A source accumulates and flushes on a\ntick; one batch per message is not the contract, and a sender that does\nthat turns every message into a pipeline cycle."
          },
          "source": {
            "$ref": "#/components/schemas/SignalSource"
          }
        }
      },
      "SignalSource": {
        "type": "string",
        "description": "Which intake a batch enters.",
        "enum": [
          "live",
          "simulated"
        ]
      },
      "SignatureSpec": {
        "type": "object",
        "properties": {
          "acoustic_db": {
            "type": "number",
            "format": "double",
            "description": "acoustic profile dB at 100m.",
            "nullable": true
          },
          "ir_signature": {
            "type": "string",
            "description": "Infrared signature level (low/medium/high).",
            "nullable": true
          },
          "rcs_dbsm": {
            "type": "number",
            "format": "double",
            "description": "Radar cross-section (RCS) in dBsm (typical range).",
            "nullable": true
          },
          "rf_bands": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "RF emissions (frequency bands, power)."
          }
        }
      },
      "SimPictureState": {
        "type": "object",
        "description": "Simulation state as it rides the picture (#60 §5): the console reads sim\nrunning/paused/clock from the one picture instead of polling\n`GET /api/simulation/status` on its own timer. `null` when no run is active.",
        "required": [
          "running",
          "paused",
          "simTimeS"
        ],
        "properties": {
          "dropped_observations": {
            "$ref": "#/components/schemas/DroppedObservations"
          },
          "force": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ForceSource"
              }
            ],
            "nullable": true
          },
          "paused": {
            "type": "boolean"
          },
          "run_id": {
            "type": "string",
            "description": "The recording run, when one is active.",
            "nullable": true
          },
          "running": {
            "type": "boolean"
          },
          "scenario_id": {
            "type": "string",
            "description": "Which scenario is running. Carried so the exercise banner can name the run\nfrom the picture instead of polling `/api/simulation/status` — and the\nscenario list alongside it — every three seconds.",
            "nullable": true
          },
          "scenario_name": {
            "type": "string",
            "nullable": true
          },
          "simTimeS": {
            "type": "number",
            "format": "double",
            "description": "Sim-time in seconds. Camel-cased on the wire to match the console field."
          },
          "truth": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SimTruthMarker"
            },
            "description": "Ground truth of every live simulated airframe (SF4). Present only while a\nsim runs; omitted from the wire when empty so a live deployment's picture\nis byte-identical to what it was before this field existed."
          },
          "world_view": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WorldView"
              }
            ],
            "nullable": true
          }
        }
      },
      "SimTruthMarker": {
        "type": "object",
        "description": "Ground truth of one simulated airframe, as it rides the picture (SF4). Drawn\nby the console **only** because the source is a simulator — a live source has\nno truth to draw, and `SimPictureState` being `None` is what guarantees no\ntruth marker can exist outside a sim run. `geo` is stamped by the simulator\n(which owns the region origin), so consumers never re-derive it.",
        "required": [
          "label",
          "hostile",
          "enu",
          "geo"
        ],
        "properties": {
          "enu": {
            "$ref": "#/components/schemas/Enu"
          },
          "geo": {
            "$ref": "#/components/schemas/Geo"
          },
          "hostile": {
            "type": "boolean"
          },
          "label": {
            "type": "string",
            "description": "The drone's identity in the world — what a tracker's ids are supposed to\ncorrespond to one-for-one."
          }
        }
      },
      "SimulationRun": {
        "type": "object",
        "description": "The durable record of one simulation run.",
        "required": [
          "id",
          "scenario_name",
          "status",
          "started_at",
          "summary"
        ],
        "properties": {
          "ended_at": {
            "type": "string",
            "description": "ISO-8601 end time; `None` while the run is [`RunStatus::Running`].",
            "nullable": true
          },
          "id": {
            "type": "string",
            "description": "The `run_id` — every event of this run carries it in `events.run_id`."
          },
          "region_id": {
            "type": "string",
            "nullable": true
          },
          "scenario_id": {
            "type": "string",
            "nullable": true
          },
          "scenario_name": {
            "type": "string"
          },
          "seed": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "started_at": {
            "type": "string",
            "description": "ISO-8601 start time."
          },
          "status": {
            "$ref": "#/components/schemas/RunStatus"
          },
          "summary": {
            "$ref": "#/components/schemas/RunSummary"
          }
        }
      },
      "SimulatorKind": {
        "type": "string",
        "description": "Which engine computes a simulation.\n\nDeliberately not a registry row. A host and a port belong in a config file\nunder change control, not in a console an operator drives during an exercise.",
        "enum": [
          "lite",
          "airsim",
          "gazebo"
        ]
      },
      "SimulatorStatus": {
        "type": "object",
        "description": "What a deployment has, and whether it answers.",
        "required": [
          "kind",
          "configured",
          "reachable"
        ],
        "properties": {
          "api_version": {
            "type": "integer",
            "format": "int32",
            "description": "The engine's own handshake — `4` for Cosys-AirSim, `1` for\n`fake-airsim.py`. Carried so a bench run cannot be reported as an engine\nrun: `reachable` alone cannot tell them apart.",
            "nullable": true,
            "minimum": 0
          },
          "configured": {
            "type": "boolean",
            "description": "Present in the deployment's configuration at all."
          },
          "error": {
            "type": "string",
            "description": "Verbatim, never summarised — *\"connection refused\"* and *\"no RPC answer\nyet\"* send an operator to different places.",
            "nullable": true
          },
          "kind": {
            "$ref": "#/components/schemas/SimulatorKind"
          },
          "reachable": {
            "type": "boolean",
            "description": "Answered a probe just now."
          }
        }
      },
      "SocketRole": {
        "type": "string",
        "description": "Which end opens the conversation.\n\nA **socket** role, not a data direction — both are legal for the same adapter and\nthe protocol does not decide between them, which is exactly why this is the one\nthing about a link an operator legitimately chooses. `udpin` binds and waits\n(SITL, telemetry radios, anything that announces itself); `udpout` dials a fixed\naddress and receives on the same socket.",
        "enum": [
          "listen",
          "dial"
        ]
      },
      "SolverWeights": {
        "type": "object",
        "description": "Tunable trade-off weights, keyed by [`SolverMetric`] — serializes as a JSON\nobject `{ \"time_to_target\": 1.0, … }`. Higher = more important. Greedy\nassignment today keys on distance/time; the full MILP consumes all metrics.",
        "additionalProperties": {
          "type": "number",
          "format": "double"
        }
      },
      "SpecField": {
        "type": "object",
        "description": "One field of a domain's specification: which key, what to call it, its unit,\nand whether a facet may be built on it. The order is the order the fleet's\nspecification column shows them.",
        "required": [
          "key",
          "label",
          "unit",
          "facet"
        ],
        "properties": {
          "facet": {
            "type": "boolean",
            "description": "Whether the results page may facet on it. Section 3b says which not to:\nan aerial `payload_kg` (integrated-camera aircraft have none), a radar\n`range_km` as one number."
          },
          "key": {
            "type": "string",
            "description": "A [`Figures`] field name."
          },
          "label": {
            "type": "string"
          },
          "unit": {
            "type": "string"
          }
        }
      },
      "StrategyInfo": {
        "type": "object",
        "description": "Serializable strategy descriptor (what `GET /api/strategies` returns).",
        "required": [
          "id",
          "name",
          "description",
          "side",
          "params"
        ],
        "properties": {
          "description": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "params": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParamInfo"
            }
          },
          "side": {
            "$ref": "#/components/schemas/StrategySide"
          }
        }
      },
      "StrategyRef": {
        "type": "object",
        "description": "A reference to a named behavior strategy from the `dome-dsl` catalog, with\noptional per-drone parameter overrides. This is the operator-facing `behavior`\nselection — pick a strategy by name (`\"evasive\"`, `\"low_altitude_run\"`, …) and,\nin advanced mode, tweak its declared knobs (`params`, keyed by the strategy's\n`ParamDecl` names). Unknown / out-of-range params are clamped or ignored at\nresolve time. Kept deliberately tiny so `dome-types` stays free of the grammar\nAST (which lives in `dome-dsl`); the scenario stores a name + tweaks, not a tree.",
        "required": [
          "strategy"
        ],
        "properties": {
          "params": {
            "type": "object",
            "description": "Advanced-mode overrides for the strategy's declared parameters. Empty ⇒ use\nthe strategy's sensible defaults.",
            "additionalProperties": {
              "type": "number",
              "format": "double"
            }
          },
          "strategy": {
            "type": "string",
            "description": "The strategy id in the library (matches a `dome-dsl` catalog entry)."
          }
        }
      },
      "StrategySide": {
        "type": "string",
        "description": "Which side a strategy is intended for (authoring / filtering hint).",
        "enum": [
          "friendly",
          "hostile",
          "any"
        ]
      },
      "StreamTicket": {
        "type": "object",
        "description": "What a client needs to open the SSE stream.",
        "required": [
          "ticket",
          "expires_in"
        ],
        "properties": {
          "expires_in": {
            "type": "integer",
            "format": "int64",
            "description": "Seconds. Ask for another when it runs out, or when the stream drops."
          },
          "ticket": {
            "type": "string",
            "description": "Put it on the stream URL as `?ticket=…`."
          }
        }
      },
      "Subject": {
        "oneOf": [
          {
            "type": "object",
            "description": "A single asset (e.g. `BLUE-01`).",
            "required": [
              "asset_id",
              "scope"
            ],
            "properties": {
              "asset_id": {
                "type": "string"
              },
              "scope": {
                "type": "string",
                "enum": [
                  "asset"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "A named group / company of our assets.",
            "required": [
              "group_id",
              "scope"
            ],
            "properties": {
              "group_id": {
                "type": "string"
              },
              "scope": {
                "type": "string",
                "enum": [
                  "group"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "scope"
            ],
            "properties": {
              "scope": {
                "type": "string",
                "enum": [
                  "swarm"
                ]
              }
            }
          }
        ],
        "description": "Who an assignment commands — one of our assets, a named group/company, or the\nwhole swarm. The countermeasures `Tasking | Swarm` duality generalized into a\ngrain, so one Plan can carry single-drone, group, and swarm commands.\nInternally tagged on `scope`.",
        "discriminator": {
          "propertyName": "scope"
        }
      },
      "SurveilTarget": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "area",
              "kind"
            ],
            "properties": {
              "area": {
                "$ref": "#/components/schemas/Area"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "area"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "point",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "point"
                ]
              },
              "point": {
                "$ref": "#/components/schemas/GeoPoint"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "track_id",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "track"
                ]
              },
              "track_id": {
                "$ref": "#/components/schemas/ObjectId"
              }
            }
          }
        ],
        "description": "The target of a [`Action::Surveil`] — an area, a point, or a tracked object.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "TargetRef": {
        "oneOf": [
          {
            "type": "object",
            "description": "A registered defended asset by id.",
            "required": [
              "asset_id",
              "kind"
            ],
            "properties": {
              "asset_id": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "asset"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Another drone in the same scenario, by its `label` (e.g. an attacker\n`\"Bandit-1\"`). The primary path for scenario-authored intercepts.",
            "required": [
              "label",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "label"
                ]
              },
              "label": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "description": "A raw `lat`/`lon` point.",
            "required": [
              "lat",
              "lon",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "point"
                ]
              },
              "lat": {
                "type": "number",
                "format": "double"
              },
              "lon": {
                "type": "number",
                "format": "double"
              }
            }
          }
        ],
        "description": "What an [`Intent::Intercept`] (or a hostile's target) refers to. Internally\ntagged on `kind`.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "Task": {
        "type": "object",
        "description": "One decision that was made and dispatched.\n\n`level` and `gate` are stamped **at authoring** and never recomputed, for the reason\n`Plan.authored_under` already exists: the dial moves during a run, and \"why did it\nask me?\" is answered by what the line said when the task was drawn, not by what it\nsays now.",
        "required": [
          "id",
          "workspace_id",
          "actor",
          "verb",
          "origin",
          "level",
          "gate",
          "status",
          "issued_at"
        ],
        "properties": {
          "actor": {
            "type": "string",
            "description": "The thing being told to do something, as the surface names it (`ast_…`, or a\nsimulated twin's own label)."
          },
          "gate": {
            "$ref": "#/components/schemas/Gate"
          },
          "id": {
            "type": "string",
            "description": "`tsk_…` — the prefixed-id convention PR #325 introduced."
          },
          "issued_at": {
            "type": "string",
            "format": "date-time"
          },
          "level": {
            "$ref": "#/components/schemas/Level"
          },
          "object": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EntityRef"
              }
            ],
            "nullable": true
          },
          "origin": {
            "$ref": "#/components/schemas/CommandOrigin"
          },
          "params": {
            "$ref": "#/components/schemas/CommandParams"
          },
          "reason": {
            "type": "string",
            "description": "Why it was refused or reverted, in the refusing rule's own words. The sentence\nan operator reads lands on the record, not in a toast that has gone by the time\nanyone looks.",
            "nullable": true
          },
          "settled_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/TaskStatus"
          },
          "verb": {
            "$ref": "#/components/schemas/CommandVerb"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "Tenancy is enforced, not assumed."
          }
        }
      },
      "TaskStatus": {
        "type": "string",
        "description": "Where a task is in its life.\n\n`RouteMissionStatus` — the persisted record this replaces — was\n`Draft | Assigned | Active | Complete | Aborted` and **had no refusal**: a refused\nstart stayed `Assigned` with `abort_reason` set, which reads as \"assigned and fine\"\nto anything looking at status alone. That is the bug this enum must not repeat, so\n`refused` is a status and not a field beside one.\n\n`superseded` and `reverted` are not decoration either. A REVERT on a taken row, a\nplan re-authored over the same airframes, and the veto window all need a state to\nmove to; before this they had a mutable field and a hope.\n\n```text\nproposed → issued → executing → complete\n→ refused\n→ superseded\n→ reverted\n```",
        "enum": [
          "proposed",
          "issued",
          "executing",
          "complete",
          "refused",
          "superseded",
          "reverted"
        ]
      },
      "Tasking": {
        "type": "object",
        "description": "One group's orders — the shared body of a Plan (proposed) and a Mission (live).\n`policy` replaces the old fixed `AssetInstruction` verb (a verb becomes a\ncatalog preset), so there is one representation of a group's tasking.",
        "required": [
          "group",
          "policy",
          "target",
          "role",
          "roe",
          "coordination",
          "status",
          "origin"
        ],
        "properties": {
          "approval": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ApprovalVerdict"
              }
            ],
            "nullable": true
          },
          "coordination": {
            "$ref": "#/components/schemas/Coordination"
          },
          "group": {
            "$ref": "#/components/schemas/Group"
          },
          "guidance": {
            "$ref": "#/components/schemas/Guidance"
          },
          "legs": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "number",
                "format": "double"
              }
            },
            "description": "The drawn movement path (geo/ENU polyline) for the tactical canvas."
          },
          "origin": {
            "$ref": "#/components/schemas/PlanOrigin"
          },
          "policy": {
            "$ref": "#/components/schemas/StrategyRef"
          },
          "roe": {
            "$ref": "#/components/schemas/Roe"
          },
          "role": {
            "$ref": "#/components/schemas/Role"
          },
          "status": {
            "$ref": "#/components/schemas/TaskStatus"
          },
          "target": {
            "$ref": "#/components/schemas/TargetRef"
          },
          "window": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TimeWindow"
              }
            ],
            "nullable": true
          }
        }
      },
      "TelemetryReport": {
        "type": "object",
        "description": "Own-asset autopilot telemetry self-report (DJI Cloud OSD, MAVLink\nGLOBAL_POSITION_INT) — a trusted friendly reporting over our C2 link.",
        "properties": {
          "asset_id": {
            "type": "string",
            "nullable": true
          },
          "autopilot": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Threat": {
        "type": "object",
        "description": "One hostile object being worked through the kill chain — the actionable unit the\noperator sees and the engagement loop keys off. Alerts roll up into `alert_ids`\n(its timeline); `stage` advances Detected→Confirmed→…→Resolved.",
        "required": [
          "id",
          "track_id",
          "stage",
          "severity",
          "classification",
          "affiliation",
          "enu",
          "cpa_m",
          "tti_s",
          "first_seen_t",
          "last_seen_t",
          "alert_ids",
          "alert_kinds"
        ],
        "properties": {
          "affiliation": {
            "type": "string",
            "description": "Disposition, e.g. `\"hostile\"` / `\"suspect\"`."
          },
          "alert_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The alerts contributing to this threat, in first-seen order (the timeline)."
          },
          "alert_kinds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Distinct alert kinds seen (for the card summary), e.g. `[\"geofence_breach\",\"rapid_closure\"]`."
          },
          "assignment": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Assignment"
              }
            ],
            "nullable": true
          },
          "auto_declared": {
            "type": "boolean",
            "description": "The MACHINE declared this a threat autonomously (weapons-free / FullAuto),\nwith no human designation. Distinct from `designation` (which is\ncontractually human-only) so the UI can show the provenance honestly —\n\"AUTO-DECLARED\", not a human's call. A human `designation` still overrides."
          },
          "classification": {
            "type": "string",
            "description": "Fused object class, e.g. `\"uav_multirotor\"`."
          },
          "cpa_m": {
            "type": "number",
            "format": "double",
            "description": "Closest-point-of-approach (m) and time-to-intercept (s) — the headline scores."
          },
          "designation": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OperatorVerdict"
              }
            ],
            "nullable": true
          },
          "enu": {
            "$ref": "#/components/schemas/Enu"
          },
          "first_seen_t": {
            "type": "number",
            "format": "double"
          },
          "id": {
            "type": "string",
            "description": "Stable object id (the backing fused track id)."
          },
          "last_seen_t": {
            "type": "number",
            "format": "double"
          },
          "provenance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EventProvenance"
              }
            ],
            "nullable": true
          },
          "resolution": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Resolution"
              }
            ],
            "nullable": true
          },
          "severity": {
            "$ref": "#/components/schemas/AlertSeverity"
          },
          "stage": {
            "$ref": "#/components/schemas/ThreatStage"
          },
          "track_id": {
            "type": "string"
          },
          "tti_s": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "ThreatScore": {
        "type": "object",
        "description": "Reflexive threat scoring for a track: disposition level, priority, the\nprotected asset, closest-point-of-approach and time-to-intercept.",
        "required": [
          "level",
          "priority",
          "protected_asset_id",
          "cpa_m",
          "tti_s"
        ],
        "properties": {
          "cpa_m": {
            "type": "number",
            "format": "double"
          },
          "level": {
            "type": "string",
            "description": "`hostile` | `suspect` | `benign` | `unknown`."
          },
          "priority": {
            "type": "integer",
            "format": "int64"
          },
          "protected_asset_id": {
            "type": "string"
          },
          "tti_s": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "ThreatStage": {
        "type": "string",
        "description": "The kill-chain state of a threat (the Palantir target-board columns). #38 drives\n`Detected`/`Confirmed`/`Resolved`; `Pairing`/`Engaging`/`Assessing` are set by\nthe semi-autonomous engagement loop (#39).",
        "enum": [
          "detected",
          "confirmed",
          "pairing",
          "engaging",
          "assessing",
          "resolved"
        ]
      },
      "ThresholdsView": {
        "type": "object",
        "required": [
          "trace",
          "not_storable",
          "version"
        ],
        "properties": {
          "not_storable": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SettingKey"
            },
            "description": "The keys the baseline config cannot yet hold, named rather than hidden.\nA screen that shows a control it cannot save is the defect this removes."
          },
          "trace": {
            "$ref": "#/components/schemas/ResolutionTrace"
          },
          "version": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          }
        }
      },
      "TimeWindow": {
        "type": "object",
        "description": "A validity window on a tasking — a re-task trigger when it lapses.",
        "required": [
          "valid_until_s"
        ],
        "properties": {
          "valid_until_s": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Track": {
        "type": "object",
        "description": "Layer 1 — fused track after tracking + cross-platform association.\n\nThe `schema` field carries the frozen wire string `\"track.v1\"` at runtime.\nEvery nested field is a concrete type (issue 37) — no `serde_json::Value`.",
        "required": [
          "schema",
          "track_id",
          "t",
          "domain",
          "classification",
          "kinematics",
          "uncertainty",
          "custody",
          "threat",
          "evidence",
          "history_ref"
        ],
        "properties": {
          "affiliation": {
            "$ref": "#/components/schemas/Affiliation"
          },
          "affiliation_source": {
            "$ref": "#/components/schemas/AffiliationSource"
          },
          "classification": {
            "$ref": "#/components/schemas/Classification"
          },
          "confidence": {
            "type": "number",
            "format": "double",
            "description": "Corroborated 0..1 confidence for this track (#54) — a **read-through** of the\ncorroboration evidence fusion already accumulates (`custody.corroboration_count`\n/ `seen_modalities` + track persistence), surfaced as one field for the UI,\nsolver, and LLM to share rather than each recomputing its own. Not a new\nestimator. Additive (`#[serde(default)]`): legacy `track.v1` records ⇒ `0.0`."
          },
          "custody": {
            "$ref": "#/components/schemas/Custody"
          },
          "domain": {
            "type": "string"
          },
          "evidence": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "existence": {
            "$ref": "#/components/schemas/Existence"
          },
          "history_ref": {
            "type": "string"
          },
          "identification": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Identification"
              }
            ],
            "nullable": true
          },
          "identity": {
            "$ref": "#/components/schemas/IdentityBelief"
          },
          "kinematics": {
            "$ref": "#/components/schemas/Kinematics"
          },
          "schema": {
            "type": "string"
          },
          "simulated": {
            "type": "boolean",
            "description": "Provenance: `true` when this track was derived from simulated\nobservations. Set by the runtime from the driving obs batch so the\noperator console can keep simulated tracks out of the real picture."
          },
          "t": {
            "type": "string"
          },
          "threat": {
            "$ref": "#/components/schemas/ThreatScore"
          },
          "track_id": {
            "type": "string"
          },
          "uncertainty": {
            "$ref": "#/components/schemas/Uncertainty"
          }
        }
      },
      "TrackExplain": {
        "type": "object",
        "description": "One track's scan: the covariance it started with, what each measurement did to\nit, and the covariance it ended with.",
        "required": [
          "track_id",
          "enu",
          "existence",
          "emitted",
          "coast_steps",
          "prior_cov",
          "posterior_cov"
        ],
        "properties": {
          "coast_steps": {
            "type": "integer",
            "format": "int64",
            "description": "Number of scans coasted without a measurement, after this one."
          },
          "contributions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MeasurementContribution"
            },
            "description": "Every measurement applied to, or refused by, this track this scan, in the\norder the tracker handled them."
          },
          "emitted": {
            "type": "boolean",
            "description": "Whether this scan put the track on the picture. A track below the\nconfirmation threshold is held by the tracker and withheld from the\npicture; the explanation shows it anyway, because the operator's \"why is\nthere nothing on the map yet\" has an answer."
          },
          "enu": {
            "$ref": "#/components/schemas/Enu"
          },
          "existence": {
            "$ref": "#/components/schemas/Existence"
          },
          "posterior_cov": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "double"
            },
            "description": "3×3 position covariance after every measurement this scan."
          },
          "prior_cov": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "double"
            },
            "description": "3×3 position covariance (m², ENU, row-major) after prediction and before any\nmeasurement this scan."
          },
          "track_id": {
            "type": "string"
          }
        }
      },
      "TrackExplainHistory": {
        "type": "object",
        "description": "The reply to `GET /api/tracks/{id}/explain`: the scans that hold the track,\npreceded by the scans just before it was born, so the detections that led to\nthe birth are in the reply as `unassociated` rows.",
        "required": [
          "track_id",
          "lead_s",
          "ticks"
        ],
        "properties": {
          "lead_s": {
            "type": "number",
            "format": "double",
            "description": "Seconds of scans before the track's first appearance that are included."
          },
          "ticks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FusionExplain"
            }
          },
          "track_id": {
            "type": "string"
          }
        }
      },
      "TrackStatus": {
        "type": "string",
        "description": "Track maturity from the SPRT on the LLR score. Ordered by maturity\n(`Deleted < Tentative < Coasting < Confirmed`) so the derived Contact/Track view\ncan compare — a *Contact* (not a track yet) is anything below `Confirmed`.",
        "enum": [
          "deleted",
          "tentative",
          "coasting",
          "confirmed"
        ]
      },
      "Transport": {
        "oneOf": [
          {
            "type": "object",
            "description": "ASTERIX radar: a group and a port, joined rather than dialled.",
            "required": [
              "group",
              "port",
              "kind"
            ],
            "properties": {
              "group": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "udp_multicast"
                ]
              },
              "port": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              }
            }
          },
          {
            "type": "object",
            "description": "CoT, MAVLink. `role` is what `udpin:` / `udpout:` have always meant.",
            "required": [
              "addr",
              "kind"
            ],
            "properties": {
              "addr": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "udp"
                ]
              },
              "role": {
                "$ref": "#/components/schemas/SocketRole"
              }
            }
          },
          {
            "type": "object",
            "description": "SAPIENT, and vendor kit that expects a stream.",
            "required": [
              "endpoint",
              "kind"
            ],
            "properties": {
              "endpoint": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "tcp"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Our own edge nodes and Dome Lite. A site integration bus — **not** a sensor\nstandard, and never assumed of real hardware.",
            "required": [
              "broker",
              "topic",
              "kind"
            ],
            "properties": {
              "broker": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "mqtt"
                ]
              },
              "topic": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "description": "Vendor REST/WS SDKs (Dedrone, Echodyne, CRFS).",
            "required": [
              "url",
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "websocket"
                ]
              },
              "url": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "description": "Legacy EO/PTZ: VISCA, Pelco-D.",
            "required": [
              "device",
              "baud",
              "kind"
            ],
            "properties": {
              "baud": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "device": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "serial"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "**A vendor cloud we call on a cadence.**\n\n[`Self::Push`] is *they* POST to us and [`Self::WebSocket`] is a stream *they*\nkeep open; neither is \"we call their REST API\". Some vendor clouds offer no\nwebhook and no stream — Inturai's is `GET /device_data`, `GET /events`,\n`GET /devices` and nothing else — so without this the integration is\nunbuildable.\n\n**This does not violate the no-new-poll rule.** That rule governs the console:\na `setInterval` that fetches in `ui/` is a bug and at rest the console makes\nzero requests. It has never governed a backend link source — the MAVLink hub\nreads its socket in a loop. What the source learns still reaches the console\nas a delta on the picture, never as a fetch the UI initiates.",
            "required": [
              "base_url",
              "poll_ms",
              "kind"
            ],
            "properties": {
              "base_url": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "https"
                ]
              },
              "poll_ms": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "push"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "in_process"
                ]
              }
            }
          }
        ],
        "description": "**How a link is carried.** Adding a protocol adds an adapter and, at most, a\nvariant here — never an endpoint field somewhere else.",
        "discriminator": {
          "propertyName": "kind"
        }
      },
      "TransportKind": {
        "type": "string",
        "description": "A [`Transport`] with the address taken off — what an adapter advertises it can\nlegally run over, and what the generated add form offers.",
        "enum": [
          "udp_multicast",
          "udp",
          "tcp",
          "mqtt",
          "websocket",
          "serial",
          "https",
          "push",
          "in_process"
        ]
      },
      "TransportMode": {
        "type": "string",
        "description": "**The link modes an operator picks between**, and the one thing a transport row\nasks before it asks anything else.\n\nNot the same question as [`TransportKind`], and the difference is the whole\nreason this exists: a kind is what an *adapter* can legally run over, so `udp`\nis one answer. An operator has two: bind a port and wait, or send to a fixed\naddress. Every ground station ever built asks it that way, because the fields\ndiffer, and a bind address is ours where a target host is theirs.",
        "enum": [
          "udp_listen",
          "udp_target",
          "tcp_connect",
          "serial"
        ]
      },
      "TransportState": {
        "type": "object",
        "description": "**One configured link, and whether it is actually open.**\n\nThe pair an operator reads on a transport row. `open` is the socket answering,\nnot the row saying it should be. It is the same distinction the integration's\nown *enabled is not listening* makes, applied one wire at a time, because a\ndeployment with a radio and a bench link has each fail for its own reason.",
        "required": [
          "transport",
          "endpoint",
          "open"
        ],
        "properties": {
          "endpoint": {
            "type": "string",
            "description": "The canonical endpoint string, so a reader can join a row to a link without\nre-deriving one."
          },
          "error": {
            "type": "string",
            "description": "The OS's own words: `No such file or directory (os error 2)`. Present when\nit is not open, absent when it is. A failure that only reached the log is a\nfailure nobody can act on.",
            "nullable": true
          },
          "heard": {
            "type": "integer",
            "description": "How many things are being heard on this one.",
            "minimum": 0
          },
          "open": {
            "type": "boolean"
          },
          "transport": {
            "$ref": "#/components/schemas/Transport"
          }
        }
      },
      "Uncertainty": {
        "type": "object",
        "description": "Track state uncertainty: position/velocity sigmas and a derived 0–1 quality.",
        "required": [
          "pos_sigma_m",
          "vel_sigma_mps",
          "quality"
        ],
        "properties": {
          "pos_cov_m2": {
            "type": "array",
            "items": {
              "type": "number",
              "format": "double"
            },
            "description": "The horizontal position covariance the estimator actually holds, m², as\n`[xx, xy, yy]` in ENU. `pos_sigma_m` is that matrix collapsed to a circle,\nand a circle is the one shape a fix held by a radar and a bearing sensor\nusually is not. Additive (`serde(default)`): a track from a tracker that\nkeeps no covariance (the scalar α-β path) carries `None`, and a consumer\ndraws the circle it always drew.",
            "nullable": true
          },
          "pos_sigma_m": {
            "type": "number",
            "format": "double"
          },
          "quality": {
            "type": "number",
            "format": "double"
          },
          "vel_sigma_mps": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Vel": {
        "type": "object",
        "description": "ENU velocity, metres/second. `vz` defaults to `0.0` for planar sensors.",
        "required": [
          "vx",
          "vy"
        ],
        "properties": {
          "vx": {
            "type": "number",
            "format": "double"
          },
          "vy": {
            "type": "number",
            "format": "double"
          },
          "vz": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "Verdict": {
        "type": "object",
        "description": "The verdict on a rule, **without saving it**.\n\nThis is the gate the generated-authoring path runs a proposal through before it\nis ever offered to the operator (`01 §5.3`). It is the same `validate_rule` the\nwrites use, so a proposal that passes here cannot be refused on save for a\nreason the operator was not shown.",
        "required": [
          "valid",
          "rejections",
          "overlaps"
        ],
        "properties": {
          "overlaps": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Rules already in the set that test the same facts. Not an error — a\ncommander may well want two — but *\"this overlaps keep_out_breach\"* is the\nthing they would want to know before adding a third."
          },
          "rejections": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Empty when valid. Rendered verbatim by the surface."
          },
          "valid": {
            "type": "boolean"
          }
        }
      },
      "VerifyOutcome": {
        "type": "string",
        "description": "**What a verify established**, in the class an operator can act on.\n\nOrdered by how much attention the answer deserves, so a page that sorts or\ncompares two results does not have to restate the ladder.",
        "enum": [
          "ok",
          "incomplete",
          "not_provable",
          "unreachable",
          "tls",
          "refused"
        ]
      },
      "VerifyRequest": {
        "type": "object",
        "description": "`POST /v1/auth/verify`",
        "required": [
          "email",
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "The six digits from the mail."
          },
          "email": {
            "type": "string"
          }
        }
      },
      "VerifyResult": {
        "type": "object",
        "description": "**One test of one credential, and when it was run.**\n\nStored on the integration so the page can say *last verified 2 h ago* rather\nthan offering a button whose last answer nobody kept.",
        "required": [
          "outcome",
          "proved",
          "at"
        ],
        "properties": {
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "expires": {
            "type": "string",
            "format": "date",
            "description": "**When the credential stops working**, where the credential itself says.\nAbsent everywhere else, including where a licence is set and carries no\ndate of its own.",
            "nullable": true
          },
          "not_proved": {
            "type": "string",
            "description": "**What it did not establish**, where a reader would otherwise assume it\nhad. `None` only when there is genuinely nothing left over.",
            "nullable": true
          },
          "outcome": {
            "$ref": "#/components/schemas/VerifyOutcome"
          },
          "proved": {
            "type": "string",
            "description": "**What this established**, concretely. Never \"success\": an operator\nreading it needs to know which half of the credential set was exercised."
          }
        }
      },
      "Workspace": {
        "type": "object",
        "description": "A workspace, as a member sees it.",
        "required": [
          "id",
          "name",
          "slug",
          "is_personal",
          "created_at"
        ],
        "properties": {
          "created_at": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "id": {
            "type": "string",
            "description": "`wsp_…`"
          },
          "is_personal": {
            "type": "boolean",
            "description": "Created for one person at first login and not shared."
          },
          "name": {
            "type": "string"
          },
          "role": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WorkspaceRole"
              }
            ],
            "nullable": true
          },
          "slug": {
            "type": "string",
            "description": "URL-safe short name, unique across the deployment."
          }
        }
      },
      "WorkspaceRole": {
        "type": "string",
        "description": "**What a member may do in a workspace.**\n\nThree rungs, ordered. A deployment at the edge usually has one owner and\nnothing else; the ladder exists because the first thing an operator asks for\nafter inviting somebody is a way to invite them without handing over the\nkeys.",
        "enum": [
          "viewer",
          "member",
          "admin",
          "owner"
        ]
      },
      "WorkspaceSimState": {
        "type": "object",
        "description": "What this workspace is attached to. Mirrors the active region — the pattern an\noperator already understands — and is the fix for a selection that vanished\nwhen a modal closed.",
        "required": [
          "mode",
          "simulator",
          "run"
        ],
        "properties": {
          "loaded_scenario_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          },
          "run": {
            "$ref": "#/components/schemas/RunState"
          },
          "simulator": {
            "$ref": "#/components/schemas/SimulatorKind"
          }
        }
      },
      "WorldState": {
        "type": "object",
        "description": "The current common operating picture: every confirmed track at one tick.",
        "required": [
          "t",
          "generation",
          "tracks"
        ],
        "properties": {
          "generation": {
            "type": "integer",
            "format": "int64",
            "description": "Monotonic snapshot counter (delta-sync cursor, #16).",
            "minimum": 0
          },
          "t": {
            "type": "number",
            "format": "double",
            "description": "Sim/pipeline time of this snapshot."
          },
          "tracks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Track"
            },
            "description": "Every confirmed track currently live. A track absent here is gone."
          }
        }
      },
      "WorldView": {
        "type": "object",
        "description": "A camera the WORLD owns, rather than one an asset carries.\n\nThe rendered world publishes one view of the whole exercise. Where it is\npointed is decided in the world -- it directs itself, or somebody pinned it\n-- and the C2 only mounts it. There is no control here on purpose: a feed\neverybody sees, aimed from a console that happens to have it open, is a\nfeed nobody can attribute.",
        "required": [
          "id",
          "label",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The world's own id for it, e.g. `god`."
          },
          "label": {
            "type": "string",
            "description": "What to call it on a dock. The world's label, not one invented here."
          },
          "url": {
            "type": "string",
            "description": "Absolute, as the world advertised it: only the world knows whether it is\nreachable from anywhere but the machine it runs on."
          }
        }
      },
      "Zone": {
        "type": "object",
        "description": "A named, categorized, tagged, shaped area. `category` is semantic (the system\nacts on it); `tags` are organizational (filter/group). Additive to `dome-types`.",
        "required": [
          "id",
          "name",
          "category",
          "shape"
        ],
        "properties": {
          "band": {
            "$ref": "#/components/schemas/AltitudeBand"
          },
          "category": {
            "$ref": "#/components/schemas/ZoneCategory"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "params": {
            "$ref": "#/components/schemas/ZoneParams"
          },
          "region_id": {
            "type": "string",
            "description": "Scoped to a region, or global (`None`).",
            "nullable": true
          },
          "shape": {
            "$ref": "#/components/schemas/ZoneShape"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ZoneCategory": {
        "type": "string",
        "description": "What a zone *means* — the system reads this to derive comms / ROE / constraints.",
        "enum": [
          "boundary",
          "no_go",
          "keep_out",
          "gps_denied",
          "jam",
          "free_fire",
          "isr_priority",
          "corridor"
        ]
      },
      "ZoneInput": {
        "type": "object",
        "description": "Operator-supplied fields to create or update a zone — the write shape for\n`POST`/`PUT /api/zones`. Validated by [`ZoneInput::validate`] before the store.",
        "required": [
          "name",
          "category",
          "shape"
        ],
        "properties": {
          "band": {
            "$ref": "#/components/schemas/AltitudeBand"
          },
          "category": {
            "$ref": "#/components/schemas/ZoneCategory"
          },
          "name": {
            "type": "string"
          },
          "params": {
            "$ref": "#/components/schemas/ZoneParams"
          },
          "region_id": {
            "type": "string",
            "nullable": true
          },
          "shape": {
            "$ref": "#/components/schemas/ZoneShape"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ZoneParams": {
        "type": "object",
        "description": "Per-category effect knobs — sparse; a category only reads the fields it uses.",
        "properties": {
          "priority": {
            "type": "number",
            "format": "double",
            "description": "`IsrPriority` coverage weight / consequence value.",
            "nullable": true
          },
          "strength": {
            "type": "number",
            "format": "double",
            "description": "`Jam` / `GpsDenied` intensity (0..1).",
            "nullable": true
          }
        }
      },
      "ZoneShape": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "center",
              "radius_m",
              "shape"
            ],
            "properties": {
              "center": {
                "type": "array",
                "items": {
                  "type": "number",
                  "format": "double"
                }
              },
              "radius_m": {
                "type": "number",
                "format": "double"
              },
              "shape": {
                "type": "string",
                "enum": [
                  "circle"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "points",
              "shape"
            ],
            "properties": {
              "points": {
                "type": "array",
                "items": {
                  "type": "array",
                  "items": {
                    "type": "number",
                    "format": "double"
                  }
                }
              },
              "shape": {
                "type": "string",
                "enum": [
                  "polygon"
                ]
              }
            }
          }
        ],
        "description": "A zone's geometry. Coordinates are geographic `[lat, lon]`. Internally tagged on\n`shape` for a clean TS discriminated union.",
        "discriminator": {
          "propertyName": "shape"
        }
      },
      "ObjectId": {
        "type": "string",
        "description": "A planner object of interest; today a track id."
      }
    },
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "A long-lived workspace key (`dak_…`) from `POST /api/keys`. Shown once at mint and hashed thereafter, so a lost key is replaced, never recovered."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "A short-lived session token from `POST /api/auth/verify`. Renew it with `POST /api/auth/refresh` before it expires."
      },
      "workspaceId": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Workspace-Id",
        "description": "Which tenant this call is against (`wsp_…`). Required with either credential; a credential that does not reach the named workspace gets `403`."
      }
    }
  },
  "security": [
    {
      "bearerAuth": [],
      "workspaceId": []
    },
    {
      "apiKeyAuth": [],
      "workspaceId": []
    }
  ],
  "tags": [
    {
      "name": "picture",
      "description": "The whole current state in one response. Hydration only; the stream carries every change after."
    },
    {
      "name": "stream",
      "description": "Server-sent events: the snapshot, every delta, and the resync contract."
    },
    {
      "name": "fusion",
      "description": "What the tracker did with each measurement: the newest explained scan, and one track's recent scans with the detections that led to it."
    },
    {
      "name": "events",
      "description": "The persisted event log, queryable by kind, time, track and run."
    },
    {
      "name": "signals",
      "description": "Typed observations into the fusion pipeline. The workspace comes from the credential, never the body. Where a sensor connects TO is a link fact and lives under `links`."
    },
    {
      "name": "threats",
      "description": "The threat board and operator designation."
    },
    {
      "name": "identities",
      "description": "Identity claims: which serials are declared ours, asserted by whom. A claim, not a tasking."
    },
    {
      "name": "assets",
      "description": "Every asset, live, and the one command path. An asset advertises its own capabilities; the client never guesses."
    },
    {
      "name": "tasking",
      "description": "The task ledger: who ordered what, when, under which line, and what came of it. One row per dispatch."
    },
    {
      "name": "plans",
      "description": "A plan is a set of tasks awaiting one approval decision. Approving part of one issues those tasks and supersedes the rest."
    },
    {
      "name": "settings",
      "description": "What this workspace saved: thresholds, the decision config, the accepted solve influence, planning profiles, and the compiled-in strategy catalogues they draw from. Export the lot as one document, and import it into another deployment."
    },
    {
      "name": "regions",
      "description": "The defended places a workspace operates in."
    },
    {
      "name": "zones",
      "description": "Tagged, categorised areas: geofence, keep-out, jam, free-fire, ISR priority."
    },
    {
      "name": "links",
      "description": "The deployment's physical links, declared in dome.yaml, and what a workspace attached to them discovers."
    },
    {
      "name": "integrations",
      "description": "The protocols this deployment speaks, and their gates."
    },
    {
      "name": "catalog",
      "description": "Product catalogue entries that specs are drawn from."
    },
    {
      "name": "secrets",
      "description": "Write-only secret store. There is deliberately no read of a value."
    },
    {
      "name": "doctrine",
      "description": "The SHIPPED catalogue and registry, hydrated in one call, plus the YAML export and import. What a workspace has actually saved is under `rules` and `settings`."
    },
    {
      "name": "rules",
      "description": "The rule set the decision loop runs."
    },
    {
      "name": "scenarios",
      "description": "Simulation scenarios: where a world runs and both its sides."
    },
    {
      "name": "simulation",
      "description": "Start, pause and stop simulation, and the workspace sim state."
    },
    {
      "name": "runs",
      "description": "Recorded simulation runs and their event logs."
    },
    {
      "name": "auth",
      "description": "Sign in, refresh, sign out. Six-digit codes by email; no passwords."
    },
    {
      "name": "workspaces",
      "description": "Workspaces, membership and invitations. The tenant a credential opens."
    },
    {
      "name": "keys",
      "description": "Long-lived API keys, shown once and hashed thereafter."
    },
    {
      "name": "copilot",
      "description": "Where Distri is, and a short-lived access token for the browser to reach it with. The deployment's own Distri key never leaves the server."
    },
    {
      "name": "health",
      "description": "Is the server up, and is the pipeline ticking."
    }
  ],
  "x-tagGroups": [
    {
      "name": "The picture",
      "tags": [
        "picture",
        "stream",
        "fusion",
        "events",
        "signals"
      ]
    },
    {
      "name": "Contacts",
      "tags": [
        "threats",
        "identities"
      ]
    },
    {
      "name": "Assets",
      "tags": [
        "assets"
      ]
    },
    {
      "name": "Tasking",
      "tags": [
        "tasking",
        "plans"
      ]
    },
    {
      "name": "Configure your site",
      "tags": [
        "settings",
        "regions",
        "zones",
        "links",
        "integrations",
        "catalog",
        "secrets"
      ]
    },
    {
      "name": "Doctrine",
      "tags": [
        "doctrine",
        "rules"
      ]
    },
    {
      "name": "Simulation",
      "tags": [
        "scenarios",
        "simulation",
        "runs"
      ]
    },
    {
      "name": "Account and access",
      "tags": [
        "auth",
        "workspaces",
        "keys",
        "copilot"
      ]
    },
    {
      "name": "Service health",
      "tags": [
        "health"
      ]
    }
  ]
}
