# Batch Report

**Batch**: 97
**Tasks**: AZ-687 (replay-mode guard for `build_pre_constructed`'s strategy-config-dependent seeds)
**Date**: 2026-05-19
**Cycle**: 1
**Commit**: `9bdc868 [AZ-687] Guard build_pre_constructed seeds in replay mode`

## Task Results

| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|------|--------|----------------|-------|-------------|--------|
| AZ-687_pre_constructed_replay_mode_guard | Done (with AC-687-3 BLOCKED on Jetson Tier-2 hardware evidence) | 2 files (1 source modified + 1 new test) | 3 new + 2150 carry-over Tier-1 | 3/4 ACs covered locally; AC-687-3 BLOCKED pending operator-supplied Jetson run | 0 blocking from AZ-687 changes; 1 unrelated pre-existing failure (`c12_operator_orchestrator/test_cli_console_script.py::test_cold_start_under_500ms_p99` — Mac dev host subprocess startup ~700 ms exceeds the 500 ms NFR; unchanged from batch 96) |

AZ-687 is a 2-point cross-cutting follow-up to AZ-618 (umbrella) surfaced by the 2026-05-19 Jetson Tier-2 e2e run captured under `_docs/03_implementation/jetson_runs/2026-05-19_az618_tier2_run.txt`. AZ-624 (Phase F) wired `build_pre_constructed` into `runtime_root.main()`; the replay CLI then exercised that path on the Jetson and failed at `storage_factory._c6_config(config)` with `KeyError: 'c6_tile_cache'` because the minimal replay `Config` synthesized by `gps-denied-replay` does not register the strategy-component blocks (`c6_tile_cache`, `c7_inference`, `c5_state`) that BUILD-PRE-CONSTRUCTED reads. The fix lives at the BUILD-PRE-CONSTRUCTED layer (NOT at `_c6_config` — silent fallback there was explicitly rejected per its docstring): a single predicate `_replay_omits_component_block(config, block_name)` gates the three guarded seed groups. Live-mode behaviour is unchanged (the predicate returns False outside replay), and replay configs that DO populate the blocks (e.g. satellite-anchor verification replays) still build the seeds.

## Files Changed

### Production

- `src/gps_denied_onboard/runtime_root/airborne_bootstrap.py`:
  - Added `_replay_omits_component_block(config, block_name) -> bool` predicate. Returns `True` iff `config.mode == "replay"` AND `block_name not in config.components`. Returns `False` outside replay mode regardless of block presence — AC-687-2 is preserved structurally, not just by test.
  - Guarded three seed groups inside `build_pre_constructed`, each behind `if not _replay_omits_component_block(config, "<block>")`:
    - `c6_tile_cache` → skips `c6_descriptor_index` + `c6_tile_store` seeds.
    - `c7_inference` → skips `c7_inference` + `c3_lightglue_runtime` seeds (the LightGlue runtime cascades on the InferenceRuntime).
    - `c5_state` → skips the eager `(StateEstimator, ISam2GraphHandle)` pair build, leaving `c5_isam2_graph_handle` and the private `_c5_prebuilt_estimator` key absent.
  - Extended `build_pre_constructed`'s docstring with the new "Replay-mode guard (AZ-687)" paragraph documenting which keys may be absent under the guard and why the per-component wrappers in `compose_root` are still safe (they only run for slugs actually in `config.components`).
  - Inline comments at each guard site document the WHY (which wrappers consume the slot, why the skip is safe in replay).

### Tests

- `tests/unit/runtime_root/test_az687_pre_constructed_replay_mode.py` (NEW, 3 tests):
  - `test_ac_687_1_replay_mode_without_c6_block_does_not_raise_keyerror` — AC-687-1 primary: a replay-mode `Config()` with empty `components` exits `build_pre_constructed` cleanly; the five guarded keys are absent from the returned dict; the five guarded builders are NOT invoked (proven via per-builder invocation flags stubbed in via `monkeypatch`).
  - `test_ac_687_1_replay_mode_with_c6_block_present_still_builds_c6_seeds` — AC-687-1 inverse: a replay-mode `Config.with_blocks(c6_tile_cache=..., c7_inference=..., c5_state=...)` configures the blocks, so the guard does NOT fire; every guarded builder runs; every guarded key is seeded. This pins the predicate's semantics — replay mode alone is not a blanket opt-out, the BLOCK ABSENCE is what triggers the skip.
  - `test_ac_687_2_live_mode_seeds_every_required_key` — AC-687-2 regression: a default-mode `Config()` exercises every guarded builder and seeds every key in `set.union(*AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS.values())`. The test asserts both "no key missing" and "no key None" — covers the AC-687-2 contract verbatim.

### Reviews

- `_docs/03_implementation/reviews/batch_97_review.md` (NEW) — code review report. Verdict: **PASS** (0 Critical / 0 High / 0 Medium / 0 Low findings). Phase walkthrough confirms SRP for `_replay_omits_component_block`, scope discipline (exactly two files touched), error-handling preservation (live-mode `KeyError` still surfaces), and architecture compliance (composition root owns the `config.mode` branch per ADR-001 / ADR-009).

### Specs

- `_docs/02_tasks/todo/AZ-687_pre_constructed_replay_mode_guard.md` → ARCHIVED to `_docs/02_tasks/done/` (committed in `9bdc868`).

## AC Test Coverage

4 ACs on the AZ-687 spec. Coverage:

| AC | Coverage |
|----|----------|
| AC-687-1 (replay-mode build_pre_constructed succeeds without `c6_tile_cache` block) | `test_ac_687_1_replay_mode_without_c6_block_does_not_raise_keyerror` (primary) + `test_ac_687_1_replay_mode_with_c6_block_present_still_builds_c6_seeds` (inverse — pins the predicate) |
| AC-687-2 (live-mode build_pre_constructed continues to seed every required key) | `test_ac_687_2_live_mode_seeds_every_required_key` |
| AC-687-3 (Jetson Tier-2 e2e crosses `replay.compose_root.ready` AND `replay.input.frame_emitted`) | **BLOCKED** — out-of-band hardware run via `scripts/run-tests-jetson.sh tests/e2e/replay/test_derkachi_1min.py`. Evidence destination: `_docs/03_implementation/jetson_runs/2026-05-19_az687_tier2_run.txt`. To be collected by the operator. Same pattern as AZ-624 AC-5 and AZ-332/AZ-333's Tier-2 follow-ups. |
| AC-687-4 (full Tier-1 pytest suite green) | Verified — see Test Run section. |

## Test Run

| Suite | Result |
|-------|--------|
| `tests/unit/runtime_root/test_az687_pre_constructed_replay_mode.py` (targeted) | 3 passed |
| `tests/unit/` (full Tier-1 suite) | 2153 passed, 85 skipped, 1 deselected |

The 1 deselected case is the pre-existing `c12_operator_orchestrator/test_cli_console_script.py::test_cold_start_under_500ms_p99` Mac-dev-host failure documented in batch 96 — unrelated to AZ-687 (the test lives in `c12_operator_orchestrator`, which AZ-687 does not touch). The 85 skips are all environment-gated (Docker for c6 Postgres tests, GPU/CUDA for c7 PyTorch FP16, TensorRT for c7 Tier-2, real Jetson for c7 engine_gate, large synthetic datasets gated behind `RUN_REPLAY_E2E=1`).

Tier-1 suite count rose from 2150 (batch 96) to 2153 (batch 97) — exactly the 3 new AZ-687 tests.

## Code Review

- **Verdict**: PASS (0 Critical, 0 High, 0 Medium, 0 Low).
- Zero auto-fix attempts; zero escalated findings.
- Full report: `_docs/03_implementation/reviews/batch_97_review.md`.

## Constraint Compliance (AZ-687 spec)

- "MUST NOT change `_c6_config` to fall back silently" → `storage_factory._c6_config` is untouched in this batch. ✓
- "MUST NOT introduce new `BUILD_*` env flags" → no new flags introduced. ✓
- "MUST NOT touch per-component factory signatures" → no per-component factory signature change; `build_descriptor_index`, `build_tile_store`, `build_inference_runtime`, `build_state_estimator` all keep their existing kwargs. ✓
- Scope discipline (per `.cursor/rules/coderule.mdc` "Scope discipline"): edits confined to `airborne_bootstrap.py` (the BUILD-PRE-CONSTRUCTED owner) plus a new test file under `tests/unit/runtime_root/`. No adjacent hygiene drift; no opportunistic refactors. ✓

## Tracker

- AZ-687 transitioned **In Progress → In Testing** in Jira at batch close (transition queried via `getTransitionsForJiraIssue` per the 2026-05-17 LESSONS entry — never hard-coded). Pending state is: AC-687-1, AC-687-2, AC-687-4 verified locally; AC-687-3 verification awaiting operator-supplied Jetson Tier-2 evidence file.

## Loop Status

- `_docs/02_tasks/todo/` is now empty after AZ-687 archived to `done/`. Two Tier-2 follow-up tasks remain in `backlog/`: AZ-592 (AZ-332 Tier-2 validation) and AZ-593 (AZ-333 Tier-2 validation) — both deferred per the Cycle-1 completeness gate's BLOCKED-with-Tier-2-handle terminal classification.
- The Implement skill's batch loop has no remaining task files in `todo/`. Next action: the Product Implementation Completeness Gate (Step 15 of the implement skill) must be re-run to account for AZ-687 (BLOCKED-on-Tier-2-evidence classification, same precedent as AZ-332 + AZ-333) before Step 7 of the greenfield autodev can advance to Step 8 (Code Testability Revision).
- **AC-687-3 BLOCKING note**: the operator must run `scripts/run-tests-jetson.sh tests/e2e/replay/test_derkachi_1min.py` (or its compose-bypass equivalent per `_docs/03_implementation/run_tests_step11_report.md` Rerun 3) and drop the terminal log under `_docs/03_implementation/jetson_runs/2026-05-19_az687_tier2_run.txt`. The full Tier-2 e2e replay must cross both `replay.compose_root.ready` AND `replay.input.frame_emitted` log lines on AC-1, AC-2, AC-5, AC-6 to clear the gate. Until the file exists, AZ-687's terminal classification stays "BLOCKED-on-Tier-2-evidence" and Step 7's completeness gate flags it as such — same lifecycle as AZ-624's AC-5.
