[AZ-643] [AZ-665] [AZ-672] mavlink+mapobjects+vlm batch 4
ci/woodpecker/push/build-arm Pipeline failed

AZ-643 mavlink_layer:
- ack demux on COMMAND_LONG/COMMAND_ACK with oneshot dispatch and
  configurable deadline; MavlinkHandle::send_command + SendCommandError
- MAVLink-2 signing: Signer/Verifier built on SHA-256, key + timestamp
  source, incompat-flag wiring in encoder, reject + counter in decoder
- new tests: tests/ack_demux.rs (3) + tests/signing.rs (5)

AZ-665 mapobjects_store:
- internal/h3_index.rs (h3o wrapper, cell_of, grid_disk, haversine)
- internal/store.rs (in-memory (cell -> Vec<MapObject>) hashmap with
  k-ring classify and class-group resolution)
- public API: MapObjectsStoreHandle::classify(ClassifyInput) ->
  Classification {New|Moved|Existing}
- AC1-4 in tests/classify.rs; AC5 perf gate (#[ignore], passes in
  --release)

AZ-672 vlm_client + autopilot:
- DisabledVlmProvider in shared::contracts; VlmProvider::name() for
  composition-root diagnostics
- vlm_client::VlmClient gated behind feature = "vlm"; placeholder
  until AZ-673 lands the real NanoLLM IPC
- autopilot: vlm_client is now optional = true under feature vlm;
  Runtime::select_vlm_provider picks DisabledVlmProvider when feature
  off OR config.vlm.enabled = false

Workspace deps: +sha2 (mavlink signing), +h3o (mapobjects index).
Batch report: _docs/03_implementation/batch_04_cycle1_report.md

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-19 13:31:42 +03:00
parent 0a87c0f716
commit 69c0629350
29 changed files with 2492 additions and 131 deletions
+78
View File
@@ -0,0 +1,78 @@
//! Feature-gated entry point. Compiled only when `--features vlm` is on.
//!
//! AZ-672 installs the trait + a placeholder constructor; the real IPC
//! body lands in AZ-673 (`vlm_client_nanollm_ipc`). Until then `assess`
//! returns `VlmAssessment::disabled()` so the runtime can be wired
//! end-to-end without a working NanoLLM peer.
use async_trait::async_trait;
use shared::contracts::VlmProvider;
use shared::error::Result;
use shared::health::ComponentHealth;
use shared::models::vlm::VlmAssessment;
use super::PROVIDER_NAME;
#[derive(Debug, Clone)]
pub struct VlmClient {
ipc_socket: String,
}
impl VlmClient {
/// Construct the feature-enabled client. Until AZ-673 lands, the
/// returned instance still answers `assess` with the disabled
/// no-op assessment — the difference vs `DisabledVlmProvider` is
/// that this socket address has been validated and the IPC
/// connection will be established here in AZ-673.
pub fn new(ipc_socket: impl Into<String>) -> Self {
Self {
ipc_socket: ipc_socket.into(),
}
}
pub fn ipc_socket(&self) -> &str {
&self.ipc_socket
}
pub fn health(&self) -> ComponentHealth {
// Until AZ-673 connects, we surface yellow with the configured
// socket so the operator sees the build *did* enable VLM but
// the IPC peer is not yet wired.
ComponentHealth::yellow(PROVIDER_NAME, format!("ipc_pending: {}", self.ipc_socket))
}
}
#[async_trait]
impl VlmProvider for VlmClient {
async fn assess(&self, _roi: Vec<u8>, _prompt: String) -> Result<VlmAssessment> {
// Real IPC call lands in AZ-673. Returning disabled keeps the
// runtime end-to-end exercisable until that task completes.
Ok(VlmAssessment::disabled())
}
fn name(&self) -> &'static str {
PROVIDER_NAME
}
}
#[cfg(test)]
mod tests {
use super::*;
use shared::models::vlm::VlmStatus;
#[tokio::test]
async fn placeholder_assess_returns_disabled_until_az_673() {
// Arrange
let c = VlmClient::new("/run/vila/ipc.sock");
// Act
let r = c
.assess(Vec::new(), String::new())
.await
.expect("placeholder path is infallible");
// Assert
assert_eq!(r.status, VlmStatus::Disabled);
assert_eq!(c.name(), "vlm_client");
assert_eq!(c.ipc_socket(), "/run/vila/ipc.sock");
}
}
+16 -65
View File
@@ -1,72 +1,23 @@
//! `vlm_client` — optional Tier 3 NanoLLM/VILA Visual-Language-Model client.
//!
//! Default impl (`feature = "vlm"` OFF) returns `VlmAssessment::disabled()`.
//! Real IPC path lands in:
//! - AZ-672 `vlm_client_provider_trait`
//! With the `vlm` Cargo feature **off**, this crate exports nothing
//! beyond a constant noting the disabled state. The composition root
//! installs `shared::contracts::DisabledVlmProvider` in that case and
//! never references `vlm_client::VlmClient`.
//!
//! With the `vlm` feature **on**, `VlmClient` is the real NanoLLM IPC
//! client. The IPC plumbing itself lands in:
//! - AZ-673 `vlm_client_nanollm_ipc`
//! - AZ-674 `vlm_client_schema_and_model_version`
//!
//! AZ-672 only wires the trait contract + feature flag.
use async_trait::async_trait;
#[cfg(feature = "vlm")]
mod enabled;
use shared::contracts::VlmProvider;
use shared::error::Result;
use shared::health::ComponentHealth;
use shared::models::vlm::VlmAssessment;
#[cfg(feature = "vlm")]
pub use enabled::VlmClient;
const NAME: &str = "vlm_client";
#[derive(Debug, Clone, Default)]
pub struct VlmClient {
enabled: bool,
}
impl VlmClient {
/// Construct the no-op `VlmClient`. Returns `VlmAssessment::disabled()`
/// from every `assess()` call.
pub fn with_default() -> Self {
Self { enabled: false }
}
#[cfg(feature = "vlm")]
pub fn enabled() -> Self {
Self { enabled: true }
}
pub fn health(&self) -> ComponentHealth {
if self.enabled {
ComponentHealth::green(NAME)
} else {
ComponentHealth::disabled(NAME)
}
}
}
#[async_trait]
impl VlmProvider for VlmClient {
async fn assess(&self, _roi: Vec<u8>, _prompt: String) -> Result<VlmAssessment> {
// Disabled path always returns the documented no-op assessment.
// The real path lands in AZ-673.
Ok(VlmAssessment::disabled())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn default_impl_returns_disabled_assessment() {
// Arrange
let c = VlmClient::with_default();
// Act
let result = c
.assess(Vec::new(), String::new())
.await
.expect("disabled path is infallible");
// Assert
assert_eq!(result.status, shared::models::vlm::VlmStatus::Disabled);
assert_eq!(result.label, shared::models::vlm::VlmLabel::Inconclusive);
}
}
/// Stable name used by tracing + `/health` to identify this crate's
/// build-time configuration. Mirrors `VlmProvider::name()`.
pub const PROVIDER_NAME: &str = "vlm_client";