[AZ-594] Implement core-three harness stubs (fdr_reader, frame_source_replay, imu_replay)

Replaces the NotImplementedError stubs AZ-406 reserved on three runner-
side helpers; these were stranded from any tracker ticket since
AZ-407/408 never came back to fill them. Concrete bodies:

* fdr_reader.iter_records: JSONL parser + wire-envelope validator;
  recursive *.jsonl walk; projects {schema_version, ts, producer_id,
  kind, payload} to runner-side FdrRecord with record_type/monotonic_ms
  renames; yields oldest-first.
* frame_source_replay.replay_video: OpenCV VideoCapture decode + JPEG
  re-encode; auto-detects file vs directory; injectable sleep_fn for
  unit-test pacing.
* imu_replay.ImuReplayer.replay: csv.DictReader parse; degrees->radians
  attitude conversion; tolerates scientific notation; same sleep_fn
  injection pattern.

Adds 34 unit tests (14 + 10 + 10). Full e2e unit suite: 558 passed (+31).
Existing scenario _harness_helpers_implemented probes still return False
because they also depend on sitl_observer / fc_proxy_runtime stubs that
remain pending; scenario probe cleanup is out of AZ-594 scope.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-17 08:42:12 +03:00
parent 2d6d44af5d
commit 1d260f7e41
10 changed files with 1196 additions and 76 deletions
@@ -0,0 +1,86 @@
# 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.