# Code Review Report

**Batch**: 76 — AZ-596 (fc_proxy_runtime driver, FDR-replay mode)
**Date**: 2026-05-17
**Verdict**: PASS

## Findings

(none)

## Findings Sweep

### Phase 1 — Context Loading

Read the AZ-596 task spec, the existing
`fixtures/injectors/fc_proxy.py` (`BlackoutSpoofProxy`,
`SpoofGpsRecord`, `ProxyAlignmentReport`, lifecycle methods), the
FT-N-04 scenario's local `_drive_fc_proxy` stub and the surrounding
call site, and the AZ-595 `sitl_observer` env-var pattern
(`E2E_SITL_REPLAY_DIR` resolution via `replay_dir()`). Verified that
`BlackoutSpoofProxy.from_schedule_file` already raises
`FileNotFoundError` for missing input and `json.JSONDecodeError` for
malformed JSON, so the runtime wrapper only needs to (a) convert
`JSONDecodeError → ValueError` with a file pointer for symmetry with
the rest of the helper layer and (b) project the proxy state into a
small audit dataclass.

### Phase 2 — Spec Compliance

| AC | Coverage | Status |
|----|----------|--------|
| AC-1 (`drive_fc_proxy` loads schedule via `BlackoutSpoofProxy.from_schedule_file`; missing → `FileNotFoundError`; malformed → `ValueError`) | `test_missing_schedule_raises_file_not_found`, `test_malformed_json_raises_value_error`, `test_happy_path_returns_well_formed_report` | Covered |
| AC-2 (`now_ms_provider` supplied → proxy activated, `alignment_err_ms` recorded; absent → `alignment_err_ms=0`, `was_replay_mode=True`) | `test_now_ms_provider_activates_proxy_and_reports_alignment`, `test_now_ms_provider_with_replay_mode_false_distinguishes_from_default`, plus the `was_replay_mode is True` assertion in the happy-path test | Covered |
| AC-3 (`replay_dir` supplied OR `E2E_SITL_REPLAY_DIR` set → `proxy_drive_report.json` written; neither → no write) | `test_writes_report_when_replay_dir_supplied`, `test_writes_report_when_env_var_set`, `test_explicit_replay_dir_overrides_env_var`, `test_no_file_written_when_neither_supplied`, `test_no_file_written_when_env_var_empty`, `test_replay_dir_is_created_when_missing` | Covered |
| AC-4 (≥5 unit tests covering happy + 3 error/edge + 1 boundary) | 11 tests total (3 schedule-load, 2 activation, 6 replay-dir write paths) | Covered (exceeds floor) |
| AC-5 (full suite passes) | 608 passed (+12 from 596 baseline; +11 new tests + 1 layout parametrize entry) | Covered |

### Phase 3 — Code Quality

* **Single responsibility**: `drive_fc_proxy` owns three things and
  three things only — (a) construct a `BlackoutSpoofProxy` from a
  schedule path, (b) optionally activate it against a caller-supplied
  clock, (c) project the proxy state into `ProxyDriveReport` and
  optionally persist it. Each branch is straight-line.
* **`ProxyDriveReport` is a frozen dataclass** with seven plain fields
  — no methods, no factories. The dataclass IS the contract; downstream
  evaluators read it via `asdict` or per-field access.
* **`_resolve_replay_dir` is the single env-var reader** in this
  module. It mirrors the equivalent reader in `sitl_observer.replay_dir`
  (same env var, same "empty string → None" semantics) — the two
  modules deliberately do not import each other so the dependency
  surface stays one-way (`fc_proxy_runtime` → `BlackoutSpoofProxy`,
  `fc_proxy_runtime` → `os.environ`; nothing else).
* **No suppressed errors**: the one `try`/`except` block converts
  `json.JSONDecodeError` to a `ValueError` with the offending file
  path AND preserves the original via `raise … from exc`. No bare
  `except`, no `2>/dev/null`, no empty `pass`.
* **Public-accessor addition on `BlackoutSpoofProxy`**: added three
  `@property` accessors (`window_start_ms`, `window_end_ms`,
  `spoof_frame_count`) so the runtime driver does NOT reach into
  private `_window_start_ms` / `_spoof_gps` attributes. The properties
  are pure, side-effect-free, single-line reads — they purely
  formalise the existing public read surface that `from_schedule_file`
  already establishes.
* **AAA comment discipline**: all 11 new tests use
  `# Arrange / # Act / # Assert`; sections omitted when not needed.
* **No code comments narrate code** — module docstring explains the
  FDR-replay rationale and the live-mode out-of-scope boundary.
  Per-function docstrings document the parameter contract.
* **Public boundary**: imports only stdlib (`json`, `os`,
  `dataclasses`, `pathlib`, `typing`) +
  `fixtures.injectors.fc_proxy.BlackoutSpoofProxy` (an existing
  test-side module). Zero `from gps_denied_onboard ...` imports.

### Phase 4 — Security

* **No new credentials, secrets, or network surface**. The driver is
  pure file I/O over caller-supplied (or env-var-rooted) paths.
* **`E2E_SITL_REPLAY_DIR`** is read-only (consistent with AZ-595).
  Written paths use `Path` arithmetic — no string-interpolation into
  shell, no `eval`, no `subprocess`.
* **JSON write path** uses `json.dumps(asdict(report))` — no opaque
  pickle, no untrusted deserialisation of caller input.
* **`replay_dir.mkdir(parents=True, exist_ok=True)`** silently creates
  intermediate directories. This is acceptable because the path comes
  from the test harness's own env var, not from external input.

### Phase 5 — Performance

* O(1) work beyond the upstream `BlackoutSpoofProxy.from_schedule_file`
  load (which itself is O(N) in the number of spoof frames; the
  dataclass projection is constant-time using the new properties).
* No I/O at module-import time.
* The JSON write path is a single `write_text` call — atomic-enough
  for the audit-only use case.

### Phase 6 — Cross-Task Consistency

* **Env-var pattern matches AZ-595**: the same `E2E_SITL_REPLAY_DIR`
  semantics (set → use; unset / empty / whitespace → ignore). A
  future fixture builder will set the env var once for the whole
  scenario run, and both `sitl_observer` (reads) + `fc_proxy_runtime`
  (writes the audit report) consume it from a single source of truth.
* **`ProxyDriveReport` field names mirror the existing
  `ProxyAlignmentReport`** in `fc_proxy.py` for `alignment_err_ms`.
  No name churn for the AC-3 / AC-NEW-3 evaluator that will eventually
  read it (`blackout_spoof_evaluator` already references
  `alignment_err_ms` from the proxy's own activation report).
* **`FileNotFoundError` / `ValueError` discipline matches the rest of
  `e2e/runner/helpers/`** (per the b73-b75 cumulative review): missing
  inputs → `FileNotFoundError`, malformed inputs → `ValueError` with a
  file pointer.
* **FT-N-04 scenario rewire**: the local `_drive_fc_proxy` stub now
  imports `runner.helpers.fc_proxy_runtime.drive_fc_proxy` and calls
  it. The scenario's `sitl_replay_ready` skip gate (added in b75)
  continues to gate on `E2E_SITL_REPLAY_DIR`; the new helper writes
  its audit report into that same directory when the gate is open.

### Phase 7 — Architecture Compliance

* **Module placement**: `e2e/runner/helpers/fc_proxy_runtime.py` (new)
  + `e2e/_unit_tests/helpers/test_fc_proxy_runtime.py` (new). Both
  registered in `e2e/_unit_tests/test_directory_layout.py`; the
  layout invariant test still passes.
* **No `src/gps_denied_onboard` imports** anywhere. Confirmed.
* **No new top-level dependencies** — stdlib only on the runner side;
  the test side adds nothing new. `requirements.txt` untouched.
* **Backwards-compatible**: the `BlackoutSpoofProxy` change is purely
  additive — three new `@property` accessors. Existing callers
  (the injector's own self-tests, the FT-N-04 fixture) keep working
  unchanged.

## Test Results

* New unit tests: **11** (3 schedule load/error, 2 activation, 6
  replay-dir write).
* Full `e2e/_unit_tests` suite: **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 (`ReadLints` clean on `fc_proxy_runtime.py`,
  `test_fc_proxy_runtime.py`, `fc_proxy.py`,
  `test_ft_n_04_blackout_spoof.py`, `test_directory_layout.py`).
