[AZ-895] Deprecate replay auto-sync surface; file AZ-908 follow-up

Option A (minimum-deprecation, 2 SP) per user complexity-budget
decision. Auto-sync stays importable as a raising stub for one cycle
so external callers see a clean ReplayInputAdapterError instead of an
ImportError. Full physical removal is filed as AZ-908 (cycle-5+ backlog).

Production:
- auto_sync.py: 700+ LOC -> 56-line no-op stub raising
  "auto-sync removed; supply --imu CSV instead"
- tlog_video_adapter.py: 700+ LOC -> 105-line deprecated stub;
  ReplayInputAdapter.open() raises immediately, close() is a no-op
- _replay_branch.py: dropped legacy auto-sync branch +
  _build_auto_sync_config; _validate_replay_paths now requires
  imu_csv_path; replay_input_adapter_factory parameter removed
- cli/replay.py: --time-offset-ms / --skip-auto-sync / --auto-trim
  emit DeprecationWarning + stderr line; values ignored
- tlog_replay_adapter.py + tlog_ground_truth.py docstrings: AUDIT-ONLY

Tests:
- DELETED test_az405_auto_sync, test_az405_replay_input_adapter,
  test_az698_window_alignment (covered code no longer runs)
- ADDED test_az895_auto_sync_deprecated_stub (5 parametrised, pins AC-1)
- test_az402_replay_cli: deprecation warnings + ignored-value asserts
- test_az401_compose_root_replay: new imu_csv_path-required gate;
  deleted the calibration-loading test that relied on the removed
  replay_input_adapter_factory injection point
- test_derkachi_real_tlog: xfail reason refreshed to AZ-848 + AZ-883
  (AC-4 "AZ-848-scoped reason")

Docs:
- module-layout.md: replay_input file list flags deprecated modules,
  adds csv_ground_truth.py
- _dependencies_table.md: +AZ-908 row, preamble + totals updated
  (179 -> 180 tasks, 567 -> 570 SP)
- AZ-908 backlog spec added; AZ-895 spec moved todo -> done
- batch_03_cycle4_report.md written

Touched-module tests green (111 passed, 1 skipped). Full unit suite
green: 2287 passed, 85 skipped, 1 deselected (pre-existing flaky perf
test, unrelated).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-26 22:09:59 +03:00
parent fdb593a775
commit 007aa36fbf
19 changed files with 600 additions and 4213 deletions
+9 -38
View File
@@ -144,9 +144,14 @@ def _make_replay_config(
if calib_path is None
else RuntimeConfig(camera_calibration_path=str(calib_path))
)
# AZ-895: imu_csv_path is required by _validate_replay_paths; the
# auto-sync surface that previously accepted (video, tlog) alone
# was deprecated. tlog_path stays set so tests covering the legacy
# config field continue to round-trip it.
replay = ReplayConfig(
video_path="/dev/null/fake.mp4",
tlog_path="/dev/null/fake.tlog",
imu_csv_path="/dev/null/fake_imu.csv",
output_path=output_path,
pace=pace,
time_offset_ms=time_offset_ms,
@@ -634,13 +639,11 @@ def test_replay_branch_rejects_empty_video_path(
build_replay_components(config)
def test_replay_branch_rejects_both_inputs_empty(
def test_replay_branch_rejects_missing_imu_csv_path(
_airborne_replay_env: Path,
) -> None:
# AZ-894: the validation gate now accepts either imu_csv_path
# (canonical) or tlog_path (legacy) — rejecting only when both
# are empty. Keeping the historical name pattern (test_*_rejects_*)
# for grep parity but renamed to reflect the new semantics.
# AZ-895: imu_csv_path is required; the legacy tlog-only branch was
# removed. The (video, CSV) bundle is the only supported composition.
# Arrange
runtime_cfg = RuntimeConfig(camera_calibration_path=str(_airborne_replay_env))
@@ -648,7 +651,7 @@ def test_replay_branch_rejects_both_inputs_empty(
runtime=runtime_cfg,
replay=ReplayConfig(
video_path="/dev/null/fake.mp4",
tlog_path="",
tlog_path="/dev/null/fake.tlog",
imu_csv_path="",
output_path="/tmp/out.jsonl",
pace="asap",
@@ -678,38 +681,6 @@ def test_replay_branch_rejects_unknown_pace_after_init(
build_replay_components(config)
def test_replay_branch_loads_camera_calibration_from_runtime_path(
_airborne_replay_env: Path,
) -> None:
"""The branch reads the SAME calibration JSON the live binary uses."""
# Arrange
config = _make_replay_config(calib_path=_airborne_replay_env)
# Act — run far enough to populate the bundle without hitting the
# real video / tlog readers. We do that by injecting a stub
# ``replay_input_adapter_factory`` that returns a fake adapter
# whose ``open()`` produces a trivial bundle.
bundle = _make_replay_bundle()
class _StubAdapter:
def __init__(self, **_kwargs: Any) -> None:
pass
def open(self) -> ReplayInputBundle:
return bundle
components, order = build_replay_components(
config,
replay_input_adapter_factory=lambda **_kwargs: _StubAdapter(),
sink_factory=lambda *_args: mock.MagicMock(spec=JsonlReplaySink),
)
# Assert
assert order == REPLAY_COMPONENT_KEYS
assert components["frame_source"] is bundle.frame_source
assert components["fc_adapter"] is bundle.fc_adapter
# ----------------------------------------------------------------------
# Smoke