[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
@@ -105,7 +105,16 @@ class ReplayInputAdapter:
the coordinator's own structured-event mirror.
- ``pace`` — :class:`ReplayPace` (``ASAP`` or ``REALTIME``).
- ``manual_time_offset_ms`` — ``None`` triggers auto-sync; an
integer bypasses auto-sync entirely (AC-8).
integer bypasses auto-sync DETECTION but the AC-9 frame-window
validator still runs on the resolved offset (AC-8).
- ``skip_auto_sync_validation`` — when ``True``, ALSO skip the
AC-9 validator. Only legal in combination with a non-``None``
``manual_time_offset_ms`` (the coordinator refuses both-None
to avoid silent-zero offset bugs). Intended for fixtures where
neither the IMU take-off detector nor the video motion-onset
detector can produce a reliable signal (mid-flight clips,
stationary still-image scenarios — see AZ-611). Default
``False``.
- ``auto_sync_config`` — :class:`AutoSyncConfig` thresholds.
Behaviour:
@@ -127,6 +136,7 @@ class ReplayInputAdapter:
"_fdr_client",
"_pace",
"_manual_time_offset_ms",
"_skip_auto_sync_validation",
"_auto_sync_config",
"_tlog_source_factory",
"_video_frames_factory",
@@ -150,6 +160,7 @@ class ReplayInputAdapter:
pace: ReplayPace,
manual_time_offset_ms: int | None,
auto_sync_config: AutoSyncConfig,
skip_auto_sync_validation: bool = False,
tlog_source_factory: Any | None = None,
video_frames_factory: Any | None = None,
video_timestamps_factory: Any | None = None,
@@ -172,6 +183,22 @@ class ReplayInputAdapter:
raise ReplayInputAdapterError(
f"pace must be a ReplayPace enum; got {type(pace).__name__}"
)
if not isinstance(skip_auto_sync_validation, bool):
raise ReplayInputAdapterError(
"skip_auto_sync_validation must be a bool; got "
f"{type(skip_auto_sync_validation).__name__}"
)
if skip_auto_sync_validation and manual_time_offset_ms is None:
# Mirror the ReplayConfig.__post_init__ gate. Without a
# manual offset there is no operator-acknowledged value
# to skip validation against — auto-sync would compute
# an offset of unknown quality and the validator that
# would catch a bad detection is disabled. Refuse so
# this can't silently mask a wrong offset.
raise ReplayInputAdapterError(
"skip_auto_sync_validation=True requires "
"manual_time_offset_ms to be set"
)
self._video_path = video_path
self._tlog_path = tlog_path
self._camera_calibration = camera_calibration
@@ -180,6 +207,7 @@ class ReplayInputAdapter:
self._fdr_client = fdr_client
self._pace = pace
self._manual_time_offset_ms = manual_time_offset_ms
self._skip_auto_sync_validation = skip_auto_sync_validation
self._auto_sync_config = auto_sync_config
self._tlog_source_factory = tlog_source_factory
self._video_frames_factory = video_frames_factory
@@ -221,21 +249,39 @@ class ReplayInputAdapter:
},
)
# Step 3 — load video frame timestamps and run AC-9 validator.
# Step 3 — load video frame timestamps and run AC-9 validator
# unless the operator explicitly opted out via
# skip_auto_sync_validation (AZ-611). The opt-out is meant for
# mid-flight + stationary fixtures where neither detector can
# produce a reliable signal; the constructor already enforced
# that the opt-out requires a manual offset.
video_frame_timestamps_ns = self._load_video_timestamps()
result_code = validate_offset_or_fail(
resolved_offset_ms,
tlog_imu_timestamps_ns,
video_frame_timestamps_ns,
threshold_pct=self._auto_sync_config.match_threshold_pct,
window_ms=self._auto_sync_config.match_window_ms,
)
if result_code != 0:
self._raise_ac8_fail(
resolved_offset_ms,
len(tlog_imu_timestamps_ns),
len(video_frame_timestamps_ns),
if self._skip_auto_sync_validation:
self._log.info(
f"{_LOG_KIND_OPEN_MANUAL}: ac9_validator_skipped "
f"(resolved_offset_ms={resolved_offset_ms})",
extra={
"kind": _LOG_KIND_OPEN_MANUAL,
"kv": {
"resolved_offset_ms": resolved_offset_ms,
"ac9_validator_skipped": True,
},
},
)
else:
result_code = validate_offset_or_fail(
resolved_offset_ms,
tlog_imu_timestamps_ns,
video_frame_timestamps_ns,
threshold_pct=self._auto_sync_config.match_threshold_pct,
window_ms=self._auto_sync_config.match_window_ms,
)
if result_code != 0:
self._raise_ac8_fail(
resolved_offset_ms,
len(tlog_imu_timestamps_ns),
len(video_frame_timestamps_ns),
)
# Step 4 — clock strategy (single instance per Invariant 2).
clock = self._build_clock()