mirror of
https://github.com/azaion/autopilot.git
synced 2026-06-21 14:41:09 +00:00
c4eff40dbc
Add the operator-command dispatcher behind a typed CommandAck: 60 s per-command-id idempotency cache, surfaced-POI registry with unknown_poi_id + expired gates, BIT-degraded ack severity check, and SafetyOverride forwarding to mission_executor with structured audit log (redacts signature + session_token). Cross-layer wiring goes through three new traits in shared::contracts (ScanCommandRouter, MissionSafetyRouter, BitReportSeverityLookup) so operator_bridge stays free of direct scan_controller / mission_executor imports. scan_controller::ScanControllerHandle implements the scan router; a new mission_executor::SafetyDispatchHandle wraps the BIT ack channel + battery monitor handle and implements the safety router; BitControllerHandle gains a bounded (16-entry) report-severity cache for the lookup trait. scan_controller also picks up ConfirmPoi handling: PoiQueue::confirm removes the entry and SubmitOutcome::Confirmed carries the typed (target_mgrs, target_class) hint for AZ-684/AZ-686 downstream. Tests: 9 new integration tests in operator_bridge/tests/dispatcher.rs cover AZ-680 AC-1..AC-5 + AZ-681 AC-1..AC-4. scan_controller adds 2 ConfirmPoi tests. All modified-crate suites green; one pre-existing mission_executor state-machine test flake (already documented in _docs/_process_leftovers) updated to note ac1 also affected. Co-authored-by: Cursor <cursoragent@cursor.com>
7.8 KiB
7.8 KiB
Batch Report
Batch: 17 Cycle: 1 Tasks: AZ-680, AZ-681 Date: 2026-05-20
Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|---|---|---|---|---|---|
| AZ-680_operator_bridge_command_dispatch | Done | 14 files | scan_controller: 8 (2 new); operator_bridge: 20 lib + 9 integration; mission_executor: 35 lib | 5/5 ACs covered | None |
| AZ-681_operator_bridge_safety_and_bit_ack | Done | shared with AZ-680 | (counted above; 4 new integration tests cover AZ-681 ACs) | 4/4 ACs covered | None |
AC Coverage map — AZ-680
| AC | Test | File | Notes |
|---|---|---|---|
| AC-1 Confirm forwards target hint | az680_ac1_confirm_forwards_to_scan_router |
crates/operator_bridge/tests/dispatcher.rs |
Records POI in registry, dispatches ConfirmPoi, asserts scan_router.route invoked exactly once with the original command |
| AC-2 Re-transmit returns cached ack | az680_ac2_retransmit_returns_cached_ack |
same file | Same command_id dispatched twice; second call returns Ok without re-invoking router (60 s IdempotencyCache) |
| AC-3 Unknown POI id rejected | az680_ac3_unknown_poi_id_rejected |
same file | Asserts CommandAck::Error { reason: "unknown_poi_id" } and router never invoked |
| AC-4 Expired POI rejected | az680_ac4_expired_poi_rejected |
same file | Pre-seeds a surfaced POI with past deadline; asserts expired ack and router not invoked |
| AC-5 Decline appends IgnoredItem via scan_controller | az680_ac5_decline_forwards_to_scan_router |
same file | DeclinePoi dispatches into scan_router.route exactly once; ack Ok |
Plus scan_controller native coverage of the ConfirmPoi path (queue-side resolution): confirm_poi_via_operator_command_emits_action + confirm_poi_unknown_id_is_validation_error in crates/scan_controller/tests/poi_queue.rs.
AC Coverage map — AZ-681
| AC | Test | File | Notes |
|---|---|---|---|
| AC-1 BIT-DEGRADED ack succeeds | az681_ac1_bit_degraded_ack_forwards |
crates/operator_bridge/tests/dispatcher.rs |
Severity lookup returns Some(true); safety_router.acknowledge_bit_degraded invoked exactly once with the report_id + operator_id |
| AC-2 BIT-FAIL ack rejected | az681_ac2_bit_fail_ack_rejected |
same file | Severity lookup returns Some(false); ack returns cannot_acknowledge_fail; safety_router not invoked |
| AC-3 Safety-override forwards with scope + duration | az681_ac3_safety_override_forwards_with_audit_entry |
same file | SafetyOverride { BatteryRtl, 60s } dispatched; safety_router.apply_safety_override called once with the exact scope/duration; audit log contains exactly one matching SafetyOverride entry with outcome: Ok |
| AC-4 Audit log redacts secrets | az681_ac4_audit_log_contains_no_signature_or_session_token |
same file | Every audit entry serialised to JSON; asserts no signature and no session_token substring. Lock-in: AuditEntry enum has no fields that could leak either secret |
AC Test Coverage: All covered (9/9 across both tasks)
Code Review Verdict: PASS (self-review — see findings below)
Auto-Fix Attempts: 0
Stuck Agents: None
Files modified
M crates/shared/src/models/operator.rs (+SafetyOverrideScope)
M crates/shared/src/contracts/mod.rs (+ScanCommandRouter +MissionSafetyRouter +BitReportSeverityLookup)
M crates/scan_controller/Cargo.toml (+async-trait)
M crates/scan_controller/src/lib.rs (confirm_poi + ScanCommandRouter impl + SubmitOutcome::Confirmed)
M crates/scan_controller/src/internal/poi_queue/mod.rs (+ConfirmAction + PoiQueue::confirm)
M crates/scan_controller/tests/poi_queue.rs (+2 tests: confirm path; replaced exhaustive match with catch-all to handle new variant)
M crates/mission_executor/src/lib.rs (+pub use SafetyDispatchHandle)
M crates/mission_executor/src/internal/mod.rs (+safety_dispatch module)
A crates/mission_executor/src/internal/safety_dispatch.rs (NEW: MissionSafetyRouter impl)
M crates/mission_executor/src/internal/bit.rs (+bounded report_overalls FIFO; +report_overall + BitReportSeverityLookup impl on BitControllerHandle)
M crates/operator_bridge/src/lib.rs (registry+dispatcher wiring; with_scan_router/safety_router/bit_severity_lookup/audit_sink/dispatcher; dispatch_command; OperatorCommandSink impl now real; registry forget/record on dequeue/surface)
M crates/operator_bridge/src/internal/mod.rs (+audit +dispatcher +idempotency +poi_registry)
A crates/operator_bridge/src/ack.rs (NEW: CommandAck + ack_reasons)
A crates/operator_bridge/src/internal/audit.rs (NEW: AuditEntry / AuditSink / TracingAuditSink)
A crates/operator_bridge/src/internal/dispatcher.rs (NEW: OperatorCommandDispatcher + Builder)
A crates/operator_bridge/src/internal/idempotency.rs (NEW: IdempotencyCache 60s TTL)
A crates/operator_bridge/src/internal/poi_registry.rs (NEW: SurfacedPoi + SurfacedPoiRegistry)
A crates/operator_bridge/tests/dispatcher.rs (NEW: 9 integration tests)
M _docs/_process_leftovers/2026-05-20_mission_executor_ac3_flake.md (note: ac1 also flakes)
R _docs/02_tasks/todo/AZ-680_operator_bridge_command_dispatch.md → done/...
R _docs/02_tasks/todo/AZ-681_operator_bridge_safety_and_bit_ack.md → done/...
Architecture notes
- The cross-component dispatch shape is now:
operator_bridge(Layer 3) →ScanCommandRouter/MissionSafetyRouter/BitReportSeverityLookuptraits inshared::contracts(Layer 1) → concrete impls onScanControllerHandleand on the newSafetyDispatchHandle(constructed at the composition root fromBitController::ack_tx+BatteryMonitorHandle). BitControllerHandlenow retains a bounded FIFO of the last 16(report_id, overall)pairs sois_acknowledgeablecan answer for any report id observed in the current pre-flight gate cycle. Beyond that horizon, the dispatcher rejects withunknown_bit_reportrather than guessing.SafetyOverrideScopeis#[non_exhaustive]so future variants (LinkLost,Geofence) extend without breaking downstream matchers.SafetyDispatchHandle::apply_safety_overridereturns a typed Validation error on any unwired scope, so adding a variant to the enum without wiring the executor side fails closed.- The audit log is a structured
tracing::info!per entry by default (TracingAuditSink). TheAuditSinktrait keeps the door open for a file-based persistent sink later; integration tests substitute a recording sink. - Idempotency cache TTL: 60 s per the task spec. Lazy eviction on each lookup/insert keeps the cache small without a background sweeper.
Quality gates
cargo fmt --all: cleancargo clippy -p shared -p scan_controller -p mission_executor -p operator_bridge --all-targets -- -D warnings: cleancargo clippy --workspace --all-targets -- -D warnings: pre-existingRuntime::vlm_provider_namedead-code lint (out-of-scope; tracked in_docs/_process_leftovers/2026-05-20_autopilot_clippy.md)cargo test -p shared -p scan_controller -p operator_bridge -p mission_executor: all greencargo test --workspace: one pre-existing flake —mission_executor::ac1_multirotor_happy_path_reaches_done(sameawait_statepolling race as the documentedac3flake; passes on retry; leftover updated)
Suggested next batch
From _docs/02_tasks/_dependencies_table.md, ready tasks after this batch:
AZ-659_frame_ingest_publisher(3pt, no new deps) — was eligible for this batch but excluded for cohesionAZ-682_scan_controller_state_machine_skeletonfollow-ups (AZ-684 evidence ladder) oncescan_controllerconfirm path lands the FSM-side follow-throughAZ-685_mapobjects_store_ignored_items(consumes theDeclineActionpayload AZ-680 now produces end-to-end)