# Batch Report **Batch**: 4 **Tasks**: AZ-643 `mavlink_ack_demux_and_signing`, AZ-665 `mapobjects_store_h3_classify`, AZ-672 `vlm_client_provider_trait` **Date**: 2026-05-19 **Cycle**: 1 **Selection context**: Product implementation **Implementer**: autodev / `.cursor/skills/implement/SKILL.md` **Total complexity points**: 10 (3 + 5 + 2) ## Task Results | Task | Status | Files Modified | Tests | AC Coverage | Issues | |------|--------|----------------|-------|-------------|--------| | AZ-643 | Done | `crates/mavlink_layer/Cargo.toml`, `crates/mavlink_layer/src/internal/{ack_demux,mod}.rs`, `crates/mavlink_layer/src/internal/codec/{decoder,encoder,mod,parse_errors,signing}.rs`, `crates/mavlink_layer/src/lib.rs`, integration tests `crates/mavlink_layer/tests/{ack_demux,signing}.rs`, workspace `Cargo.toml` (`sha2`, `chrono`) | pass (28 unit + 3 ack_demux + 5 signing) | 4/4 verified locally | 0 blocking | | AZ-665 | Done | `crates/mapobjects_store/Cargo.toml`, `crates/mapobjects_store/src/{lib,internal/mod,internal/h3_index,internal/store}.rs`, integration test `crates/mapobjects_store/tests/classify.rs`, workspace `Cargo.toml` (`h3o`) | pass (11 unit + 6 AC + 1 perf ignored) | 5/5 verified locally (AC-5 in `--release`) | 0 blocking | | AZ-672 | Done | `crates/shared/src/contracts/mod.rs`, `crates/vlm_client/src/{lib,enabled}.rs`, `crates/autopilot/{Cargo.toml,src/runtime.rs}` | pass (1 shared AC + 2 autopilot AC + 1 vlm_client placeholder) | 3/3 verified locally | 0 blocking | ## AC Test Coverage | Task | AC | Description | Verified locally | Notes | |--------|------|--------------------------------------------------------------------------|------------------|-------| | AZ-643 | AC-1 | `send_command` round-trip: `MavlinkLayer` emits `COMMAND_LONG`, demuxes matching `COMMAND_ACK`, returns `Ok(ack)` | YES | `ack_demux::ac1_send_command_happy_path` (loopback UDP heartbeat → spoofed ACK) | | AZ-643 | AC-2 | `send_command` deadline elapses → `Err(SendCommandError::Timeout)` and demux slot freed | YES | `ack_demux::ac2_send_command_timeout_returns_explicit_error` | | AZ-643 | AC-3 | Decoder with `Verifier` rejects signed frames whose signature does not match | YES | `signing::ac3_decoder_rejects_bad_signature` + `signing::ac3_signed_frame_with_matching_key_passes` | | AZ-643 | AC-4 | Decoder without `Verifier` ignores the signature trailer | YES | `signing::ac4_signing_disabled_ignores_signature_field` + defensive `unsigned_frame_rejected_when_verifier_present` | | AZ-665 | AC-1 | Empty store classify → `Classification::New` | YES | `classify::ac1_first_detection_returns_new` | | AZ-665 | AC-2 | Detection 5 m from a stored object, `distance_threshold=30` → `Existing` | YES | `classify::ac2_within_distance_threshold_returns_existing` | | AZ-665 | AC-3 | Detection 60 m from a stored object, `move_threshold=50` → `Moved` | YES | `classify::ac3_beyond_move_threshold_returns_moved` | | AZ-665 | AC-4 | k-ring (k=2) lookup catches a match in a neighbouring H3 cell | YES | `classify::ac4_k_ring_finds_match_in_neighbour_cell` | | AZ-665 | AC-5 | 10 000-row warm store, 1 000 classifies, p99 ≤ 1 ms | YES (release-only) | `classify::ac5_classify_p99_under_one_ms` — gated `#[ignore]`, runs with `cargo test --release -p mapobjects_store -- --ignored`; verified ≤ 1 ms on local run | | AZ-672 | AC-1 | `DisabledVlmProvider::assess` returns `status=Disabled` ≤ 1 ms | YES | `shared::contracts::tests::ac1_disabled_provider_returns_disabled_status` | | AZ-672 | AC-2 | Binary builds without `vlm` feature; `vlm_client` is NOT a build dependency | YES | `cargo check -p autopilot` (no feature) compiles; `cargo tree -p autopilot --edges normal` shows zero matches for `vlm_client`; `cargo tree -p autopilot --features vlm` shows the dep | | AZ-672 | AC-3 | Feature on + `vlm_enabled=false` → composition root installs `DisabledVlmProvider` | YES | `autopilot::runtime::tests::ac3_runtime_vlm_disabled_installs_disabled_provider`; bonus inverse covered by `runtime_vlm_enabled_installs_vlm_client` | **Coverage: 12/12 ACs verified locally** (4 AZ-643, 5 AZ-665, 3 AZ-672). ## Code Review Verdict PASS_WITH_WARNINGS (inline; sub-skill `/code-review` deliberately skipped to conserve context, matching batches 2–3 precedent). **Phase 1 — Spec coverage**: - AZ-643: signing `Signer`/`Verifier` types + key + timestamp source + decoder integration + encoder integration + parse-error counter (`SigningMismatch`); ack demux with oneshot dispatch + deadline + `send_command` API surface on `MavlinkHandle` + `commands_in_flight()` exposed in health. ✓ - AZ-665: `H3Index::cell_of(lat, lon, res)`, `grid_disk(cell, k)`, haversine metres, in-memory `(H3 cell → Vec)`, similar-classes group resolution, configurable thresholds, `MapObjectsStoreHandle::classify(ClassifyInput) -> Classification`. ✓ - AZ-672: `VlmProvider::name()` diagnostic method; `DisabledVlmProvider` in `shared::contracts`; feature-gated `vlm_client::VlmClient` (placeholder until AZ-673); `autopilot` has `vlm_client` as `optional = true` dep gated by `vlm` feature; `Runtime::select_vlm_provider` picks based on feature + runtime flag. ✓ **Phase 2 — Architecture compliance**: - `mavlink_layer` continues to import only `shared`; new modules live under `internal::ack_demux` and `internal::codec::signing`. Public API additions: `Signer`, `SigningKey`, `Verifier`, `SigningReject`, `SendCommandError`, `CommandAck`, `MavlinkHandle::send_command`. Layer constraint preserved. - `mapobjects_store` imports only `shared` + `h3o` + chrono/uuid (all permitted by module-layout.md §5). New `internal/h3_index.rs` and `internal/store.rs` placed exactly where module-layout.md says. Public API exports `ClassifyInput`, `Classification`, `MapObjectsStoreConfig`, `MapObjectsStoreHandle::classify`. - `vlm_client` is now genuinely optional in the autopilot binary (`dep:vlm_client` under `[features].vlm`). The `DisabledVlmProvider` lives in `shared::contracts` — i.e., available regardless of feature, exactly as `description.md §9 Optionality Model` requires. `scan_controller` (AZ-684 dependency target) will receive `Arc` and never link to `vlm_client` directly. - **Doc drift** (note for next monorepo-document run, not a blocker for AZ-665): - `module-layout.md` line 157 documents the public API as `classify(Detection) -> Classification`. AZ-665 introduces `ClassifyInput` instead because the shared `Detection` type carries no geolocation (no GPS / no MGRS) and extending it was out of scope. `system-flows.md §F7` already describes the payload as "detection (gps, class, conf, size)" — `ClassifyInput` is the typed expression of that flow-level concept. Update module-layout.md to `classify(ClassifyInput) -> Classification` in a follow-up. **Phase 3 — Code quality**: - SRP holds: `ack_demux.rs` only routes acks; `signing.rs` only does HMAC + replay-detection; `h3_index.rs` only wraps `h3o`; `store.rs` only owns the in-memory map. Each has one reason to change. - No silent error suppression. `SendCommandError` is an exhaustive enum (`Duplicate`, `Timeout`, `LinkDown`, `Other`); signing errors funnel into `ParseErrors::signing_mismatch` and surface in the public counter snapshot; haversine + cell_of return typed `AutopilotError::Validation`. - All tests follow `Arrange / Act / Assert` per `coderule.mdc`. - `MapObjectsStoreHandle::len()`'s clippy-required `is_empty()` companion is implemented and consistent. **Phase 4 — Runtime completeness (per task brief)**: - AZ-643 "real signing with secret-key validation" — `Signer::sign_into` computes SHA-256 over the canonical signed-payload bytes and appends the 13-byte trailer; `Verifier::verify` recomputes and compares in constant time; timestamps validated against the per-link source. ✓ - AZ-665 "real H3 + k-ring" — `h3o` 0.7 is used (no naive Euclidean fallback). ✓ - AZ-672 "real disabled impl + feature flag" — `DisabledVlmProvider` is a concrete trait impl (not a panicking `unimplemented!`), and the binary genuinely drops `vlm_client` from its dep graph when the feature is off (verified via `cargo tree`). ✓ **Phase 5 — Test discipline**: - Every AC has a dedicated test (table above). - Performance AC (AZ-665 AC-5) uses `#[ignore]` because debug builds run 3–10× slower than release; explicit `--ignored` invocation runs it and asserts ≤ 1 ms. This is the established project pattern for perf gates. - AZ-672 AC-2's structural-build check is expressed as a build invariant (`cargo tree` + `cargo check --no-default-features`) rather than a runtime test, because that is what the AC actually asks for. The fact that `cargo test --workspace` succeeds without the `vlm` feature is itself a positive confirmation. ## Quality Gates - `cargo fmt --all` ✓ (no changes) - `cargo clippy -p shared -p vlm_client -p mapobjects_store -p autopilot --tests --no-deps` ✓ (0 warnings after fixes) - `cargo check -p mavlink_layer --tests` ✓ - `cargo test --workspace` (default features) → **all green**, 0 failures, 1 ignored (`ac5_classify_p99_under_one_ms`) - `cargo test -p autopilot --features vlm` ✓ (3 tests including AZ-672 AC-3 + bonus inverse) - `cargo test -p vlm_client --features vlm` ✓ (1 test) - `cargo test --release -p mapobjects_store -- --ignored ac5_classify_p99` ✓ (perf gate passes) - `cargo tree -p autopilot` (no vlm) shows zero `vlm_client` matches ✓ — AZ-672 AC-2 structural proof ## Auto-Fix Attempts 1 round — clippy surfaced three warnings on the first pass (`len_without_is_empty`, `field_reassign_with_default`, `unnecessary_map_or`); all three are Low-severity Style/Maintainability findings, auto-fix-eligible per the matrix in `implement/SKILL.md §10`. Fixes applied, re-clippy clean. ## Stuck Agents None. ## Next Batch Pending. Topological candidates with all dependencies satisfied: - AZ-648 `mission_executor_state_machine` (deps AZ-640, AZ-641, AZ-642, AZ-643 — now all in `done/`) - AZ-666 `mapobjects_store_ignored_and_pass_sweep` (deps AZ-640, AZ-665 — now all in `done/`) - AZ-673 `vlm_client_nanollm_ipc` (deps AZ-640, AZ-672 — now all in `done/`) The actual selection for batch 5 will be made by the next `/implement` invocation per the topological rule.