# Batch 74 Report — Harness Stubs (cycle 1, batch 8 of test phase) **Batch**: 74 **Date**: 2026-05-17 **Context**: Test implementation (greenfield Step 10 — Implement Tests) **Tasks**: AZ-594 (4 cp) — 1 task (umbrella ticket for the core three harness stubs) **Cycle**: 1 **Verdict**: COMPLETE — PASS (self-reviewed; see `reviews/batch_74_review.md`) ## Summary A planning-gap fix-up batch. The skip-gates in batches 71-73 referenced `AZ-441 / AZ-407 / AZ-416 leftovers` — but on inspection none of those tickets actually owned the deferred harness stubs (`frame_source_replay`, `imu_replay`, `fdr_reader`, `sitl_observer`, `fc_proxy_runtime`). The stubs were AZ-406 surface reservations that AZ-407/408 never came back to fill, and were stranded without a tracker entry. This batch creates a single umbrella ticket (AZ-594) and ships the core three of the five — the ones with the lowest implementation risk and the broadest unblock surface: * `fdr_reader.iter_records` — JSONL parser + wire-schema validator. * `frame_source_replay.replay_video` — OpenCV-backed decode + sink emission. * `imu_replay.ImuReplayer.replay` — CSV parser + emitter driver. The remaining two stubs (`sitl_observer.get_observer` + `read_*` surfaces; `fc_proxy_runtime` driver + docker wiring) need separate tickets — they touch live pymavlink / yamspy / TCP plumbing and don't fit in a single batch alongside the core three. Those become batch 75 candidates. ### AZ-594 — Harness stubs (core three) (4 cp) * **`runner/helpers/fdr_reader.py`** — `iter_records(fdr_archive_root)` recursively walks `*.jsonl` files, validates each line against the wire envelope (`schema_version, ts, producer_id, kind, payload`), projects onto the runner-side `FdrRecord` dataclass (`record_type` for `kind`; `monotonic_ms` derived from ISO 8601 `ts`), and yields records oldest-first. Raises `FileNotFoundError` on missing root + `ValueError` with file+line on malformed envelopes. `archive_size_bytes` body was already present. * **`runner/helpers/frame_source_replay.py`** — `FrameSourceReplayer` now backs both `replay_image_directory(dir)` and `replay_video(path)`. `replay_video` auto-detects file vs directory (so AZ-408 injector frame-directory outputs work via the same entry point). OpenCV `VideoCapture` decodes MP4; every frame is re-JPEG-encoded so the sink always receives JPEG bytes. Cadence pacing is parameterised via a `sleep_fn` injection so unit tests can drop wall-clock entirely. * **`runner/helpers/imu_replay.py`** — `ImuReplayer.replay(csv_path)` parses `data_imu.csv` (the AZ-408 schema with possibly scientific- notation floats), constructs typed `ImuSample`s with attitude converted from degrees → radians, and drives `FcInboundEmitter.emit`. Same `sleep_fn` + `realtime` injection pattern as `frame_source_replay` for test parity. ## Scenario-probe interaction The existing `_harness_helpers_implemented` probes in batches 71-73 pass `/tmp/non-existent` to each helper. Previously the inner `except NotImplementedError: return False` fired; with the new bodies, the outer `except Exception: return False` catches the `FileNotFoundError` and still returns False. So: * No existing scenario silently starts running its full E2E path. * All eight skip-gated scenarios from batches 71-73 continue to skip cleanly because they ALSO depend on `sitl_observer` / `mavproxy_tlog_reader` (for some) / `fc_proxy_runtime` (for FT-N-04). * Probe-cleanup is explicitly excluded from AZ-594 scope and will land in the future batch that fills the remaining two stubs. ## Test Results * New unit tests: 14 (fdr_reader) + 10 (frame_source_replay) + 10 (imu_replay) = **34 new tests**. * Full `e2e/_unit_tests` suite: **558 passed in 139 s** (previous cumulative: 527 → +31 net). * No new linter errors. ## State * Spec moved: `_docs/02_tasks/todo/AZ-594_harness_stubs_core_three.md` → `_docs/02_tasks/done/`. * `_docs/_autodev_state.md` advanced to `last_completed_batch: 74`. * Cumulative review window: `last_cumulative_review = batches_70-72`; next K=3 cumulative review fires at the end of batch 75.