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>
4.2 KiB
Batch 76 Report — fc_proxy_runtime driver (cycle 1, batch 10 of test phase)
Batch: 76
Date: 2026-05-17
Context: Test implementation (greenfield Step 10 — Implement Tests)
Tasks: AZ-596 (2 cp) — 1 task (fc_proxy_runtime driver, FDR-replay mode)
Cycle: 1
Verdict: COMPLETE — PASS (self-reviewed; see reviews/batch_76_review.md)
Summary
Final piece of the harness-stubs arc that started in batch 74. The
FT-N-04 (test_ft_n_04_blackout_spoof) scenario called a local
_drive_fc_proxy stub that raised
NotImplementedError("FC-inbound spoof proxy driver is owned by runner.helpers.fc_proxy_runtime"). That module didn't exist. The
BlackoutSpoofProxy state machine (load schedule, activate, replace
inbound GPS frames inside the window) was already fully implemented
under AZ-406 in fixtures/injectors/fc_proxy.py — what was missing
was the scenario-facing wrapper.
This batch adds runner/helpers/fc_proxy_runtime.py (one function +
one dataclass) using the same FDR-replay strategy as AZ-595:
the runtime driver does not plumb into a live MAVLink router. It
loads the schedule, optionally activates the proxy against a
caller-supplied clock, and writes a small audit JSON
(proxy_drive_report.json) into ${E2E_SITL_REPLAY_DIR} so the
downstream FDR evaluators can correlate. Live-mode driving (real
router + real FC) is explicitly a separate live-mode infrastructure
ticket.
AZ-596 — fc_proxy_runtime driver (2 cp)
runner/helpers/fc_proxy_runtime.py—drive_fc_proxy(schedule_path, *, now_ms_provider=None, replay_dir=None):- Loads the schedule via
BlackoutSpoofProxy.from_schedule_file(schedule_path). - Wraps
json.JSONDecodeErrorasValueErrorwith a file pointer (consistent with the rest ofe2e/runner/helpers/). - When
now_ms_provideris supplied, activates the proxy and records the resultingalignment_err_ms. When absent, setswas_replay_mode=True. - Resolves the write directory in this order: explicit
replay_dirargument >${E2E_SITL_REPLAY_DIR}env var > no write. The chosen directory is created if missing. - Returns
ProxyDriveReport(frozen dataclass withschedule_path, window_start_ms, window_end_ms, spoof_frame_count, alignment_err_ms, was_replay_mode).
- Loads the schedule via
fixtures/injectors/fc_proxy.py— added three additive@propertyaccessors (window_start_ms,window_end_ms,spoof_frame_count) so the runtime wrapper does NOT reach into private attributes. Existing callers unaffected.- FT-N-04 scenario — local
_drive_fc_proxystub replaced withfrom runner.helpers.fc_proxy_runtime import drive_fc_proxy; drive_fc_proxy(schedule_path). The scenario's b75sitl_replay_readyskip gate continues to govern when this code path actually runs. - Directory layout test — registered the new
runner/helpers/fc_proxy_runtime.pypath.
Out of scope (deferred)
- Live MAVLink router + FC inbound transport — the runtime
driver currently does not wire
proxy.process_inbound_messageinto a real router. A live-mode follow-up ticket will own the docker-compose-bound MAVLink router that plumbs in the per-message replace. - 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 will get its own follow-up ticket. They remainNotImplementedErrorstubs in their respective scenario files; thesitl_replay_readyskip gate ensures they're never reached in unit-test mode.
Test Results
- New unit tests: 11 (3 schedule load/error, 2 activation, 6 replay-dir write).
- Full
e2e/_unit_testssuite: 608 passed in 124 s (previous cumulative: 596 → +12 net = +11 new fc_proxy_runtime tests + 1 new directory-layout parametrize entry). - No new linter errors.
State
- Spec moved:
_docs/02_tasks/todo/AZ-596_fc_proxy_runtime.md→_docs/02_tasks/done/. _docs/_autodev_state.mdadvanced tolast_completed_batch: 76.last_cumulative_reviewremainsbatches_73-75; next K=3 cumulative review fires at the end of batch 78.