# Cumulative Code Review — Batches 04–06 (Cycle 1)

**Trigger**: `implement/SKILL.md` Step 14.5 — `K=3` batches completed since the last cumulative review (`cumulative_review_batches_01-03_cycle1_report.md`).
**Date**: 2026-05-19
**Cycle**: 1
**Scope**: union of files changed in `batch_04_cycle1`, `batch_05_cycle1`, `batch_06_cycle1` (range `69c0629^..HEAD`, excluding `_docs/`).
**Mode**: inline (matching the per-batch precedent in batches 1–6; sub-skill `/code-review` deliberately skipped to conserve context).
**Baseline**: `_docs/02_document/architecture_compliance_baseline.md` still does not exist (greenfield project — no Architecture Baseline Scan ran). No `## Baseline Delta` section is produced. The previous cumulative review noted it would become the de-facto baseline; that intent is carried forward — once Step 12 (Test-Spec Sync) lands, an explicit baseline snapshot is worth promoting.

## Tasks in scope

| Batch | Tasks                                                                                                | Components touched                                          |
|-------|------------------------------------------------------------------------------------------------------|-------------------------------------------------------------|
| 04    | AZ-643 (`mavlink_ack_demux_and_signing`), AZ-665 (`mapobjects_store_h3_classify`), AZ-672 (`vlm_client_provider_trait`) | `mavlink_layer`, `mapobjects_store`, `shared::contracts`, `vlm_client` (placeholder), `autopilot` runtime |
| 05    | AZ-666 (`mapobjects_store_ignored_and_pass_sweep`), AZ-673 (`vlm_client_nanollm_ipc`), AZ-648 (`mission_executor_state_machine`) | `mapobjects_store`, `vlm_client`, `mission_executor`, `autopilot` runtime |
| 06    | AZ-649 (`mission_executor_telemetry_forwarding`), AZ-674 (`vlm_client_schema_and_model_version`), AZ-667 (`mapobjects_store_hydrate_and_pending`) | `mission_executor`, `vlm_client`, `mapobjects_store`, `shared::models` (`telemetry`, `vlm`, `poi`) |

Per-batch AC verification (rolled up from individual reports): **35 / 35 ACs verified locally** (12 in batch 04 + 11 in batch 05 + 12 in batch 06). One Linux-only test (`vlm_client::ac2_peer_cred_mismatch_hard_fails_connect`) deliberately skips on the macOS dev host; the code path is build-checked and runtime-checked on the Jetson Linux target. One perf-gated test (`mapobjects_store::ac5_classify_p99_under_one_ms`) runs `--ignored` under `--release`.

## Phase 1 — Spec coverage

Every Included scope item from these 9 tasks is implemented in production code (not just tests / not just trait placeholders):

- **AZ-643**: MAVLink v2 signing (`Signer`/`Verifier`/`SigningKey` + 13-byte trailer + replay-defence), ack demux (`OneshotMap` keyed by `command_id` + deadline) and `MavlinkHandle::send_command` round-trip. `commands_in_flight()` surfaces on the health snapshot.
- **AZ-665**: `H3Index` (h3o-backed cell-of + k-ring), haversine, in-memory `BTreeMap<h3_cell, Vec<MapObject>>`, `ClassifyInput` typed input, `MapObjectsStoreHandle::classify`. AC-5 perf gate `≤ 1 ms p99` in release.
- **AZ-672**: `VlmProvider` trait final shape, `DisabledVlmProvider` in `shared::contracts`, feature-gated `dep:vlm_client` in `autopilot` (`cargo tree --no-default-features` drops `vlm_client` from the dep graph as verified).
- **AZ-666**: `IgnoredSet` (HashSet keyed `(mgrs, class_group)` + per-uuid round-trip map), `PassTracker` (per-region observed-uuid set + bbox-anchored `end_of_pass`), `Classification::Ignored` discriminator, decline → `IgnoredItem` append.
- **AZ-673**: `tokio::net::UnixStream`-based `NanoLlmClient`, Linux `SO_PEERCRED` peer-credential gate, length-prefixed JSON wire, pre-send `prompt::validate` (ROI size + JPEG/PNG header + non-empty prompt), per-request deadline, bounded reconnect with hard-stop on peer-cred mismatch.
- **AZ-648**: Variant-aware `MissionState` enum (multirotor + fixed-wing transition tables) with `MissionDriver` trait, per-transition retry budget keyed by `TransitionKey`, broadcast `TransitionEvent` stream, pause-and-flip-red on cap exhaustion.
- **AZ-649**: `UavTelemetry` canonical record in `shared::models::telemetry`, `TelemetryForwarder` (atomic snapshot via `ArcSwap` + three lossy `tokio::sync::broadcast` channels keyed by `Consumer`), `MavlinkProjection::from_mavlink` for the 4 telemetry-bearing MAVLink ids, `DropCountingReceiver`. Wired to `mavlink_layer` at the binary edge by `mission_executor::spawn_mavlink_pump`.
- **AZ-674**: `AssessmentParser` (typed `VlmAssessmentWire` → canonical `VlmAssessment`, schema-invalid downgrade to `VlmStatus::SchemaInvalid`, size-capped raw-bytes warn log, single-emit model-version change tracker, `Inconclusive` variant added so `VlmStatus` matches stay exhaustive without `_` arms).
- **AZ-667**: `Store::hydrate(MapObjectsBundle)` (full re-population from bundle; `freshness=Stale` ⇒ `sync_state=CachedFallback`, else `Synced`), `pending_observations` + `pending_ignored` append-only logs, `drain_pending`, `cascade_mission` (real `retain` pass), `last_pull_ts` / `last_push_ts` / `mark_pushed_ok`.

Deferred (still explicitly Excluded by task scope, not by these batches):

- Production `MissionDriver` impl over `mavlink_layer` — slated for AZ-650 (BIT F9) / AZ-652 (safety + resume) which carry the operational driver wiring.
- VLM persistence-layer mapping into `mapobjects_store` — covered by AZ-668 (next batch).
- Failsafe ladders (`failsafe_trigger`, `insert_middle_waypoint`) on `MissionExecutorHandle` remain `NotImplemented` per AZ-651 / AZ-652 scope.

No scope creep observed across the three batches.

## Phase 2 — Architecture compliance (layer + Public API)

Dependency reality (from `Cargo.toml` of each touched crate, cross-checked against `module-layout.md`):

| Component        | Documented Layer | `Imports from` (module-layout)                  | Actual workspace deps                                         | Status |
|------------------|------------------|-------------------------------------------------|---------------------------------------------------------------|--------|
| `shared`         | 1                | (none)                                          | external only                                                 | ✓      |
| `mavlink_layer`  | 2                | `shared`                                        | `shared` + tokio/bytes/chrono/tracing/thiserror/sha2          | ✓      |
| `mapobjects_store` | 2              | `shared`                                        | `shared` + h3o/chrono/uuid/serde/tokio                        | ✓      |
| `vlm_client`     | 2                | `shared`                                        | `shared` + (feature-gated) serde/serde_json/thiserror/base64/libc | ✓ (drops out of `autopilot` dep graph when `vlm` is off) |
| `mission_executor` | 3              | `shared`, `mavlink_layer`, `mission_client`, `mapobjects_store`, `gimbal_controller` (later) | `shared` + `mavlink_layer` + `mission_client` + `mapobjects_store` + tokio/serde/chrono/thiserror | ✓ (Layer 3 → Layer 2 only) |
| `autopilot` bin  | 5                | every component                                 | currently the bootstrapped + landed crates only               | ✓      |

No Layer 2 → Layer 2 import, no same-layer cross-crate import, no Layer 3 → Layer 3 import (no other Layer 3 component has landed yet). The `mission_executor::spawn_mavlink_pump` wiring lives **at the crate boundary** (the binary-edge pump function) and does the cross-component glue — exactly the layered shape `module-layout.md §5` prescribes.

Public API surface for the three Layer 2 actors changed this window matches `module-layout.md` to within the doc-drift items listed below:

- `mavlink_layer`: added `Signer`, `SigningKey`, `Verifier`, `SigningReject`, `SendCommandError`, `CommandAck`, `MavlinkHandle::send_command`. All exported from `lib.rs`. ✓
- `mapobjects_store`: added `ClassifyInput`, `Classification` (incl. `Ignored`), `MapObjectsStoreConfig`, `IgnoredItem`, `RegionBbox`, `RemovedCandidate`, `Store::hydrate`, `Store::drain_pending`, `Store::cascade_mission`, `Store::set_sync_state`, `Store::mark_pushed_ok`, plus the `pending_*_count` / `last_*_ts` accessors. All re-exported from `lib.rs`. ✓
- `vlm_client`: added `VlmClient`, `VlmClient::open`, `VlmClient::connect`, `VlmClient::new`, plus the public `AssessmentParser`. Feature-gated. ✓
- `mission_executor`: added `MissionExecutor`, `MissionExecutorConfig`, `MissionState`, `Telemetry`, `Variant`, `TransitionEvent`, `TransitionKey`, `StepOutcome`, `MissionDriver`, `DriverError`, `Consumer`, `DropCountingReceiver`, `MavlinkProjection`, `TelemetryForwarder`, `spawn_mavlink_pump`. ✓

**Doc drift carried over and added during this window** (not blocking; queued for Step 13 / the next `monorepo-document` pass):

- `module-layout.md` line ~157: documents `mapobjects_store` public API as `classify(Detection) -> Classification`. AZ-665 introduced `ClassifyInput` (which carries `lat/lon/class/uav_id/observed_at_monotonic_ns` and is the shape `scan_controller` actually feeds in). Update line: `classify(ClassifyInput) -> Classification`.
- `module-layout.md` (same component): public API list does not yet list `hydrate`, `drain_pending`, `cascade_mission`, or the new `sync_state` / `pending_*` accessors. Add them.
- `architecture.md §5.6` (mission FSM): documented flow is `… → ARMED → TAKE_OFF → AUTO → LAND → POST_FLIGHT_SYNC → DONE`. AZ-648 introduces an explicit `MissionUploaded` state between `TakeOff` and `FlyMission` (rather than overloading `AUTO`). Match the diagram to the task brief.
- `_docs/02_document/components/mapobjects_store/description.md §3.sync_state`: documented diagram is `fresh_boot → synced | cached_fallback | degraded`. Implementation adds explicit `FreshBoot` initial state and a `Failed` terminal state (per `description.md §7` "bounded-retries-exhausted"). Refresh the diagram to include both.
- `_docs/02_document/data_model.md`:
  - `VlmStatus` enum is missing the `Inconclusive` variant added in AZ-674 (carries an explicit "no confident judgement; do not advance to Done" semantics; required so the AC-4 exhaustive-match has no `_` arm).
  - `UavTelemetry` (introduced in AZ-649 in `shared::models::telemetry`) is not yet listed as a canonical entity. Add a row pointing to `crates/shared/src/models/telemetry.rs`.

## Phase 3 — Code quality (cross-batch)

- **SRP holds across all touched modules.** New modules each own exactly one concern: `ack_demux.rs` (oneshot dispatch), `codec/signing.rs` (HMAC SHA-256 + replay defence), `internal/h3_index.rs` (h3o wrapper only), `internal/ignored.rs` (suppression set only), `internal/passes.rs` (per-region observed-id tracking only), `peer_cred.rs` (Linux `SO_PEERCRED` only), `prompt.rs` (ROI + prompt validation only), `wire.rs` (length-prefixed frame I/O only), `uds_client.rs` (UDS connection lifecycle only), `parser.rs` (schema validation + model-version tracking only), `fsm.rs` (transition stepping only), per-variant `multirotor` / `fixed_wing` tables, `internal/telemetry.rs` (atomic snapshot + lossy broadcast fan-out only), `internal/store.rs` (in-memory map + pending logs only). No god modules introduced.
- **Error handling**: every crate-level boundary exposes a typed error (`SendCommandError`, `SigningReject`, `DriverError`, `WireError`, `ValidateError`, `ConnectError`, `VlmStatus::SchemaInvalid` downgrade, classify returns typed errors via `AutopilotError::Validation`). No `.unwrap()` on runtime paths except the once-init schema-compile `OnceLock` (compile-time correctness).
- **No silent suppression**: CRC mismatches, schema failures, transient HTTP errors, ack-deadline expiry, signing replay, peer-cred mismatch, broadcast `Lagged(n)` events — all surface to typed counters, logs, or per-receiver counters.
- **Tests follow Arrange / Act / Assert** per `coderule.mdc` across all 35 new ACs.
- `cargo fmt --all` clean.
- `cargo clippy --workspace --all-features --all-targets -- -D warnings` returns one (1) pre-existing warning on `autopilot::runtime::vlm_provider_name` (introduced in batch 04 as a runtime helper for the disabled-provider name surface; subsequently shadowed by direct usage and not yet removed). All warnings introduced by these batches are resolved. Recommend removing or `#[allow(dead_code)]`-annotating `vlm_provider_name` when the runtime composition expands in AZ-650 / AZ-678.

## Phase 4 — Test quality (cross-batch)

| Layer                                  | Test count (new in 04–06)                            | Test technology                                                  |
|----------------------------------------|------------------------------------------------------|------------------------------------------------------------------|
| `mavlink_layer` ack_demux              | 3 integration                                        | loopback UDP + spoofed ACK                                       |
| `mavlink_layer` signing                | 5 integration + 2 codec round-trip                   | real HMAC-SHA256 sign/verify                                     |
| `mapobjects_store` classify (AZ-665)   | 6 integration + 1 perf-gated `#[ignore]`             | real h3o + real haversine                                        |
| `mapobjects_store` ignored_and_sweep   | 5 integration (3 AC + 2 supplementary)               | in-process                                                        |
| `mapobjects_store` hydrate_and_pending | 8 integration (5 AC + 3 supplementary)               | real `Store` via `MapObjectsStoreHandle`                          |
| `vlm_client` enabled (AZ-673)          | 6 integration (4 AC + 2 supplementary)               | real `tokio::net::UnixStream` + temp-dir socket fixture           |
| `vlm_client` parser (AZ-674)           | 4 integration                                        | real `serde_json`; exhaustive-match invariant check               |
| `vlm_client` wire / peer_cred / prompt | 4 + 2 + 4 unit                                       | in-process                                                        |
| `mission_executor` state_machine       | 4 AC integration + 1 unit                            | `ScriptedDriver` fake (driver behind the FSM is the seam)         |
| `mission_executor` telemetry           | 3 AC integration + 3 unit                            | real `tokio::sync::broadcast`, real `ArcSwap`, in-process pump    |

Fakes used: `ScriptedDriver` for AZ-648 (driver behind the FSM under test — the FSM is the unit of test, the driver is the seam) and the canned-JSON UDS fixture for AZ-673 / AZ-674 (the parser + wire framing is under test; the model-server is the external system). No fakes for HTTP, sockets, FS, or codecs inside the test boundary.

The Linux-only AC-2 (`vlm_client::ac2_peer_cred_mismatch_hard_fails_connect`) is the right shape: build-checks the code path on every host, runtime-checks on the Jetson Linux production target. Documented in `vlm_client/description.md §8`.

The perf-gated AC (`mapobjects_store::ac5_classify_p99_under_one_ms`) is the right shape: `#[ignore]`-gated on debug, asserted under `--release --ignored` and verified locally.

## Phase 5 — Docs alignment

- All new code paths point at their owning task (`AZ-NNN`) in module-level rustdoc.
- Schemas remain co-owned in `crates/shared/contracts/` (`mission-schema.json`, the three mapobjects schemas, plus the new `nanollm_request`/`nanollm_response` shapes carried internally to `vlm_client`'s parser). The cumulative open question from batches 01–03 ("missions-repo extraction" — W5) is still open; no new code has expanded the surface area, so the impact is unchanged.
- `architecture.md` and `data_model.md` updates are queued (see Phase 2 doc-drift list).

## Phase 6 — Cross-task consistency

Concerns that span batches 04–06:

1. **`mission_executor::Telemetry` (guard view) vs `shared::models::telemetry::UavTelemetry` (canonical record)** — *Medium / Maintainability / Cross-Task-Consistency*. The FSM tick consumes a `watch::Receiver<mission_executor::Telemetry>` (`link_up`, `health_ok`, `bit_ok`, `armed`, `takeoff_complete`, `flight_mode_auto`, `mission_reached_final`, `landed_disarmed` — all booleans). The MAVLink projection produces `shared::models::telemetry::UavTelemetry` (typed snapshot with `UavPosition`, `UavAttitude`, `UavMode`, `UavSysStatus`). No adapter exists yet that turns one into the other. This is acceptable today (AZ-649 was scoped narrowly to "forwarding"; the FSM uses a fake telemetry source in tests) but becomes a real wiring gap the moment AZ-650 / AZ-651 / AZ-652 connect the FSM to live MAVLink. Two architecturally clean options:
   - **(a) Adapter in `mission_executor`**: a small `from_uav_telemetry(&UavTelemetry, &PrevTelemetry) -> Telemetry` function that derives the boolean guards from the canonical record (with hysteresis for `link_up` / `health_ok`). Lives in `mission_executor::internal::telemetry` (already created by AZ-649) and is the only place that knows the projection rules.
   - **(b) Fold both views into one canonical pair**: replace the FSM-local `Telemetry` with `(UavTelemetry, FsmGuards)` where `FsmGuards` is the boolean view. Mechanically more code; semantically the same.
   - **Recommendation**: (a). The bool view IS a guard projection — the canonical record stays the source of truth. Add this to AZ-650's task brief or pre-create a 1-pt remediation task `mission_executor_telemetry_adapter`. Not blocking these batches.

2. **`ExponentialBackoff` duplication (carried over from batches 01–03 — W2 / W3)** — still present, still acceptable. The current count is 2 crates (`mavlink_layer::internal::retry` 1 call site; `mission_client::internal::retry` 4 call sites). Batches 04–06 did NOT introduce a third call site (the `vlm_client` bounded-reconnect uses a simpler fixed-backoff because peer-cred mismatch is a hard-stop, not a transient). The "promote to `shared::retry` when the third crate joins" trigger is still pending; the next crate that needs exponential backoff (likely `detection_client` AZ-660 / AZ-661 or `mission_executor` for the BIT F9 retry envelope in AZ-650) should land the move.

3. **`Inconclusive` `VlmStatus` variant + exhaustive matching across the workspace** — AZ-674 added `VlmStatus::Inconclusive` and required the AC-4 exhaustive-match invariant (no `_` arm). The variant is currently consumed only inside `vlm_client::parser` and by the AC-4 test. Once `scan_controller` (AZ-684 evidence ladder) starts matching on `VlmStatus`, the exhaustive-match invariant is what will catch any future variant addition. No drift today; the test is the structural contract.

4. **`MapObjectsStoreHandle` API growth across 04 → 05 → 06** — additive, no breaking changes. Public methods added in each batch reuse the existing types in `shared::models` (`MapObject`, `MapObjectObservation`, `IgnoredItem`, `RemovedCandidate`, `MapObjectsBundle`) so consumers don't see churn. The handle's expanded surface (`classify`, `append_ignored`, `is_ignored`, `pass_start`, `end_of_pass`, `apply_decline`, `hydrate`, `drain_pending`, `cascade_mission`, `set_sync_state`, `mark_pushed_ok`, `pending_*_count`, `last_*_ts`) is the 1:1 expression of `description.md §3 Inputs/Outputs` (modulo the persistence layer still pending AZ-668).

5. **`shared::contracts::VlmProvider::name()` (added in AZ-672)** is consumed via the runtime composition root. The `autopilot::runtime::vlm_provider_name` helper (also added in AZ-672) duplicates what `VlmProvider::name()` already provides. Cleanup pending — see Phase 3 clippy note.

6. **Constructor flavours on `vlm_client::VlmClient`** — both lazy (`VlmClient::new`) and eager (`VlmClient::open` / `connect`) constructors are exposed. The composition root uses lazy (because `Runtime::new` is synchronous and the UDS connect must be async). Tests use eager when they want construct-time failure semantics. The two paths are explicit and documented in rustdoc; not a finding.

## Phase 7 — Architecture compliance (re-confirmation, post-batch-06)

| Check                                                            | Result |
|------------------------------------------------------------------|--------|
| No cyclic crate dependencies                                     | ✓      |
| No Layer 2 → Layer 2 import                                      | ✓      |
| Layer 3 → Layer 2 only (mission_executor)                        | ✓      |
| No Layer 3 → Layer 3 import (no second Layer 3 crate yet)        | ✓      |
| Public API matches `module-layout.md` (modulo Phase 2 doc-drift) | ✓      |
| Forbidden technologies absent                                    | ✓ (no `mavlink`-rs, no pymavlink-bindgen, no OpenSSL on the airframe, no TLS in the UDS path) |
| Frozen choices (`architecture.md`) respected                     | ✓ (in-flight central writes forbidden — AZ-647 enforces terminal-only push; UDS peer-cred validates identity instead of TLS per `vlm_client/description.md §6`; the FSM core remains transport-agnostic and the MAVLink wiring sits at the binary edge per `architecture.md §5.6`) |
| No new cyclic module-level dependencies                          | ✓ (`mission_executor::spawn_mavlink_pump` does not introduce a cycle — it imports `mavlink_layer::MavlinkHandle` only, no reverse import) |
| Duplicate symbols across components                              | None new. Workspace-level safe (each crate is its own compilation unit; `cargo doc` namespace inspection clean). `ExponentialBackoff` remains intentionally duplicated (see Phase 6.2). |
| Cross-cutting concerns not locally re-implemented                | ✓ (canonical `UavTelemetry` lives in `shared::models::telemetry`, not in `mission_executor`; canonical `VlmAssessment` / `VlmStatus` live in `shared::models::vlm`, not in `vlm_client`) |

## Duplicate symbol detection

- No two crates expose a public type with the same fully-qualified path.
- No two integration test files define a `pub fn` with the same name within the same crate (rustc enforces).
- `ExponentialBackoff` is intentionally duplicated across `mavlink_layer` and `mission_client` (carried-over from 01–03 — see Phase 6.2).
- `Telemetry` exists at two paths (`mission_executor::Telemetry` — guard view; `shared::models::telemetry::UavTelemetry` — canonical record). Different types, different responsibilities — not a duplicate-symbol finding, but Phase 6.1 records the adapter-gap follow-up.

## Findings summary

| # | Severity | Category               | File:Line                                                              | Title                                                                                          |
|---|----------|------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
| 1 | Medium   | Maintainability        | `crates/mission_executor/src/internal/types.rs:61` + `crates/shared/src/models/telemetry.rs:69` | No adapter from `UavTelemetry` (canonical) to `mission_executor::Telemetry` (guard view) yet — wiring gap surfaces in AZ-650/AZ-651/AZ-652 |
| 2 | Low      | Maintainability        | `_docs/02_document/module-layout.md` (`mapobjects_store` block)        | Public API list out of sync with implementation (`classify`, `hydrate`, `drain_pending`, `cascade_mission`, sync_state/pending_* accessors) |
| 3 | Low      | Maintainability        | `_docs/02_document/architecture.md §5.6`                               | Mission FSM diagram missing the explicit `MissionUploaded` state                                |
| 4 | Low      | Maintainability        | `_docs/02_document/components/mapobjects_store/description.md §3`     | `sync_state` diagram missing `FreshBoot` and `Failed` states                                    |
| 5 | Low      | Maintainability        | `_docs/02_document/data_model.md`                                      | `VlmStatus` missing `Inconclusive` variant; `UavTelemetry` row not yet present                  |
| 6 | Low      | Maintainability        | `crates/autopilot/src/runtime.rs:vlm_provider_name`                    | Pre-existing dead-code warning from batch 04 — remove or wire on next runtime composition pass  |

No Critical / High / Security findings. No new Architecture findings.

## Verdict

**PASS_WITH_WARNINGS**.

All findings are Medium or Low severity. F1 (telemetry adapter) is the highest-severity item and is a known wiring gap that the next batch (AZ-650 BIT F9) will surface naturally — the recommendation is to either pre-create a 1-pt remediation task or include the adapter in AZ-650's brief. F2–F6 are doc drift that the Step 13 (Update Docs) pass will sync; the project's autodev rule already routes these.

Auto-Fix Gate matrix (`implement/SKILL.md §10`): F1 is Medium-Maintainability — auto-fix-eligible if attempted; F2–F5 are Low-Maintainability doc updates (auto-fix-eligible but deliberately deferred to Step 13 to consolidate the documentation sync into one coherent pass); F6 is Low-Maintainability dead-code (auto-fix-eligible, deferred to the next runtime composition pass). No escalation required.

## Continuation

Proceed to batch 7. Per the dependency graph and the candidates flagged in `batch_06_cycle1_report.md` (corrected for the batch-6 report's name-typos against `_dependencies_table.md`), the topologically-ready tasks for batch 7 are:

- AZ-650 `mission_executor_bit_f9` (5pt; deps AZ-640, AZ-648, AZ-649, AZ-644, AZ-646 — all done)
- AZ-651 `mission_executor_lost_link_ladder` (3pt; deps AZ-640, AZ-648, AZ-649 — all done)
- AZ-652 `mission_executor_safety_and_resume` (5pt; deps AZ-640, AZ-648, AZ-649, AZ-643, AZ-647 — all done)
- AZ-653 `gimbal_a40_transport` (5pt; deps AZ-640 — done)
- AZ-657 `frame_ingest_rtsp_session` (3pt; deps AZ-640 — done)
- AZ-668 `mapobjects_store_persistence` (3pt; deps AZ-640, AZ-665, AZ-667 — all done)
- AZ-682 `scan_controller_state_machine` (5pt; deps AZ-640, AZ-649 — both done)

Recommendation: finish the `mission_executor` epic (AZ-636) first by selecting `[AZ-650, AZ-651, AZ-652]` — three same-component tasks, total 13 pts (matches the 10–13 pt cadence of the prior batches), closes one entire epic, and concentrates the AZ-649 telemetry-adapter follow-up (Finding F1 above) into a single component where the same person/agent who has the context can author the adapter inline. The actual selection is delegated to the next `/implement` invocation per its topological rule.
