One Cloud Custodian per drone, per tenant. The Custodian has two jobs, not one: it maintains the dTwin — the canonical row in tenant.drones that answers “what does FLYsafe.live believe is true about this drone right now?” — and it independently verifies, via the aircraft's real telemetry, that the Autopilot Runner executed what RTM Core commanded.
RTM Core decides. The Runner executes. The Custodian records and verifies. No component performs another's job: RTM Core never talks to the aircraft directly, the Runner never re-derives whether a conflict exists, and the Custodian never relays a command or picks a maneuver — it maintains the state of record and independently checks that execution matched intent.
Naming note: this document folds two prior strategies into one model. The Aircraft Custodian (formerly Aircraft Agent) is renamed Cloud Custodian here to match its counterpart, the Autopilot Runner — the protocol-specific edge component that used to be described separately as a “GCS Plugin” (telemetry-in) and “Custodian SW” (state-maintenance). The Runner now covers both directions: it streams telemetry into the Custodian and executes the Custodian-verified, RTM Core–computed avoidance maneuvers back onto the airframe. One edge component, one name, per protocol (MAVLink Runner, DJI Runner).
drones.drones (Β§4): daa_status, current_avoidance_maneuver_id, current_avoidance_state, last_daa_verification_at — the Custodian's verification obligation made durable, alongside the existing v1.13 daa_enabled flag and drones.daa_rules table.Three components, one contract each. RTM Core is the cloud brain: flight binding, the fleet-wide picture, flight authorization, flight tracking, and all DAA reasoning. It publishes avoidance commands and proximity alerts once, and never talks to an aircraft directly. The Cloud Custodian is a per-aircraft supervisory layer: it maintains the dTwin in tenant.drones from telemetry the Runner forwards, and it independently verifies — from that same telemetry — that the Runner did what RTM Core said. The Autopilot Runner is the protocol-specific edge: it turns RTM Core's maneuver into native MAVLink or DJI commands, confirms execution from real telemetry (not just a command acknowledgment), and is the same component that streamed telemetry in in the first place.
tenant.drones β the dTwindaa_status / current_avoidance_state to tenant.drones, and reports tiered DAA status (full / degraded / no qualified surveillance) to RTM Core.tenant.drones — that is still its first job.tenant.drones. DAA status and avoidance state are just more columns on that same row.This is the structural view carried forward unchanged from v1.13: where drone records originate, how they enter tenant.drones, and where identity propagates.
drones.dlb_drones β raw DLB staging tabledlb_guid; seeds two speed defaults on first insertdrones.drones + v_dtwin β the dTwinThe Custodian maintains its drone's row in tenant.drones — that is its primary job. Telemetry enters via the Autopilot Runner (Β§3), gets normalized, and the Custodian writes state through the API. Keycloak.ΞΈ.Drone registers each drone as a user (<tenant>-<serial>) purely for authentication — no drone metadata lives there. AADMS resolves inbound ADS-B callsigns to (tenant, serial) for cross-tenant routing.
An Cloud Custodian is the entity inside FLYsafe.live RTM responsible for representing one specific drone. There is exactly one Custodian per drone per tenant. In v1.13 it carried three obligations; v2.0 adds a fourth, sourced directly from the DAA strategy's parallel-AMQP-consumer redesign (Β§3.7).
Single point of drone identity in RTM. The canonical row in tenant.drones.
Observable via: GET /drones/{serial}; row presence.
Honest telemetry flow with explicit failure declaration, forwarded by the Runner. See Β§3.6.
Observable via: data_source_status, adapter_health.connected.
Maintains canonical drone state — position, attitude, battery, mission binding, ADS-B status, OI defaults.
Observable via: timestamps.source_ts, updated_at.
Independently confirms, via the aircraft's real telemetry, that the Runner executed RTM Core's avoidance command. Reports tiered DAA status back to RTM Core — never relays the command itself.
Observable via: daa_status, current_avoidance_state, last_daa_verification_at.
The data path into the Cloud Custodian is a Runner embedded inside the operator's GCS — not a separate edge container the operator has to install and manage. The same Runner also executes RTM Core's avoidance commands and confirms execution from real telemetry. Two Runner form factors cover the bulk of the fleet:
Host: embedded plugin inside Mission Planner desktop GCS · Protocol: MAVLink v2
Taps the MAVLink stream the operator already receives from the autopilot, normalizes selected messages, and forwards them to the Custodian API as HTTPS POSTs. On an avoidance command, streams SET_POSITION_TARGET_GLOBAL_INT at heartbeat rate and watches live telemetry to confirm execution.
Host: Chrome GCS Plugin (browser extension) or direct DJI Cloud API · Protocol: GCS web APIs / DRC channel
Reads telemetry from the GCS's exposed web APIs and forwards to the Custodian API. On an avoidance command, issues descent-and-hold over the DRC live-control MQTT channel and watches control-status flags to confirm execution.
The Cloud Custodian doesn't just maintain telemetry-derived state — it's also the system of record for how each aircraft connects. The protocol-specific configuration for every supported telemetry source (MAVLink autopilot, DJI FlightHub 2, DJI Cloud API, ADS-B) is stored in tenant.drones and exposed through the Custodian APIs.
The Custodian calls the APIs to obtain its own configuration at boot — GET /drones/{serial}/config returns the full configuration document including which protocol adapter to load and its connection parameters. The Runner doesn't carry hardcoded protocol details; everything is data, served from tenant.drones.
The Custodian provides protocol configuration through the API surface — operators (via the RTM Map UI) and AI agents (via MCP) read and update these configurations through the same APIs. One central place to manage every aircraft's connection, regardless of vendor.
For: ArduPilot, PX4, Sentaeros 6
Configuration: autopilot host/port, MAVLink dialect, heartbeat interval, reconnect delay, system ID, message rate overrides
Served via the MAVLink Runner's configuration endpoint
For: DJI fleets managed via FH2 web GCS
Configuration: FH2 workspace ID, device alias, MQTT broker endpoint, telemetry topic filter, OSD subscription set
Served to the DJI Runner on session establish
For: Direct DJI Cloud API integration (server-to-server, no FH2)
Configuration: Cloud API endpoint, application credentials reference, device binding token, DRC command channel enablement
Used when a drone is enrolled with DJI Cloud rather than FH2
For: Per-drone ADS-B provisioning intent
Configuration: ADS-B In/Out, surrogate mode, ICAO hex mode, TIS-B rebroadcast participation
Drives both CIFIB upstream gating and Flight_Live ownership tagging
{ "credential_ref": "vault://airmarket/dji-cloud/app-key" }), not by value. Actual secret material is held in a secrets store and resolved at runtime by the Custodian using its tenant-scoped service identity. tenant.drones never holds plaintext credentials.
Every Custodian is responsible for knowing whether data is actually arriving from the aircraft, and at what rate and latency. A silent source is worse than a failed source, because a silent source looks the same as a healthy one until an operator notices the position hasn't moved.
| Status | Trigger | What downstream consumers do |
|---|---|---|
| HEALTHY | Rate within ±20% of publish_rate_hz and p95 latency below data_source_max_latency_ms. | Position on map is authoritative. OI Automation uses it directly. |
| DEGRADED | Rate below expected but still delivering, or p95 latency exceeded but data still arriving. | Map shows a warning halo. OI Automation continues but flags OIs generated during degradation. OCC is notified. |
| STALE | No message received in the last N × the expected interval (default N = 5). | Map suppresses live position; renders last-known with a stale marker. OI Automation pauses regeneration. Alert raised. |
| DISCONNECTED | Source has explicitly disconnected. | Same as STALE plus the map badge changes to off-network. Mission binding held; operator can re-attach source. |
tenant.drones as tiered DAA status.
This mirrors TCAS-style monitoring rather than gating: the safety-critical path (RTM Core → Runner) has no extra hop that can add latency or become a silent single point of failure, and the Custodian becomes a true independent check rather than a relay that could itself fail to pass something along. See Fig. 2 above and Β§9 for the full DAA execution model this verification loop backs.
| Message | Channel | Priority |
|---|---|---|
| Avoidance command | AMQP only β flight declaration's own fanout exchange | critical |
| Proximity alert, active or resolved | AMQP and MQTT topic daa_alerts | info / warning / critical by alert level |
tenant.dronesThe dTwin is a row in tenant.drones. The Custodian maintains it. Everything that wants to know the current state of an aircraft — including its DAA verification status — reads through the API surface that sits on top of this table. This section is fully self-contained: every schema the Custodian reads or writes, including the v2.0 DAA-verification columns, is reproduced here in full rather than pointed at v1.13.
The table below maps every field the FLYsafe.software DLB exposes for a drone to its role in the Cloud Custodian. Stored in dlb_drones fields are persisted verbatim in drones.dlb_drones and surfaced through the v_dtwin view — they are never copied into drones.drones. Seeds RTM default fields supply initial values the Import process seeds into drones.drones on first insert; operators may later override via Custodian APIs. FK link fields are written to drones.drones once to establish the foreign key relationship. This table is unchanged from v1.13 — the DLB import contract is orthogonal to the Custodian/Runner DAA redesign.
| DLB Field (exact name) | Example Value | Role | Maps to / Note |
|---|---|---|---|
guid |
02150819-48AC-A3C6-… |
FK link | PK in drones.dlb_drones; stored as dlb_guid in drones.drones as a FK on first insert. Links the two tables. All other DLB identity fields are read from dlb_drones via v_dtwin using this key. |
serial_number |
14334 |
FK link | Also written to drones.drones on first insert — forms the composite PK (tenant_id, serial_number), the binding key across all systems. |
name |
AIRTL01 |
Stored in dlb_drones | Exposed as drone_name in v_dtwin. Not copied to drones.drones. |
brand |
UAVSystemsinternatinoal |
Stored in dlb_drones | Aircraft manufacturer. Available via v_dtwin. |
model |
UAVSYSTEMSX6C |
Stored in dlb_drones | Aircraft model identifier. Available via v_dtwin. |
drone_type |
Hexacopter |
Stored in dlb_drones | Frame classification (Hexacopter, Fixed-wing, etc.). Available via v_dtwin. |
identification_number |
C-2109072432 |
Stored in dlb_drones | TC / regulatory registration. Exposed as TC_Registration in v_dtwin. Not copied to drones.drones. |
inventory_number |
(empty in example) | Stored in dlb_drones | Operator-assigned callsign used for ADS-B routing via AADMS. Exposed as Callsign in v_dtwin. Not copied to drones.drones. |
status |
Airworthy |
Stored in dlb_drones | Airworthiness status (Airworthy, Grounded, etc.). Available via v_dtwin. |
max_horizontal_speed |
15 |
Seeds RTM default | Seeds horizontal_speed_ms (m/s) on first import. Operator may override via PUT /drones/{serial}/oi-defaults. |
max_vertical_speed |
10 |
Seeds RTM default | Seeds vertical_speed_ms (m/s) on first import. Operator may override via PUT /drones/{serial}/oi-defaults. |
hardware_version, firmware_version, propulsion_type, payload_capacity, weight, purchase_date, max_flight_time, insurable_value, color, company_guid, user_guid, controller_serial_number(2), flight_controller_serial_number, tech_number, notes |
various | Stored / informational | Persisted verbatim in drones.dlb_drones for reference; available via v_dtwin. None of these are copied into drones.drones. |
drones.dlb_drones (raw staging table). A view (drones.v_dlb_drones) renames the two display identifiers (inventory_number → Callsign, identification_number → TC_Registration) for operator-facing display and reporting. The main Custodian dTwin table (drones.drones) carries a curated subset of DLB fields under RTM-friendly names alongside RTM-managed configuration, live telemetry state, and — new in v2.0 — DAA verification state.
drones.dlb_drones)Stores every field from the DLB API with column names exactly as they arrive. This is the upsert target for the Import process (Steve Smith). No renaming — the schema matches the DLB source verbatim. Unchanged from v1.13.
-- drones.dlb_drones Β· /tenant/rtm-app-db/schema/dlb_drones.sql -- Raw DLB import table. Column names match the DLB API verbatim. -- Written by the Import process (Steve Smith). Never modified by RTM side. CREATE TABLE IF NOT EXISTS drones.dlb_drones ( -- DLB primary identity (exact field names from DLB API) guid TEXT NOT NULL, -- DLB drone UUID serial_number TEXT NOT NULL, name TEXT, -- drone display name brand TEXT, model TEXT, drone_type TEXT, status TEXT, -- e.g. Airworthy, Grounded color TEXT, -- Regulatory / operational identifiers identification_number TEXT, -- TC registration (AS TC_Registration in v_dtwin) inventory_number TEXT, -- operator callsign (AS Callsign in v_dtwin) -- Hardware & firmware hardware_version TEXT, -- e.g. Pixhawk Cube firmware_version TEXT, -- e.g. 4.0.7 flight_controller_serial_number TEXT, controller_serial_number TEXT, controller_serial_number2 TEXT, -- Performance specs (stored as TEXT to match DLB source; cast on use) max_horizontal_speed TEXT, -- seeds horizontal_speed_ms in drones.drones max_vertical_speed TEXT, -- seeds vertical_speed_ms in drones.drones max_flight_time TEXT, -- rated endurance (minutes) payload_capacity TEXT, -- kg weight TEXT, propulsion_type TEXT, -- ELECTRIC | ICE | HYBRID -- Ownership & financial company_guid TEXT, user_guid TEXT, insurable_value TEXT, purchase_date TEXT, -- ISO-8601 string from DLB; cast on use tech_number TEXT, notes TEXT, -- Import bookkeeping imported_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (guid) ); CREATE UNIQUE INDEX IF NOT EXISTS idx_dlb_drones_serial ON drones.dlb_drones (serial_number);
drones.drones)The canonical per-drone RTM state table maintained by the Cloud Custodian. Holds only RTM-specific data — configuration, live state, DAA verification state, and the link key (dlb_guid) back to drones.dlb_drones. All DLB identity fields live exclusively in dlb_drones and are accessed via the v_dtwin view. Everything through data_source_* is unchanged from v1.13; the DAA verification block is new in v2.0, backing the Custodian's fourth obligation (Β§2) and the parallel-AMQP-consumer model (Β§3.7).
-- /tenant/rtm-app-db/schema/drones.sql -- One row per drone per tenant. Maintained by the Custodian. PK = (tenant, serial). -- DLB identity data lives in drones.dlb_drones; join via dlb_guid. Use v_dtwin for the full dTwin record. CREATE SCHEMA IF NOT EXISTS drones; CREATE TABLE drones.drones ( -- Core identity tenant_id text NOT NULL, serial_number text NOT NULL, dlb_guid text NOT NULL, -- FK → drones.dlb_drones.guid -- Custodian configuration (RTM-managed; written via Custodian APIs) adapter_type text DEFAULT 'mavlink'::text NOT NULL, -- mavlink | dji_fh2 | dronesense adapter_config jsonb DEFAULT '{}'::jsonb NOT NULL, publish_rate_hz int4 DEFAULT 4 NOT NULL, -- OI defaults & FLYplan automation (speeds seeded from dlb_drones on first insert) oi_automation_enabled bool DEFAULT false NOT NULL, -- pilot-granted authority; see Β§7 horizontal_speed_ms float8 DEFAULT 15.0 NOT NULL, -- seeded from dlb_drones.max_horizontal_speed vertical_speed_ms float8 DEFAULT 6.0 NOT NULL, -- seeded from dlb_drones.max_vertical_speed -- Flight Volume (FV) / Contingency Volume (CV) buffers — horizontal only; vertical separation -- from other traffic is handled by the NMAC/SWC crewed/uncrewed minima below, not by FV/CV. fv_buffer_m float8 DEFAULT 56.0 NOT NULL, -- horizontal buffer around flight path cv_buffer_m float8 DEFAULT 150.0 NOT NULL, -- additional horizontal buffer past FV edge -- OI geometry parameters (see Β§5A) oi_automation_square_m float8 DEFAULT 700.0 NOT NULL, -- OI square side (m); valid 350–1400 oi_automation_radius_m float8 DEFAULT 400.0 NOT NULL, -- OI radius (m); valid 200–800 oi_automation_amend_min_trigger_m float8 DEFAULT 100.0 NOT NULL, -- OI amend min trigger (m); valid 50–200 oi_automation_amend_max_alt_agl_m float8 DEFAULT 300.0 NOT NULL, -- OI amend max altitude AGL (m) oi_automation_amend_enabled bool DEFAULT false NOT NULL, -- Flight monitoring — radius within which a mission waypoint is considered reached. waypoint_buffer_m float8 DEFAULT 50.0 NOT NULL, -- valid 5–500 -- Default pilot binding — pre-filled pilot when a mission is created without explicit selection default_pilot_name text NULL, default_pilot_fs_guid text NULL, -- FLYsafe.software pilot GUID -- Separation defaults — per-drone conflict-evaluation minima, metres, split crewed/uncrewed × horizontal/vertical. nmac_h_crewed_m float8 DEFAULT 150.0 NOT NULL, -- NMAC horizontal, crewed traffic nmac_v_crewed_m float8 DEFAULT 30.0 NOT NULL, -- NMAC vertical, crewed traffic nmac_h_uncrewed_m float8 DEFAULT 15.0 NOT NULL, -- NMAC horizontal, uncrewed traffic nmac_v_uncrewed_m float8 DEFAULT 8.0 NOT NULL, -- NMAC vertical, uncrewed traffic swc_h_crewed_m float8 DEFAULT 1500.0 NOT NULL, -- SWC horizontal, crewed traffic swc_v_crewed_m float8 DEFAULT 150.0 NOT NULL, -- SWC vertical, crewed traffic swc_h_uncrewed_m float8 DEFAULT 150.0 NOT NULL, -- SWC horizontal, uncrewed traffic swc_v_uncrewed_m float8 DEFAULT 30.0 NOT NULL, -- SWC vertical, uncrewed traffic eval_distance_m float8 DEFAULT 1852.0 NOT NULL, -- Evaluation ring (~1 NM) -- ADS-B provisioning adsb_in_enabled bool DEFAULT false NOT NULL, adsb_out_enabled bool DEFAULT false NOT NULL, adsb_surrogate_enabled bool DEFAULT false NOT NULL, tisb_rebroadcast_enabled bool DEFAULT false NOT NULL, -- enables rebroadcast of this aircraft’s own telemetry to TIS-B -- Conspicuity / ADS-B detail — provisioning detail behind the flags above. ec_adsb_hex text NULL, -- static ADS-B ICAO hex; set when adsb_hex_mode = 'static' adsb_out_device text NULL, -- transponder model, e.g. Ping978ec | Ping2020i | SkyEcho 2 | TISB | other adsb_hex_mode text DEFAULT 'dynamic'::text NOT NULL, -- dynamic (ADDRt) | static (user-entered, see ec_adsb_hex) surveillance_mode text NULL, -- descriptive surveillance method, e.g. "MLAT + ADS-B", "ADS-B + Vision" daa_enabled bool DEFAULT false NOT NULL, -- Detect-and-Avoid active for this drone (provisioning intent β see daa_status below for outcome) broadcast_binding text NULL, -- e.g. Bound | Unbound — binding status between this drone and its broadcast identity networkid_streaming_enabled bool DEFAULT false NOT NULL, -- gates NetworkID into RTM viz + DAA; independent of tisb_rebroadcast_enabled -- Observed ADS-B state (read-only) — from surveillance feeds, not the provisioning intent above adsb_observed_broadcasting bool NULL, adsb_observed_last_icao_hex text NULL, adsb_observed_last_seen_by_cifib_at timestamptz NULL, -- last seen by the CIFIB upstream gating pipeline -- DAA verification (v2.0) — the Custodian's fourth obligation; see Β§2, Β§3.7, Β§9 daa_status text NULL, -- FULL | DEGRADED | NO_QUALIFIED_SURVEILLANCE current_avoidance_maneuver_id text NULL, -- FK to the AMQP avoidance_maneuver message's alert_id, while active current_avoidance_state text NULL, -- NONE | COMMANDED | EXECUTING | CONFIRMED | MISMATCH | CLEARED last_daa_verification_at timestamptz NULL, -- last time the Custodian compared Runner telemetry against a published command -- Ownership flag (see Β§8) ownership_flag_enabled bool DEFAULT false NOT NULL, -- Data source monitoring — the Custodian’s vigilance obligation; see Β§3.6 data_source_mechanism text NULL, -- mavlink_runner | dji_runner | dji_cloud | adsb | multi data_source_rate_hz float8 NULL, data_source_latency_ms_p50 float8 NULL, data_source_latency_ms_p95 float8 NULL, data_source_status text NULL, -- HEALTHY | DEGRADED | STALE | DISCONNECTED data_source_max_latency_ms int4 DEFAULT 2000 NOT NULL, -- expected envelope data_source_last_seen_at timestamptz NULL, -- Current flight — the mission this drone is executing right now. -- Populated by FLYsafe.software on mission publish; progress fields updated by the Custodian from telemetry. current_mission_id text NULL, current_flight_state text NULL, -- PLANNED | ACTIVE | COMPLETED | ABORTED current_flight_window_start timestamptz NULL, current_flight_window_end timestamptz NULL, current_waypoint_index int4 NULL, current_waypoint_total int4 NULL, current_progress_pct int4 NULL, -- Current OI — the currently active Operational Intent for this drone. -- Written by the Flink OI Automation job on every generation/regeneration. current_oi_id text NULL, current_oi_state text NULL, -- DRAFT | SUBMITTED | ACCEPTED | ACTIVE | COMPLETED | REJECTED current_oi_source text NULL, -- AUTOMATED | MANUAL | MISSION_PUBLISH current_oi_window_start timestamptz NULL, current_oi_window_end timestamptz NULL, oi_last_regenerated_at timestamptz NULL, oi_next_regeneration_at timestamptz NULL, -- Live state (Custodian writes these from Autopilot Runner telemetry) position_lat float8 NULL, position_lon float8 NULL, position_alt float8 NULL, flight_mode text NULL, armed bool NULL, battery_remaining_pct int4 NULL, link_rssi_dbm int4 NULL, adapter_connected bool DEFAULT false NOT NULL, last_telemetry_at timestamptz NULL, -- Cross-references aadms_id text NULL, -- cross-tenant identity reference -- Timestamps created_at timestamptz DEFAULT now() NOT NULL, updated_at timestamptz DEFAULT now() NOT NULL, retired_at timestamptz NULL, CONSTRAINT drones_pkey PRIMARY KEY (tenant_id, serial_number), CONSTRAINT drones_dlb_guid_fk FOREIGN KEY (dlb_guid) REFERENCES drones.dlb_drones(guid), CONSTRAINT drones_dlb_guid_unique UNIQUE (dlb_guid) ); CREATE INDEX idx_drones_serial ON drones.drones USING btree (serial_number); CREATE INDEX idx_drones_daa_status ON drones.drones USING btree (daa_status) -- v2.0 WHERE daa_status IS NOT NULL;
daa_enabled and the new DAA verification columns. daa_enabled is the per-drone provisioning intent — whether DAA is turned on for this aircraft at all — and drones.daa_rules (below) is the ordered rule set the Conspicuity/DAA panel edits. daa_status, current_avoidance_maneuver_id, current_avoidance_state, and last_daa_verification_at are downstream of both: they record the outcome of DAA being active — what RTM Core commanded, what state the Runner reached, and whether the Custodian's independent check confirmed it.
drones.v_dtwin)Read-only view that joins drones.drones with drones.dlb_drones on the dlb_guid foreign key. This is the authoritative dTwin data record — the single consistent view consumers should use when they need both RTM state and DLB identity together. The Custodian APIs serve responses that reflect this aggregated record, now including the DAA verification block.
-- drones.v_dtwin Β· /tenant/rtm-app-db/schema/v_dtwin.sql -- dTwin view: joins drones.drones (RTM state) with drones.dlb_drones (DLB identity). CREATE OR REPLACE VIEW drones.v_dtwin AS SELECT d.tenant_id, d.serial_number, d.dlb_guid, -- DLB identity (from dlb_drones; renamed for clarity) dlb.name AS drone_name, dlb.brand, dlb.model, dlb.drone_type, dlb.status, dlb.color, dlb.identification_number AS "TC_Registration", dlb.inventory_number AS "Callsign", dlb.hardware_version, dlb.firmware_version, dlb.max_horizontal_speed, dlb.max_vertical_speed, dlb.max_flight_time, dlb.payload_capacity, dlb.weight, dlb.propulsion_type, -- Custodian configuration, OI defaults, pilot & separation defaults, ADS-B provisioning d.adapter_type, d.adapter_config, d.publish_rate_hz, d.oi_automation_enabled, d.horizontal_speed_ms, d.vertical_speed_ms, d.fv_buffer_m, d.cv_buffer_m, d.oi_automation_square_m, d.oi_automation_radius_m, d.oi_automation_amend_min_trigger_m, d.oi_automation_amend_max_alt_agl_m, d.oi_automation_amend_enabled, d.waypoint_buffer_m, d.default_pilot_name, d.default_pilot_fs_guid, d.nmac_h_crewed_m, d.nmac_v_crewed_m, d.nmac_h_uncrewed_m, d.nmac_v_uncrewed_m, d.swc_h_crewed_m, d.swc_v_crewed_m, d.swc_h_uncrewed_m, d.swc_v_uncrewed_m, d.eval_distance_m, d.adsb_in_enabled, d.adsb_out_enabled, d.adsb_surrogate_enabled, d.tisb_rebroadcast_enabled, d.ec_adsb_hex, d.adsb_out_device, d.adsb_hex_mode, d.surveillance_mode, d.daa_enabled, d.broadcast_binding, d.networkid_streaming_enabled, d.adsb_observed_broadcasting, d.adsb_observed_last_icao_hex, d.adsb_observed_last_seen_by_cifib_at, -- DAA verification (v2.0) β see Β§2, Β§3.7, Β§9 d.daa_status, d.current_avoidance_maneuver_id, d.current_avoidance_state, d.last_daa_verification_at, d.ownership_flag_enabled, -- Data source monitoring β see Β§3.6 d.data_source_mechanism, d.data_source_rate_hz, d.data_source_latency_ms_p50, d.data_source_latency_ms_p95, d.data_source_status, d.data_source_max_latency_ms, d.data_source_last_seen_at, -- Current flight & current OI d.current_mission_id, d.current_flight_state, d.current_flight_window_start, d.current_flight_window_end, d.current_waypoint_index, d.current_waypoint_total, d.current_progress_pct, d.current_oi_id, d.current_oi_state, d.current_oi_source, d.current_oi_window_start, d.current_oi_window_end, d.oi_last_regenerated_at, d.oi_next_regeneration_at, -- Live state d.position_lat, d.position_lon, d.position_alt, d.flight_mode, d.armed, d.battery_remaining_pct, d.link_rssi_dbm, d.adapter_connected, d.last_telemetry_at, -- Cross-references & timestamps d.aadms_id, d.created_at, d.updated_at, d.retired_at FROM drones.drones d JOIN drones.dlb_drones dlb ON dlb.guid = d.dlb_guid;
aadms.drones)Cross-tenant drone lookup table used by the ADS-B routing pipeline. A materialized view that aggregates drones.drones (joined to dlb_drones for callsign) from every tenant schema into a single flat surface. ADS-B records arrive carrying only callsign; AADMS resolves callsign → (tenant_id, serial_number). Unchanged from v1.13.
-- aadms.drones Β· cross-tenant drone lookup CREATE SCHEMA IF NOT EXISTS aadms; CREATE MATERIALIZED VIEW aadms.drones AS SELECT d.tenant_id, d.serial_number, d.dlb_guid AS guid, dlb.inventory_number AS callsign, d.aadms_id AS id FROM tenant_airmarket.drones.drones d JOIN tenant_airmarket.drones.dlb_drones dlb ON dlb.guid = d.dlb_guid WHERE d.retired_at IS NULL UNION ALL SELECT d.tenant_id, d.serial_number, d.dlb_guid AS guid, dlb.inventory_number AS callsign, d.aadms_id AS id FROM tenant_sait.drones.drones d JOIN tenant_sait.drones.dlb_drones dlb ON dlb.guid = d.dlb_guid WHERE d.retired_at IS NULL -- ... repeat for each tenant ... ; CREATE UNIQUE INDEX idx_aadms_drones_callsign ON aadms.drones (callsign); CREATE INDEX idx_aadms_drones_serial ON aadms.drones (serial_number);
REFRESH MATERIALIZED VIEW CONCURRENTLY aadms.drones at the end of each Import sync run. DEV and PROD use separate AADMS schemas so refreshes are independent.
fv_buffer_m/cv_buffer_m are configuration — a single horizontal buffer distance. The three computed geometries (Flight Volume, Contingency Volume, Submitted Volume) for current and upcoming flights are persisted in the flights_binding table, outside the drones schema — not in a drones.oi_geometry table (that was specified in early drafts but never built). See Β§12 for the API that computes these three geometries.
drones.daa_rules)One row per Detect-and-Avoid rule per drone — the ordered rule set the Conspicuity/DAA panel edits as daa_rules[]. Rules are evaluated in sort_order; each can be individually enabled/disabled without deleting it. Unchanged from v1.13.
-- drones.daa_rules Β· one row per DAA rule per drone -- Read/written by the Custodian API's DAA endpoints; evaluated by the DAA service at runtime. CREATE TABLE drones.daa_rules ( tenant_id text NOT NULL, serial_number text NOT NULL, rule_id text NOT NULL, -- e.g. R1, R2, R3 condition_text text NOT NULL, -- human-readable trigger condition, e.g. "Target within NMAC radius" action_text text NOT NULL, -- action taken when triggered, e.g. "RTH β Immediate" priority text NOT NULL, -- critical | high | medium | low enabled bool NOT NULL DEFAULT true, sort_order int4 NOT NULL DEFAULT 0, -- evaluation / display order PRIMARY KEY (tenant_id, serial_number, rule_id), FOREIGN KEY (tenant_id, serial_number) REFERENCES drones.drones(tenant_id, serial_number) );
drones.dlb_drones and drones.drones work togetherdrones.dlb_drones is the primary store for all raw DLB API data, written by the Import process and never touched by RTM. drones.drones holds only RTM state — configuration, live telemetry, DAA verification, and the dlb_guid foreign key. Three distinct processes write to these tables at different times; each owns specific columns and must not write to columns owned by another.
drones.dlb_drones. On first insert, also creates the corresponding row in drones.drones with dlb_guid and seeds the two speed defaults.
position_*, flight_mode, armed, battery_remaining_pct, adapter_connected, last_telemetry_at) and the DAA verification columns whenever it cross-checks a POST /avoidance/report from the Runner against that same telemetry (daa_status, current_avoidance_state, last_daa_verification_at). Both are Custodian-authored writes triggered by Runner input — neither the Import process nor operators write these columns directly.
| Category | Primary store | Writer | Examples |
|---|---|---|---|
| DLB identity | drones.dlb_drones |
Import script (Steve Smith) | name, brand, model, identification_number, inventory_number |
| RTM-managed | drones.drones |
Custodian APIs (operator UI, MCP agents) | adapter_type, oi_automation_enabled, NMAC/SWC/EVAL minima, ADS-B flags, ownership_flag_enabled |
| Live state | drones.drones |
Custodian, from Runner telemetry | position_lat/lon/alt, flight_mode, armed, adapter_connected, last_telemetry_at |
| DAA verification New in v2.0 | drones.drones |
Custodian, cross-referencing Runner's avoidance/report against telemetry |
daa_status, current_avoidance_maneuver_id, current_avoidance_state, last_daa_verification_at |
drones.dlb_drones is overwritten on every sync run. drones.drones is only touched by the Import process on first insert. RTM-managed, live-state, and DAA-verification columns are never in scope for the Import process.
The dTwin is the conceptual digital twin of one aircraft — the canonical answer to “what does FLYsafe.live believe is true about this drone right now?” It is the v_dtwin record for that drone. This is the complete GET /drones/{serial} response — every block, including the new daa block.
{
"identity": {
"serial_number": "1581F5BBB1F2A",
"tc_registration": "C-FAIRTL69",
"callsign": "AIRTL69",
"tenant": "airmarket",
"adapter_type": "mavlink"
},
"position": { "lat": 53.5461, "lon": -113.4938, "alt_msl_m": 720.4 },
"flight_mode": "AUTO",
"armed": true,
"battery": { "remaining_pct": 68 },
"adsb": {
"in_enabled": true, "out_enabled": true,
"surrogate_enabled": false, "ownership_flagged": true
},
"pilot": { "default_name": "Jane Chen", "default_fs_guid": "pilot-8f3a2e19-4c1a-4e6b-9c17-2b9e5d3f7a0c" },
"oi": {
"automation_enabled": true, "default_distance_m": 300, "waypoint_buffer_m": 50.0
},
"volumes": { "flight_volume": { "buffer_m": 56.0 }, "contingency_volume": { "buffer_m": 150.0 } },
"separation": {
"nmac_crewed_m": { "h": 150.0, "v": 30.0 },
"nmac_uncrewed_m": { "h": 15.0, "v": 8.0 },
"swc_crewed_m": { "h": 1500.0, "v": 150.0 },
"swc_uncrewed_m": { "h": 150.0, "v": 30.0 },
"eval_distance_m": 1852.0
},
"data_source": {
"mechanism": "mavlink_runner", "rate_hz": 4.0,
"latency_ms_p50": 180, "latency_ms_p95": 420, "max_latency_ms": 2000,
"status": "HEALTHY", "last_seen_at": "2026-09-05T14:22:18.315Z"
},
"current_flight": {
"mission_id": "mission-airmarket-2026090514200", "state": "ACTIVE",
"window": { "start": "2026-09-05T14:00:00Z", "end": "2026-09-05T14:45:00Z" },
"waypoints": { "current_index": 5, "total": 12 }, "progress_pct": 42
},
"current_oi": {
"oi_id": "oi-airmarket-2026090514201", "state": "ACTIVE", "source": "AUTOMATED",
"last_regenerated_at": "2026-09-05T14:21:15Z", "next_regeneration_at": "2026-09-05T14:21:45Z",
"geometry_ref": {
"flight_volume": "/api/v1/drones/1581F5BBB1F2A/oi/current/flight-volume",
"contingency_volume": "/api/v1/drones/1581F5BBB1F2A/oi/current/contingency-volume",
"submitted_volume": "/api/v1/drones/1581F5BBB1F2A/oi/current"
}
},
"daa": { // v2.0 β the Custodian's verification obligation, Β§2/Β§3.7
"enabled": true, // drones.drones.daa_enabled
"status": "FULL", // FULL | DEGRADED | NO_QUALIFIED_SURVEILLANCE
"active_maneuver": {
"maneuver_id": "alert-7f2a9c31-4e5b-4c8a-9d21-8b6f3e2a1c04",
"state": "CONFIRMED", // NONE | COMMANDED | EXECUTING | CONFIRMED | MISMATCH | CLEARED
"commanded_at": "2026-09-05T14:22:03.100Z",
"verified_at": "2026-09-05T14:22:04.850Z"
},
"last_verification_at": "2026-09-05T14:22:04.850Z"
},
"adapter_health": { "connected": true, "last_publish_ago_ms": 247 },
"timestamps": { "updated_at": "2026-09-05T14:22:18.523Z", "source_ts": "2026-09-05T14:22:18.491Z" }
}
β Returned by GET /drones/{serial}. There is no canonical “dTwin payload schema” separate from the API response — the response is the schema. Cheaper subset endpoints (/state, /current-flight, /current-oi, /data-source, /daa-status) exist for consumers that don’t want the full payload every time.
/daa-status/watch, v2.0). The system of record is the Postgres row.
This is the complete catalog of API operations the Cloud Custodian exposes, categories A through K, fully specified. The same APIs are consumed by:
All endpoints are tenant-scoped (the tenant is resolved from the JWT). The base path is /api/v1. Verbs use REST conventions: GET reads, POST creates/triggers, PUT updates, DELETE removes. Where a notification stream is useful, a WebSocket endpoint is offered as a parallel option to polling.
drones.drones.
| Cat. | Category | Covers |
|---|---|---|
| A | FLYplan & OI Automation | Automated OI creation, per-drone OI defaults, OI automation toggling, FV/CV buffers, waypoint-arrival buffer. |
| B | ADS-B Provisioning | In/Out/surrogate flags, TIS-B rebroadcast, observed ADS-B status. |
| C | Ownership Flag | Operator's surveillance-intent flag for own-aircraft tagging. |
| D | Custodian Configuration | Adapter type, Runner endpoints, publish rate, OI defaults bundled as the Runner's boot config. |
| E | State Read & Telemetry Ingestion | Full/partial state reads; the ingestion endpoint the Autopilot Runner posts to. |
| F | Mission Binding | Binds an active mission to a drone for an OI window. |
| G | Identity & Cross-Tenant Routing | Keycloak registration, AADMS publication. |
| H | Health & Lifecycle | Custodian health probe; retire/restore. |
| I | Pilot & Separation Defaults | Default pilot binding; NMAC/SWC/EVAL minima. |
| J | Data Source Monitoring | Read-only vigilance state surface. |
| K | DAA Verification & Avoidance Status New in v2.0 | Tiered DAA status, active-maneuver state, and post-flight avoidance-event history from the Custodian's verification loop (Β§3.7). |
Operations supporting the FLYplan workflow — automated OI creation, per-drone OI defaults, OI automation toggling, FV/CV volume buffers, and the waypoint-arrival buffer.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/oi-automation | Read the OI Automation status for a drone (enabled, defaults, last regeneration time). | Frontend Β· MCP |
| PUT/drones/{serial}/oi-automation | Enable or disable OI Automation for this drone. Body: { enabled: bool }. | Frontend Β· MCP |
| PUT/drones/{serial}/oi-defaults | Set OI default parameters: horizontal_speed_ms, vertical_speed_ms, oi_automation_square_m, oi_automation_radius_m, oi_automation_amend_min_trigger_m. | Frontend |
| GET/drones/{serial}/oi-defaults | Read the current OI defaults. | Frontend Β· MCP Β· Runner (boot) |
| GET/drones/{serial}/volumes | Read the drone’s FV and CV buffer defaults: { flight_volume: { buffer_m }, contingency_volume: { buffer_m } }. | Frontend Β· MCP Β· Flink Β· Runner (boot) |
| PUT/drones/{serial}/volumes | Set FV and/or CV buffer defaults. Server-side clamp enforces fv_buffer_m ≤ cv_buffer_m. | Frontend |
| GET/drones/{serial}/waypoint-buffer | Read the waypoint-arrival buffer in metres (default 50 m). | Frontend Β· MCP Β· Flink |
| PUT/drones/{serial}/waypoint-buffer | Set the waypoint-arrival buffer. Server-side clamp: 5–500 m. | Frontend |
| POST/drones/{serial}/oi/regenerate | Force-regenerate the OI now. Backend/ops-tooling only. | MCP |
| GET/drones/{serial}/oi/current | Fetch the Submitted Volume — the GeoJSON polygon combining FV and CV, sent to FLYrtm DSS. | Frontend Β· Flink |
| GET/drones/{serial}/oi/current/flight-volume | Fetch the Flight Volume geometry alone as its own GeoJSON polygon. | Frontend (Map) Β· Compliance AI |
| GET/drones/{serial}/oi/current/contingency-volume | Fetch the Contingency Volume geometry alone. Always contains the Flight Volume. | Frontend (Map) Β· Compliance AI |
| GET/drones/{serial}/current-oi | Fetch the current OI summary: { oi_id, state, source, window, last_regenerated_at, next_regeneration_at, geometry_ref }. | Frontend Β· MCP Β· Compliance AI |
| WS/drones/{serial}/current-oi/watch | Subscribe to current-OI state transitions and regeneration events. | Frontend Β· OCC |
| GET/drones/{serial}/oi/history | List previous OI generations, including all three stored geometries per generation. | Compliance AI Β· Evidence |
oi_automation_square_m, oi_automation_radius_m, and oi_automation_amend_min_trigger_m were tuned together and must scale together — valid range for each is 0.5× to 2.0× its default. The frontend presents them as a single factor slider rather than three independent inputs.
Per-drone ADS-B configuration.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/adsb | Read ADS-B configuration block: in_enabled, out_enabled, surrogate_enabled. | Frontend Β· MCP Β· Custodian |
| PUT/drones/{serial}/adsb | Update the full ADS-B configuration block in one call. | Frontend |
| PUT/drones/{serial}/adsb-surrogate | Toggle just the ADS-B Surrogate flag. | Frontend Β· MCP |
| PUT/drones/{serial}/tisb-rebroadcast | Toggle the TIS-B Rebroadcast flag. Body: { enabled: bool }. | Frontend Β· MCP |
| GET/drones/{serial}/adsb/status | Read the live ADS-B status from telemetry: is the transponder actually broadcasting? | Frontend Β· Compliance AI |
The operator’s explicit surveillance intent for one of their drones. When set, Flight_Live (Β§8) tags inbound records of this aircraft as own-aircraft.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/ownership | Read the current ownership flag state. | Frontend Β· Flight_Live |
| PUT/drones/{serial}/ownership | Set the ownership flag. Body: { enabled: bool, reason?: string }. | Frontend Β· MCP (permission-scoped) |
The configuration block the Autopilot Runner fetches at boot — adapter type, Runner endpoints, broker addresses, publish rate, and OI defaults bundled as one document.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/config | Runner boot config — adapter type, publish rate, ADS-B block, OI defaults, ownership flag, etc. | Runner (on boot) |
| GET/drones/{serial}/config?since={ts} | Returns config only if changed since the given timestamp. | Runner (watch loop) |
| WS/drones/{serial}/config/watch | WebSocket subscription that pushes config changes as they happen. | Runner (optional) |
| PUT/drones/{serial}/adapter-config | Update adapter-specific connection block (MAVLink host/port, DJI FH2 endpoint, DRC channel enablement). | Frontend |
| PUT/drones/{serial}/publish-rate | Change the telemetry publish rate (Hz). Runner hot-reloads. | Frontend Β· MCP |
State-read endpoints for consumers, and the ingestion endpoint the Autopilot Runner POSTs to.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones | List drones for the tenant. Supports filters (active flight, callsign substring, model). Paginated. | Frontend Β· MCP |
| GET/drones/{serial} | Full drone record — identity, config, current live state, DAA status. The canonical dTwin view. | All consumers |
| GET/drones/{serial}/state | Just the live-state subset without configuration noise. | Map (live layer) |
| GET/drones/{serial}/current-flight | Fetch the current-flight block: { mission_id, state, window, waypoints, progress_pct }. | Frontend Β· MCP Β· Compliance AI |
| WS/drones/{serial}/current-flight/watch | Subscribe to flight-state transitions and waypoint progress. | Frontend |
| GET/drones/{serial}/telemetry | Recent telemetry timeseries within a time range. Backed by InfluxDB. | Map (trails) Β· Evidence |
| WS/drones/{serial}/state/watch | Subscribe to live state updates for one drone. | Frontend (map live layer) |
| POST/drones/{serial}/telemetry | Ingestion endpoint. The Autopilot Runner POSTs normalized telemetry frames here. The Custodian writes the live-state columns and appends to InfluxDB. | Autopilot Runner (MAVLink, DJI) |
| POST/drones/{serial}/alerts | Runner-emitted alerts (mode change, RTL triggered, geofence breach, low battery). | Autopilot Runner Β· OCC AI |
Binds an active mission to a drone for the duration of an OI window.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/mission | Read the active mission binding. | Frontend Β· Flink Β· Compliance AI |
| POST/drones/{serial}/mission | Create a mission binding: mission_id, callsign, OI window, pilot_id. | FLYsafe.software (on mission publish) |
| DELETE/drones/{serial}/mission | Clear the active mission binding. | FLYsafe.software Β· Frontend |
Identity registration in Keycloak and cross-tenant publication into AADMS.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| POST/drones/{serial}/identity/keycloak | Register the drone as a user in Keycloak. ID format <tenant>-<serial>. Idempotent. | Import process Β· Custodian (boot) |
| POST/drones/{serial}/identity/aadms | Publish the drone’s (tenant, serial, callsign) tuple to AADMS. | Import process Β· Custodian |
| GET/drones/{serial}/identity | Inspect identity state. | Frontend (admin view) |
Health probes for the Custodian, and lifecycle events.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/health | Health of the Custodian for this drone: adapter_connected, last_telemetry_at, runner_session_id, runner_version. | OCC Β· Monitoring Β· Compliance AI |
| POST/drones/{serial}/retire | Mark drone as retired. Stops accepting telemetry, clears live state, archives the row. | Frontend (admin) |
| POST/drones/{serial}/restore | Restore a retired drone. | Frontend (admin) |
Per-drone default pilot binding and conflict-evaluation minima.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/default-pilot | Read the default pilot: { name, fs_guid }. | Frontend Β· MCP |
| PUT/drones/{serial}/default-pilot | Set the default pilot. fs_guid validated against FLYsafe.software’s pilot registry. | Frontend |
| DELETE/drones/{serial}/default-pilot | Clear the default pilot binding. | Frontend |
| GET/drones/{serial}/separation-defaults | Read per-drone NMAC, SWC, EVAL distances, split crewed/uncrewed × horizontal/vertical. | Frontend Β· MCP Β· Flink |
| PUT/drones/{serial}/separation-defaults | Set NMAC, SWC, and EVAL distances. Server-side validation enforces nmac < swc < eval. | Frontend |
Read-only surface for the vigilance state the Custodian maintains for each drone (Β§3.6).
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/data-source | Read the summary source-health block: { mechanism, rate_hz, latency_ms_p50, latency_ms_p95, max_latency_ms, status, last_seen_at }. | Frontend (Map) Β· OCC Β· MCP |
| PUT/drones/{serial}/data-source/envelope | Set the expected envelope: { max_latency_ms }. | Frontend (admin) |
| WS/drones/{serial}/data-source/watch | WebSocket that pushes status transitions as they happen. | OCC AI (live) |
| GET/drones/{serial}/data-source/history | Time-range query of status transitions and rate/latency samples. Feeds CAR 922 evidence. | Compliance AI Β· Evidence |
Read-only surface for the Custodian's Verification obligation (Β§2). Backs the tiered DAA status RTM Core consumes and gives operators/OCC/Compliance AI visibility into every avoidance event without polling raw telemetry.
| Endpoint | Purpose | Primary Caller |
|---|---|---|
| GET/drones/{serial}/daa-status | Read the current tiered DAA status block: { enabled, status, active_maneuver, last_verification_at }. status is FULL, DEGRADED, or NO_QUALIFIED_SURVEILLANCE. | Frontend (Map) Β· OCC Β· RTM Core Β· MCP |
| WS/drones/{serial}/daa-status/watch | Subscribe to current_avoidance_state transitions (COMMANDED → EXECUTING → CONFIRMED or MISMATCH → CLEARED) as the Custodian's verification loop observes them. | Frontend Β· OCC AI |
| GET/drones/{serial}/avoidance/history | Time-range query of past avoidance events: command received, Runner state reached, verification outcome, elapsed time to confirmation. Feeds CAR 922 evidence. | Compliance AI Β· Evidence Β· OCC |
| POST/drones/{serial}/avoidance/report | Ingestion endpoint. The Runner reports its own execution state here (COMMANDED acknowledged, EXECUTING, telemetry-confirmed CONFIRMED). The Custodian cross-checks this report against its own independent read of telemetry before writing current_avoidance_state — a Runner self-report alone is never sufficient to mark a maneuver CONFIRMED. | Autopilot Runner |
COMMAND_ACK alone is not trustworthy on MAVLink — a GUIDED setpoint outside a loaded fence can be silently rejected (Β§9). The Custodian only advances current_avoidance_state to CONFIRMED after independently correlating the Runner's report against the position/attitude telemetry it already receives via the Runner's normal telemetry stream (category E). A report that says EXECUTING but telemetry shows no heading/altitude change writes MISMATCH, not CONFIRMED.
The MCP server wraps a curated subset of the above as AI-callable tools. The principle is read freely, write narrowly: agents can read drone state, OI status, ownership state, identity, health, and DAA status without restriction; writes are scoped to operationally-safe toggles only.
| MCP tool name | Maps to | Read/Write | Notes |
|---|---|---|---|
drone.list | GET /drones | R | List drones in scope. |
drone.get | GET /drones/{serial} | R | Full record. |
drone.get_state | GET /drones/{serial}/state | R | Live state only. |
drone.get_telemetry | GET /drones/{serial}/telemetry | R | Time-range query. |
drone.set_oi_automation | PUT /drones/{serial}/oi-automation | W | Operationally safe. |
drone.set_oi_distance | PUT /drones/{serial}/oi-defaults | W | Numeric clamp applied. |
drone.set_adsb_surrogate | PUT /drones/{serial}/adsb-surrogate | W | Operationally safe. |
drone.set_ownership | PUT /drones/{serial}/ownership | W | Permission-scoped. |
drone.regenerate_oi | POST /drones/{serial}/oi/regenerate | W | Operationally safe. |
drone.get_health | GET /drones/{serial}/health | R | Used heavily by OCC AI. |
drone.get_default_pilot | GET /drones/{serial}/default-pilot | R | Prefill for mission creation. |
drone.set_default_pilot | PUT /drones/{serial}/default-pilot | W | Permission-scoped; validates against FLYsafe.software. |
drone.get_separation_defaults | GET /drones/{serial}/separation-defaults | R | NMAC / SWC (crewed/uncrewed × horizontal/vertical) / EVAL in metres. |
drone.set_separation_defaults | PUT /drones/{serial}/separation-defaults | W | Server-side clamp enforces nmac < swc < eval. |
drone.get_data_source | GET /drones/{serial}/data-source | R | Source-vigilance summary. High value for OCC AI. |
drone.get_data_source_history | GET /drones/{serial}/data-source/history | R | Post-flight connectivity review; feeds Compliance AI. |
drone.get_volumes | GET /drones/{serial}/volumes | R | FV and CV buffer defaults. |
drone.set_volumes | PUT /drones/{serial}/volumes | W | Server-side clamp enforces CV ≥ FV. |
drone.get_current_flight | GET /drones/{serial}/current-flight | R | Current mission state. |
drone.get_current_oi | GET /drones/{serial}/current-oi | R | Current OI summary. |
drone.get_submitted_volume | GET /drones/{serial}/oi/current | R | The polygon actually sent to DSS. Heavy payload. |
drone.get_flight_volume_geometry | GET /drones/{serial}/oi/current/flight-volume | R | FV polygon alone. |
drone.get_contingency_volume_geometry | GET /drones/{serial}/oi/current/contingency-volume | R | CV polygon alone. |
drone.get_waypoint_buffer | GET /drones/{serial}/waypoint-buffer | R | Radius in metres for waypoint arrival detection. |
drone.set_waypoint_buffer | PUT /drones/{serial}/waypoint-buffer | W | Server-side clamp: 5–500 m. |
drone.get_daa_status | GET /drones/{serial}/daa-status | R | High value for OCC AI β answers “is this drone's DAA coverage trustworthy right now?” v2.0 |
drone.get_avoidance_history | GET /drones/{serial}/avoidance/history | R | Post-flight avoidance review; feeds Compliance AI. v2.0 |
Operations not on this list (retire, identity registration, mission binding writes, adapter-config changes) are deliberately not exposed via MCP. POST /drones/{serial}/avoidance/report is likewise excluded — it is a machine-to-machine Runner-to-Custodian channel, not an operator or agent action.
Unchanged from v1.13. FLYsafe.live carries a drone's identity across four systems; mixing them up breaks the binding pipeline.
| System | Identity Key | Holds | Purpose |
|---|---|---|---|
| FLYsafe.software DLB | dlb_guid | Authoritative drone inventory | System of record for inventory |
| tenant.drones + dlb_drones β v_dtwin | (tenant_id, serial_number) | RTM-managed config + live state + DAA status + dlb_guid FK | The dTwin lives here |
| Keycloak.ΞΈ.Drone | <tenant>-<serial> | Drone-as-user authentication | Authentication only |
| AADMS | (tenant, serial) + callsign | Cross-tenant identity lookup | Cross-tenant routing |
Unchanged from v1.13 Β§7 β the automated creation and maintenance of Operational Intents. See that document for the full seven-step table (onboarding, pilot-granted OI Automation authority, mission publish, telemetry-driven state advance, OI regeneration, operator review, mission completion).
Unchanged from v1.13 Β§8. Flight_Live consults each drone's ownership_flag_enabled (read from the Custodian API) to tag incoming surveillance records as own-aircraft or external. Records are tagged, not filtered, at ingest β each downstream consumer applies its own filtering policy. See that document for the full decision record.
This section is folded in from FLYsafe Architecture β DAA, Custodian & Runner Strategy, grounded in the am-flyrtm-blender repository (a Flight Blender / InterUSS derivative). It is what RTM Core actually computes, and what the Autopilot Runner does with it.
DefaultAvoidanceEngine.compute_maneuver() doesn't apply a fixed rule — it generates several candidate maneuvers and predicts the outcome of each before picking a winner. This is what makes it rich avoidance.
insufficient_time, ground_risk_conflict, or envelope_limit when it isn't.The Runner isn't only a translator for RTM Core's commands β it's also a local sensor-aggregation and injection layer that keeps functioning when RTM Core isn't reachable at all.
| Tier | DJI | MAVLink / ArduPilot | Requires RTM Core link? | Who decides |
|---|---|---|---|---|
| 1 β Hardware floor | Native onboard vision/infrared obstacle detection. Universal, always on. | Optional Only present if the airframe has physical proximity sensors wired to ArduPilot's PRX parameters. |
No | Flight-controller firmware β physical-object stopgap only |
| 2 β Local sensor injection | Runner aggregates whatever's wired to the companion computer and synthesizes a sensor-contact injection feeding onboard avoidance logic locally. | Runner synthesizes an ADSB_VEHICLE message from aggregated sensor data and injects it into the MAVLink stream; AP_Avoidance reacts via its tuned AVD_* parameters. |
No β this is the no-connectivity fallback | Onboard avoidance logic (DJI) / AP_Avoidance (ArduPilot), reacting to Runner-fed sensor data |
| 3 β Cloud DAA | RTM Core's forward-simulated, geofence-checked maneuver, delivered over the DRC channel (DJI) or streamed Guided Mode setpoints (MAVLink). Definitive when available. | Yes | RTM Core (DefaultAvoidanceEngine) |
|
On the MAVLink side, the Runner has two mechanisms available to cause an avoidance maneuver. Which one runs is an operator-configurable deployment setting β FLYsafe's default deployment uses Guided Mode.
| Path A β ADS-B Injection Alternative | Path B β Guided Mode FLYsafe Default | |
|---|---|---|
| Mechanism | Runner synthesizes an ADSB_VEHICLE message and injects it as a sensor contact. | Runner receives RTM Core's computed maneuver and streams SET_POSITION_TARGET_GLOBAL_INT at heartbeat rate. |
| Who decides | ArduPilot's own AP_Avoidance, using its tuned AVD_* parameters. | RTM Core β already checked against ground risk and geofences. |
| Known risk | Depends entirely on ArduPilot's own tuning being correct. | GUIDED setpoints outside a loaded fence can be silently rejected β COMMAND_ACK alone is not trustworthy; telemetry confirmation is mandatory (see Β§5K). |
Both protocol implementations follow the same shape: automatic entry, automatic exit β no manual confirmation gate by default, matching the precedent already set by UAvionix Casia.
| Stage | MAVLink / ArduPilot | DJI / Cloud API DRC Channel |
|---|---|---|
| Automatic entry | Runner commands GUIDED mode directly. | Cloud sends cloud_control_auth_request; aircraft reports is_cloud_control_auth = true. |
| Command delivery | Streamed SET_POSITION_TARGET_GLOBAL_INT at heartbeat rate. | Joystick-style near-real-time commands over the DRC live-control MQTT channel. |
| Operator visibility | Runner sends STATUSTEXT into the Mission Planner messages panel. | FlightHub 2 control-status flags only β Gap no free-text HUD equivalent, closed by the planned Chrome extension. |
| Automatic exit | Once RTM Core signals the conflict cleared, Runner commands mode back to AUTO. | Runner automatically releases DRC authority; DJI Dock's Resume Flight From Breakpoint resumes cleanly. |
ArduPilot is the muscle β dumb and reactive, doing exactly what any operator's ArduPilot does. FLYsafe's differentiation is the orchestration layer above it: RTM Core's DAA reasoning, delivery-and-trust verification through the Custodian, and the Runner's translation-and-confirmation logic that turns a cloud-level “avoid this” into a reliable, provably-executed action on a stock autopilot — without modifying the autopilot itself.
The Mission Planner Plugin (already built, Tim Tuxworth) injects STATUSTEXT-style DAA status directly into Mission Planner's messages panel. The planned FLYsafe.live RTM Chrome Extension (Maykon Ferrero) closes the equivalent gap for any browser-based GCS β augmenting the web page UI directly rather than integrating with a single vendor's API, so the same pattern extends to future web GCS platforms without rework.
All v1.13 lifecycle events (drone added in DLB, Runner installed, Custodian boots, telemetry arrives, OI automation granted, source degrades, drone armed/airborne, OI accepted, mission completes, drone retired) carry forward unchanged β see v1.13 Β§9. Two DAA-specific events are new in v2.0:
| Lifecycle Event | What Happens |
|---|---|
| Conflict detected, avoidance command issued | RTM Core publishes an avoidance_maneuver message to the flight declaration's fanout exchange. Custodian and Runner each receive their own copy. Custodian writes current_avoidance_state = COMMANDED, current_avoidance_maneuver_id. Runner begins execution immediately β no wait on the Custodian. |
| Custodian verifies Runner execution | Runner reports its own state via POST /drones/{serial}/avoidance/report; Custodian cross-checks against independently-observed telemetry. Match → current_avoidance_state = CONFIRMED, daa_status stays/returns to FULL. Mismatch → MISMATCH, daa_status = DEGRADED or NO_QUALIFIED_SURVEILLANCE, OCC alerted. Conflict clears → CLEARED. |
Tracked across the wiki subpages under TECH-002/FSL RTTP. Carried forward from both source documents.
command_type == "avoidance_maneuver".MISSION_TYPE_FENCE protocol code exists yet; the reference airframe shows FENCE_TOTAL -103 β no fence points loaded.Β§4 established that three geometries — Flight Volume, Contingency Volume, and the Submitted Volume — are stored in flights_binding and exposed via the Β§5A endpoints. This section specifies the API that actually computes those three polygons from a predicted trajectory and the drone's FV/CV buffer defaults. It does not yet exist; JC is building it. Everything below is the requirements contract for that work — not a description of a shipped API. Unchanged from v1.13.
Flink’s OI Automation job (FLYplan step 5, Β§7) predicts the trajectory and calls this API to get the three polygons back. Flink then submits the Submitted Volume to FLYrtm DSS and, on acceptance, is responsible for persisting all three geometries to flights_binding. This API’s job is purely geometric — trajectory + buffers in, three polygons out. It does not talk to DSS and does not write to Postgres itself.
| Endpoint | Purpose | Caller |
|---|---|---|
| POST/drones/{serial}/oi-geometry/compute | Compute the Flight Volume, Contingency Volume, and Submitted Volume polygons for one predicted trajectory. Stateless — does not write to flights_binding; the caller (Flink) persists the result there after DSS submission. | Flink (OI Automation job) |
{
"tenant_id": "airmarket",
"serial_number": "1581F5BBB1F2A",
"trajectory": [ // predicted path, ordered, ≥ 2 points
{ "lat": 53.5461, "lon": -113.4938, "alt_msl_m": 720.4, "t": "2026-09-05T14:22:00Z" },
{ "lat": 53.5480, "lon": -113.4901, "alt_msl_m": 725.1, "t": "2026-09-05T14:23:30Z" }
// ...
],
"fv_buffer_m": 56.0, // horizontal only
"cv_buffer_m": 150.0
}
{
"flight_volume": { "type": "Polygon", "coordinates": [ /* GeoJSON PolygonZ */ ] },
"contingency_volume": { "type": "Polygon", "coordinates": [ /* GeoJSON PolygonZ */ ] },
"submitted_volume": { "type": "Polygon", "coordinates": [ /* GeoJSON PolygonZ — see open question below */ ] },
"computed_at": "2026-09-05T14:22:18.600Z"
}
| Requirement | Detail |
|---|---|
| FV polygon | Horizontal only: the trajectory linestring expanded outward by fv_buffer_m. The polygon keeps the trajectory’s own altitude at each vertex (PolygonZ) — there is no vertical buffer expansion. Vertical separation from other traffic is handled by the NMAC/SWC crewed/uncrewed minima (Β§5I), not by FV/CV. |
| CV polygon | Same construction as FV, using cv_buffer_m instead. Must fully contain the FV polygon horizontally — the API should validate this and reject (422) any input where cv_buffer_m < fv_buffer_m. |
| Submitted Volume | The single polygon that is the combination of FV and CV, structured however FLYrtm DSS’s OI submission format requires it (see open question below). This is the only one of the three actually transmitted to DSS. |
| Determinism | Same trajectory + same buffers must always produce the same three polygons, byte-for-byte. Regeneration diffing (Β§7 step 5) depends on this. |
| Latency | Called on every OI regeneration tick, so this must return well within Flink’s regeneration cadence. Target: p95 < 200 ms per call. |
| No side effects | Purely a compute step — must not write to flights_binding, must not call DSS, must not read/write any drone state. Keeps the geometry math independently testable. |
submitted_volume actually contains and must be confirmed against the DSS integration spec before this is built.flights_binding's geometry columns) and how many decimal places of lat/lon precision are required for DSS acceptance.PolygonZ — FV/CV no longer buffer vertically, but the polygon still carries the trajectory’s real altitude at each vertex.