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>
10 KiB
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/Verifiertypes + key + timestamp source + decoder integration + encoder integration + parse-error counter (SigningMismatch); ack demux with oneshot dispatch + deadline +send_commandAPI surface onMavlinkHandle+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<MapObject>), similar-classes group resolution, configurable thresholds,MapObjectsStoreHandle::classify(ClassifyInput) -> Classification. ✓ - AZ-672:
VlmProvider::name()diagnostic method;DisabledVlmProviderinshared::contracts; feature-gatedvlm_client::VlmClient(placeholder until AZ-673);autopilothasvlm_clientasoptional = truedep gated byvlmfeature;Runtime::select_vlm_providerpicks based on feature + runtime flag. ✓
Phase 2 — Architecture compliance:
mavlink_layercontinues to import onlyshared; new modules live underinternal::ack_demuxandinternal::codec::signing. Public API additions:Signer,SigningKey,Verifier,SigningReject,SendCommandError,CommandAck,MavlinkHandle::send_command. Layer constraint preserved.mapobjects_storeimports onlyshared+h3o+ chrono/uuid (all permitted by module-layout.md §5). Newinternal/h3_index.rsandinternal/store.rsplaced exactly where module-layout.md says. Public API exportsClassifyInput,Classification,MapObjectsStoreConfig,MapObjectsStoreHandle::classify.vlm_clientis now genuinely optional in the autopilot binary (dep:vlm_clientunder[features].vlm). TheDisabledVlmProviderlives inshared::contracts— i.e., available regardless of feature, exactly asdescription.md §9 Optionality Modelrequires.scan_controller(AZ-684 dependency target) will receiveArc<dyn VlmProvider>and never link tovlm_clientdirectly.- Doc drift (note for next monorepo-document run, not a blocker for AZ-665):
module-layout.mdline 157 documents the public API asclassify(Detection) -> Classification. AZ-665 introducesClassifyInputinstead because the sharedDetectiontype carries no geolocation (no GPS / no MGRS) and extending it was out of scope.system-flows.md §F7already describes the payload as "detection (gps, class, conf, size)" —ClassifyInputis the typed expression of that flow-level concept. Update module-layout.md toclassify(ClassifyInput) -> Classificationin a follow-up.
Phase 3 — Code quality:
- SRP holds:
ack_demux.rsonly routes acks;signing.rsonly does HMAC + replay-detection;h3_index.rsonly wrapsh3o;store.rsonly owns the in-memory map. Each has one reason to change. - No silent error suppression.
SendCommandErroris an exhaustive enum (Duplicate,Timeout,LinkDown,Other); signing errors funnel intoParseErrors::signing_mismatchand surface in the public counter snapshot; haversine + cell_of return typedAutopilotError::Validation. - All tests follow
Arrange / Act / Assertpercoderule.mdc. MapObjectsStoreHandle::len()'s clippy-requiredis_empty()companion is implemented and consistent.
Phase 4 — Runtime completeness (per task brief):
- AZ-643 "real signing with secret-key validation" —
Signer::sign_intocomputes SHA-256 over the canonical signed-payload bytes and appends the 13-byte trailer;Verifier::verifyrecomputes and compares in constant time; timestamps validated against the per-link source. ✓ - AZ-665 "real H3 + k-ring" —
h3o0.7 is used (no naive Euclidean fallback). ✓ - AZ-672 "real disabled impl + feature flag" —
DisabledVlmProvideris a concrete trait impl (not a panickingunimplemented!), and the binary genuinely dropsvlm_clientfrom its dep graph when the feature is off (verified viacargo 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--ignoredinvocation 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 thatcargo test --workspacesucceeds without thevlmfeature 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 zerovlm_clientmatches ✓ — 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 indone/) - AZ-666
mapobjects_store_ignored_and_pass_sweep(deps AZ-640, AZ-665 — now all indone/) - AZ-673
vlm_client_nanollm_ipc(deps AZ-640, AZ-672 — now all indone/)
The actual selection for batch 5 will be made by the next /implement invocation per the topological rule.