{
  "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\": ... }`; the schemas on each operation show the full wrapped body. This `/api` surface is the console's own: unauthenticated in this build, and stable to nothing but the console. Errors carry a sentence naming what failed.",
    "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"
                }
              }
            }
          }
        }
      }
    },
    "/api/assets": {
      "get": {
        "tags": [
          "assets"
        ],
        "summary": "List the registered assets in the current environment.",
        "operationId": "get_api_assets",
        "responses": {
          "200": {
            "description": "every registry row in the workspace's current environment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Asset"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "assets"
        ],
        "summary": "Add a platform to the registry; membership is the friendly claim.",
        "operationId": "post_api_assets",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Add a platform to the registry.\n\nThere is no `affiliation` here any more. Putting a platform in the registry IS\nthe claim that it is ours, and the tag that used to follow read `friend` on\nevery row it was ever written to — the operator saying the same thing twice.\n`dome_types::friend_whitelist` now derives from membership, so an added platform\nis known to the tracker with no second step. Recording something that is *not*\nours (a known adversary emitter) is still possible and still explicit: post an\nassignment to `/api/assignments/confirm`, which is the contact-adjudication path\nand always was.",
                "required": [
                  "name",
                  "spec"
                ],
                "properties": {
                  "kind": {
                    "$ref": "#/components/schemas/AssetKind"
                  },
                  "name": {
                    "type": "string"
                  },
                  "spec": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored asset, with its server-assigned id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Asset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the spec does not parse as an asset spec",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/assets/from-catalog": {
      "post": {
        "tags": [
          "assets"
        ],
        "summary": "`POST /api/assets/from-catalog` — choose a platform type, name it, done.",
        "operationId": "post_api_assets_from_catalog",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Add a platform **from the catalog** (ST1 §1, the door that fixes the endurance bug).\n\nThe whole point is the one field: `spec.model` is written as the catalog's own\n`name`, exactly, so the console's `catalogFor()` resolves and the card can read\n`14 min of flight left` instead of `78% charge`. An unknown catalog id is a 404\nrather than a row with a model nothing matches — a silent near-miss here is the\ndefect this endpoint exists to delete.",
                "required": [
                  "name",
                  "catalog"
                ],
                "properties": {
                  "catalog": {
                    "type": "string",
                    "description": "Catalog entry name (`DJI Mavic 3`). Tolerant of case and a missing `DJI `\nprefix on the way in; canonical on the way out."
                  },
                  "domain": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/AssetDomain"
                      }
                    ],
                    "nullable": true
                  },
                  "name": {
                    "type": "string",
                    "description": "Callsign. `BLUE-04`."
                  },
                  "provenance": {
                    "$ref": "#/components/schemas/Provenance"
                  },
                  "remote_id_serial": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored asset, its spec.model written as the catalogue spells it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Asset"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no catalogue platform with this name",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "the entry's domain disagrees with 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's registry row.",
        "operationId": "get_api_assets_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "asset id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the registry row",
            "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"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "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",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "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"
                  }
                }
              }
            }
          },
          "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 mirror of `DELETE /api/sensors/{id}`, and guarded the same way**: that\nroute refuses to delete anything that is not a sensor, and this one refuses\nanything that is not a force. Without the pair, either namespace could remove a\nrow belonging to the other, and because the friendly whitelist is derived from\nregistry membership, deleting an aircraft by side effect drops it off the\nwhitelist — our own drone starts scoring as an unknown contact (invariant 10).\n\nThis did not exist, and its absence had a cost worth recording: with no route,\na row could only be removed with `psql`, which bypasses the refreshes below. The\nruntime kept the deleted vehicle's payload sensors in its declared set, so a\nphantom `<uuid>/eo-turret` sat on the LIVE rail reading NO RETURNS, belonging to\nan asset that no longer existed.",
        "operationId": "delete_api_assets_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "asset id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the asset is gone; a link issued for it alone is revoked with it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "404": {
            "description": "no force asset with this id in the 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": "`POST /api/assets/{id}/bind` — pair a heard platform with an existing row.",
        "operationId": "post_api_assets_id_bind",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "asset id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "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": [
          "fleet"
        ],
        "summary": "Command an asset. Refuses any verb the asset does not advertise, with the reason.",
        "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": {
          "200": {
            "description": "the command was accepted and sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "verb": "return_to_base"
                }
              }
            }
          },
          "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": "`POST /api/assets/{id}/payloads` — 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",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "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/assignments": {
      "get": {
        "tags": [
          "assignments"
        ],
        "summary": "List assignments, newest first.",
        "operationId": "get_api_assignments",
        "responses": {
          "200": {
            "description": "every assignment in the workspace, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Assignment"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "assignments"
        ],
        "summary": "Create an assignment on an asset.",
        "operationId": "post_api_assignments",
        "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 assignment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Assignment"
                    },
                    "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/assignments/confirm": {
      "post": {
        "tags": [
          "assignments"
        ],
        "summary": "Confirm an affiliation for the platform behind a Remote-ID serial.",
        "operationId": "post_api_assignments_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 assignment, superseding any active one for the asset",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Assignment"
                    },
                    "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/assignments/{id}": {
      "get": {
        "tags": [
          "assignments"
        ],
        "summary": "One assignment by id.",
        "operationId": "get_api_assignments_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "assignment id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the assignment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Assignment"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no assignment with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "assignments"
        ],
        "summary": "Update an assignment's affiliation, operator, role or active flag.",
        "operationId": "post_api_assignments_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "assignment 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 assignment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Assignment"
                    },
                    "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 assignment with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/catalog/builtins": {
      "get": {
        "tags": [
          "catalog"
        ],
        "summary": "The built-in sensing catalog (DJI Mavic family + contrast platforms). Pure /",
        "description": "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": "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",
                "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/seed-builtins": {
      "post": {
        "tags": [
          "catalog"
        ],
        "summary": "Backfill the workspace catalog with any built-in platforms it is missing.",
        "operationId": "post_api_catalog_seed_builtins",
        "responses": {
          "200": {
            "description": "how many built-in platforms were inserted; 0 when the workspace already has them all",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "Result of a builtin-seed request.",
                      "required": [
                        "inserted"
                      ],
                      "properties": {
                        "inserted": {
                          "type": "integer",
                          "minimum": 0
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/decision-config": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "`GET /api/decision-config` — the policy in force.",
        "description": "A 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"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "settings"
        ],
        "summary": "`PUT /api/decision-config` — save the policy and push it into the runtime.",
        "description": "`version` in the request body is ignored; the server owns it and bumps it, so\ntwo operators editing concurrently cannot both claim the same version. The\nresponse carries the version actually assigned.",
        "operationId": "put_api_decision_config",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionConfig"
              }
            }
          },
          "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 config cannot be stored as sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/discovery": {
      "get": {
        "tags": [
          "discovery"
        ],
        "summary": "Read the discovery switch and every declared link's effective policy.",
        "operationId": "get_api_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 deployment-wide switch."
                        },
                        "links": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/LinkPolicy"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "discovery"
        ],
        "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_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 deployment-wide switch."
                        },
                        "links": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/LinkPolicy"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/doctrine": {
      "get": {
        "tags": [
          "doctrine"
        ],
        "summary": "`GET /api/doctrine` — the whole Doctrine section, hydrated once.",
        "operationId": "get_api_doctrine",
        "responses": {
          "200": {
            "description": "posture profiles and matrices, release table, rule catalogue, settings registry with its default trace, 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": [
                        "posture_profiles",
                        "posture_matrices",
                        "release_table",
                        "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"
                          }
                        },
                        "posture_matrices": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PostureMatrix"
                          },
                          "description": "One per profile, generated from its ladder — the surface never builds a\nmatrix itself, so switching a profile cannot leave the screen and the\nengine disagreeing about which cells exist."
                        },
                        "posture_profiles": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PostureProfile"
                          }
                        },
                        "release_table": {
                          "$ref": "#/components/schemas/ReleaseTable"
                        },
                        "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": "`GET /api/doctrine/export` — the workspace's doctrine as a `doctrine.v1` YAML",
        "description": "document.\n\nThe 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"
          },
          "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": "`POST /api/doctrine/import` — replace the workspace's rules from a `doctrine.v1`",
        "description": "document.\n\nThe 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` could not.",
        "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": "`GET /api/doctrine/telemetry` — 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/engagement/mode": {
      "get": {
        "tags": [
          "engagement"
        ],
        "summary": "`GET /api/engagement/mode` — the current autonomy ceiling.",
        "operationId": "get_api_engagement_mode",
        "responses": {
          "200": {
            "description": "the autonomy ceiling in force",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "mode"
                      ],
                      "properties": {
                        "mode": {
                          "type": "string",
                          "description": "The ceiling in force: `manual`, `semi_auto`, `auto` or `full_auto`."
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "engagement"
        ],
        "summary": "`PUT /api/engagement/mode` `{ \"mode\": \"manual\"|\"semi_auto\"|\"auto\" }` — set the gate.",
        "operationId": "put_api_engagement_mode",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "mode"
                ],
                "properties": {
                  "mode": {
                    "type": "string",
                    "description": "One of `manual`, `semi_auto`, `auto`, `full_auto`. Aliases accepted:\n`semi`, `semiauto`, `fullauto`, `weapons_free`, and hyphenated spellings."
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the ceiling now in force, in canonical spelling: manual, semi_auto, auto or full_auto",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "mode"
                      ],
                      "properties": {
                        "mode": {
                          "type": "string",
                          "description": "The ceiling in force: `manual`, `semi_auto`, `auto` or `full_auto`."
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "not a recognised autonomy mode (accepted: manual, semi_auto, auto, full_auto, and the aliases semi, semiauto, fullauto, weapons_free)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/engagement/plan/run": {
      "post": {
        "tags": [
          "engagement"
        ],
        "summary": "`POST /api/engagement/plan/run` `{ \"variation_count\"?: 1..=3 }` — the operator's",
        "description": "PLAN button (#61). Runs EXACTLY the authoring path the autonomous cadence runs,\nbypassing the cadence floor; the in-flight state rides the engagement picture\n(`EngagementState.plan_run`), so a clicked run and an autonomous run are\nindistinguishable to the UI and two clients cannot start overlapping passes.",
        "operationId": "post_api_engagement_plan_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/engagement/plans": {
      "get": {
        "tags": [
          "engagement"
        ],
        "summary": "`GET /api/engagement/plans` — the authored candidate set (0–3 plans), each",
        "description": "carrying its strategy label, predicted outcome, and LLM review.",
        "operationId": "get_api_engagement_plans",
        "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"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/engagement/plans/{id}/approve": {
      "post": {
        "tags": [
          "engagement"
        ],
        "summary": "`POST /api/engagement/plans/{id}/approve` — the operator picks THIS plan:",
        "description": "its assignments are adopted as operator-approved tasks and the transport\ndelivers them to the drones. Unchosen candidates are superseded. `404` when\nthe id is no longer in the candidate set (a replan superseded it) — the\nclient refetches and decides on the fresh set, never on a stale card.\n\n`409` when the release table withholds an assignment's effect in the posture\nin force. A press is consent, not authority: it satisfies CONFIRM and\nTWO_PERSON, which is doctrine asking a human, and it does not satisfy\nWITHHELD, which is the effect not being ours to exercise at this control\nstatus.",
        "operationId": "post_api_engagement_plans_id_approve",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "candidate plan id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "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"
                    }
                  }
                }
              }
            }
          },
          "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; 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/engagement/plans/{id}/reject": {
      "post": {
        "tags": [
          "engagement"
        ],
        "summary": "`POST /api/engagement/plans/{id}/reject` — decline a candidate. Rejecting",
        "description": "the adopted (executing) plan withdraws approval and recalls the drones.",
        "operationId": "post_api_engagement_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/engagement/task/area": {
      "post": {
        "tags": [
          "tasking"
        ],
        "summary": "`POST /api/engagement/task/area` `{lat, lon, radius_m}` — orbit/surveil the area.",
        "operationId": "post_api_engagement_task_area",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "lat",
                  "lon",
                  "radius_m"
                ],
                "properties": {
                  "lat": {
                    "type": "number",
                    "format": "double"
                  },
                  "lon": {
                    "type": "number",
                    "format": "double"
                  },
                  "radius_m": {
                    "type": "number",
                    "format": "double"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "which asset was tasked and with what verb",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "asset": "blue-01",
                  "verb": "surveil"
                }
              }
            }
          },
          "400": {
            "description": "no free drone available to surveil",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "the selected drone is not in the asset registry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "refused: an exercise is running and the asset is real, or the asset does not advertise the verb",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/engagement/task/point": {
      "post": {
        "tags": [
          "tasking"
        ],
        "summary": "`POST /api/engagement/task/point` `{lat, lon}` — send the nearest free drone there.",
        "operationId": "post_api_engagement_task_point",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "lat",
                  "lon"
                ],
                "properties": {
                  "lat": {
                    "type": "number",
                    "format": "double"
                  },
                  "lon": {
                    "type": "number",
                    "format": "double"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "which asset was tasked and with what verb",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "asset": "blue-01",
                  "verb": "move_to"
                }
              }
            }
          },
          "400": {
            "description": "no free drone available to surveil",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "the selected drone is not in the asset registry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "refused: an exercise is running and the asset is real, or the asset does not advertise the verb",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/environments": {
      "get": {
        "tags": [
          "environments"
        ],
        "summary": "List the workspace's environments and which one is attached.",
        "operationId": "get_api_environments",
        "responses": {
          "200": {
            "description": "all three environments, with the attached one's id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "Every environment in the workspace, plus which one is attached right now.",
                      "required": [
                        "environments",
                        "current"
                      ],
                      "properties": {
                        "current": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The attached one's id. Never null — a workspace that has never chosen is\nlooking at the real world."
                        },
                        "environments": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Environment"
                          }
                        }
                      }
                    },
                    "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/fleet": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "The live fleet: registry identity plus link state, motion, health and capabilities.",
        "operationId": "get_api_fleet",
        "responses": {
          "200": {
            "description": "every asset's live view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AssetView"
                      }
                    },
                    "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/fleet/{id}": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "One asset's live view.",
        "operationId": "get_api_fleet_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "registry uuid or adoption key",
            "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",
            "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/fusion/health": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "`GET /api/fusion/health` — the tracker's self-report.",
        "description": "Every 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
                }
              }
            }
          }
        }
      }
    },
    "/api/influence": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "`GET /api/influence` — the influence in force (hydration; changes ride the",
        "description": "stream as `influence.v1`).",
        "operationId": "get_api_influence",
        "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"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "settings"
        ],
        "summary": "`PUT /api/influence` — 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/ingest": {
      "get": {
        "tags": [
          "ingest"
        ],
        "summary": "**What this deployment publishes for something to connect to.**",
        "operationId": "get_api_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/ingest/obs": {
      "post": {
        "tags": [
          "ingest"
        ],
        "summary": "Ingest one tick's worth of observations into the bound intake.",
        "operationId": "post_api_ingest_obs",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "description": "Which intake the batch enters: `live` (the default) or `simulated`.",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/IngestSource"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Observation"
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "how many observations were accepted for fusion",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "accepted": 12
                }
              }
            }
          },
          "500": {
            "description": "the runtime's ingest channel is closed",
            "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}/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/links": {
      "get": {
        "tags": [
          "links"
        ],
        "summary": "Saved links, plus the one the attached environment carries.",
        "description": "A link is a runtime connection, not a row — so \"Dome Lite should have a\nSimLink\" is not answered by a seed. Attaching the environment is what puts it\nup. Before this the pane listed sockets, and a Dome Lite exercise has none, so\nit sat empty through every run while three aircraft flew on an in-process link\nnothing named.\n\nThe assembly moved to [`fleet::link_snapshots`] when the integration slice\nstarted riding the picture (#300): the settings page and the picture must\nanswer *is this radio up* with one number, and two assemblies would\neventually disagree in front of an operator with no way to tell which was\nright.",
        "operationId": "get_api_links",
        "responses": {
          "200": {
            "description": "saved links plus the one the attached environment carries, with live state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LinkSnapshot"
                      }
                    },
                    "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"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "links"
        ],
        "summary": "Connect a link and save it.",
        "operationId": "post_api_links",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Open a link. Defaults to the conventional ground-station port.",
                "properties": {
                  "endpoint": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the connected link, with its live state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LinkSnapshot"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "a busy port or a malformed endpoint; the message says which",
            "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/links/discovered": {
      "get": {
        "tags": [
          "links"
        ],
        "summary": "Platforms heard on a link and not yet adopted.",
        "operationId": "get_api_links_discovered",
        "responses": {
          "200": {
            "description": "everything announcing itself that no registry row claims",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DiscoveredAsset"
                      }
                    },
                    "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/links/discovered/adopt": {
      "post": {
        "tags": [
          "links"
        ],
        "summary": "Adopt a discovered platform into the registry.",
        "operationId": "post_api_links_discovered_adopt",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Adopt a discovered platform.",
                "required": [
                  "key"
                ],
                "properties": {
                  "key": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the adopted platform's live view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AssetView"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "nothing discovered with this key",
            "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/links/{id}": {
      "delete": {
        "tags": [
          "links"
        ],
        "summary": "**Forget a link.**",
        "description": "Two kinds arrive here and they end differently. A MAVLink hub owns a socket\nand disconnecting it closes one. A declared inbound link owns no socket —\nit is a row and a credential — so forgetting it must **revoke**, or a\nsensor that kept its password goes on authenticating against a link nothing\ndeclares and has its traffic dropped as unclaimed instead of refused.",
        "operationId": "delete_api_links_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "a MAVLink hub's link id, or a declared inbound link's uuid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the link is forgotten; a declared inbound link also reports \"revoked\": true",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "404": {
            "description": "no link with this id",
            "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/manifest": {
      "get": {
        "tags": [
          "manifest"
        ],
        "summary": "Everything this workspace holds, as one document.",
        "operationId": "get_api_manifest",
        "responses": {
          "200": {
            "description": "the workspace's assets and links as one manifest document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AssetManifest"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "manifest"
        ],
        "summary": "Validate, and apply unless `dry_run`.",
        "operationId": "post_api_manifest",
        "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/manifest.yaml": {
      "get": {
        "tags": [
          "manifest"
        ],
        "summary": "The same document as YAML, for `curl > assets.yaml`.",
        "operationId": "get_api_manifest_yaml",
        "responses": {
          "200": {
            "description": "the assets.yaml manifest, served as a download"
          }
        }
      }
    },
    "/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": {
                      "type": "object",
                      "description": "The bootstrap payload: everything the client needs on first load — the default\nregion (for centering), all regions, the friendly asset roster, and the threat\ncatalog. New fields are additive; `region` is preserved for existing clients.",
                      "required": [
                        "regions",
                        "assets",
                        "assignments",
                        "catalog"
                      ],
                      "properties": {
                        "assets": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Asset"
                          },
                          "description": "Fixed platforms (the asset registry). Affiliation-agnostic."
                        },
                        "assignments": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Assignment"
                          },
                          "description": "Dynamic assignments (operator-confirmed affiliation/operator/role per asset).\nThe client joins these to `assets` by `asset_id` for the current picture."
                        },
                        "catalog": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CatalogEntry"
                          },
                          "description": "The threat catalog (drone-type library)."
                        },
                        "region": {
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/RegionSummary"
                            }
                          ],
                          "nullable": true
                        },
                        "regions": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/RegionSummary"
                          },
                          "description": "All regions for the workspace."
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/metrics": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Liveness plus the `degraded` flag the console reads.",
        "operationId": "get_api_metrics",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "description": "Accepted for compatibility; the response is always JSON.",
            "required": false,
            "schema": {
              "type": "string",
              "nullable": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "pipeline liveness and degraded flag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "pipeline_alive": true,
                  "degraded": false
                }
              }
            }
          }
        }
      }
    },
    "/api/missions": {
      "get": {
        "tags": [
          "missions"
        ],
        "summary": "List the committed missions.",
        "operationId": "get_api_missions",
        "responses": {
          "200": {
            "description": "every mission in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Mission"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "missions"
        ],
        "summary": "Store a mission.",
        "operationId": "post_api_missions",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Mission"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored mission, with its server-assigned id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Mission"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/missions/{id}": {
      "get": {
        "tags": [
          "missions"
        ],
        "summary": "One mission by id.",
        "operationId": "get_api_missions_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "mission id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the mission",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Mission"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no mission with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/openapi.json": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "The OpenAPI 3 document for this server.",
        "operationId": "get_api_openapi_json",
        "responses": {
          "200": {
            "description": "the OpenAPI 3 document describing every route on this server"
          }
        }
      }
    },
    "/api/picture": {
      "get": {
        "tags": [
          "picture"
        ],
        "summary": "`GET /api/picture` — the whole picture, once. Thereafter the client applies",
        "description": "SSE deltas and never re-fetches this on a timer while the stream is healthy;\nthe 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": "`GET /api/planning-profiles` — 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": "`PUT /api/planning-profiles` — 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": "List candidate plans, newest first.",
        "operationId": "get_api_plans",
        "responses": {
          "200": {
            "description": "every candidate plan in the workspace, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PlanCandidate"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "plans"
        ],
        "summary": "Store a candidate plan.",
        "operationId": "post_api_plans",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanCandidate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the stored candidate, with its server-assigned id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PlanCandidate"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/plans/propose": {
      "post": {
        "tags": [
          "plans"
        ],
        "summary": "Generate candidate plans, one per strategy, and persist them.",
        "operationId": "post_api_plans_propose",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProposeReq"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the generated candidates, one per strategy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PlanCandidate"
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/plans/{id}": {
      "get": {
        "tags": [
          "plans"
        ],
        "summary": "One candidate plan by id.",
        "operationId": "get_api_plans_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "plan id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the candidate plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PlanCandidate"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no plan with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/plans/{id}/approve": {
      "post": {
        "tags": [
          "plans"
        ],
        "summary": "Approve a candidate plan into a mission.",
        "operationId": "post_api_plans_id_approve",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "plan id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional name for the mission an approval creates.",
                "properties": {
                  "name": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the materialised mission; the plan is marked approved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Mission"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no plan with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/posture": {
      "get": {
        "tags": [
          "settings"
        ],
        "summary": "`GET /api/posture` — the posture in force.",
        "operationId": "get_api_posture",
        "responses": {
          "200": {
            "description": "the posture in force, with its cell name, summary and profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The posture, plus enough to render it without a second fetch.",
                      "required": [
                        "posture",
                        "cell_name",
                        "cell_summary",
                        "profile"
                      ],
                      "properties": {
                        "cell_name": {
                          "type": "string",
                          "description": "The cell's name and summary — `RED / HOLD`, \"Automatic through the plan.\""
                        },
                        "cell_summary": {
                          "type": "string"
                        },
                        "posture": {
                          "$ref": "#/components/schemas/Posture"
                        },
                        "profile": {
                          "$ref": "#/components/schemas/PostureProfile"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "settings"
        ],
        "summary": "`PUT /api/posture` — issue a posture.",
        "description": "An unreachable cell is refused rather than clamped: *cleared to fire at a\nthreat nobody expects* is not a posture, and silently moving the operator to a\nneighbouring cell would be worse than saying no.",
        "operationId": "put_api_posture",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "alert_level",
                  "control"
                ],
                "properties": {
                  "alert_level": {
                    "type": "integer",
                    "format": "int32",
                    "description": "Level on the profile's ladder.",
                    "minimum": 0
                  },
                  "co_signed_by": {
                    "type": "string",
                    "nullable": true
                  },
                  "control": {
                    "$ref": "#/components/schemas/ControlStatus"
                  },
                  "issued_by": {
                    "type": "string",
                    "description": "Who is issuing, and who countersigned.\n\nRequired only for a cell whose `issue_requires` is two-person — today\nthat is any FREE cell, because FREE releases an engagement to a\nfive-second veto window. The signature is taken **here**, once, out of\ncontact. Demanding it per engagement instead would be ceremony at the one\nmoment there is no time for it.",
                    "nullable": true
                  },
                  "profile_id": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the issued posture",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The posture, plus enough to render it without a second fetch.",
                      "required": [
                        "posture",
                        "cell_name",
                        "cell_summary",
                        "profile"
                      ],
                      "properties": {
                        "cell_name": {
                          "type": "string",
                          "description": "The cell's name and summary — `RED / HOLD`, \"Automatic through the plan.\""
                        },
                        "cell_summary": {
                          "type": "string"
                        },
                        "posture": {
                          "$ref": "#/components/schemas/Posture"
                        },
                        "profile": {
                          "$ref": "#/components/schemas/PostureProfile"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "no such level on the profile's ladder, an unreachable cell, or a two-person cell issued without both signatures",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/products": {
      "get": {
        "tags": [
          "products"
        ],
        "summary": "The product catalogue, with each connection recipe's form fields resolved.",
        "operationId": "get_api_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/regions": {
      "get": {
        "tags": [
          "regions"
        ],
        "summary": "List the workspace's regions.",
        "operationId": "get_api_regions",
        "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"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "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": {
                      "type": "object",
                      "description": "Response for a successful region delete.",
                      "required": [
                        "deleted"
                      ],
                      "properties": {
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    },
                    "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": "`GET /api/rules` — 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": "`POST /api/rules` — 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": "`PUT /api/rules` — 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": "`GET /api/rules/library` — 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": "`POST /api/rules/validate` — 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": "`DELETE /api/rules/{id}` — 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": "`PATCH /api/rules/{id}` — 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",
                "required": [
                  "name",
                  "spec"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "spec": {
                    "type": "object",
                    "description": "The raw [`dome_types::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": {
                      "type": "object",
                      "description": "Response for a successful secret delete.",
                      "required": [
                        "deleted"
                      ],
                      "properties": {
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    },
                    "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/sensors": {
      "get": {
        "tags": [
          "sensors"
        ],
        "summary": "The sensors this workspace has placed.",
        "operationId": "get_api_sensors",
        "responses": {
          "200": {
            "description": "every fixed sensor placed in the workspace's current environment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "A sensor, as the settings surface reads it back.",
                        "required": [
                          "id",
                          "name",
                          "profile",
                          "modality",
                          "bearing_only",
                          "lat",
                          "lon",
                          "height_m",
                          "max_range_m",
                          "fov_deg",
                          "boresight_deg",
                          "provenance"
                        ],
                        "properties": {
                          "bearing_only": {
                            "type": "boolean",
                            "description": "True when this sensor reports bearing without range, which changes what the\npicture can do with one of them alone."
                          },
                          "boresight_deg": {
                            "type": "number",
                            "format": "double"
                          },
                          "fov_deg": {
                            "type": "number",
                            "format": "double"
                          },
                          "height_m": {
                            "type": "number",
                            "format": "double",
                            "description": "**Height above ground, metres** — the emplacement's ENU `z`. Reported so a\nplacement can be read back and corrected without the number being lost on\nthe round trip; the simulator's sensor geometry ranges from it, so a mast\nat 6 m and one at ground level do not see the same thing."
                          },
                          "id": {
                            "type": "string",
                            "description": "The registry asset id — a sensor is an asset, so this addresses it everywhere."
                          },
                          "lat": {
                            "type": "number",
                            "format": "double"
                          },
                          "lon": {
                            "type": "number",
                            "format": "double"
                          },
                          "max_range_m": {
                            "type": "number",
                            "format": "double"
                          },
                          "modality": {
                            "type": "string",
                            "description": "`rf` · `radar` · `acoustic` · `eo` — decides the renderer and the obs shape."
                          },
                          "name": {
                            "type": "string"
                          },
                          "profile": {
                            "type": "string",
                            "description": "Profile id: `rf-df`, `ground-radar-360`, `acoustic-array`, `eo-turret`, …"
                          },
                          "provenance": {
                            "$ref": "#/components/schemas/Provenance"
                          }
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "sensors"
        ],
        "summary": "Place a sensor at a geodetic point; the profile supplies everything parametric.",
        "operationId": "post_api_sensors",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Place a sensor. A position is a place you click, not two number fields — the\nroute takes the geodetic point the click produced.",
                "required": [
                  "profile",
                  "lat",
                  "lon"
                ],
                "properties": {
                  "boresight_deg": {
                    "type": "number",
                    "format": "double",
                    "description": "Where it looks. Ignored by an omni profile (`fov_deg >= 360`)."
                  },
                  "lat": {
                    "type": "number",
                    "format": "double"
                  },
                  "lon": {
                    "type": "number",
                    "format": "double"
                  },
                  "name": {
                    "type": "string",
                    "description": "Display name. Absent ⇒ derived from the profile (`rf-df-1`).",
                    "nullable": true
                  },
                  "profile": {
                    "type": "string",
                    "description": "Which built-in sensor profile this runs."
                  },
                  "provenance": {
                    "$ref": "#/components/schemas/Provenance"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the placed sensor, as the settings surface reads it back",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "A sensor, as the settings surface reads it back.",
                      "required": [
                        "id",
                        "name",
                        "profile",
                        "modality",
                        "bearing_only",
                        "lat",
                        "lon",
                        "height_m",
                        "max_range_m",
                        "fov_deg",
                        "boresight_deg",
                        "provenance"
                      ],
                      "properties": {
                        "bearing_only": {
                          "type": "boolean",
                          "description": "True when this sensor reports bearing without range, which changes what the\npicture can do with one of them alone."
                        },
                        "boresight_deg": {
                          "type": "number",
                          "format": "double"
                        },
                        "fov_deg": {
                          "type": "number",
                          "format": "double"
                        },
                        "height_m": {
                          "type": "number",
                          "format": "double",
                          "description": "**Height above ground, metres** — the emplacement's ENU `z`. Reported so a\nplacement can be read back and corrected without the number being lost on\nthe round trip; the simulator's sensor geometry ranges from it, so a mast\nat 6 m and one at ground level do not see the same thing."
                        },
                        "id": {
                          "type": "string",
                          "description": "The registry asset id — a sensor is an asset, so this addresses it everywhere."
                        },
                        "lat": {
                          "type": "number",
                          "format": "double"
                        },
                        "lon": {
                          "type": "number",
                          "format": "double"
                        },
                        "max_range_m": {
                          "type": "number",
                          "format": "double"
                        },
                        "modality": {
                          "type": "string",
                          "description": "`rf` · `radar` · `acoustic` · `eo` — decides the renderer and the obs shape."
                        },
                        "name": {
                          "type": "string"
                        },
                        "profile": {
                          "type": "string",
                          "description": "Profile id: `rf-df`, `ground-radar-360`, `acoustic-array`, `eo-turret`, …"
                        },
                        "provenance": {
                          "$ref": "#/components/schemas/Provenance"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no sensor profile with this name",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sensors/profiles": {
      "get": {
        "tags": [
          "sensors"
        ],
        "summary": "The sensor profiles that can be placed — the SIMULATED door's modality chips.",
        "operationId": "get_api_sensors_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/sensors/{id}": {
      "delete": {
        "tags": [
          "sensors"
        ],
        "summary": "Un-place a sensor.",
        "description": "**Guarded on `kind`.** A sensor is stored as an ordinary registry row, which is\nwhat makes it an asset like everything else — and it also means an id typed into\nthis route addresses *any* row, including a real aircraft. Without the check,\n`DELETE /api/sensors/<a-drone-id>` removes that drone from the registry, and\nbecause the friendly whitelist is derived from registry membership it drops off\nthe whitelist at the same time: our own aircraft would start being scored as an\nunknown contact. Invariant 10 — nothing may remove a real asset by side effect.",
        "operationId": "delete_api_sensors_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the sensor's registry asset id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the placement is removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "deleted"
                      ],
                      "properties": {
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no placed sensor with this id in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sensors/{sensor_id}/cue": {
      "post": {
        "tags": [
          "sensors"
        ],
        "summary": "`POST /api/sensors/{sensor_id}/cue` `{ \"track_id\": \"T-4\" }` — point it here.",
        "description": "The mount latches to the caller until released: automatic arbitration may not\ntake it back, however good its reason. `sensor_id` is the id observations\ncarry (`Observation.sensor.id`), which is what the picture's sensor slice is\nkeyed by — not the registry row's UUID.",
        "operationId": "post_api_sensors_sensor_id_cue",
        "parameters": [
          {
            "name": "sensor_id",
            "in": "path",
            "description": "the id observations carry, not the registry row's uuid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "track_id"
                ],
                "properties": {
                  "track_id": {
                    "type": "string",
                    "description": "The live track to point at."
                  },
                  "user": {
                    "type": "string",
                    "description": "Who is pointing it. Absent ⇒ `operator`; never blank, because an\nunattributed manual override is indistinguishable from a bug.",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "what the mount is doing after the call",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "What the mount is doing after the call — what actually happened, never what\nwas asked for.",
                      "required": [
                        "sensor_id",
                        "pointing"
                      ],
                      "properties": {
                        "pointing": {
                          "$ref": "#/components/schemas/SensorPointing"
                        },
                        "sensor_id": {
                          "type": "string"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the sensor is omnidirectional and cannot be pointed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no such sensor on the picture, or no such live track",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sensors/{sensor_id}/cue/release": {
      "post": {
        "tags": [
          "sensors"
        ],
        "summary": "`POST /api/sensors/{sensor_id}/cue/release` — give it back to automation,",
        "description": "which resumes on the very next tick.",
        "operationId": "post_api_sensors_sensor_id_cue_release",
        "parameters": [
          {
            "name": "sensor_id",
            "in": "path",
            "description": "the id observations carry, not the registry row's uuid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "what the mount is doing after the release",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "What the mount is doing after the call — what actually happened, never what\nwas asked for.",
                      "required": [
                        "sensor_id",
                        "pointing"
                      ],
                      "properties": {
                        "pointing": {
                          "$ref": "#/components/schemas/SensorPointing"
                        },
                        "sensor_id": {
                          "type": "string"
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "the sensor is omnidirectional and cannot be pointed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no such sensor on the picture",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/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/simulators": {
      "get": {
        "tags": [
          "simulation"
        ],
        "summary": "`GET /api/sim/simulators` — 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": "`POST /api/sim/start` — 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": {
                  "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": "`GET /api/sim/state` — 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": "`PUT /api/sim/state` — 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": "`POST /api/sim/stop` — 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/simulation/pause": {
      "post": {
        "tags": [
          "simulation"
        ],
        "summary": "Freeze / unfreeze the running simulation in place. Reversible and does",
        "description": "NOT finalize the run (that is `stop`), so the operator can hold the\npicture still, inspect it, then carry on from the same instant.",
        "operationId": "post_api_simulation_pause",
        "responses": {
          "200": {
            "description": "the world is held; running=false when nothing is running",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "running": true,
                  "paused": true
                }
              }
            }
          },
          "501": {
            "description": "built without the simulator feature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/simulation/resume": {
      "post": {
        "tags": [
          "simulation"
        ],
        "summary": "Resume a paused simulation from the instant it was held.",
        "operationId": "post_api_simulation_resume",
        "responses": {
          "200": {
            "description": "the world is stepping again; running=false when nothing is running",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "running": true,
                  "paused": false
                }
              }
            }
          },
          "501": {
            "description": "built without the simulator feature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/simulation/runs": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "`GET /api/simulation/runs` — a workspace's runs, newest-first.",
        "operationId": "get_api_simulation_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/simulation/runs/{id}": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "`GET /api/simulation/runs/{id}` — one run record.",
        "operationId": "get_api_simulation_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_simulation_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/simulation/runs/{id}/events": {
      "get": {
        "tags": [
          "runs"
        ],
        "summary": "`GET /api/simulation/runs/{id}/events` — the run's persisted event stream,",
        "description": "chronological. Drives replay (default `kind=track`) and overlays.",
        "operationId": "get_api_simulation_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/simulation/start": {
      "post": {
        "tags": [
          "simulation"
        ],
        "summary": "Start a scenario in the embedded simulator (legacy kinematic-world path).",
        "operationId": "post_api_simulation_start",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scenario_id"
                ],
                "properties": {
                  "region_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Optional region to run the simulation in. When present, the selected\nregion overrides the scenario's stored region so the simulated world is\nanchored where the operator's map is centered. Omit to use the scenario's\nown region.",
                    "nullable": true
                  },
                  "scenario_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "world_id": {
                    "type": "string",
                    "description": "Which registered world to run in\n(`docs/specs/2026-08-07-worlds/01-world-registry.md`). Omit for the\ndeployment default, which is what `DOME_WORLD_CONFIG` describes.",
                    "nullable": true
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "the run is open and the world is stepping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "running": true,
                  "scenario_id": "5f2b7c1e-0000-0000-0000-000000000000",
                  "run_id": "9d8a4c2f-0000-0000-0000-000000000000"
                }
              }
            }
          },
          "400": {
            "description": "the stored scenario spec is invalid, the named region does not exist, or the world refused to 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"
                    }
                  }
                }
              }
            }
          },
          "501": {
            "description": "built without the simulator feature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/simulation/status": {
      "get": {
        "tags": [
          "simulation"
        ],
        "summary": "Report the world's status, the active run, and what the binding turned away.",
        "operationId": "get_api_simulation_status",
        "responses": {
          "200": {
            "description": "the world provider's status plus run_id, binding and dropped_observations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "running": true,
                  "run_id": "9d8a4c2f-0000-0000-0000-000000000000",
                  "binding": null,
                  "dropped_observations": 0
                }
              }
            }
          },
          "501": {
            "description": "built without the simulator feature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/simulation/stop": {
      "post": {
        "tags": [
          "simulation"
        ],
        "summary": "Stop the running simulation and finalise its run as complete.",
        "operationId": "post_api_simulation_stop",
        "responses": {
          "200": {
            "description": "the world is stopped and the run is finalised",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "running": false
                }
              }
            }
          },
          "501": {
            "description": "built without the simulator feature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/strategies": {
      "get": {
        "tags": [
          "strategies"
        ],
        "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\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
            }
          }
        ],
        "responses": {
          "200": {
            "description": "the SSE stream: a snapshot event, then generation-stamped deltas until the client disconnects"
          }
        }
      }
    },
    "/api/threats": {
      "get": {
        "tags": [
          "threats"
        ],
        "summary": "`GET /api/threats` — the current live threats (one per hostile object), each",
        "description": "with its rolled-up alert timeline and kill-chain stage. Carries the authored\ncandidate plans too (the same snapshot the console's ONE poll feeds on, so\nthe 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": "`POST /api/threats/designate` — 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# SECURITY: the attribution here is NOT trustworthy yet\n\n`by` is **supplied by the caller and never verified**, and this whole `/api`\nscope is unauthenticated (the walking skeleton runs tokenless in dev — see\n`router.rs`). So the audit trail records *a claim about who decided*, not a\nproven identity: anyone who can reach this port can declare anything hostile,\nunder any name.\n\nThat is a **system-wide** posture, not a hole unique to this route —\n`PUT /api/engagement/mode`, which sets the weapons-release autonomy gate, is\nequally open. Gating only this endpoint would buy no real safety while creating\na false impression that designation is authenticated. The fix is the `/v1`\nauthenticated scope (scaffolded, childless) growing to cover the mutating\nsurface, tracked separately. Until then: **do not treat `by` as identity**, and\ndo not expose this port outside a trusted network.",
        "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": "`GET /api/threats/designations` — 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": "`GET /api/thresholds`: 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": {
                      "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
                        }
                      }
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "settings"
        ],
        "summary": "`PUT /api/thresholds` — 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": {
                      "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
                        }
                      }
                    },
                    "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/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/world": {
      "get": {
        "tags": [
          "picture"
        ],
        "summary": "`GET /api/world` — the current common operating picture (every live track).",
        "operationId": "get_api_world",
        "responses": {
          "200": {
            "description": "the authoritative live track set the runtime maintains",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorldState"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/zones": {
      "get": {
        "tags": [
          "zones"
        ],
        "summary": "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": {
                      "type": "object",
                      "description": "Response for a successful zone delete.",
                      "required": [
                        "deleted"
                      ],
                      "properties": {
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    },
                    "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"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "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": {
        "oneOf": [
          {
            "type": "object",
            "description": "The missing intelligence action: change what we believe something IS.",
            "required": [
              "track_id",
              "affiliation",
              "why",
              "action"
            ],
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "designate"
                ]
              },
              "affiliation": {
                "$ref": "#/components/schemas/Affiliation"
              },
              "track_id": {
                "$ref": "#/components/schemas/ObjectId"
              },
              "why": {
                "type": "string",
                "description": "Why the disposition changed, in one line (e.g. the firing rule's id)."
              }
            }
          },
          {
            "type": "object",
            "description": "Commit an asset against a target (today's [`AssetInstruction`], same meaning).",
            "required": [
              "asset_id",
              "target",
              "verb",
              "action"
            ],
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "assign"
                ]
              },
              "asset_id": {
                "type": "string"
              },
              "target": {
                "$ref": "#/components/schemas/ObjectId"
              },
              "verb": {
                "$ref": "#/components/schemas/AssignVerb"
              }
            }
          },
          {
            "type": "object",
            "description": "Watch an area/point/track without committing to an engagement.",
            "required": [
              "at",
              "action"
            ],
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "surveil"
                ]
              },
              "asset_id": {
                "type": "string",
                "nullable": true
              },
              "at": {
                "$ref": "#/components/schemas/SurveilTarget"
              }
            }
          },
          {
            "type": "object",
            "description": "Point an existing sensor to raise confidence before committing anything.",
            "required": [
              "sensor_id",
              "target",
              "action"
            ],
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "cue_sensor"
                ]
              },
              "sensor_id": {
                "type": "string"
              },
              "target": {
                "$ref": "#/components/schemas/ObjectId"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "asset_id",
              "action"
            ],
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "hold"
                ]
              },
              "asset_id": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "asset_id",
              "action"
            ],
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "rtb"
                ]
              },
              "asset_id": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "description": "Tell the operator something, without doing anything.",
            "required": [
              "severity",
              "reason",
              "action"
            ],
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "alert"
                ]
              },
              "reason": {
                "type": "string"
              },
              "severity": {
                "$ref": "#/components/schemas/AlertSeverity"
              }
            }
          }
        ],
        "description": "The ONE action vocabulary, emitted by every tier. Internally tagged on `action`.",
        "discriminator": {
          "propertyName": "action"
        }
      },
      "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
          }
        }
      },
      "ActionVerb": {
        "type": "string",
        "description": "The verb an action performs, as the operator sees it. This is the vocabulary\n[`EngagementPolicy::auto_approve`] is written in — the replacement for\n`Authority::{Reflexive, Proposed, Kinetic}`, which encoded the same policy as\nan enum variant an operator could neither see nor change.",
        "enum": [
          "designate",
          "intercept",
          "follow",
          "surveil",
          "move_to",
          "jam",
          "spoof",
          "cue_sensor",
          "alert",
          "hold",
          "rtb"
        ]
      },
      "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"
        ]
      },
      "AlertLevel": {
        "type": "object",
        "description": "One rung of a profile's alert ladder.\n\n`level` is the ordinal used for comparison *within this profile only*; `name`\nis what the operator sees (`RED`, `Bravo`, `MARSEC 2`) and `meaning` is the\ndoctrinal gloss shown under it.",
        "required": [
          "level",
          "name",
          "meaning"
        ],
        "properties": {
          "level": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "meaning": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "AlertSeverity": {
        "type": "string",
        "description": "Severity, ordered so `>` means \"more severe\" (drives escalation).",
        "enum": [
          "medium",
          "high",
          "critical"
        ]
      },
      "AlertState": {
        "type": "object",
        "description": "**What the world is doing.** A level within a named profile.\n\nOrdering is defined **only within a profile**. `partial_cmp` across two\nprofiles is `None`, deliberately: asking whether MARSEC 2 outranks FPCON Bravo\nis a category error, and a type that answered it would make the error\ninvisible.",
        "required": [
          "profile_id",
          "level"
        ],
        "properties": {
          "level": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "profile_id": {
            "type": "string"
          }
        }
      },
      "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."
          }
        }
      },
      "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."
      },
      "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."
          }
        }
      },
      "Approver": {
        "type": "string",
        "description": "Who may release an effect that is gated.",
        "enum": [
          "none",
          "operator",
          "supervisor_plus_one"
        ]
      },
      "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::Assignment`].",
        "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
              },
              "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.",
                "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."
              }
            }
          }
        ],
        "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::Assignment`] layered on top of the asset. Concrete\nfields (no bare `Value` in domain code); a flattened `extra` carries\nforward-compat metadata at the boundary."
      },
      "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"
          },
          "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."
          },
          "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
          }
        }
      },
      "AssignVerb": {
        "type": "string",
        "description": "The commit verb of an [`Action::Assign`] — the target-bearing instructions that\ncommit an asset against an object. Mirrors the [`AssetInstruction`] verbs that\ncarry a target; the non-committing verbs are their own [`Action`] variants.",
        "enum": [
          "intercept",
          "follow",
          "jam",
          "spoof"
        ]
      },
      "Assignment": {
        "type": "object",
        "description": "A dynamic operational assignment layered on a fixed [`crate::asset::Asset`].",
        "required": [
          "id",
          "workspace_id",
          "asset_id",
          "affiliation",
          "active",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "active": {
            "type": "boolean",
            "description": "Whether this is the currently-in-force assignment for the asset. Superseded\nassignments 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 assignment applies to."
          },
          "created_at": {
            "type": "string",
            "description": "Audit timestamps."
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique assignment 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 assignment belongs to."
          }
        }
      },
      "AssignmentStatus": {
        "type": "string",
        "description": "The lifecycle of one assignment (its execution state on the asset).",
        "enum": [
          "proposed",
          "approved",
          "en_route",
          "on_station",
          "complete",
          "aborted"
        ]
      },
      "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 much of a stage runs without a human.\n\nOrdered: `Manual < OnApproval < Automatic`. The ordering is what makes a\nceiling expressible as a `min`.",
        "enum": [
          "manual",
          "on_approval",
          "automatic"
        ]
      },
      "AutomationVector": {
        "type": "object",
        "description": "One automation level per OODA stage — the thing a posture cell actually sets.",
        "required": [
          "observe",
          "orient",
          "decide",
          "act"
        ],
        "properties": {
          "act": {
            "$ref": "#/components/schemas/Automation"
          },
          "decide": {
            "$ref": "#/components/schemas/Automation"
          },
          "observe": {
            "$ref": "#/components/schemas/Automation"
          },
          "orient": {
            "$ref": "#/components/schemas/Automation"
          }
        }
      },
      "AutonomyMode": {
        "type": "string",
        "description": "The global autonomy ceiling — the ONE authority gate (`PUT /api/engagement/mode`).",
        "enum": [
          "manual",
          "semi_auto",
          "auto",
          "full_auto"
        ]
      },
      "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"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "bound"
            ],
            "properties": {
              "bound": {
                "type": "string",
                "enum": [
                  "narrowing_only"
                ]
              }
            }
          }
        ],
        "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": [
          {
            "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.)."
              },
              "dimensions": {
                "$ref": "#/components/schemas/DimensionsSpec"
              },
              "domain": {
                "$ref": "#/components/schemas/AssetDomain"
              },
              "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."
              },
              "signature": {
                "$ref": "#/components/schemas/SignatureSpec"
              }
            }
          }
        ],
        "description": "Detailed threat specifications."
      },
      "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": {
              "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"
                ]
              },
              "loop_route": {
                "type": "boolean",
                "description": "Repeat the route rather than stopping at the last point."
              },
              "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",
          "jam",
          "spoof",
          "designate"
        ]
      },
      "CommandVerbClass": {
        "type": "string",
        "description": "How a verb is governed. `Tasking` verbs pass through the decision loop's ROE gate;\n`Lifecycle` and `Navigation` are direct control of a platform that is already ours.",
        "enum": [
          "lifecycle",
          "navigation",
          "tasking"
        ]
      },
      "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"
          }
        }
      },
      "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",
          "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"
          }
        }
      },
      "ControlStatus": {
        "type": "string",
        "description": "**What we are permitted to do.** Generalised off weapons (`04 §1.1`), so the\nsame word carries jamming a drone, committing an interceptor, and committing a\nsearch drone to a sector alike.\n\nThis is `mission::Posture` renamed. The wire strings `{hold, tight, free}` are\n**frozen** — only the Rust name changes.",
        "enum": [
          "hold",
          "tight",
          "free"
        ]
      },
      "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
          }
        }
      },
      "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"
          },
          "engagement": {
            "$ref": "#/components/schemas/EngagementPolicy"
          },
          "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
          }
        }
      },
      "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
          }
        }
      },
      "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"
        ]
      },
      "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/ActionVerb"
              }
            }
          },
          {
            "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/GatedEffect"
                }
              },
              "scope": {
                "$ref": "#/components/schemas/Scope"
              }
            }
          },
          {
            "type": "object",
            "description": "A readiness order. **Ratchet only** — see [`PostureProposal`].",
            "required": [
              "effect"
            ],
            "properties": {
              "alert": {
                "type": "integer",
                "format": "int32",
                "nullable": true,
                "minimum": 0
              },
              "auto": {
                "type": "boolean",
                "description": "Whether the escalation is pre-authorised to happen without a click."
              },
              "control": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ControlStatus"
                  }
                ],
                "nullable": true
              },
              "effect": {
                "type": "string",
                "enum": [
                  "propose_posture"
                ]
              }
            }
          }
        ],
        "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",
          "auto_approve",
          "require_approval_for_engagement",
          "max_effectors_per_target"
        ],
        "properties": {
          "assign_below_tti_s": {
            "type": "number",
            "format": "double",
            "description": "Assign a defender when time-to-impact drops below this."
          },
          "auto_approve": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionVerb"
            },
            "description": "Which verbs may execute without approval. **Data the operator reads and\nedits**, replacing the `Follow ⇒ Reflexive ⇒ auto under SEMI` mapping that\nlived in the type system."
          },
          "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
          },
          "require_approval_for_engagement": {
            "type": "boolean",
            "description": "Kinetic engagement always requires a human unless explicitly cleared here.\nOutranks `auto_approve`: listing `Intercept` there does nothing while this\nis set."
          }
        }
      },
      "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"
          }
        }
      },
      "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"
          }
        }
      },
      "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",
          "posture"
        ],
        "properties": {
          "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."
          },
          "gate": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Gate"
              }
            ],
            "nullable": true
          },
          "posture": {
            "$ref": "#/components/schemas/Posture"
          },
          "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",
          "posture_alert",
          "posture_control",
          "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"
        ]
      },
      "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"
        ]
      },
      "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": [
                  "auto"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Runs **unless** stopped inside the veto window. Human on the loop.",
            "required": [
              "secs",
              "gate"
            ],
            "properties": {
              "gate": {
                "type": "string",
                "enum": [
                  "notify"
                ]
              },
              "secs": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              }
            }
          },
          {
            "type": "object",
            "required": [
              "gate"
            ],
            "properties": {
              "gate": {
                "type": "string",
                "enum": [
                  "confirm"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "gate"
            ],
            "properties": {
              "gate": {
                "type": "string",
                "enum": [
                  "two_person"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "gate"
            ],
            "properties": {
              "gate": {
                "type": "string",
                "enum": [
                  "withheld"
                ]
              }
            }
          }
        ],
        "description": "Five rungs, replacing `ApprovalVerdict.needs_approval: bool`.\n\nMaps onto DoDD 3000.09's in-the-loop / on-the-loop / out-of-the-loop.\n\n**NOTIFY is the rung we do not have, and it is the one that matters.** For a\nfast inbound at 60 seconds, asking is too slow and not asking removes the human\nentirely; *\"firing in 5 — stop me\"* is the correct answer and a bool cannot say\nit.\n\n**WITHHELD is the other missing one.** It is not \"off\". Off says *we chose not\nto*; withheld says *this authority is not ours to exercise*, which is the honest\nstate of kinetic engagement at a commercial site.",
        "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"
          }
        }
      },
      "GatedEffect": {
        "type": "string",
        "description": "What the release table has a row for.\n\nWider than [`ActionVerb`] in two directions, both forced by the worked\nscenarios:\n\n* **Designation splits by affiliation.** `designate friend` is AUTO and\n`designate hostile` may be WITHHELD *in the same table* — declaring something\nhostile is a state act while declaring something friendly is a restriction we\nplace on ourselves. One `Designate` row cannot hold both.\n* **Some effects have no weapon in them at all.** The `emergency-response`\nprofile gates *payload release near people* and must never gate *vacate for\nmanned aircraft*. Neither is an [`ActionVerb`], and pretending they were would\nbe the naming error `04 §1.1` warns about.",
        "enum": [
          "alert",
          "cue_sensor",
          "move_to",
          "surveil",
          "hold",
          "rtb",
          "follow",
          "jam",
          "spoof",
          "intercept",
          "designate_friend",
          "designate_suspect",
          "designate_hostile",
          "roster_enrolment",
          "assign_search_sector",
          "vacate_for_manned_aircraft",
          "climb_above_smoke",
          "payload_release_near_people"
        ]
      },
      "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": "Guidance 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": {
          "eta_s": {
            "type": "number",
            "format": "double",
            "description": "Estimated time to reach/intercept (s).",
            "nullable": true
          },
          "intercept_enu": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Enu"
              }
            ],
            "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."
          }
        }
      },
      "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"
            }
          }
        }
      },
      "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_built"
        ]
      },
      "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"
            ],
            "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"
              },
              "enabled": {
                "type": "boolean"
              },
              "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."
      },
      "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"
          }
        }
      },
      "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"
        ]
      },
      "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": {
        "allOf": [
          {
            "type": "object"
          },
          {
            "type": "object",
            "required": [
              "can_discover",
              "effective"
            ],
            "properties": {
              "can_discover": {
                "type": "boolean",
                "description": "**Can this adapter discover at all?** A KLV feed carries video and never says\n*I am MAST-EO-1*, so `offer` on it would be inert. Surfaced so the console can\nsay why the control is disabled rather than leaving it looking broken."
              },
              "effective": {
                "type": "string",
                "description": "What is in force once the master switch is applied. `off` when the switch is."
              },
              "ingest": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/LinkIngest"
                  }
                ],
                "nullable": true
              }
            }
          }
        ],
        "description": "One declared link, plus whether it can actually honour what it claims."
      },
      "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"
        ]
      },
      "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`."
          }
        }
      },
      "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"
        ]
      },
      "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/Posture"
          },
          "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"
          }
        }
      },
      "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."
      },
      "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."
          },
          "engagement_mode": {
            "$ref": "#/components/schemas/AutonomyMode"
          },
          "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."
          },
          "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",
          "autonomy_mode",
          "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"
            }
          },
          "autonomy_mode": {
            "$ref": "#/components/schemas/AutonomyMode"
          },
          "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/Posture"
          },
          "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/AssignmentStatus"
          }
        }
      },
      "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"
        ]
      },
      "Posture": {
        "type": "object",
        "description": "The two axes, issued together — because neither is meaningful alone. In the\nair-defence profile this is one `AIRDEFWARN` message.",
        "required": [
          "alert",
          "control"
        ],
        "properties": {
          "alert": {
            "$ref": "#/components/schemas/AlertState"
          },
          "control": {
            "$ref": "#/components/schemas/ControlStatus"
          }
        }
      },
      "PostureCell": {
        "type": "object",
        "description": "One cell of the alert × control matrix.\n\n**Four of nine cells in the air-defence profile are unreachable on purpose.** A\nmatrix whose every cell is reachable has not been thought about: *cleared to\nfire at a threat nobody expects* is not a posture, it is a mistake with a name.",
        "required": [
          "alert_level",
          "control",
          "name",
          "summary",
          "reachable",
          "automation",
          "issue_requires"
        ],
        "properties": {
          "alert_level": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "automation": {
            "$ref": "#/components/schemas/AutomationVector"
          },
          "control": {
            "$ref": "#/components/schemas/ControlStatus"
          },
          "issue_requires": {
            "$ref": "#/components/schemas/Approver"
          },
          "name": {
            "type": "string",
            "description": "The cell's own name — `SUPERVISED`, `RED / HOLD`, `SATURATION`. Empty when\nthe cell is unreachable."
          },
          "reachable": {
            "type": "boolean",
            "description": "`false` for a cell doctrine does not permit; `summary` then says why not."
          },
          "summary": {
            "type": "string",
            "description": "One sentence on what this cell does."
          }
        }
      },
      "PostureMatrix": {
        "type": "object",
        "description": "The alert × control grid, generated from a [`PostureProfile`].\n\nSwitching a deployment's profile regenerates this with the new ladder; gates,\nfloors and grants are unaffected, because they hang off the *effect*, not off\nthe cell.",
        "required": [
          "profile",
          "cells"
        ],
        "properties": {
          "cells": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PostureCell"
            },
            "description": "Row-major: for each alert level, one cell per [`ControlStatus`]."
          },
          "profile": {
            "$ref": "#/components/schemas/PostureProfile"
          }
        }
      },
      "PostureProfile": {
        "type": "object",
        "description": "A deployment's alert vocabulary. The matrix, the labels and the number of rows\nare generated from this, so the engine, the gate ladder and the grant mechanism\nare identical across domains and **only the vocabulary changes**.\n\nOne active profile per deployment in v1. Doctrine supports several at once and\nthe honest multi-profile rule is *the most restrictive gate across all active\nprofiles wins*; that roughly doubles the matrix surface for a case we do not\nhave yet, so it is deferred. [`AlertState`] carries its `profile_id` from the\nstart so nothing here has to be unpicked to add it later.",
        "required": [
          "id",
          "label",
          "ladder"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "ladder": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AlertLevel"
            },
            "description": "Ascending by `level`. Index 0 is the calmest rung."
          }
        }
      },
      "PreDelegation": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "when"
            ],
            "properties": {
              "when": {
                "type": "string",
                "enum": [
                  "always"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Pre-delegated once the alert state reaches `level` on its own profile.",
            "required": [
              "level",
              "when"
            ],
            "properties": {
              "level": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "when": {
                "type": "string",
                "enum": [
                  "alert_at_least"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Pre-delegated once control status reaches `control`.",
            "required": [
              "control",
              "when"
            ],
            "properties": {
              "control": {
                "$ref": "#/components/schemas/ControlStatus"
              },
              "when": {
                "type": "string",
                "enum": [
                  "control_at_least"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Pre-delegated at `level`, and only with `types` independent sensor types\nagreeing. Corroboration is the whole basis of trust in the picture, so a\npre-delegation that ignored it would be buying authority with repetition.",
            "required": [
              "level",
              "types",
              "when"
            ],
            "properties": {
              "level": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "types": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "when": {
                "type": "string",
                "enum": [
                  "alert_and_corroboration"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Pre-delegated at `control`, and only with `types` independent sensor\ntypes agreeing.\n\nThis is what weapons free actually means. FREE is *engage anything not\npositively identified as friendly* — and positively identifying friendlies\nis exactly what a degraded picture stops you doing. So the release is\nconditioned on the picture that makes it safe, and when GNSS is contested\nand one sensor type is left, the engagement falls back to a human without\nanyone deciding to tighten. Nobody has to notice.",
            "required": [
              "control",
              "types",
              "when"
            ],
            "properties": {
              "control": {
                "$ref": "#/components/schemas/ControlStatus"
              },
              "types": {
                "type": "integer",
                "format": "int32",
                "minimum": 0
              },
              "when": {
                "type": "string",
                "enum": [
                  "control_and_corroboration"
                ]
              }
            }
          },
          {
            "type": "object",
            "required": [
              "when"
            ],
            "properties": {
              "when": {
                "type": "string",
                "enum": [
                  "requires_grant"
                ]
              }
            }
          }
        ],
        "description": "When an effect is pre-delegated without a written grant — the *\"PRE-DELEGATED\nWHEN\"* column. Deliberately a tiny closed set: anything richer belongs in a\ngrant, where it acquires an issuer and an expiry.",
        "discriminator": {
          "propertyName": "when"
        }
      },
      "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"
        ]
      },
      "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"
        ]
      },
      "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."
      },
      "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"
          }
        }
      },
      "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"
          }
        }
      },
      "ReleaseRow": {
        "type": "object",
        "description": "One row of the release table.",
        "required": [
          "effect",
          "rung",
          "default_gate",
          "pre_delegated",
          "pre_delegated_gate",
          "who_may_approve"
        ],
        "properties": {
          "default_gate": {
            "$ref": "#/components/schemas/Gate"
          },
          "effect": {
            "$ref": "#/components/schemas/GatedEffect"
          },
          "needs_control": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ControlStatus"
              }
            ],
            "nullable": true
          },
          "pre_delegated": {
            "$ref": "#/components/schemas/PreDelegation"
          },
          "pre_delegated_gate": {
            "$ref": "#/components/schemas/Gate"
          },
          "rung": {
            "$ref": "#/components/schemas/Rung"
          },
          "who_may_approve": {
            "$ref": "#/components/schemas/Approver"
          }
        }
      },
      "ReleaseTable": {
        "type": "object",
        "description": "The configured values — rows are effects, grouped by how hard they hit.\n\nThis **replaces** `EngagementPolicy.auto_approve: Vec<ActionVerb>`. Two\nproperties a checkbox list cannot have:\n\n* **Every row shows the gate in force *now*,** computed from the live posture\nand any active grant — not the switch position.\n* **The table is a property of the deployment.** [`ReleaseTable::default`] is\nthe air-defence table, where the control status releases the engagement\nbecause that is what weapons hold / tight / free has always meant.\n[`ReleaseTable::civil`] is a commercial site, which has no kinetic authority\nat any posture and cannot acquire one by moving a dial.\n\nGetting that split wrong is how a commander's instrument becomes decorative:\nput the civil answer in the default table and *no* posture releases an\nengagement anywhere, which is not caution, it is a defence system that cannot\ndefend.",
        "required": [
          "rows"
        ],
        "properties": {
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReleaseRow"
            }
          }
        }
      },
      "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": "One 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/Posture"
          }
        }
      },
      "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.",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "alt_m": {
            "type": "number",
            "format": "float",
            "description": "Altitude above launch. `None` ⇒ hold the current working altitude.",
            "nullable": true
          },
          "lat": {
            "type": "number",
            "format": "double"
          },
          "lon": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "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
          }
        }
      },
      "Rung": {
        "type": "string",
        "description": "The graduated-response rung an effect sits on: **warn → deny → disable →\ndestroy** is how doctrine thinks, and *\"what is the softest thing that stops\nthis\"* is the question a commander asks first.",
        "enum": [
          "warn",
          "position",
          "commit",
          "deny",
          "destroy",
          "identity"
        ]
      },
      "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 `dome_types::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"
          }
        }
      },
      "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"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "owner"
            ],
            "properties": {
              "owner": {
                "type": "string",
                "enum": [
                  "posture"
                ]
              }
            }
          }
        ],
        "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"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind",
              "value"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "verbs"
                ]
              },
              "value": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ActionVerb"
                },
                "description": "`authority.auto_approve` — the one list-valued setting, and the one with a\nnarrowing-only rule."
              }
            }
          }
        ],
        "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"
        }
      },
      "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."
          }
        }
      },
      "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"
        ]
      },
      "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"
        }
      },
      "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"
        ]
      },
      "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"
        }
      },
      "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/AssignmentStatus"
          },
          "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"
        ]
      },
      "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"
          }
        }
      },
      "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_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"
        ]
      },
      "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."
          }
        }
      },
      "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."
          }
        }
      },
      "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."
      }
    }
  },
  "tags": [
    {
      "name": "health",
      "description": "Is the server up, and is the pipeline ticking."
    },
    {
      "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": "ingest",
      "description": "Typed observations into the fusion pipeline."
    },
    {
      "name": "events",
      "description": "The persisted event log, queryable by kind, time, track and run."
    },
    {
      "name": "threats",
      "description": "The threat board and operator designation."
    },
    {
      "name": "engagement",
      "description": "The autonomy gate and candidate plan approval."
    },
    {
      "name": "tasking",
      "description": "Task the nearest free asset from a point or area on the map."
    },
    {
      "name": "assets",
      "description": "The asset registry: what you have declared."
    },
    {
      "name": "fleet",
      "description": "Live asset state and the one command path. An asset advertises its own capabilities."
    },
    {
      "name": "links",
      "description": "Transport links and what they discover."
    },
    {
      "name": "sensors",
      "description": "Emplaced sensors, their profiles, and cueing."
    },
    {
      "name": "assignments",
      "description": "Friendly assignments: which platforms are declared ours."
    },
    {
      "name": "zones",
      "description": "Tagged, categorised areas: geofence, keep-out, jam, free-fire, ISR priority."
    },
    {
      "name": "regions",
      "description": "The defended places a workspace operates in."
    },
    {
      "name": "missions",
      "description": "Stored missions."
    },
    {
      "name": "plans",
      "description": "Stored plan candidates and their approval into missions."
    },
    {
      "name": "doctrine",
      "description": "The exportable doctrine document: rules, thresholds, posture, profiles."
    },
    {
      "name": "rules",
      "description": "The rule set the decision loop runs."
    },
    {
      "name": "settings",
      "description": "Thresholds, decision config, posture, influence, planning profiles."
    },
    {
      "name": "strategies",
      "description": "The compiled-in behaviour and solver strategy catalogues."
    },
    {
      "name": "catalog",
      "description": "Product catalogue entries that specs are drawn from."
    },
    {
      "name": "scenarios",
      "description": "Simulation scenarios: where a world runs and both its sides."
    },
    {
      "name": "environments",
      "description": "The environments a workspace holds."
    },
    {
      "name": "simulation",
      "description": "Start, pause and stop simulation, and the workspace sim state."
    },
    {
      "name": "runs",
      "description": "Recorded simulation runs and their event logs."
    },
    {
      "name": "integrations",
      "description": "The protocols this deployment speaks, and their gates."
    },
    {
      "name": "discovery",
      "description": "The discovery switch."
    },
    {
      "name": "products",
      "description": "The product catalogue the add screen is made of."
    },
    {
      "name": "manifest",
      "description": "The whole deployment as one committable document."
    },
    {
      "name": "secrets",
      "description": "Write-only secret store. There is deliberately no read of a value."
    },
    {
      "name": "meta",
      "description": "The API describing itself."
    }
  ]
}
