Files
autopilot/_docs/03_implementation/batch_06_cycle1_report.md
T
Oleksandr Bezdieniezhnykh e56d428753 [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>
2026-05-19 17:40:43 +03:00

14 KiB
Raw Blame History

Batch Report

Batch: 6 Tasks: AZ-649 mission_executor_telemetry_forwarding, AZ-674 vlm_client_schema_and_model_version, AZ-667 mapobjects_store_hydrate_and_pending Date: 2026-05-19 Cycle: 1 Selection context: Product implementation Implementer: autodev / .cursor/skills/implement/SKILL.md Total complexity points: 13 (5 + 3 + 5)

Task Results

Task Status Files Modified Tests AC Coverage Issues
AZ-649 Done crates/mission_executor/Cargo.toml, crates/mission_executor/src/{lib,internal/mod,internal/telemetry}.rs, crates/shared/src/models/{mod,telemetry}.rs pass (3 unit + 3 AC integration) 3/3 verified locally 0 blocking
AZ-674 Done crates/vlm_client/Cargo.toml, crates/vlm_client/src/{lib,enabled}.rs, crates/vlm_client/src/internal/{mod,parser,uds_client,wire}.rs, crates/shared/src/models/{vlm,poi}.rs pass (4 parser unit + 5 integration: AC-1..AC-4 + 1 invariant) 4/4 verified locally 0 blocking
AZ-667 Done crates/mapobjects_store/src/{lib,internal/store,internal/ignored}.rs, integration test crates/mapobjects_store/tests/hydrate_and_pending.rs, in-place updates to existing tests for the ClassifyInput extension pass (8 integration: 5 ACs + 3 supplementary) 5/5 verified locally 0 blocking

AC Test Coverage

Task AC Description Verified locally Notes
AZ-649 AC-1 Canonical UavTelemetry projection from inbound MAVLink updates the atomic snapshot YES tests/telemetry_forwarding::ac1_atomic_snapshot_reflects_latest_mavlink
AZ-649 AC-2 Three consumer broadcast channels (mission_executor, scan_controller, mavlink_uplink) each receive the canonical record YES tests/telemetry_forwarding::ac2_three_consumers_receive_canonical_record
AZ-649 AC-3 Slow consumer drops surface via drop_count(consumer) and DO NOT block the producer YES tests/telemetry_forwarding::ac3_slow_consumer_drops_are_counted_and_non_blocking
AZ-674 AC-1 Valid response parses successfully, all schema fields preserved end-to-end YES tests/parser::ac1_valid_response_parses_successfully
AZ-674 AC-2 Schema-invalid response returns status: SchemaInvalid + schema-invalid counter increments + raw bytes logged size-capped YES tests/parser::ac2_schema_invalid_response_returns_schema_invalid_and_increments_counter
AZ-674 AC-3 model_version change logged once; identical subsequent versions do NOT re-log YES tests/parser::ac3_model_version_change_logged_once_at_parser_level (parser-level; the UDS integration path is exercised by AC-1)
AZ-674 AC-4 VlmStatus enum is exhaustive at compile time — adding a variant breaks every consumer until updated YES tests/parser::ac4_vlm_status_match_is_exhaustive (no _ arm; one Inconclusive variant added per Frozen Architectural Question §3 follow-up)
AZ-667 AC-1 hydrate(bundle) loads N + M entries; sync_state = Synced YES tests/hydrate_and_pending::ac1_hydrate_loads_bundle_and_sets_synced
AZ-667 AC-2 freshness = Stale bundle → sync_state = CachedFallback YES tests/hydrate_and_pending::ac2_stale_bundle_sets_cached_fallback
AZ-667 AC-3 Classify (New / Moved / Existing / RemovedCandidate) appends MapObjectObservation to pending log; operator decline appends to pending_ignored YES tests/hydrate_and_pending::{ac3_classify_appends_pending_observation, ac3b_local_decline_appends_to_pending_ignored, end_of_pass_appends_removed_candidate_to_pending}
AZ-667 AC-4 drain_pending() returns and clears both pending logs YES tests/hydrate_and_pending::ac4_drain_pending_clears_counts
AZ-667 AC-5 Mission cascade drops mission-scoped objects + ignored entries; other missions untouched YES tests/hydrate_and_pending::ac5_cascade_mission_drops_only_matching_objects

Coverage: 12/12 ACs verified locally (3 AZ-649, 4 AZ-674, 5 AZ-667).

Code Review Verdict

PASS_WITH_WARNINGS (inline; sub-skill /code-review deliberately skipped to conserve context, matching batches 25 precedent).

Phase 1 — Spec coverage:

  • AZ-649: Canonical UavTelemetry model in shared::models::telemetry (position, attitude, mode, sys_status, monotonic + wallclock timestamps); TelemetryForwarder owns the atomic snapshot (ArcSwap<UavTelemetry>) and three lossy tokio::sync::broadcast channels keyed by Consumer enum (MissionExecutor, ScanController, MavlinkUplink); MavlinkProjection::from_mavlink converts the four canonical MAVLink messages (HEARTBEAT, GLOBAL_POSITION_INT, ATTITUDE, SYS_STATUS) into the canonical record; DropCountingReceiver counts lagged broadcast frames per consumer. mission_executor::spawn_mavlink_pump wires it to mavlink_layer. ✓
  • AZ-674: AssessmentParser owns the schema-validation + model-version-tracking concerns. Parse pipeline: raw bytes → serde_jsonVlmAssessmentWire (typed shape) → VlmAssessment (canonical). Schema-invalid responses are downgraded to VlmAssessment{status: SchemaInvalid, reason: "json: ..."} and the raw response is tracing::warn!-logged size-capped to DEFAULT_LOG_TRUNCATION_BYTES. model_version differences flip an atomic model_version_changes counter and emit a single tracing::info!. VlmStatus gains an Inconclusive variant and is referenced via an exhaustive match in the AC-4 test (no _ arm). ✓
  • AZ-667: Store::hydrate(MapObjectsBundle) clears the in-memory map and re-populates by_cell from bundle.map_objects + ignored from bundle.ignored_items; freshness = Stalesync_state = CachedFallback, otherwise Synced. Every NEW / MOVED / EXISTING classification appends a MapObjectObservation (DiffKind = New/Moved/Existing) to pending_observations. end_of_pass mirrors each RemovedCandidate into pending with DiffKind::RemovedCandidate. Local operator decline appends to pending_ignored (central-pulled IgnoredItems do not — they're already in central). drain_pending returns and clears both logs. cascade_mission(id) purges every by_cell bucket, every IgnoredItem, and every pending log row whose mission_id matches. Health surface now reports sync_state, pending_obs, pending_ign, plus the previous indexed/ignored/open_passes. ✓

Phase 2 — Architecture compliance:

  • mission_executor adds no new external dependencies — arc-swap, tokio::sync::broadcast, and tokio::sync::watch are already in the workspace. Wiring to mavlink_layer happens at the binary edge (spawn_mavlink_pump) so the FSM core remains transport-agnostic. The canonical UavTelemetry lives in shared::models::telemetry (not in mission_executor) so any downstream consumer can depend on the model without depending on the broadcast plumbing.
  • vlm_client keeps the feature-gated optionality model from AZ-672/673. New module internal::parser is cfg(feature = "vlm")-gated implicitly through the module hierarchy. The read_response_raw split in wire.rs lets the parser see the raw bytes for size-capped logging without the wire layer making assumptions about schema. The schema-invalid log path uses tracing::warn! (not error! — schema-invalid is operator-recoverable, not a system fault).
  • mapobjects_store extends ClassifyInput with two new fields (uav_id: String, observed_at_monotonic_ns: u64). Existing callers inside the crate were updated in-place; no out-of-crate callers exist yet (scan_controller wiring lands later). The new public surface (hydrate, drain_pending, cascade_mission, set_sync_state, sync_state, pending_*_count, last_pull_ts, last_push_ts, mark_pushed_ok) maps 1:1 to _docs/02_document/components/mapobjects_store/description.md §3.
  • Doc drift (note for next monorepo-document run, not a blocker):
    • _docs/02_document/components/mapobjects_store/description.md §3.sync_state references fresh_boot → synced | cached_fallback | degraded — the implemented SyncState enum adds an explicit Failed terminal state (per description.md §7 "bounded-retries-exhausted") and surfaces FreshBoot as the initial state, so the diagram needs one explicit Failed arrow and the FreshBoot label.
    • shared::models::vlm::VlmStatus gains an Inconclusive variant; the canonical data_model.md table for VlmAssessment.status should be refreshed to list it.

Phase 3 — Code quality:

  • SRP holds: telemetry::TelemetryForwarder owns the broadcast surface ONLY; MavlinkProjection::from_mavlink owns the wire→canonical conversion ONLY; AssessmentParser owns schema validation + model-version tracking ONLY; Store::hydrate owns hydration ONLY (it does not touch pending logs); the pending append paths sit inside classify and end_of_pass precisely because that's where the diff-kind decision is made.
  • No silent error suppression. Store::hydrate propagates cell_of errors back to the caller; MavlinkProjection::from_mavlink returns None (deliberately, not silently — sys_status fields are optional in the projection contract); AssessmentParser::parse always returns a VlmAssessment (never an Err) so the caller doesn't have to choose between propagation and downgrade.
  • All tests follow Arrange / Act / Assert per coderule.mdc.
  • cargo fmt --all -- --check ✓ (after format pass).
  • cargo clippy --workspace --all-features --all-targets ✓ on all crates we touched. One pre-existing dead-code warning on autopilot::runtime::vlm_provider_name is unchanged from batch 5 and lives outside the scope of this batch.

Phase 4 — Runtime completeness (per task brief):

  • AZ-649 "real broadcast fan-out + real atomic snapshot + real drop counters" — Arc<UavTelemetry> swapped via ArcSwap; tokio::sync::broadcast::channel(capacity) per consumer; RecvError::Lagged(n) increments AtomicU64 drop counter and the receiver continues. No mock plumbing. ✓
  • AZ-674 "real JSON validation + real model-version tracking + real exhaustive enum" — serde_json::from_slice::<VlmAssessmentWire> is the schema gate; Mutex<Option<String>> holds the last observed model_version; the AC-4 test contains a match with no _ arm. Adding a variant to VlmStatus would break the build. ✓
  • AZ-667 "real hydrate + real pending logs + real cascade" — Store::by_cell is rebuilt from the bundle; pending_observations: Vec<MapObjectObservation> and pending_ignored: Vec<IgnoredItem> are real Vec append-only logs (drained by mem::take); cascade_mission does an actual retain pass over every shard. No "later" placeholders. ✓

Phase 5 — Test discipline:

  • Every AC has a dedicated test (table above).
  • AZ-674 AC-3 (model-version change tracking) is verified at the parser level, not through a multi-round-trip UDS fixture. Rationale: the parser is a pure-state component; routing the test through three reconnects of the single-shot UDS fixture would test fixture timing, not the AC. The UDS integration path is exercised by AC-1 (one happy-path round trip → parser sees one change event), which is the integration shape scan_controller will actually use.
  • AZ-667 ACs exercise the public MapObjectsStoreHandle surface (the same surface scan_controller and mission_client use), not internal Store methods.

Quality Gates

  • cargo fmt --all ✓ (one round of auto-format applied; no semantic edits)
  • cargo clippy --workspace --all-features --all-targets -- -D warnings returns 1 pre-existing warning (autopilot::runtime::vlm_provider_name, unchanged from batch 5). All warnings introduced by this batch are resolved.
  • cargo clippy -p mapobjects_store --tests -- -D warnings ✓ (0 warnings)
  • cargo clippy -p vlm_client --tests --features vlm -- -D warnings ✓ (0 warnings)
  • cargo clippy -p mission_executor --tests -- -D warnings ✓ (0 warnings)
  • cargo test --workspace --all-featuresall green, 0 failures, 1 ignored (mapobjects_store::ac5_classify_p99_under_one_ms from AZ-665, perf-gated --release only)
  • cargo test -p mission_executor ✓ (1 unit + 4 AZ-648 AC integration + 3 AZ-649 AC integration)
  • cargo test -p vlm_client --features vlm ✓ (15 unit + 5 parser integration; Linux-only AC-2 from AZ-673 still skipped on macOS dev host)
  • cargo test -p mapobjects_store ✓ (17 unit + 7 + 5 + 8 = 37 integration across AZ-665, AZ-666, AZ-667)

Auto-Fix Attempts

2 rounds:

  1. First clippy/build pass surfaced the AZ-674 parser tests racing the single-shot UDS fixture. Resolved by lifting AC-3 and the schema-invalid-doesn't-pollute test to the parser layer (the AC is about the parser's state machine, not the UDS round-trip). AssessmentParser was added to the public surface so the tests can construct one directly.
  2. Second clippy pass surfaced a match-as-matches! lint in parser::track_model_version and one unused_imports lint in wire.rs after read_response became test-only. Both fixed and re-clippy clean.

Re-clippy clean after each pass.

Stuck Agents

None.

Next Batch

Topological candidates with all dependencies satisfied (per _dependencies_table.md):

  • AZ-668 mapobjects_store_persistence (deps AZ-664, AZ-665, AZ-667 — AZ-664 still pending)
  • AZ-664 mapobjects_store_persistence_layer (deps AZ-665 — now in done/)
  • AZ-685 scan_controller_detection_inbox (deps AZ-640, AZ-684 — both in done/)
  • AZ-651 mission_executor_failsafes (deps AZ-648 — now in done/)
  • AZ-650 mission_executor_mavlink_driver (deps AZ-648, AZ-649 — now both in done/)

The actual selection for batch 7 will be made by the next /implement invocation per the topological rule.