[AZ-611] Add --skip-auto-sync flag to bypass AC-9 validator

Mid-flight fixtures (Derkachi) and stationary-still scenarios
(FT-P-01) have no take-off spike for the IMU detector and produce
false-positive video motion onsets, so the AC-9 frame-window
validator rejects every plausible offset. Add an operator-acknowledged
opt-out: a new ReplayConfig.skip_auto_sync_validation flag that
suppresses validation, paired with a hard requirement that
time_offset_ms also be set (silent-zero guard at both schema and
adapter layers).

Wired through schema -> CLI (--skip-auto-sync) -> composition root
-> ReplayInputAdapter; Derkachi e2e fixture now passes
time_offset_ms=0 + skip_auto_sync=True by default since the synth
tlog and the video share the same t=0 anchor by construction.

5 new unit tests:
  * schema gate rejects skip=True without manual offset
  * schema gate accepts the legal pair
  * default field value is False (default-construction safety)
  * adapter constructor mirrors the schema gate
  * adapter open() bypasses validate_offset_or_fail when flag is set

All 38 unit tests in test_az401 + test_az405 pass on Mac.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-18 09:04:26 +03:00
parent e114bfd9b8
commit bd41956164
7 changed files with 269 additions and 21 deletions
@@ -41,6 +41,7 @@ from gps_denied_onboard.components.c8_fc_adapter.tlog_replay_adapter import (
)
from gps_denied_onboard.config import (
Config,
ConfigError,
ReplayAutoSyncConfig,
ReplayConfig,
RuntimeConfig,
@@ -733,3 +734,58 @@ def test_compose_root_replay_with_no_calib_path_raises(
match=r"(camera_calibration_path|CAMERA_CALIBRATION_PATH)",
):
compose_root(config)
# ----------------------------------------------------------------------
# AZ-611 — ReplayConfig.skip_auto_sync_validation schema gate
def test_az611_skip_auto_sync_without_manual_offset_rejected_at_init() -> None:
"""``__post_init__`` refuses ``skip_auto_sync_validation=True`` paired
with ``time_offset_ms=None`` — the bypass is only legal once the
operator has committed to an explicit manual offset.
"""
# Act / Assert
with pytest.raises(
ConfigError,
match=r"skip_auto_sync_validation=True requires.*time_offset_ms",
):
ReplayConfig(
video_path="/dev/null/fake.mp4",
tlog_path="/dev/null/fake.tlog",
output_path="/tmp/replay.jsonl",
pace="asap",
time_offset_ms=None,
skip_auto_sync_validation=True,
target_fc_dialect="ardupilot_plane",
)
def test_az611_skip_auto_sync_with_manual_offset_accepted() -> None:
"""The legal combination — ``skip_auto_sync_validation=True`` with an
explicit ``time_offset_ms`` — constructs cleanly and round-trips
both flags onto the frozen dataclass.
"""
# Act
cfg = ReplayConfig(
video_path="/dev/null/fake.mp4",
tlog_path="/dev/null/fake.tlog",
output_path="/tmp/replay.jsonl",
pace="asap",
time_offset_ms=0,
skip_auto_sync_validation=True,
target_fc_dialect="ardupilot_plane",
)
# Assert
assert cfg.skip_auto_sync_validation is True
assert cfg.time_offset_ms == 0
def test_az611_skip_auto_sync_defaults_to_false() -> None:
"""Default-constructed ReplayConfig must not opt out of validation."""
# Act
cfg = ReplayConfig()
# Assert
assert cfg.skip_auto_sync_validation is False