{
  "openapi": "3.1.0",
  "info": {
    "title": "ARCADIA HORIZON — shipboard control-plane API",
    "version": "0.1.0",
    "description": "The on-board component's API: detections, alerts, configuration and administration over REST, and events pushed to subscribers by webhook (or read from the on-board buffer by sequence number). Everything here is available on the shipboard deployment — the ship has no dependency on shore to raise or deliver an alert — and API access carries no separate licence. Payloads carry no identity or biometric attribute: the only person-related field is an opaque Re-ID track id, and it can be switched off.\n\nThe browser prototype implements the event side of this document (catalogue, envelope, webhook delivery with store-and-forward, identity modes) and exposes the policy configuration it describes; the REST paths specify the edge component."
  },
  "servers": [
    {
      "url": "https://{ship}.arcadia.local/v1",
      "variables": {
        "ship": {
          "default": "arcadia-horizon"
        }
      },
      "description": "Shipboard (edge). Reachable on the ship LAN; the same API, same licence, with or without a shore link."
    }
  ],
  "tags": [
    {
      "name": "detections"
    },
    {
      "name": "alerts"
    },
    {
      "name": "events"
    },
    {
      "name": "config"
    },
    {
      "name": "admin"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Liveness, engine, kernel version, cameras enrolled",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "description": ""
                    },
                    "engine": {
                      "type": "string",
                      "description": ""
                    },
                    "version": {
                      "type": "string",
                      "description": ""
                    },
                    "cameras": {
                      "type": "integer",
                      "description": ""
                    },
                    "linkUp": {
                      "type": "boolean",
                      "description": ""
                    },
                    "queued": {
                      "type": "integer",
                      "description": ""
                    }
                  },
                  "description": "",
                  "additionalProperties": false
                }
              }
            }
          }
        }
      }
    },
    "/cameras": {
      "get": {
        "tags": [
          "detections"
        ],
        "summary": "Cameras enrolled",
        "responses": {
          "200": {
            "description": "All cameras",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Camera"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/cameras/{id}/flag": {
      "get": {
        "tags": [
          "detections"
        ],
        "summary": "Current flag and metrics for one camera",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Camera id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Flag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "flag": {
                      "type": "string",
                      "enum": [
                        "NORMAL",
                        "BUFFET",
                        "SHOPPING",
                        "CROWD",
                        "DOOR",
                        "MOB",
                        "IR_CONTACT",
                        "OBJECT",
                        "OCCUPIED",
                        "CREW",
                        "TRAPPED",
                        "QUEUE",
                        "BOTTLENECK",
                        "SPILL",
                        "SHELF_GAP",
                        "OBSTRUCTION",
                        "ALTERCATION",
                        "FIRE",
                        "SMOKE",
                        "FALL",
                        "PPE"
                      ],
                      "description": ""
                    },
                    "raw": {
                      "type": "string",
                      "enum": [
                        "NORMAL",
                        "BUFFET",
                        "SHOPPING",
                        "CROWD",
                        "DOOR",
                        "MOB",
                        "IR_CONTACT",
                        "OBJECT",
                        "OCCUPIED",
                        "CREW",
                        "TRAPPED",
                        "QUEUE",
                        "BOTTLENECK",
                        "SPILL",
                        "SHELF_GAP",
                        "OBSTRUCTION",
                        "ALTERCATION",
                        "FIRE",
                        "SMOKE",
                        "FALL",
                        "PPE"
                      ],
                      "description": ""
                    },
                    "confidence": {
                      "type": "number",
                      "description": ""
                    },
                    "detail": {
                      "type": "string",
                      "description": ""
                    },
                    "metrics": {
                      "type": "object",
                      "properties": {},
                      "description": "Kernel metrics, frame fractions",
                      "additionalProperties": false
                    },
                    "analysedAt": {
                      "type": "string",
                      "description": "",
                      "format": "date-time"
                    }
                  },
                  "description": "",
                  "additionalProperties": false
                }
              }
            }
          }
        }
      }
    },
    "/cameras/{id}/frame": {
      "get": {
        "tags": [
          "detections"
        ],
        "summary": "Latest analysed frame (clean, 96×54) — or a redacted one when privacy mode is on",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Camera id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG",
            "content": {
              "image/png": {}
            }
          }
        }
      }
    },
    "/detections": {
      "get": {
        "tags": [
          "detections"
        ],
        "summary": "Flag transitions",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "description": "Sequence number to start after",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "camera",
            "in": "query",
            "description": "Filter by camera id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "flag",
            "in": "query",
            "description": "Filter by flag",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transitions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Event_detection_flag"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/alerts": {
      "get": {
        "tags": [
          "alerts"
        ],
        "summary": "Open and recent alerts",
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "description": "open | closed | all",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Alerts (alert.* events) with their current state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Event"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/alerts/{id}/ack": {
      "post": {
        "tags": [
          "alerts"
        ],
        "summary": "Acknowledge an alert",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Acknowledged"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "events"
        ],
        "summary": "Read the on-board event buffer by sequence number (the pull alternative to webhooks; survives link outages)",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "description": "Return events with seq greater than this",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "Filter by catalogue type",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "",
            "schema": {
              "type": "integer",
              "default": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Events in order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Event"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/events/catalogue": {
      "get": {
        "tags": [
          "events"
        ],
        "summary": "The event catalogue with schemas (this document's components)",
        "responses": {
          "200": {
            "description": "Catalogue"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "events"
        ],
        "summary": "Subscriptions",
        "responses": {
          "200": {
            "description": "Subscriptions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Webhook"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "events"
        ],
        "summary": "Subscribe a URL to event types",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Webhook"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "tags": [
          "events"
        ],
        "summary": "Unsubscribe",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          }
        }
      }
    },
    "/config/policies": {
      "get": {
        "tags": [
          "config"
        ],
        "summary": "Decision policies (dispatch, station-keeping, thermal, object, trapped) as editable constants",
        "responses": {
          "200": {
            "description": "Policies",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Policies"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "config"
        ],
        "summary": "Replace the policy constants (audited)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Policies"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Applied"
          }
        }
      }
    },
    "/config/privacy": {
      "get": {
        "tags": [
          "config"
        ],
        "summary": "Identity mode, frame redaction, retention",
        "responses": {
          "200": {
            "description": "Privacy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Privacy"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "config"
        ],
        "summary": "Change privacy settings (audited)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Privacy"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Applied"
          }
        }
      }
    },
    "/admin/audit": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Audit log: configuration changes, acknowledgements, exports, logins",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "description": "",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Audit entries"
          }
        }
      }
    },
    "/admin/users": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Operators and roles (operator / auditor / admin)",
        "responses": {
          "200": {
            "description": "Users"
          }
        }
      }
    },
    "/admin/models": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Model governance: kernel and model versions, thresholds, last change, scorecard summary",
        "responses": {
          "200": {
            "description": "Models"
          }
        }
      }
    }
  },
  "webhooks": {
    "detection.flag": {
      "post": {
        "summary": "Camera flag changed",
        "description": "A camera's context flag changed — raised or cleared. Emitted per camera, from its own pixels, for every transition that involves a non-NORMAL flag. Default severity 1.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_detection_flag"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.mob": {
      "post": {
        "summary": "Man overboard confirmed",
        "description": "The dispatch policy confirmed a person in the water and launched the rescue drone. `sensor` says which watch raised it: the optical rail cameras or the thermal ring. Default severity 3.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_mob"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.ir_contact": {
      "post": {
        "summary": "Thermal first contact (datum)",
        "description": "The thermal ring saw a human heat signature in the water for the first time in this incident. The position is the sensor's own fix — the blob centroid inverted through the camera geometry — remembered as a sea-fixed grid cell (the SAR datum). Default severity 3.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_ir_contact"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.object": {
      "post": {
        "summary": "Object overboard",
        "description": "Something that is not a person went into the sea. Several decks' cameras ranged it by depression angle and the estimates were fused in the sea frame; the catalogue guessed what it is from size and colour. Default severity 2.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.door": {
      "post": {
        "summary": "Restricted door open",
        "description": "A camera saw a crew-only door open (magenta door-ajar beacon in its pixels). Access control says whether a badge opened it; Re-ID says who is at it. Default severity 2.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_door"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.trapped": {
      "post": {
        "summary": "Person trapped in a restricted space",
        "description": "Fusion: the camera inside a crew-only space sees a person, access control says the door is shut and locked and no badge opened it. Emitted when raised and again on each escalation (level rises with time and hazard). Default severity 3.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_trapped"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.medical": {
      "post": {
        "summary": "Medical escalation",
        "description": "A person has gone down: the camera read no motion for the policy's still-time, or the crew found them down on opening the door. The stretcher team is called. Default severity 3.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_medical"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.fire": {
      "post": {
        "summary": "Fire / smoke",
        "description": "An engine-room camera reads flame (saturated red-orange pixels whose mask changes between frames — a prop does not flicker) or smoke (a grey veil lighter than the learned room). The fire team is sent and the main vertical zone's fire doors are ordered shut. Default severity 3.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_fire"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.fall": {
      "post": {
        "summary": "Person down",
        "description": "A camera reads a person lying on the floor — a head with a torso beside it at the same row and floor below — confirmed over consecutive frames. A medical runner is sent. Known blind spot: blue and pale clothing (scorecard limitations). Default severity 3.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_fall"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "alert.generic": {
      "post": {
        "summary": "Operational alert",
        "description": "An operational class raised from the cell grid or the background model (queue, corridor congestion, spill, shelf gap, escape route obstructed, altercation, PPE breach) and the policy's response. `flag` says which; `features` carries what was measured; `response` the action. Default severity 2.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_alert_generic"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "rescue.drone": {
      "post": {
        "summary": "Rescue drone state",
        "description": "The flotation drone's state machine: READY → LAUNCH → ENROUTE → ONSCENE → MARKING (ring delivered, holding station with the searchlight) → RETURN → READY. `mode` is \"rescue\" or \"investigate\" (sent to look at an object). Default severity 2.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_rescue_drone"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "rescue.boat": {
      "post": {
        "summary": "Fast rescue boat state",
        "description": "DOCKED → SWING_OUT → LOWER → TRANSIT → PICKUP → RETURN → HOIST → SWING_IN → DOCKED, from the gravity davit on the casualty's side. Launch is held while the ship is above the davit crew's speed limit. Default severity 2.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_rescue_boat"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "rescue.stretcher": {
      "post": {
        "summary": "Stretcher team",
        "description": "The stretcher team carrying a casualty to the Medical Bay — from the davit after a sea rescue, or from a restricted door. Default severity 1.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_rescue_stretcher"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "rescue.crew": {
      "post": {
        "summary": "Crew runner",
        "description": "A crew member sent from a crew station to a door, arrived, or back at the station. Default severity 1.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_rescue_crew"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "bridge.speed": {
      "post": {
        "summary": "Bridge: speed order",
        "description": "The bridge ordered a change of speed in response to an incident. The engines answer after a lag; the ship keeps way on. Default severity 2.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_bridge_speed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "bridge.signal": {
      "post": {
        "summary": "Bridge: distress signalling",
        "description": "DSC distress alert, AIS-MOB target, VHF 16 MAYDAY and MRCC notification sent, with the position used (the datum if the ring has one, else the ship's). Default severity 3.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_bridge_signal"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "access.badge": {
      "post": {
        "summary": "Access control: badge",
        "description": "The access-control feed: a crew badge on a restricted door's reader, in or out. Relayed so the consumer can do the same fusion the control plane does. Default severity 0.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_access_badge"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "incident.closed": {
      "post": {
        "summary": "Incident closed",
        "description": "The rescue or release ended and the alert cleared. Default severity 1.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_incident_closed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "guest.track": {
      "post": {
        "summary": "Re-ID track event",
        "description": "A followed track was lost to a coverage gap or reacquired on a camera; `predicted` says whether the reacquiring camera was in the predicted set. Default severity 0.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_guest_track"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "concierge.push": {
      "post": {
        "summary": "Guest app push",
        "description": "A location-aware message the control plane pushed to a guest's app. The text is operational copy, not identity. Default severity 0.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_concierge_push"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "system.link": {
      "post": {
        "summary": "Ship–shore link",
        "description": "The link the webhook and shoreside consumers depend on went down or came back. While down, events queue on board; on restore they replay in order with `delivery.replayed = true`. Default severity 1.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_system_link"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    },
    "system.status": {
      "post": {
        "summary": "System status",
        "description": "Engine online, kernel version, camera enrolment — the control plane describing itself. Default severity 0.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event_system_status"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx is an ack; anything else (or no answer) is retried with back-off, then counted failed."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique within the ship's event stream (evt_<seq>)"
          },
          "seq": {
            "type": "integer",
            "description": "Monotonic sequence number — gap-free, so a consumer can detect loss"
          },
          "type": {
            "type": "string",
            "enum": [
              "detection.flag",
              "alert.mob",
              "alert.ir_contact",
              "alert.object",
              "alert.door",
              "alert.trapped",
              "alert.medical",
              "alert.fire",
              "alert.fall",
              "alert.generic",
              "rescue.drone",
              "rescue.boat",
              "rescue.stretcher",
              "rescue.crew",
              "bridge.speed",
              "bridge.signal",
              "access.badge",
              "incident.closed",
              "guest.track",
              "concierge.push",
              "system.link",
              "system.status"
            ],
            "description": "Catalogue type"
          },
          "version": {
            "type": "string",
            "description": "Event schema version"
          },
          "ts": {
            "type": "string",
            "description": "Wall-clock time, ISO 8601 UTC",
            "format": "date-time"
          },
          "t": {
            "type": "number",
            "description": "Seconds since the control plane booted (monotonic)"
          },
          "severity": {
            "type": "integer",
            "description": "0 info · 1 activity · 2 concern · 3 emergency",
            "minimum": 0,
            "maximum": 3
          },
          "source": {
            "type": "object",
            "properties": {
              "ship": {
                "type": "string",
                "description": ""
              },
              "system": {
                "type": "string",
                "description": ""
              },
              "version": {
                "type": "string",
                "description": "Software version"
              },
              "engine": {
                "type": "string",
                "description": "WASM or JS kernel"
              }
            },
            "description": "Producer",
            "required": [
              "ship",
              "system"
            ],
            "additionalProperties": false
          },
          "camera": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Camera id as enrolled, e.g. CAM-D09-POOL-03 or IR-D03-12"
              },
              "zone": {
                "type": "string",
                "description": "Venue the camera watches"
              },
              "deck": {
                "type": "integer",
                "description": "Deck number"
              },
              "mvz": {
                "type": "integer",
                "description": "SOLAS main vertical zone the camera sits in"
              },
              "sensor": {
                "type": "string",
                "enum": [
                  "optical",
                  "thermal"
                ],
                "description": "Sensor type"
              }
            },
            "description": "The camera that produced the event (null for events with no camera)",
            "required": [
              "id",
              "zone",
              "deck",
              "mvz",
              "sensor"
            ],
            "additionalProperties": false,
            "nullable": true
          },
          "position": {
            "type": "object",
            "properties": {
              "ship": {
                "type": "object",
                "properties": {
                  "x": {
                    "type": "number",
                    "description": "Metres along the hull, + forward"
                  },
                  "z": {
                    "type": "number",
                    "description": "Metres across the beam, + port"
                  }
                },
                "description": "Ship frame",
                "required": [
                  "x",
                  "z"
                ],
                "additionalProperties": false
              },
              "geo": {
                "type": "object",
                "properties": {
                  "lat": {
                    "type": "number",
                    "description": "Decimal degrees"
                  },
                  "lon": {
                    "type": "number",
                    "description": "Decimal degrees"
                  }
                },
                "description": "WGS-84, dead-reckoned from the bridge position",
                "required": [
                  "lat",
                  "lon"
                ],
                "additionalProperties": false
              },
              "basis": {
                "type": "string",
                "enum": [
                  "camera",
                  "sensor-fix",
                  "fused-mark",
                  "door",
                  "ship"
                ],
                "description": "What the position is derived from — never the simulation truth"
              }
            },
            "description": "Where the event refers to",
            "required": [
              "ship",
              "geo",
              "basis"
            ],
            "additionalProperties": false,
            "nullable": true
          },
          "subject": {
            "type": "object",
            "properties": {
              "track": {
                "type": "string",
                "description": "Opaque Re-ID track id assigned by the control plane (e.g. PAX-07, CREW-03). Not a name, cabin or biometric template. Absent when identity mode is \"none\"."
              }
            },
            "description": "The person the event concerns, if any — pseudonymous only",
            "required": [
              "track"
            ],
            "additionalProperties": false,
            "nullable": true
          },
          "confidence": {
            "type": "number",
            "description": "Classifier confidence 0..1 where applicable",
            "nullable": true,
            "minimum": 0,
            "maximum": 1
          },
          "detail": {
            "type": "string",
            "description": "One line for a human, identity-free"
          },
          "data": {
            "type": "object",
            "properties": {},
            "description": "Type-specific payload — see the per-type schemas",
            "additionalProperties": false
          },
          "delivery": {
            "type": "object",
            "properties": {
              "replayed": {
                "type": "boolean",
                "description": "Delivered from the store-and-forward buffer after a link outage"
              },
              "queuedS": {
                "type": "number",
                "description": "Seconds it waited in the buffer",
                "nullable": true
              },
              "attempt": {
                "type": "integer",
                "description": "Webhook delivery attempt",
                "nullable": true
              }
            },
            "description": "Delivery metadata, set when the event leaves the ship",
            "required": [
              "replayed"
            ],
            "additionalProperties": false
          }
        },
        "description": "Event envelope",
        "required": [
          "id",
          "seq",
          "type",
          "version",
          "ts",
          "severity",
          "source",
          "detail",
          "data"
        ],
        "additionalProperties": false
      },
      "Camera": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Camera id as enrolled, e.g. CAM-D09-POOL-03 or IR-D03-12"
          },
          "zone": {
            "type": "string",
            "description": "Venue the camera watches"
          },
          "deck": {
            "type": "integer",
            "description": "Deck number"
          },
          "mvz": {
            "type": "integer",
            "description": "SOLAS main vertical zone the camera sits in"
          },
          "sensor": {
            "type": "string",
            "enum": [
              "optical",
              "thermal"
            ],
            "description": "Sensor type"
          }
        },
        "description": "The camera that produced the event (null for events with no camera)",
        "required": [
          "id",
          "zone",
          "deck",
          "mvz",
          "sensor"
        ],
        "additionalProperties": false
      },
      "Position": {
        "type": "object",
        "properties": {
          "ship": {
            "type": "object",
            "properties": {
              "x": {
                "type": "number",
                "description": "Metres along the hull, + forward"
              },
              "z": {
                "type": "number",
                "description": "Metres across the beam, + port"
              }
            },
            "description": "Ship frame",
            "required": [
              "x",
              "z"
            ],
            "additionalProperties": false
          },
          "geo": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number",
                "description": "Decimal degrees"
              },
              "lon": {
                "type": "number",
                "description": "Decimal degrees"
              }
            },
            "description": "WGS-84, dead-reckoned from the bridge position",
            "required": [
              "lat",
              "lon"
            ],
            "additionalProperties": false
          },
          "basis": {
            "type": "string",
            "enum": [
              "camera",
              "sensor-fix",
              "fused-mark",
              "door",
              "ship"
            ],
            "description": "What the position is derived from — never the simulation truth"
          }
        },
        "description": "Where the event refers to",
        "required": [
          "ship",
          "geo",
          "basis"
        ],
        "additionalProperties": false
      },
      "Subject": {
        "type": "object",
        "properties": {
          "track": {
            "type": "string",
            "description": "Opaque Re-ID track id assigned by the control plane (e.g. PAX-07, CREW-03). Not a name, cabin or biometric template. Absent when identity mode is \"none\"."
          }
        },
        "description": "The person the event concerns, if any — pseudonymous only",
        "required": [
          "track"
        ],
        "additionalProperties": false
      },
      "Event_detection_flag": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "detection.flag"
              },
              "data": {
                "type": "object",
                "properties": {
                  "flag": {
                    "type": "string",
                    "enum": [
                      "NORMAL",
                      "BUFFET",
                      "SHOPPING",
                      "CROWD",
                      "DOOR",
                      "MOB",
                      "IR_CONTACT",
                      "OBJECT",
                      "OCCUPIED",
                      "CREW",
                      "TRAPPED",
                      "QUEUE",
                      "BOTTLENECK",
                      "SPILL",
                      "SHELF_GAP",
                      "OBSTRUCTION",
                      "ALTERCATION",
                      "FIRE",
                      "SMOKE",
                      "FALL",
                      "PPE"
                    ],
                    "description": "The flag now"
                  },
                  "previous": {
                    "type": "string",
                    "enum": [
                      "NORMAL",
                      "BUFFET",
                      "SHOPPING",
                      "CROWD",
                      "DOOR",
                      "MOB",
                      "IR_CONTACT",
                      "OBJECT",
                      "OCCUPIED",
                      "CREW",
                      "TRAPPED",
                      "QUEUE",
                      "BOTTLENECK",
                      "SPILL",
                      "SHELF_GAP",
                      "OBSTRUCTION",
                      "ALTERCATION",
                      "FIRE",
                      "SMOKE",
                      "FALL",
                      "PPE"
                    ],
                    "description": "The flag before"
                  },
                  "raw": {
                    "type": "string",
                    "enum": [
                      "NORMAL",
                      "BUFFET",
                      "SHOPPING",
                      "CROWD",
                      "DOOR",
                      "MOB",
                      "IR_CONTACT",
                      "OBJECT",
                      "OCCUPIED",
                      "CREW",
                      "TRAPPED",
                      "QUEUE",
                      "BOTTLENECK",
                      "SPILL",
                      "SHELF_GAP",
                      "OBSTRUCTION",
                      "ALTERCATION",
                      "FIRE",
                      "SMOKE",
                      "FALL",
                      "PPE"
                    ],
                    "description": "What the classifier said before any fusion step (TRAPPED and CREW are fusion results over an OCCUPIED camera)"
                  }
                },
                "description": "Flag transition",
                "required": [
                  "flag",
                  "previous",
                  "raw"
                ],
                "additionalProperties": false
              }
            },
            "description": "Camera flag changed",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A camera's context flag changed — raised or cleared. Emitted per camera, from its own pixels, for every transition that involves a non-NORMAL flag."
      },
      "Event_alert_mob": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.mob"
              },
              "data": {
                "type": "object",
                "properties": {
                  "sensor": {
                    "type": "string",
                    "enum": [
                      "optical",
                      "thermal"
                    ],
                    "description": "Which sensor confirmed"
                  },
                  "consecutiveHits": {
                    "type": "integer",
                    "description": "Confirming frames behind the decision"
                  },
                  "shipSpeedKn": {
                    "type": "number",
                    "description": "Speed over ground at confirmation"
                  }
                },
                "description": "Confirmation",
                "required": [
                  "sensor",
                  "consecutiveHits",
                  "shipSpeedKn"
                ],
                "additionalProperties": false
              }
            },
            "description": "Man overboard confirmed",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "The dispatch policy confirmed a person in the water and launched the rescue drone. `sensor` says which watch raised it: the optical rail cameras or the thermal ring."
      },
      "Event_alert_ir_contact": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.ir_contact"
              },
              "data": {
                "type": "object",
                "properties": {
                  "cell": {
                    "type": "string",
                    "description": "Sea-grid cell id, permanent for the life of the cell (e.g. 12·P1)"
                  },
                  "thermalFraction": {
                    "type": "number",
                    "description": "Fraction of the frame reading hot-over-cold"
                  },
                  "boxPx": {
                    "type": "object",
                    "properties": {
                      "w": {
                        "type": "integer",
                        "description": "px"
                      },
                      "h": {
                        "type": "integer",
                        "description": "px"
                      }
                    },
                    "description": "Blob bounding box at 96×54",
                    "additionalProperties": false
                  }
                },
                "description": "Datum",
                "required": [
                  "cell"
                ],
                "additionalProperties": false
              }
            },
            "description": "Thermal first contact (datum)",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "The thermal ring saw a human heat signature in the water for the first time in this incident. The position is the sensor's own fix — the blob centroid inverted through the camera geometry — remembered as a sea-fixed grid cell (the SAR datum)."
      },
      "Event_alert_object": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.object"
              },
              "data": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Catalogue guess, e.g. \"deck chair\" (null if unidentified)",
                    "nullable": true
                  },
                  "size": {
                    "type": "string",
                    "enum": [
                      "large",
                      "small",
                      "unknown"
                    ],
                    "description": "Size class"
                  },
                  "tone": {
                    "type": "string",
                    "description": "Colour class: pale / red / grey / coloured",
                    "nullable": true
                  },
                  "cameras": {
                    "type": "integer",
                    "description": "Cameras that saw it"
                  },
                  "decks": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "description": ""
                    },
                    "description": "Decks those cameras are on"
                  },
                  "rangeM": {
                    "type": "number",
                    "description": "Range from the first camera, by depression angle"
                  },
                  "spreadM": {
                    "type": "number",
                    "description": "Largest disagreement between the cameras' estimates"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "LOG",
                      "MARK",
                      "DRONE"
                    ],
                    "description": "What the object policy chose"
                  },
                  "headcount": {
                    "type": "object",
                    "properties": {
                      "onCamera": {
                        "type": "integer",
                        "description": ""
                      },
                      "transit": {
                        "type": "integer",
                        "description": ""
                      },
                      "cabin": {
                        "type": "integer",
                        "description": ""
                      },
                      "total": {
                        "type": "integer",
                        "description": ""
                      }
                    },
                    "description": "Re-ID headcount at the time: is anyone unaccounted for",
                    "additionalProperties": false
                  }
                },
                "description": "Object",
                "required": [
                  "size",
                  "cameras",
                  "action"
                ],
                "additionalProperties": false
              }
            },
            "description": "Object overboard",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Something that is not a person went into the sea. Several decks' cameras ranged it by depression angle and the estimates were fused in the sea frame; the catalogue guessed what it is from size and colour."
      },
      "Event_alert_door": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.door"
              },
              "data": {
                "type": "object",
                "properties": {
                  "door": {
                    "type": "string",
                    "description": "Door id, e.g. GALLEY-9"
                  },
                  "label": {
                    "type": "string",
                    "description": "Door label"
                  },
                  "hazard": {
                    "type": "string",
                    "enum": [
                      "high",
                      "low"
                    ],
                    "description": "Hazard class of the space behind it"
                  },
                  "authorised": {
                    "type": "boolean",
                    "description": "A crew badge opened it (or the crew are releasing someone) — no alert"
                  },
                  "badge": {
                    "type": "string",
                    "description": "Badge id on the reader, if any",
                    "nullable": true
                  }
                },
                "description": "Door",
                "required": [
                  "door",
                  "label",
                  "hazard",
                  "authorised"
                ],
                "additionalProperties": false
              }
            },
            "description": "Restricted door open",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A camera saw a crew-only door open (magenta door-ajar beacon in its pixels). Access control says whether a badge opened it; Re-ID says who is at it."
      },
      "Event_alert_trapped": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.trapped"
              },
              "data": {
                "type": "object",
                "properties": {
                  "door": {
                    "type": "string",
                    "description": "Door id"
                  },
                  "label": {
                    "type": "string",
                    "description": "Space label"
                  },
                  "hazard": {
                    "type": "string",
                    "enum": [
                      "high",
                      "low"
                    ],
                    "description": ""
                  },
                  "level": {
                    "type": "string",
                    "enum": [
                      "ALERT",
                      "URGENT",
                      "MEDICAL"
                    ],
                    "description": "Policy level"
                  },
                  "lockedForS": {
                    "type": "number",
                    "description": "Seconds the door has been locked behind them"
                  },
                  "crewSent": {
                    "type": "boolean",
                    "description": "A crew runner is on the way"
                  }
                },
                "description": "Trapped",
                "required": [
                  "door",
                  "level",
                  "lockedForS"
                ],
                "additionalProperties": false
              }
            },
            "description": "Person trapped in a restricted space",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Fusion: the camera inside a crew-only space sees a person, access control says the door is shut and locked and no badge opened it. Emitted when raised and again on each escalation (level rises with time and hazard)."
      },
      "Event_alert_medical": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.medical"
              },
              "data": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "enum": [
                      "no-motion",
                      "found-down",
                      "recovered-from-sea"
                    ],
                    "description": ""
                  },
                  "stillForS": {
                    "type": "number",
                    "description": "Seconds without motion before the call (no-motion)",
                    "nullable": true
                  },
                  "door": {
                    "type": "string",
                    "description": "Door id, for a restricted space",
                    "nullable": true
                  }
                },
                "description": "Medical",
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              }
            },
            "description": "Medical escalation",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A person has gone down: the camera read no motion for the policy's still-time, or the crew found them down on opening the door. The stretcher team is called."
      },
      "Event_alert_fire": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.fire"
              },
              "data": {
                "type": "object",
                "properties": {
                  "flag": {
                    "type": "string",
                    "enum": [
                      "FIRE",
                      "SMOKE"
                    ],
                    "description": "What the camera read"
                  },
                  "flamePx": {
                    "type": "integer",
                    "description": "Flame pixels in the 96×54 frame"
                  },
                  "flickerPx": {
                    "type": "integer",
                    "description": "Flame-mask pixels that changed since the previous frame"
                  },
                  "smokePct": {
                    "type": "number",
                    "description": "Percent of the frame reading as smoke"
                  },
                  "mvz": {
                    "type": "integer",
                    "description": "Main vertical zone"
                  },
                  "team": {
                    "type": "string",
                    "description": "Crew station sent",
                    "nullable": true
                  }
                },
                "description": "Fire",
                "required": [
                  "flag"
                ],
                "additionalProperties": false
              }
            },
            "description": "Fire / smoke",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "An engine-room camera reads flame (saturated red-orange pixels whose mask changes between frames — a prop does not flicker) or smoke (a grey veil lighter than the learned room). The fire team is sent and the main vertical zone's fire doors are ordered shut."
      },
      "Event_alert_fall": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.fall"
              },
              "data": {
                "type": "object",
                "properties": {
                  "pronePx": {
                    "type": "integer",
                    "description": "Pixels matching the lying-down shape"
                  },
                  "consecutiveHits": {
                    "type": "integer",
                    "description": ""
                  },
                  "team": {
                    "type": "string",
                    "description": "Crew station sent",
                    "nullable": true
                  }
                },
                "description": "Fall",
                "required": [
                  "pronePx"
                ],
                "additionalProperties": false
              }
            },
            "description": "Person down",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A camera reads a person lying on the floor — a head with a torso beside it at the same row and floor below — confirmed over consecutive frames. A medical runner is sent. Known blind spot: blue and pale clothing (scorecard limitations)."
      },
      "Event_alert_generic": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "alert.generic"
              },
              "data": {
                "type": "object",
                "properties": {
                  "flag": {
                    "type": "string",
                    "enum": [
                      "NORMAL",
                      "BUFFET",
                      "SHOPPING",
                      "CROWD",
                      "DOOR",
                      "MOB",
                      "IR_CONTACT",
                      "OBJECT",
                      "OCCUPIED",
                      "CREW",
                      "TRAPPED",
                      "QUEUE",
                      "BOTTLENECK",
                      "SPILL",
                      "SHELF_GAP",
                      "OBSTRUCTION",
                      "ALTERCATION",
                      "FIRE",
                      "SMOKE",
                      "FALL",
                      "PPE"
                    ],
                    "description": ""
                  },
                  "level": {
                    "type": "string",
                    "enum": [
                      "INFO",
                      "ALERT",
                      "URGENT"
                    ],
                    "description": "Policy level"
                  },
                  "features": {
                    "type": "object",
                    "properties": {},
                    "description": "Measured features (pixels per class, cells), type-specific",
                    "additionalProperties": false
                  },
                  "response": {
                    "type": "string",
                    "description": "What the policy chose: LOG / NOTIFY / SEND",
                    "nullable": true
                  },
                  "team": {
                    "type": "string",
                    "description": "Crew station role sent, if any",
                    "nullable": true
                  },
                  "mvz": {
                    "type": "integer",
                    "description": "Main vertical zone"
                  }
                },
                "description": "Alert",
                "required": [
                  "flag",
                  "level"
                ],
                "additionalProperties": false
              }
            },
            "description": "Operational alert",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "An operational class raised from the cell grid or the background model (queue, corridor congestion, spill, shelf gap, escape route obstructed, altercation, PPE breach) and the policy's response. `flag` says which; `features` carries what was measured; `response` the action."
      },
      "Event_rescue_drone": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "rescue.drone"
              },
              "data": {
                "type": "object",
                "properties": {
                  "state": {
                    "type": "string",
                    "enum": [
                      "READY",
                      "LAUNCH",
                      "ENROUTE",
                      "ONSCENE",
                      "MARKING",
                      "RETURN"
                    ],
                    "description": ""
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "rescue",
                      "investigate"
                    ],
                    "description": ""
                  },
                  "releaseReason": {
                    "type": "string",
                    "description": "Why it left station (boat / endurance / looked)",
                    "nullable": true
                  },
                  "searchlight": {
                    "type": "boolean",
                    "description": "Searchlight on"
                  }
                },
                "description": "Drone",
                "required": [
                  "state",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "description": "Rescue drone state",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "The flotation drone's state machine: READY → LAUNCH → ENROUTE → ONSCENE → MARKING (ring delivered, holding station with the searchlight) → RETURN → READY. `mode` is \"rescue\" or \"investigate\" (sent to look at an object)."
      },
      "Event_rescue_boat": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "rescue.boat"
              },
              "data": {
                "type": "object",
                "properties": {
                  "state": {
                    "type": "string",
                    "description": ""
                  },
                  "davit": {
                    "type": "string",
                    "description": "Davit id (P / S)",
                    "nullable": true
                  },
                  "held": {
                    "type": "boolean",
                    "description": "Launch held for ship speed"
                  },
                  "shipSpeedKn": {
                    "type": "number",
                    "description": ""
                  }
                },
                "description": "Boat",
                "required": [
                  "state"
                ],
                "additionalProperties": false
              }
            },
            "description": "Fast rescue boat state",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "DOCKED → SWING_OUT → LOWER → TRANSIT → PICKUP → RETURN → HOIST → SWING_IN → DOCKED, from the gravity davit on the casualty's side. Launch is held while the ship is above the davit crew's speed limit."
      },
      "Event_rescue_stretcher": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "rescue.stretcher"
              },
              "data": {
                "type": "object",
                "properties": {
                  "state": {
                    "type": "string",
                    "enum": [
                      "dispatched",
                      "arrived"
                    ],
                    "description": ""
                  },
                  "from": {
                    "type": "string",
                    "enum": [
                      "davit",
                      "door"
                    ],
                    "description": ""
                  },
                  "destination": {
                    "type": "string",
                    "description": "Medical Bay"
                  }
                },
                "description": "Stretcher",
                "required": [
                  "state",
                  "from"
                ],
                "additionalProperties": false
              }
            },
            "description": "Stretcher team",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "The stretcher team carrying a casualty to the Medical Bay — from the davit after a sea rescue, or from a restricted door."
      },
      "Event_rescue_crew": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "rescue.crew"
              },
              "data": {
                "type": "object",
                "properties": {
                  "state": {
                    "type": "string",
                    "enum": [
                      "dispatched",
                      "at_door",
                      "home"
                    ],
                    "description": "at_door also means \"at the scene\" for an operational call"
                  },
                  "station": {
                    "type": "string",
                    "description": "Crew station id"
                  },
                  "door": {
                    "type": "string",
                    "description": "Target: the door id, or the camera id of the venue for an operational call"
                  },
                  "kind": {
                    "type": "string",
                    "description": "door, or the operational story kind (spill, fire, fall, …)",
                    "nullable": true
                  },
                  "flag": {
                    "type": "string",
                    "description": "The class the runner was sent for, if operational",
                    "nullable": true
                  },
                  "routeM": {
                    "type": "number",
                    "description": "Route length",
                    "nullable": true
                  },
                  "etaS": {
                    "type": "number",
                    "description": "Estimated seconds to arrive",
                    "nullable": true
                  }
                },
                "description": "Crew",
                "required": [
                  "state",
                  "station",
                  "door"
                ],
                "additionalProperties": false
              }
            },
            "description": "Crew runner",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A crew member sent from a crew station to a door, arrived, or back at the station."
      },
      "Event_bridge_speed": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "bridge.speed"
              },
              "data": {
                "type": "object",
                "properties": {
                  "fromKn": {
                    "type": "number",
                    "description": ""
                  },
                  "toKn": {
                    "type": "number",
                    "description": ""
                  },
                  "lagS": {
                    "type": "number",
                    "description": "Seconds before the engines answer"
                  },
                  "reason": {
                    "type": "string",
                    "description": ""
                  }
                },
                "description": "Speed order",
                "required": [
                  "fromKn",
                  "toKn"
                ],
                "additionalProperties": false
              }
            },
            "description": "Bridge: speed order",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "The bridge ordered a change of speed in response to an incident. The engines answer after a lag; the ship keeps way on."
      },
      "Event_bridge_signal": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "bridge.signal"
              },
              "data": {
                "type": "object",
                "properties": {
                  "signals": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": ""
                    },
                    "description": "Signals sent"
                  }
                },
                "description": "Signals",
                "required": [
                  "signals"
                ],
                "additionalProperties": false
              }
            },
            "description": "Bridge: distress signalling",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "DSC distress alert, AIS-MOB target, VHF 16 MAYDAY and MRCC notification sent, with the position used (the datum if the ring has one, else the ship's)."
      },
      "Event_access_badge": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "access.badge"
              },
              "data": {
                "type": "object",
                "properties": {
                  "door": {
                    "type": "string",
                    "description": "Door / reader id"
                  },
                  "direction": {
                    "type": "string",
                    "enum": [
                      "in",
                      "out"
                    ],
                    "description": ""
                  }
                },
                "description": "Badge",
                "required": [
                  "door",
                  "direction"
                ],
                "additionalProperties": false
              }
            },
            "description": "Access control: badge",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "The access-control feed: a crew badge on a restricted door's reader, in or out. Relayed so the consumer can do the same fusion the control plane does."
      },
      "Event_incident_closed": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "incident.closed"
              },
              "data": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "mob",
                      "trapped",
                      "object",
                      "fire",
                      "fall",
                      "queue",
                      "congestion",
                      "spill",
                      "obstruction",
                      "altercation",
                      "ppe",
                      "shelf",
                      "other"
                    ],
                    "description": ""
                  },
                  "durationS": {
                    "type": "number",
                    "description": "From confirmation to close"
                  },
                  "outcome": {
                    "type": "string",
                    "description": ""
                  }
                },
                "description": "Close",
                "required": [
                  "kind",
                  "durationS",
                  "outcome"
                ],
                "additionalProperties": false
              }
            },
            "description": "Incident closed",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "The rescue or release ended and the alert cleared."
      },
      "Event_guest_track": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "guest.track"
              },
              "data": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "enum": [
                      "reacquired",
                      "lost"
                    ],
                    "description": ""
                  },
                  "predicted": {
                    "type": "boolean",
                    "description": "Reacquired on a camera the coverage-gap prediction named"
                  }
                },
                "description": "Track",
                "required": [
                  "event"
                ],
                "additionalProperties": false
              }
            },
            "description": "Re-ID track event",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A followed track was lost to a coverage gap or reacquired on a camera; `predicted` says whether the reacquiring camera was in the predicted set."
      },
      "Event_concierge_push": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "concierge.push"
              },
              "data": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "activity",
                      "offer",
                      "service",
                      "heads-up",
                      "cabin"
                    ],
                    "description": ""
                  },
                  "where": {
                    "type": "string",
                    "description": ""
                  },
                  "text": {
                    "type": "string",
                    "description": ""
                  },
                  "live": {
                    "type": "boolean",
                    "description": "Grounded in a live detection flag"
                  }
                },
                "description": "Push",
                "required": [
                  "kind",
                  "text"
                ],
                "additionalProperties": false
              }
            },
            "description": "Guest app push",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A location-aware message the control plane pushed to a guest's app. The text is operational copy, not identity."
      },
      "Event_system_link": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "system.link"
              },
              "data": {
                "type": "object",
                "properties": {
                  "up": {
                    "type": "boolean",
                    "description": ""
                  },
                  "queued": {
                    "type": "integer",
                    "description": "Events waiting when the state changed"
                  },
                  "outageS": {
                    "type": "number",
                    "description": "Length of the outage just ended",
                    "nullable": true
                  }
                },
                "description": "Link",
                "required": [
                  "up",
                  "queued"
                ],
                "additionalProperties": false
              }
            },
            "description": "Ship–shore link",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "The link the webhook and shoreside consumers depend on went down or came back. While down, events queue on board; on restore they replay in order with `delivery.replayed = true`."
      },
      "Event_system_status": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "system.status"
              },
              "data": {
                "type": "object",
                "properties": {
                  "engine": {
                    "type": "string",
                    "enum": [
                      "WASM",
                      "JS"
                    ],
                    "description": "Detection kernel in use"
                  },
                  "cameras": {
                    "type": "integer",
                    "description": "Cameras enrolled"
                  },
                  "note": {
                    "type": "string",
                    "description": ""
                  }
                },
                "description": "Status",
                "required": [
                  "engine",
                  "cameras"
                ],
                "additionalProperties": false
              }
            },
            "description": "System status",
            "required": [
              "type",
              "data"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Engine online, kernel version, camera enrolment — the control plane describing itself."
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": ""
          },
          "url": {
            "type": "string",
            "description": "",
            "format": "uri"
          },
          "types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "detection.flag",
                "alert.mob",
                "alert.ir_contact",
                "alert.object",
                "alert.door",
                "alert.trapped",
                "alert.medical",
                "alert.fire",
                "alert.fall",
                "alert.generic",
                "rescue.drone",
                "rescue.boat",
                "rescue.stretcher",
                "rescue.crew",
                "bridge.speed",
                "bridge.signal",
                "access.badge",
                "incident.closed",
                "guest.track",
                "concierge.push",
                "system.link",
                "system.status"
              ],
              "description": ""
            },
            "description": "Subscribed types (empty = all)"
          },
          "identity": {
            "type": "string",
            "enum": [
              "pseudonymous",
              "none"
            ],
            "description": ""
          },
          "active": {
            "type": "boolean",
            "description": ""
          }
        },
        "description": "Webhook subscription",
        "required": [
          "url"
        ],
        "additionalProperties": false
      },
      "Policies": {
        "type": "object",
        "properties": {
          "drone": {
            "type": "object",
            "properties": {
              "minConsecutiveHits": {
                "type": "integer",
                "description": ""
              },
              "minConfidence": {
                "type": "number",
                "description": ""
              },
              "cooldownMs": {
                "type": "integer",
                "description": ""
              }
            },
            "description": "Optical MOB → drone",
            "additionalProperties": false
          },
          "ir": {
            "type": "object",
            "properties": {
              "minConsecutiveHits": {
                "type": "integer",
                "description": ""
              },
              "minConfidence": {
                "type": "number",
                "description": ""
              },
              "cooldownMs": {
                "type": "integer",
                "description": ""
              }
            },
            "description": "Thermal contact → rescue",
            "additionalProperties": false
          },
          "station": {
            "type": "object",
            "properties": {
              "releaseAt": {
                "type": "string",
                "description": "Boat state at which the drone leaves station"
              }
            },
            "description": "",
            "additionalProperties": false
          },
          "object": {
            "type": "object",
            "properties": {
              "minConsecutiveHits": {
                "type": "integer",
                "description": ""
              },
              "minConfidence": {
                "type": "number",
                "description": ""
              },
              "cooldownMs": {
                "type": "integer",
                "description": ""
              }
            },
            "description": "",
            "additionalProperties": false
          },
          "trapped": {
            "type": "object",
            "properties": {
              "minConsecutiveHits": {
                "type": "integer",
                "description": ""
              },
              "minLockedS": {
                "type": "object",
                "properties": {
                  "high": {
                    "type": "number",
                    "description": ""
                  },
                  "low": {
                    "type": "number",
                    "description": ""
                  }
                },
                "description": "",
                "additionalProperties": false
              },
              "urgentAfterS": {
                "type": "number",
                "description": ""
              },
              "stillS": {
                "type": "number",
                "description": ""
              }
            },
            "description": "",
            "additionalProperties": false
          }
        },
        "description": "Policy constants (src/policy.js)",
        "additionalProperties": false
      },
      "Privacy": {
        "type": "object",
        "properties": {
          "identity": {
            "type": "string",
            "enum": [
              "pseudonymous",
              "none"
            ],
            "description": "Whether events carry the opaque track id"
          },
          "redactFrames": {
            "type": "boolean",
            "description": "Person pixels blanked in exported frames"
          },
          "retentionDays": {
            "type": "integer",
            "description": "Event and frame retention"
          }
        },
        "description": "Privacy settings",
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Shipboard identity provider token; roles operator / auditor / admin"
      }
    }
  },
  "security": [
    {
      "bearer": []
    }
  ]
}
