[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
+7 -6
View File
@@ -395,13 +395,14 @@ Bootstrap reference: `_docs/02_tasks/todo/AZ-263_initial_structure.md`. Architec
### shared/replay_input
- **Directory**: `src/gps_denied_onboard/replay_input/`
- **Purpose**: Layer-4 cross-cutting coordinator that converges `(video, tlog)` inputs into the standard `FrameSource` + `FcAdapter` + `Clock` surfaces the airborne composition root consumes. Owns the time-alignment between video frames and tlog IMU/attitude ticks (manual via `--time-offset-ms` or automatic via the AZ-405 IMU-take-off detector). The composition root, in replay mode, builds a `ReplayInputAdapter`, calls `.open()`, and wires the returned `ReplayInputBundle` into the same C1C5 pipeline as live. New under ADR-011 (replaces the v1.0.0 design where replay was a separate composition root).
- `__init__.py` (re-exports `ReplayInputAdapter`, `ReplayInputBundle`, `AutoSyncDecision`, `AutoSyncConfig`, `ReplayInputAdapterError`, plus the AZ-697 / AZ-836 surfaces: `TlogGpsFix`, `TlogGroundTruth`, `load_tlog_ground_truth`, `RouteSpec`, `RouteExtractionError`, `extract_route_from_tlog`)
- `interface.py` (`ReplayInputAdapter` class declaration + `ReplayInputBundle` DTO + `AlignedWindow` / `AutoSyncConfig` / `AutoSyncDecision` DTOs)
- **Purpose**: Layer-4 cross-cutting coordinator. Under AZ-894 the production replay pipeline drives off the operator's IMU+GPS CSV via `CsvReplayFcAdapter`. The legacy `(video, tlog)` auto-sync surface was deprecated by AZ-895 and will be physically removed by AZ-908. The composition root, in replay mode, builds the CSV bundle (frame source + CSV FC adapter + clock) and wires the returned `ReplayInputBundle` into the same C1C5 pipeline as live. New under ADR-011 (replaces the v1.0.0 design where replay was a separate composition root).
- `__init__.py` (re-exports `ReplayInputAdapter`, `ReplayInputBundle`, `AutoSyncDecision`, `AutoSyncConfig`, `ReplayInputAdapterError`, `CsvGpsFix`, `CsvGroundTruth`, `load_csv_ground_truth`, plus the AZ-697 / AZ-836 surfaces: `TlogGpsFix`, `TlogGroundTruth`, `load_tlog_ground_truth`, `RouteSpec`, `RouteExtractionError`, `extract_route_from_tlog`)
- `csv_ground_truth.py` (AZ-894 — `load_csv_ground_truth` + `CsvGpsFix` / `CsvGroundTruth`; the canonical replay ground-truth surface)
- `interface.py` (`ReplayInputAdapter` class declaration + `ReplayInputBundle` DTO + `AlignedWindow` / `AutoSyncConfig` / `AutoSyncDecision` DTOs — the auto-sync DTOs are deprecated by AZ-895 and slated for removal in AZ-908)
- `errors.py` (AZ-405 — `ReplayInputAdapterError` envelope; subclass of `RuntimeError` so the airborne main maps every coordinator-scope failure to CLI exit code 2)
- `tlog_video_adapter.py` (concrete `ReplayInputAdapter` that instantiates `VideoFileFrameSource` + `TlogReplayFcAdapter` + chosen `Clock`)
- `auto_sync.py` (AZ-405 IMU-take-off / video-motion-onset detectors + combined offset computation + AC-8 frame-window-match validator)
- `tlog_ground_truth.py` (AZ-697 — `load_tlog_ground_truth` + `TlogGpsFix` / `TlogGroundTruth` for direct binary tlog GPS-truth extraction; consumed by `helpers.gps_compare` and `tlog_route.py`)
- `tlog_video_adapter.py` **DEPRECATED (AZ-895)**: `ReplayInputAdapter.open()` raises `ReplayInputAdapterError`; retained as an import-stable stub for one cycle. AZ-908 removes it.
- `auto_sync.py` **DEPRECATED (AZ-895)**: every detector + validator raises `ReplayInputAdapterError`; retained as an import-stable stub for one cycle. AZ-908 removes it.
- `tlog_ground_truth.py` (AZ-697 — `load_tlog_ground_truth` + `TlogGpsFix` / `TlogGroundTruth` for direct binary tlog GPS-truth extraction; AUDIT-ONLY after AZ-895, retained for the AZ-699 / AZ-701 validation paths against legacy `.tlog` archives)
- `tlog_route.py` (AZ-836 — `extract_route_from_tlog` + `RouteExtractionError`; re-exports `RouteSpec` from `_types.route`. Reduces a tlog to a coarsened route via Douglas-Peucker on local ENU; consumed by `c11_tile_manager.route_client.SatelliteProviderRouteClient.seed_route`)
- `tests/`
- **Owned by**: AZ-265 (E-DEMO-REPLAY) — task AZ-405 (auto-sync + coordinator).
File diff suppressed because one or more lines are too long
@@ -0,0 +1,59 @@
# Replay: hard removal of deprecated auto-sync surface (AZ-895 follow-up)
**Task**: AZ-908_replay_auto_sync_hard_removal
**Name**: Cycle-5+ cleanup that physically removes the auto-sync surface AZ-895 deprecated
**Description**: Follow-up to AZ-895 (cycle 4). AZ-895 made the auto_sync surface a no-op and deprecated the CLI flags (`--time-offset-ms`, `--skip-auto-sync`, `--auto-trim`) with one-cycle warnings, but left the call sites, config fields, and interface DTOs intact for backward compat. AZ-908 completes the removal in cycle 5+ after a one-cycle deprecation window has passed.
**Complexity**: 3 SP
**Dependencies**: AZ-895 (hard — must ship first; AZ-908 removes what AZ-895 deprecated), AZ-842 (hard — replay protocol docs coordinate)
**Component**: replay_input (auto_sync.py + tlog_video_adapter.py + interface.py), cli/replay, runtime_root/_replay_branch + runtime_root/__init__, config/schema + config/loader + config/__init__, replay_api/app
**Tracker**: AZ-908 (https://denyspopov.atlassian.net/browse/AZ-908)
**Parent Epic**: (none — cycle-4 replay-input redesign follow-up)
## Why
Auto-sync surface is dead in production code: AZ-894 (cycle 4) made the CSV-driven path mandatory via required `--imu`, and AZ-895 (cycle 4) deprecated the surface. After one cycle's deprecation window the deprecation warnings should fire in real CI runs (if any operator scripts still pass the deprecated flags); that surface area can then be removed without breaking anyone.
## Touch list (production)
- DELETE `src/gps_denied_onboard/replay_input/auto_sync.py` (currently a no-op stub from AZ-895)
- DELETE `src/gps_denied_onboard/replay_input/tlog_video_adapter.py` (currently a deprecated coordinator from AZ-895)
- Drop `AutoSyncConfig`, `AutoSyncDecision`, `AlignedWindow` DTOs from `replay_input/interface.py`. Drop `auto_sync_result` + `aligned_window` fields from `ReplayInputBundle`.
- Drop `--time-offset-ms`, `--skip-auto-sync`, `--auto-trim` CLI flags from `cli/replay.py` entirely
- Drop `ReplayConfig.time_offset_ms`, `.skip_auto_sync_validation`, `.auto_trim`, `.auto_sync` from `config/schema.py`. Drop `ReplayAutoSyncConfig` class.
- Drop `REPLAY_TIME_OFFSET_MS` env var + `auto_sync` block handling from `config/loader.py`
- Update `runtime_root/_replay_branch.py` to drop any lingering imports / dead code
- Update `runtime_root/__init__.py` if it references removed symbols
- Update `replay_api/app.py` if it references removed symbols
- Update `e2e/fixtures/sitl_replay_builder/builder.py` if it references removed symbols
## Touch list (tests)
- Delete remaining auto-sync test residue (no-op stub tests from AZ-895)
- Update CLI tests to drop deprecated-flag assertions (the flags no longer exist)
- Confirm `test_az401_compose_root_replay.py` is clean
## Touch list (docs)
- Update `_docs/02_document/module-layout.md` replay_input file list — remove deleted entries
- Update `_docs/02_document/contracts/replay/replay_protocol.md` — remove auto-sync surface narrative (coordinate with AZ-842)
- Update `_docs/02_document/contracts/replay/csv_replay_format.md` cross-references
## Acceptance Criteria
- **AC-1**: All files listed under "DELETE" above are removed from the workspace
- **AC-2**: Unit tests pass with no auto-sync, AutoSyncConfig, AutoSyncDecision, or AlignedWindow symbols in `src/gps_denied_onboard/**`
- **AC-3**: CLI `--help` output does not mention `--time-offset-ms`, `--skip-auto-sync`, or `--auto-trim`
- **AC-4**: `_docs/02_document/module-layout.md` does not mention `auto_sync.py` or `tlog_video_adapter.py`
- **AC-5**: `tests/e2e/replay/test_derkachi_real_tlog.py` continues to `@xfail` with AZ-848-scoped reason
## Out of scope
- AZ-848 / AZ-883 structural fix (tlog clock bug) — unchanged from AZ-895
- Replacing the deprecated coordinator with something else — the CSV path is the replacement (see `_replay_branch._build_csv_bundle`)
## References
- Companion in cycle 4: AZ-894 (CSV adapter), AZ-895 (deprecation)
- Decision audit trail: this file + AZ-895 batch_03_cycle4_report.md
- User decision 2026-05-26 (cycle-4 /autodev batch 3): chose Option A (light deprecation now, file AZ-908 for hard removal in cycle 5+) over Option B (full removal in cycle 4).
@@ -0,0 +1,207 @@
# Batch Report — cycle 4, batch 03
**Batch**: 03
**Cycle**: 4
**Tasks**: AZ-895
**Total complexity**: 2 SP
**Date**: 2026-05-26
**Commit**: pending (this batch)
## Task Selection
AZ-895 (deprecate `auto_sync` surface) ships solo. It is the natural
follow-up to AZ-894 (CSV adapter, batch 02): now that the CSV-driven
path is the primary replay surface, the legacy tlog auto-sync
infrastructure can be retired. Per `_dependencies_table.md`, AZ-895
has a hard dependency on AZ-894 which closed in batch 02.
### Complexity-budget user decision (Option A — minimum)
A naïve full removal of the auto-sync surface would have touched:
- 4 production modules: `auto_sync.py` (delete), `tlog_video_adapter.py`
(delete), `interface.py` (drop AutoSync DTOs + `ReplayInputBundle`
field), `_replay_branch.py` (strip legacy branch + `_build_auto_sync_config`)
- 3 config files: `config/schema.py` (drop `ReplayConfig` auto-sync
fields + the `ReplayAutoSyncConfig` class), `config/loader.py` (drop
`REPLAY_TIME_OFFSET_MS` env + auto_sync block handler),
`config/__init__.py` (drop re-exports)
- 1 CLI: drop the three deprecated flags entirely
- 4 test files needing rewrite or deletion
- Cascading docs in `replay_protocol.md` (AZ-842 sibling work)
Estimated at 45 SP, well over the ticket's 2 SP budget. Per
`meta-rule.mdc` Complexity Budget Check, the user was offered four
options and chose **Option A — minimum**:
> "Minimum (~2 SP): no-op-stub auto_sync.py (raises documented error),
> strip tlog_video_adapter.open() to raise too, drop the unreachable
> legacy branch in _replay_branch.py, deprecate --time-offset-ms /
> --skip-auto-sync / --auto-trim CLI flags (emit warning + ignored),
> keep config fields, delete obsolete tests, update docstrings. File
> AZ-902 (cycle 5) for hard surface removal."
The follow-up ticket was filed as **AZ-908** (Jira renumbered from the
proposed AZ-902 because AZ-902AZ-907 were already taken). AZ-908 is
in `backlog/` and depends on AZ-895 + AZ-842.
## Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|------|--------|----------------|-------|-------------|--------|
| AZ-895_deprecate_auto_sync_surface | Done | 8 modified, 1 added (test) | 3 unit-test files deleted; 1 new test added; 1 existing test file updated; 2,287 unit tests green | 5/5 | None |
### Files touched
Production (`src/gps_denied_onboard/**`):
- REPLACED `replay_input/auto_sync.py` — was a 700+ LOC detector
module; now a 56-line no-op stub whose every public callable raises
`ReplayInputAdapterError("auto-sync removed; supply --imu CSV instead")`.
`__all__` preserved so any external import still resolves.
- REPLACED `replay_input/tlog_video_adapter.py` — was a 700+ LOC
coordinator; now a 105-line deprecated-stub that keeps the
`ReplayInputAdapter` class signature for source-compat. `open()`
raises `ReplayInputAdapterError(...)` immediately; `close()` is a
no-op. Re-exports `ReplayPace` so `_replay_branch.py` can continue
to import it from the same path (preserving the AZ-401 AC-8 import
boundary).
- MODIFIED `runtime_root/_replay_branch.py` — removed the legacy
`ReplayInputAdapter` instantiation branch in
`_build_replay_input_bundle`; deleted the `_build_auto_sync_config`
helper; tightened `_validate_replay_paths` to require `imu_csv_path`
(no more tlog fallback); dropped unused `WgsConverter` and
`AutoSyncConfig` imports; removed the `replay_input_adapter_factory`
test-injection parameter; updated module + function docstrings;
cleaned `auto_sync_used` from the ready-log kv (always None now).
- MODIFIED `replay_input/__init__.py` — docstring rewritten to flag
the deprecation status of the `tlog_video_adapter` / `auto_sync`
surfaces. Re-exports preserved.
- MODIFIED `cli/replay.py``--time-offset-ms`, `--skip-auto-sync`,
`--auto-trim` help text replaced with `DEPRECATED (AZ-895)` notice;
`_print_startup_banner` now emits a `DeprecationWarning` + stderr
line when any of the three are non-default; `_build_replay_config`
hard-codes the corresponding `ReplayConfig` fields to None / False
so the deprecated values cannot influence composition.
- MODIFIED `components/c8_fc_adapter/tlog_replay_adapter.py` — module
docstring reframed as **AUDIT-ONLY** (AC-5). Code unchanged.
- MODIFIED `replay_input/tlog_ground_truth.py` — module docstring
reframed as **AUDIT-ONLY** (AC-5). Code unchanged.
Tests (`tests/**`):
- DELETED `tests/unit/replay_input/test_az405_auto_sync.py` (386 LOC).
Rationale: tested the AZ-405 detector algorithm + AC-9 validator
which no longer execute. Per AC-3, the deprecation-stub test below
replaces it.
- DELETED `tests/unit/replay_input/test_az405_replay_input_adapter.py`
(645 LOC). Rationale: tested the `ReplayInputAdapter` coordinator's
six-step `open()` workflow which now raises immediately.
- DELETED `tests/unit/replay_input/test_az698_window_alignment.py`
(745 LOC). Rationale: tested the AZ-698 IMU↔optical-flow
cross-correlation aligner which no longer executes.
- ADDED `tests/unit/replay_input/test_az895_auto_sync_deprecated_stub.py`
— 5 parametrised tests pinning the AC-1 contract: every public
callable raises `ReplayInputAdapterError` with the documented
message.
- MODIFIED `tests/unit/test_az402_replay_cli.py` — renamed
`test_ac4_time_offset_forwarded``test_ac4_time_offset_ignored_after_az895`
with asserts inverted (value now `None` regardless of flag); added
`test_az895_skip_auto_sync_ignored_and_warned`,
`test_az895_auto_trim_ignored_and_warned`,
`test_az895_no_deprecated_flags_no_warning`; `_argv` helper grew
`skip_auto_sync` and `auto_trim` overrides.
- MODIFIED `tests/unit/test_az401_compose_root_replay.py` — renamed
`test_replay_branch_rejects_both_inputs_empty`
`test_replay_branch_rejects_missing_imu_csv_path` with body updated
to the new gate semantics; `_make_replay_config` helper now sets
`imu_csv_path` by default; deleted
`test_replay_branch_loads_camera_calibration_from_runtime_path`
(only verified the now-removed `replay_input_adapter_factory`
injection path; calibration loading is exercised indirectly by the
full compose-root tests and by the e2e suite).
- MODIFIED `tests/e2e/replay/test_derkachi_real_tlog.py` — xfail
reason text refreshed to reference AZ-848 + AZ-883 (the live
tlog-clock root cause) instead of the closed AZ-776 + AZ-777 (AC-4
literally specifies "AZ-848-scoped reason").
Docs (`_docs/**`):
- MODIFIED `_docs/02_document/module-layout.md``replay_input` file
list flags `tlog_video_adapter.py` + `auto_sync.py` as
**DEPRECATED (AZ-895)**, adds `csv_ground_truth.py`, updates the
package purpose to lead with the CSV path.
- ADDED `_docs/02_tasks/backlog/AZ-908_replay_auto_sync_hard_removal.md`
— cycle-5+ follow-up spec.
- MODIFIED `_docs/02_tasks/_dependencies_table.md` — preamble +
Total Tasks (179 → 180) + Total Complexity (567 → 570); AZ-908 row
added under Cycle-4 / AZ-895 follow-up.
Tracker (Jira):
- AZ-895 — transitioned `To Do``In Progress` (transition id `21`).
- AZ-908 — created (`Replay: hard removal of deprecated auto-sync
surface (AZ-895 follow-up)`), 3 SP estimate, deps AZ-895 (hard) +
AZ-842 (hard). Filed via `createJiraIssue` MCP.
## File-Ownership Note
All touched paths are owned by the cycle-4 replay-input redesign
envelope (`replay_input/` + `cli/replay.py` + `runtime_root/_replay_branch.py`)
plus the AC-5 audit-only docstring updates inside
`components/c8_fc_adapter/tlog_replay_adapter.py` (the c8 owner
already accepted the audit-only reframing in AZ-894). No
out-of-scope edits.
## AC Test Coverage
| AC | Coverage | Test |
|----|----------|------|
| AC-1 (`auto_sync.py` is deleted or made a no-op raising the documented error) | Direct | `tests/unit/replay_input/test_az895_auto_sync_deprecated_stub.py::test_az895_public_callable_raises_with_documented_message[*]` — 5 parametrised cases, one per public symbol (`detect_tlog_takeoff`, `detect_video_motion_onset`, `compute_offset`, `validate_offset_or_fail`, `find_aligned_window`); each asserts `ReplayInputAdapterError("auto-sync removed; supply --imu CSV instead")` |
| AC-2 (CLI flags removed or marked deprecated with one-cycle warning) | Direct | `test_az402_replay_cli.py::test_ac4_time_offset_ignored_after_az895`, `::test_az895_skip_auto_sync_ignored_and_warned`, `::test_az895_auto_trim_ignored_and_warned`, `::test_az895_no_deprecated_flags_no_warning` — assert `DeprecationWarning` is emitted, the stderr banner contains the documented `--flag is deprecated (AZ-895)` text, the value is ignored on the `ReplayConfig`, and the no-flag baseline emits no warning |
| AC-3 (`test_az405_auto_sync` tests pass against the new behaviour or are deleted with rationale recorded in the batch report) | Direct (rationale below) | Deleted; rationale: the AZ-405 tests covered the detector algorithm + AC-9 validator which AZ-895 makes unreachable. Replaced by the AC-1 deprecation-stub test above |
| AC-4 (`test_derkachi_real_tlog.py` continues to `@xfail` with the AZ-848-scoped reason) | Direct | `tests/e2e/replay/test_derkachi_real_tlog.py::test_az699_real_flight_validation_emits_verdict_and_report``@pytest.mark.xfail` decorator retained; `reason` text now names AZ-848 + AZ-883 as the live blocker |
| AC-5 (module docstrings of `tlog_replay_adapter.py` and `tlog_ground_truth.py` updated to call out their new audit-only roles) | Direct | Manual: both module docstrings now lead with `AUDIT-ONLY (AZ-895)` and explain the demotion; verified by inspection at the head of each file |
## Test-Run Summary
- Touched-module focused suite: 111 passed, 1 skipped (RUN_REPLAY_E2E
gate, expected).
- Full unit suite: 2,287 passed, 85 skipped (hardware/Docker gates),
1 deselected (the timing-flaky perf test
`test_cli_console_script.py::TestConsoleScript::test_cold_start_under_1000ms_p99`
— unrelated to this batch, pre-existing).
## Open Items → AZ-908 (cycle-5+ backlog)
The deferred hard-removal surface (full spec in
`_docs/02_tasks/backlog/AZ-908_replay_auto_sync_hard_removal.md`):
- Delete `replay_input/auto_sync.py` + `replay_input/tlog_video_adapter.py`.
- Drop `AutoSyncConfig` / `AutoSyncDecision` / `AlignedWindow` DTOs +
`ReplayInputBundle.auto_sync_result` / `aligned_window` fields.
- Drop the three deprecated CLI flags + their tests.
- Drop `ReplayConfig.time_offset_ms` / `.skip_auto_sync_validation` /
`.auto_trim` / `.auto_sync` + `ReplayAutoSyncConfig` class.
- Drop `BUILD_TLOG_REPLAY_ADAPTER` build flag from `REPLAY_BUILD_FLAGS`.
- Coordinate with AZ-842 to remove the auto-sync surface narrative
from `replay_protocol.md`.
## Lessons Captured
- The user-decision Choose A/B/C/D flow worked exactly as designed:
the agent surfaced the budget overrun before writing code, the
user picked the minimum path with a clear follow-up ticket, and
the batch shipped within its SP budget.
- Keeping deprecated symbols as raising stubs (rather than deleting
them outright in this cycle) gives operators one cycle of upgrade
signal: they import the same name, get a clean `ReplayInputAdapterError`
with a "supply --imu CSV instead" hint, and have a `DeprecationWarning`
to silence in any test fixtures.
- Architectural lint (`test_ac8_replay_branch_imports_only_public_apis`)
caught a mid-batch attempt to import `ReplayPace` directly from the
c8 internals — the lint forces the import to go through the
documented re-export path (`replay_input.tlog_video_adapter`). Even
though that re-export sits inside a deprecated module, the lint's
allow-list is the architectural contract; routing around it would
have been the wrong fix.