[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
@@ -610,6 +610,81 @@ def test_ac7_ac8_validator_hard_fail_raises_on_open(
adapter.open()
# ---------------------------------------------------------------------
# AZ-611 — skip_auto_sync_validation bypasses the AC-9 validator
def test_az611_skip_auto_sync_validation_bypasses_ac9(
synthetic_video: Path,
synthetic_tlog_path: Path,
camera_calibration: CameraCalibration,
fake_wgs_converter: mock.MagicMock,
fake_fdr_client: mock.MagicMock,
) -> None:
"""A manual offset that WOULD hard-fail AC-9 succeeds when the
operator explicitly opts out via ``skip_auto_sync_validation=True``.
Mirrors the AC-7 hard-fail scenario above so the bypass is the
only variable.
"""
# Arrange — same manual offset (60 s) that AC-7 above proves
# pushes every frame outside the IMU window.
messages = _build_takeoff_messages()
adapter = ReplayInputAdapter(
video_path=synthetic_video,
tlog_path=synthetic_tlog_path,
camera_calibration=camera_calibration,
target_fc_dialect=FcKind.ARDUPILOT_PLANE,
wgs_converter=fake_wgs_converter,
fdr_client=fake_fdr_client,
pace=ReplayPace.ASAP,
manual_time_offset_ms=60_000,
skip_auto_sync_validation=True,
auto_sync_config=AutoSyncConfig(),
tlog_source_factory=_factory_for(messages),
video_timestamps_factory=_video_timestamps_factory(),
)
# Act
try:
bundle = adapter.open()
# Assert — the bypass let the open() complete with the manual
# offset intact, even though the validator would have rejected it.
assert bundle.resolved_time_offset_ms == 60_000
assert bundle.auto_sync_result is None
finally:
adapter.close()
def test_az611_skip_auto_sync_validation_requires_manual_offset(
synthetic_video: Path,
synthetic_tlog_path: Path,
camera_calibration: CameraCalibration,
fake_wgs_converter: mock.MagicMock,
fake_fdr_client: mock.MagicMock,
) -> None:
"""Constructor refuses ``skip_auto_sync_validation=True`` paired
with ``manual_time_offset_ms=None`` (silent-zero guard).
"""
# Act / Assert
with pytest.raises(
ReplayInputAdapterError,
match=r"skip_auto_sync_validation=True requires.*manual_time_offset_ms",
):
ReplayInputAdapter(
video_path=synthetic_video,
tlog_path=synthetic_tlog_path,
camera_calibration=camera_calibration,
target_fc_dialect=FcKind.ARDUPILOT_PLANE,
wgs_converter=fake_wgs_converter,
fdr_client=fake_fdr_client,
pace=ReplayPace.ASAP,
manual_time_offset_ms=None,
skip_auto_sync_validation=True,
auto_sync_config=AutoSyncConfig(),
)
# ---------------------------------------------------------------------
# AC-6 — low combined confidence WARN-and-proceed