mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 18:51:15 +00:00
[AZ-596] Batch 76: fc_proxy_runtime driver (FDR-replay mode)
Add `runner/helpers/fc_proxy_runtime.py` wrapping the existing
`BlackoutSpoofProxy` (AZ-406) with a scenario-facing `drive_fc_proxy`
entry point. FDR-replay mode only: loads `schedule.json`, optionally
activates the proxy against a caller clock for alignment verification,
and writes a `proxy_drive_report.json` audit record into
`${E2E_SITL_REPLAY_DIR}` for downstream evaluators.
Replaces the local `_drive_fc_proxy` stub in FT-N-04. Adds 3
@property accessors on `BlackoutSpoofProxy` so the wrapper does not
reach into private attributes. +11 unit tests (608 total, up from
596). Live-mode router wiring remains out of scope (future ticket).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# fc_proxy_runtime driver (FDR-replay mode) for FT-N-04
|
||||
|
||||
**Task**: AZ-596_fc_proxy_runtime
|
||||
**Name**: Implement runtime driver wrapping BlackoutSpoofProxy + replace FT-N-04 stub
|
||||
**Description**: Add `runner/helpers/fc_proxy_runtime.py` with `drive_fc_proxy(schedule_path, *, now_ms_provider=None)` that loads the blackout-spoof schedule via the existing `fixtures/injectors/fc_proxy.BlackoutSpoofProxy`, returns a `ProxyDriveReport`, and (when `E2E_SITL_REPLAY_DIR` is set) writes `proxy_drive_report.json` for downstream evaluators. Replace the local `_drive_fc_proxy` `NotImplementedError` stub in FT-N-04 with the new helper. FDR-replay mode only — live MAVLink router wiring is out of scope.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-406 (`fixtures/injectors/fc_proxy.BlackoutSpoofProxy`), AZ-595 (`sitl_observer.replay_dir`)
|
||||
**Component**: Blackbox Tests / Test Infrastructure (epic AZ-262)
|
||||
**Tracker**: AZ-596
|
||||
**Epic**: AZ-262 (E-BBT)
|
||||
|
||||
## Problem
|
||||
|
||||
FT-N-04 (`test_ft_n_04_blackout_spoof`) calls a local stub:
|
||||
|
||||
```python
|
||||
def _drive_fc_proxy(schedule_path: Path) -> None:
|
||||
raise NotImplementedError(
|
||||
"FC-inbound spoof proxy driver is owned by AZ-441 / runner.helpers.fc_proxy_runtime"
|
||||
)
|
||||
```
|
||||
|
||||
The `BlackoutSpoofProxy` itself was implemented under AZ-406's injector
|
||||
module — fully unit-tested, accepts a `now_ms_provider`, exposes
|
||||
`activate(...)` + `process_inbound_message(...)`. What's missing is the
|
||||
**runtime driver** that the scenario calls to wrap the proxy: load the
|
||||
schedule, activate it, optionally record what happened so downstream
|
||||
evaluators (`sitl_observer.read_gps_health_samples` /
|
||||
`read_consistency_check_events`) can correlate.
|
||||
|
||||
In **FDR-replay mode** (the AZ-595 strategy), the actual FC inbound
|
||||
transport is not real — the SITL replay fixture builder pre-bakes the
|
||||
expected spoofed-GPS-rejected events into the FDR JSON files. So the
|
||||
runtime driver doesn't need to plumb into a live MAVLink router. It
|
||||
just needs to (a) validate the schedule loads correctly, (b) optionally
|
||||
align with the harness clock, and (c) write a small audit report into
|
||||
`${E2E_SITL_REPLAY_DIR}` so evaluators can verify the schedule actually
|
||||
ran.
|
||||
|
||||
Live-mode driving (real MAVLink router + actual FC inbound) is out of
|
||||
scope for this ticket and is a separate live-mode infrastructure task.
|
||||
|
||||
## Surfaces
|
||||
|
||||
* `drive_fc_proxy(schedule_path: Path, *, now_ms_provider: NowMsProvider | None = None, replay_dir: Path | None = None) -> ProxyDriveReport`
|
||||
* Loads the schedule via `BlackoutSpoofProxy.from_schedule_file(schedule_path)`.
|
||||
* If `now_ms_provider` is supplied: activates the proxy and reads
|
||||
`alignment_err_ms` from the activation report.
|
||||
* If `now_ms_provider` is None: emits `ProxyDriveReport` with
|
||||
`alignment_err_ms=0` and `was_replay_mode=True`.
|
||||
* If `replay_dir` is supplied (or resolved from `E2E_SITL_REPLAY_DIR`):
|
||||
writes `proxy_drive_report.json` into that directory.
|
||||
* `ProxyDriveReport` (frozen dataclass): `schedule_path: Path`,
|
||||
`window_start_ms: int`, `window_end_ms: int`, `spoof_frame_count: int`,
|
||||
`alignment_err_ms: int`, `was_replay_mode: bool`.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1**: `drive_fc_proxy(schedule_path)` loads the schedule via
|
||||
`BlackoutSpoofProxy.from_schedule_file`. Missing `schedule_path` →
|
||||
`FileNotFoundError` (inherited from `BlackoutSpoofProxy`). Malformed
|
||||
JSON → `ValueError` with the file path.
|
||||
|
||||
**AC-2**: When `now_ms_provider` is supplied, the driver activates the
|
||||
proxy and records `alignment_err_ms` on the report. When unsupplied,
|
||||
the report fills `alignment_err_ms=0` and `was_replay_mode=True`.
|
||||
|
||||
**AC-3**: When `replay_dir` is supplied (or `E2E_SITL_REPLAY_DIR` env
|
||||
var is set), the driver writes `proxy_drive_report.json` into that
|
||||
directory. When neither is supplied, no file is written.
|
||||
|
||||
**AC-4**: ≥5 unit tests covering: happy path, missing schedule path,
|
||||
malformed schedule JSON, replay-mode JSON write, no-write when env var
|
||||
unset, alignment-error path with injected clock.
|
||||
|
||||
**AC-5**: Full e2e unit-test suite passes (regression gate).
|
||||
|
||||
## Out of Scope
|
||||
|
||||
* Live MAVLink router wiring + docker-compose orchestration.
|
||||
* Other per-scenario `_resolve_*` / `_drive_*` stubs (`_resolve_frame_sink`,
|
||||
`_resolve_fc_inbound_emitter`, `_resolve_outage_injection_frames`,
|
||||
`_resolve_gt_per_frame`, `_drive_imu_replay`, `_resolve_frame_period_ms`)
|
||||
— each gets its own follow-up ticket.
|
||||
|
||||
## Files Touched
|
||||
|
||||
* `e2e/runner/helpers/fc_proxy_runtime.py` (new)
|
||||
* `e2e/_unit_tests/helpers/test_fc_proxy_runtime.py` (new)
|
||||
* `e2e/tests/negative/test_ft_n_04_blackout_spoof.py` (replace local stub)
|
||||
* `e2e/_unit_tests/test_directory_layout.py` (register new module)
|
||||
Reference in New Issue
Block a user