# AZ-687 — build_pre_constructed must guard c6_descriptor_index when config.mode == 'replay'

**Task**: AZ-687_pre_constructed_replay_mode_guard
**Name**: build_pre_constructed must guard c6_descriptor_index when config.mode == 'replay'
**Description**: Follow-up to AZ-618 (umbrella) surfaced by the 2026-05-19 Jetson Tier-2 e2e run. The newly-wired `build_pre_constructed` unconditionally seeds `c6_descriptor_index`, which reads `config.components["c6_tile_cache"]` via `storage_factory._c6_config`. In replay mode the CLI synthesizes a `Config` that does NOT include a `c6_tile_cache` block, so the call raises `KeyError`. The descriptor index is not consumed in replay mode (replay sources frames from the tlog + mp4 directly), so the fix is a replay-mode guard at the bootstrap layer — NOT a silent fallback in `_c6_config` (the docstring explicitly rejects that to keep import-time wiring honest).
**Complexity**: 2 points
**Dependencies**: AZ-619, AZ-620, AZ-624 (the c6 seed introduced by AZ-620 and wired by AZ-624 must already be in `done/` — they are, as of batch 96).
**Component**: runtime_root (cross-cutting)
**Tracker**: AZ-687
**Epic**: AZ-602 (parent: AZ-618 umbrella; AZ-687 is a sibling follow-up under the same Epic)

## Outcome

- `runtime_root/airborne_bootstrap.py::build_pre_constructed` (or its `_build_c6_descriptor_index` helper) guards the c6 descriptor index + c6 tile store seeds on `config.mode == "replay"`.
- Equivalent guard applied to any other c6/c7/cN seed whose underlying `_cN_config` lookup fails on a replay-synthesized config AND whose result is not consumed by the replay path.
- New unit test pins the replay-mode path; existing live-mode tests continue to assert every key in `AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS` is populated.
- Jetson Tier-2 e2e replay run crosses both `replay.compose_root.ready` AND `replay.input.frame_emitted` log lines on AC-1, AC-2, AC-5, AC-6.

## Scope

### Included

- Replay-mode guard inside `runtime_root/airborne_bootstrap.py` covering the c6 descriptor index + c6 tile store seeds (and any other replay-irrelevant seed that currently relies on a config block the replay CLI does not synthesize).
- New unit test under `tests/unit/runtime_root/` (e.g. `test_az687_pre_constructed_replay_mode.py`) that builds a replay-mode Config with no `c6_tile_cache` block and asserts `build_pre_constructed(config)` returns successfully.
- Live-mode regression coverage: extend or assert against existing tests so they continue to require every documented key.
- Re-run Jetson Tier-2 via `scripts/run-tests-jetson.sh` (compose-bypass path documented in `_docs/03_implementation/run_tests_step11_report.md` Rerun 3 if companion/operator-orchestrator builds remain broken on the Jetson).
- Batch report `Tier-2 evidence:` field populated per `_docs/02_document/tests/tier2-jetson-testing.md`.

### Excluded

- Any change to component factory signatures or per-component config schemas.
- New `BUILD_*` env flags.
- Operator-binary `pre_constructed` assembly (still explicitly deferred per AZ-591).
- Fixing the operator-orchestrator Dockerfile build order (`pip install -e .` before `COPY src ./src`) — unrelated; this task uses the compose-bypass workaround.
- AC-3 (camera calibration xfail) or AC-8 (mock-sat dep) — both have their own existing gates.

## Acceptance Criteria

**AC-687-1: replay-mode build_pre_constructed succeeds without c6_tile_cache block**
Given a `Config` with `mode == "replay"` and `components` that do NOT include a `c6_tile_cache` block
When `build_pre_constructed(config)` is called
Then it returns without raising `KeyError`
And the returned dict either omits `c6_descriptor_index` / `c6_tile_store` or maps them to a sentinel the consuming wrapper factories recognise as "not built for replay".

**AC-687-2: live-mode build_pre_constructed continues to seed every required key**
Given a `Config` with `mode == "live"` and the full default `components` block
When `build_pre_constructed(config)` is called
Then the returned dict contains every key in `set.union(*AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS.values())`
And no key maps to `None`.

**AC-687-3: Jetson Tier-2 e2e crosses both bootstrap log lines**
Given AZ-687 is landed
When `scripts/run-tests-jetson.sh tests/e2e/replay/test_derkachi_1min.py` is invoked (or its compose-bypass equivalent)
Then AC-1, AC-2, AC-5, AC-6 each log `replay.compose_root.ready` AND `replay.input.frame_emitted`
And the batch report `Tier-2 evidence:` field captures terminal log path, JetPack version (6.2.2+b24), L4T (R36.5.0), and run timestamp.

**AC-687-4: full Tier-1 pytest suite green**
Given AZ-687 is landed
When the full Tier-1 pytest suite is run
Then 0 new failures appear (the existing pre-existing `c12_operator_orchestrator/test_cli_console_script.py::test_cold_start_under_500ms_p99` Mac-dev-host failure remains out of scope).

## Constraints

- MUST NOT change `_c6_config` to fall back silently — silent fallback was explicitly rejected per the existing docstring (the rationale: "if the package has not been imported, KeyError here is the right failure mode — silent fallback would mask a missing import"). The fix lives at the BUILD-PRE-CONSTRUCTED layer, not the storage_factory layer.
- MUST NOT introduce new `BUILD_*` env flags.
- MUST NOT touch per-component factory signatures.

## Evidence

- Jetson run log (failure evidence): `_docs/03_implementation/jetson_runs/2026-05-19_az618_tier2_run.txt`
- AZ-618 umbrella spec (defines AC-5 expectations): `_docs/02_tasks/done/AZ-618_airborne_bootstrap_pre_constructed.md`
- AZ-620 Phase B (introduces `_build_c6_descriptor_index`): `_docs/02_tasks/done/AZ-620_pre_constructed_phase_b_c6_storage.md`
- AZ-624 Phase F (wires `main()`): `_docs/02_tasks/done/AZ-624_pre_constructed_phase_f_wire_main.md`
- `_c6_config` rationale: `src/gps_denied_onboard/runtime_root/storage_factory.py:55-62`
- Tier-2 testing policy: `_docs/02_document/tests/tier2-jetson-testing.md`

## Tracker relationships

- Parent Epic: AZ-602 (E2E Tier-1 harness rehabilitation)
- Surfaces from: AZ-618 (umbrella) — specifically AC-618-5 Jetson Tier-2 reality gate
