# 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 4–5 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-902–AZ-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.
