mirror of
https://github.com/azaion/autopilot.git
synced 2026-06-22 06:01:09 +00:00
[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>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# Telemetry Forwarding from Mission Executor
|
||||
|
||||
**Task**: AZ-649_mission_executor_telemetry_forwarding
|
||||
**Name**: Telemetry forwarding to scan, movement, telemetry, BIT input
|
||||
**Description**: Forward decoded MAVLink telemetry (position, attitude, mode, sys-status) from `mavlink_layer` to `scan_controller` (proximity + middle-waypoint computation), `movement_detector` (ego-motion compensation), and `telemetry_stream` (operator overlay). Provide a typed `UavTelemetry` snapshot for BIT consumption.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-640_initial_structure, AZ-648_mission_executor_state_machine
|
||||
**Component**: mission_executor
|
||||
**Tracker**: AZ-649
|
||||
**Epic**: AZ-636
|
||||
|
||||
## Problem
|
||||
|
||||
`mission_executor` is the only component subscribed to the raw decoded MAVLink stream — it owns the airframe relationship. Downstream components (`scan_controller`, `movement_detector`, `telemetry_stream`) and the BIT path need the same telemetry, but in a typed, projection-friendly form (`UavTelemetry { position, attitude, mode, sys_status, monotonic_ts }`). Forwarding must not duplicate decode work and must not drop messages silently.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `UavTelemetry` is published on three lossy broadcast channels (one per downstream consumer) with monotonic timestamps; consumers that fall behind get drops counted, not blocking.
|
||||
- `UavTelemetrySnapshot` (latest-state view) is exposed for BIT and health-check consumers.
|
||||
- Health surface: `last_telemetry_ts`, per-consumer drop counters.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Subscribe to the typed `MavlinkMessage` enum from `mavlink_layer`.
|
||||
- Project to `UavTelemetry` (`data_model.md §UavTelemetry`).
|
||||
- Publish on three Tokio broadcast channels.
|
||||
- Maintain an atomic latest-snapshot for synchronous reads.
|
||||
|
||||
### Excluded
|
||||
- Decoding MAVLink (task 03).
|
||||
- Geofence/battery checks (task 13).
|
||||
- BIT logic (task 11).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Telemetry reaches all three consumers**
|
||||
Given a healthy SITL link
|
||||
When `GLOBAL_POSITION_INT` and `ATTITUDE` arrive at 10 Hz
|
||||
Then `UavTelemetry` is observed at ≥10 Hz on all three downstream channels, with monotonic timestamps.
|
||||
|
||||
**AC-2: Slow consumer drops, fast consumers unaffected**
|
||||
Given a slow consumer that yields every 500 ms while telemetry arrives at 10 Hz
|
||||
When the channels back-pressure
|
||||
Then the slow consumer's drop counter increments while the other two channels deliver every frame.
|
||||
|
||||
**AC-3: Latest-snapshot is monotonic**
|
||||
Given a sequence of telemetry messages with monotonically advancing timestamps
|
||||
When `latest_snapshot()` is read concurrently
|
||||
Then every read returns a snapshot whose `monotonic_ts` is `>=` the previously observed value.
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
**Performance**
|
||||
- Telemetry republish adds ≤2 ms to the MAVLink decode-to-consumer path.
|
||||
|
||||
**Reliability**
|
||||
- Slow consumer never blocks fast consumers (lossy broadcast).
|
||||
- Drops are counted, never silent.
|
||||
|
||||
## Runtime Completeness
|
||||
|
||||
- **Named capability**: typed telemetry fan-out to three concurrent consumers.
|
||||
- **Production code that must exist**: real Tokio broadcast or equivalent; real atomic snapshot.
|
||||
- **Unacceptable substitutes**: blocking single-consumer queue is not acceptable (it would gate the slowest downstream).
|
||||
@@ -0,0 +1,80 @@
|
||||
# Pre-Flight Hydrate + Sync State Machine + Pending Logs
|
||||
|
||||
**Task**: AZ-667_mapobjects_store_hydrate_and_pending
|
||||
**Name**: Pre-flight hydrate from MapObjectsBundle + sync_state machine + pending_observations/pending_ignored append logs
|
||||
**Description**: Hydrate the store from a `MapObjectsBundle` (from `mission_client`'s pull). Maintain a `sync_state` enum (`synced | cached_fallback | degraded | failed`). Append every NEW / MOVED / EXISTING / REMOVED-CANDIDATE / IgnoredItem event to `pending_observations` / `pending_ignored` for the post-flight push.
|
||||
**Complexity**: 5 points
|
||||
**Dependencies**: AZ-640_initial_structure, AZ-665_mapobjects_store_h3_classify, AZ-666_mapobjects_store_ignored_and_pass_sweep
|
||||
**Component**: mapobjects_store
|
||||
**Tracker**: AZ-667
|
||||
**Epic**: AZ-633
|
||||
|
||||
## Problem
|
||||
|
||||
The on-device working copy is hydrated pre-flight from the central API. The sync_state machine (`fresh_boot → synced | cached_fallback | degraded`) tracks the relationship to the central source of truth. During flight, every classification event is appended to `pending_observations` (or, for declines, `pending_ignored`) — central writes are forbidden mid-flight (Frozen choice 6). The pending logs feed the post-flight push.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `hydrate(bundle: MapObjectsBundle) -> Result<()>` loads the bundle into the in-memory hashmap + IgnoredSet; sets `sync_state = synced` (or `cached_fallback` if `bundle.fallback_used`).
|
||||
- `on_classify_result(classification, detection)` appends a `MapObjectObservation` to `pending_observations` for NEW / MOVED / EXISTING / REMOVED-CANDIDATE.
|
||||
- `on_decline(ignored_item)` appends to `pending_ignored`.
|
||||
- `drain_pending() -> (Vec<MapObjectObservation>, Vec<IgnoredItem>)` is called by `mission_client::push_mapobjects_diff` post-flight.
|
||||
- Health surface: `sync_state`, `pending_observations_count`, `pending_ignored_count`, `last_pull_ts`, `last_push_ts`.
|
||||
- On `DELETE /missions/{id}` cascade signal from `mission_client`, drop mission-scoped objects.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- `MapObjectsBundle` hydration (model = `data_model.md §MapObjectsBundle`).
|
||||
- Sync-state enum + transitions.
|
||||
- Append-only `pending_observations` + `pending_ignored` logs (in-memory; durable disk handoff lives in `mission_client` task 08).
|
||||
- Drain API.
|
||||
- Mission-cascade handler.
|
||||
|
||||
### Excluded
|
||||
- H3 classify (task 26).
|
||||
- Disk persistence (task 29) — this task keeps pending in memory + lets `mission_client` task 08 handle disk durability.
|
||||
- Post-flight push (lives in `mission_client` task 08).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Hydrate from bundle**
|
||||
Given a `MapObjectsBundle` with N MapObjects and M IgnoredItems
|
||||
When `hydrate(bundle)` is called
|
||||
Then the store contains all N + M entries and `sync_state = "synced"`.
|
||||
|
||||
**AC-2: Fallback bundle sets cached_fallback**
|
||||
Given a bundle with `fallback_used = true`
|
||||
When `hydrate(bundle)` is called
|
||||
Then `sync_state = "cached_fallback"`.
|
||||
|
||||
**AC-3: Classify appends pending observation**
|
||||
Given the store hydrated and a detection that classifies as `New`
|
||||
When `on_classify_result(New, detection)` is called
|
||||
Then `pending_observations_count` increments by 1.
|
||||
|
||||
**AC-4: Drain returns and clears pending**
|
||||
Given pending_observations_count = 5, pending_ignored_count = 2
|
||||
When `drain_pending()` is called
|
||||
Then it returns 5 observations + 2 ignored items; counts return to 0.
|
||||
|
||||
**AC-5: Cascade drops mission-scoped objects**
|
||||
Given `M1` (mission A) and `M2` (mission B) objects in the store
|
||||
When the cascade signal for mission A arrives
|
||||
Then `M1` is dropped; `M2` remains.
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
**Performance**
|
||||
- Hydrate from a 30 km × 30 km bundle: ≤2 s (peer of pre-flight pull's 30 s budget).
|
||||
- Append per classification: ≤100 µs.
|
||||
|
||||
## Contract
|
||||
|
||||
- Canonical typed model: `data_model.md §MapObjectsBundle`, `§MapObjectObservation`.
|
||||
|
||||
## Runtime Completeness
|
||||
|
||||
- **Named capability**: hydrate + sync_state + pending event logs.
|
||||
- **Production code that must exist**: real hydrate; real pending append; real drain.
|
||||
- **Unacceptable substitutes**: central writes mid-flight are forbidden (Frozen choice 6).
|
||||
@@ -0,0 +1,72 @@
|
||||
# VlmAssessment Schema Validation + Model-Version Tracking
|
||||
|
||||
**Task**: AZ-674_vlm_client_schema_and_model_version
|
||||
**Name**: VlmAssessment schema validation + model_version tracking + status enum coverage
|
||||
**Description**: Validate every NanoLLM response against the `VlmAssessment` schema. On schema-invalid, return `status: schema_invalid` + log the raw response (size-capped) for offline analysis. Capture `model_version` on every assessment for forensic correlation; log on change.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: AZ-640_initial_structure, AZ-673_vlm_client_nanollm_ipc
|
||||
**Component**: vlm_client
|
||||
**Tracker**: AZ-674
|
||||
**Epic**: AZ-631
|
||||
|
||||
## Problem
|
||||
|
||||
The NanoLLM process emits free-form text, but the autopilot consumes ONLY a validated structured `VlmAssessment`. Schema-invalid responses MUST not propagate as malformed evidence — they're returned as `status: schema_invalid` with the raw response logged size-capped for offline analysis. Model-version capture supports forensic correlation when an assessment's quality is later disputed.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `VlmAssessmentParser::parse(raw_response) -> VlmAssessment` validates the response against the schema; on failure returns `VlmAssessment { status: SchemaInvalid, .. }` and logs the raw response (size-capped to e.g. 4 KB) at warn level.
|
||||
- `model_version` field is populated on every assessment from the NanoLLM-reported version; changes are logged at info level once per change.
|
||||
- Status enum exhaustively covers `Ok | Inconclusive | Timeout | SchemaInvalid | IpcError | Disabled`; consumer match-exhaustion is enforced by the type.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Schema definition in `shared/contracts/vlm-assessment.json` (or equivalent Rust schema).
|
||||
- Parser implementation.
|
||||
- Model-version change detection.
|
||||
- Size-capped raw-response logging.
|
||||
|
||||
### Excluded
|
||||
- The UDS transport (task 34).
|
||||
- Provider trait wiring (task 33).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Valid response parses successfully**
|
||||
Given a fixture NanoLLM response with all required fields
|
||||
When `parse(raw)` runs
|
||||
Then it returns `VlmAssessment { status: Ok, label, confidence, model_version, .. }`.
|
||||
|
||||
**AC-2: Schema-invalid response returns schema_invalid + logs**
|
||||
Given a fixture response missing a required field
|
||||
When `parse(raw)` runs
|
||||
Then it returns `VlmAssessment { status: SchemaInvalid, .. }` and the raw response excerpt (size-capped) is observable in log output.
|
||||
|
||||
**AC-3: Model version change logged once**
|
||||
Given an assessment with `model_version = "v1.0"` followed by another with `model_version = "v1.1"`
|
||||
When the change is detected
|
||||
Then a single log entry observes the change; subsequent assessments with `v1.1` do NOT re-log.
|
||||
|
||||
**AC-4: Status enum is exhaustive**
|
||||
Given consumer code that matches on `VlmAssessment.status`
|
||||
When a new variant is added (compile-time)
|
||||
Then the compiler forces handling of the new variant; no `_ => …` catch-all in the policy code-path.
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
**Performance**
|
||||
- Schema validation: ≤2 ms.
|
||||
|
||||
**Reliability**
|
||||
- Schema mismatches never silent.
|
||||
|
||||
## Contract
|
||||
|
||||
- Canonical typed model: `data_model.md §VlmAssessment`. Schema lives at `shared/contracts/vlm-assessment.json`.
|
||||
|
||||
## Runtime Completeness
|
||||
|
||||
- **Named capability**: VlmAssessment schema validation + model-version awareness.
|
||||
- **Production code that must exist**: real schema validator; real model-version tracker.
|
||||
- **Unacceptable substitutes**: silently mapping a schema-invalid response to `status: Ok` with placeholder fields is unacceptable.
|
||||
Reference in New Issue
Block a user