[AZ-649] [AZ-674] [AZ-667] telemetry + vlm schema + mapobjects hydrate batch 6

AZ-649 mission_executor telemetry forwarding:
- shared::models::telemetry::UavTelemetry canonical model
- TelemetryForwarder with atomic ArcSwap snapshot + 3 lossy
  tokio::sync::broadcast channels (MissionExecutor, ScanController,
  MavlinkUplink) + per-consumer drop counters
- MavlinkProjection::from_mavlink for HEARTBEAT/GLOBAL_POSITION_INT/
  ATTITUDE/SYS_STATUS
- spawn_mavlink_pump bridges mavlink_layer into the forwarder at the
  binary edge

AZ-674 vlm_client schema validation + model_version tracking:
- AssessmentParser owns schema validation + model-version state
- wire::read_response_raw splits raw bytes from parsing so invalid
  payloads can be logged size-capped
- VlmStatus gains an Inconclusive variant; exhaustive-match test
  guards downstream consumers
- VlmPipelineStatus mirrors the new variant in shared::models::poi

AZ-667 mapobjects_store hydrate + pending logs + cascade:
- SyncState enum aligned with description.md (FreshBoot, Synced,
  CachedFallback, Degraded, Failed)
- Store::hydrate(MapObjectsBundle) replaces in-memory map atomically;
  freshness=Stale -> CachedFallback
- classify() + end_of_pass append MapObjectObservation events to
  pending_observations (New/Moved/Existing/RemovedCandidate)
- apply_decline + LocalAppended ignored items append to pending_ignored
- drain_pending() returns and clears both logs
- cascade_mission(id) purges by_cell + IgnoredSet + pending logs
- Health surface reports sync_state, pending_obs, pending_ign

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-19 17:40:43 +03:00
parent b5cc0c321c
commit e56d428753
26 changed files with 2122 additions and 62 deletions
+46
View File
@@ -33,6 +33,9 @@ use shared::models::mission::{Coordinate, MissionItem, MissionWaypoint};
mod internal;
pub use internal::driver::{DriverError, MissionDriver};
pub use internal::telemetry::{
Consumer, DropCountingReceiver, MavlinkProjection, TelemetryForwarder,
};
pub use internal::types::{
MissionState, StepOutcome, Telemetry, TransitionEvent, TransitionKey, Variant,
};
@@ -267,6 +270,49 @@ impl HealthDetail for ComponentHealth {
}
}
/// Spawn a task that subscribes to `mavlink_handle.subscribe_inbound()`
/// and republishes every telemetry-bearing message through
/// `forwarder`. Returns the task handle.
///
/// Non-telemetry MAVLink messages (mission protocol, command acks,
/// status text, etc.) are intentionally ignored — they are consumed
/// by other paths inside `mavlink_layer` (`send_command` demux,
/// `mission_client` pull, …).
///
/// `RecvError::Lagged(n)` on the inbound subscription is treated as
/// a hard drop on this side too: we log `n` skipped frames at warn
/// level (the forwarder doesn't even see them) and continue. The
/// forwarder's downstream channels are independent and unaffected.
pub fn spawn_mavlink_pump(
mavlink_handle: mavlink_layer::MavlinkHandle,
forwarder: Arc<TelemetryForwarder>,
) -> JoinHandle<()> {
let mut rx = mavlink_handle.subscribe_inbound();
tokio::spawn(async move {
loop {
match rx.recv().await {
Ok(inbound) => {
if let Some(projection) = MavlinkProjection::from_mavlink(&inbound.message) {
forwarder.publish_from_mavlink(&projection);
}
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
tracing::warn!(
skipped = n,
"mission_executor telemetry pump lagged on mavlink inbound stream"
);
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
tracing::info!(
"mission_executor telemetry pump: mavlink inbound stream closed"
);
return;
}
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;