mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 07:01:14 +00:00
[AZ-621] Phase C: build_pre_constructed seeds c7_inference
Third subtask of AZ-618. Extends airborne_bootstrap.build_pre_constructed additively with c7_inference (GPU InferenceRuntime). Wraps the existing inference_factory.build_inference_runtime so a BUILD_TENSORRT_RUNTIME / BUILD_PYTORCH_FP16_RUNTIME mismatch surfaces a clear operator-facing AirborneBootstrapError naming BOTH airborne C7 flags plus the consuming component slug, rather than bubbling up RuntimeNotAvailableError with no context. New public const C7_AIRBORNE_BUILD_FLAGS pairs each airborne runtime with its gating env flag (onnx_trt_ep deliberately omitted — research only). Tests stub at the factory boundary; real GPU/TensorRT load remains Tier-2 only (consolidated at AZ-624). AZ-619 and AZ-620 test files extended with a _stub_c7_inference_builder autouse fixture mirroring the AZ-620 pattern for _build_c6_*. 18/18 runtime_root unit tests pass. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# Batch 92 Report — AZ-621 Phase C: build_pre_constructed seeds c7_inference (cycle 1)
|
||||
|
||||
**Batch**: 92
|
||||
**Date**: 2026-05-19
|
||||
**Context**: Product implementation (greenfield Step 7 — Implement; AZ-618 umbrella, Phase C of 6)
|
||||
**Tasks**: AZ-621 (3 cp) — 1 task
|
||||
**Cycle**: 1
|
||||
**Verdict**: COMPLETE — PASS (self-reviewed inline; 18/18 runtime_root tests green)
|
||||
|
||||
## Summary
|
||||
|
||||
Third of six subtasks decomposing the AZ-618 umbrella. Phase C extends `airborne_bootstrap.build_pre_constructed(config)` additively with `c7_inference` (the GPU InferenceRuntime). Wraps the existing `inference_factory.build_inference_runtime` so that a `BUILD_TENSORRT_RUNTIME`/`BUILD_PYTORCH_FP16_RUNTIME` mismatch surfaces a clear operator-facing `AirborneBootstrapError` naming BOTH airborne C7 flags + the consuming component slug, rather than bubbling up the lower-level `RuntimeNotAvailableError` with no consuming-component context.
|
||||
|
||||
### AZ-621 — build_pre_constructed Phase C (3 cp)
|
||||
|
||||
* **`src/gps_denied_onboard/runtime_root/airborne_bootstrap.py`**:
|
||||
* Imports `build_inference_runtime` from `runtime_root.inference_factory` (Layer-5 sibling).
|
||||
* New public constant `C7_AIRBORNE_BUILD_FLAGS: tuple[tuple[str, str], ...] = (("tensorrt", "BUILD_TENSORRT_RUNTIME"), ("pytorch_fp16", "BUILD_PYTORCH_FP16_RUNTIME"))` — single source of truth for the airborne-buildable C7 runtimes + their gating env flags (deliberately omits `onnx_trt_ep`, which is research-only per `module-layout.md`). Exported in `__all__`.
|
||||
* New module-private helper `_build_c7_inference(config)` — calls `build_inference_runtime`; on `RuntimeNotAvailableError`, re-raises as `AirborneBootstrapError` naming `c7_inference` (the missing key), BOTH `C7_AIRBORNE_BUILD_FLAGS` entries (so operator sees production-default + Tier-0 fallback), and the consuming component slug(s) via `_configured_consumers_of_pre_constructed_key`. Preserves the original error via `raise ... from exc`.
|
||||
* `build_pre_constructed(config)` now returns five keys: `c13_fdr`, `clock`, `c6_descriptor_index`, `c6_tile_store`, `c7_inference`. Additivity preserved per AZ-621 Constraint "MUST be additive on top of AZ-619 + AZ-620"; docstring updated to mention AZ-621 Phase C and the new raise path.
|
||||
* No new `BUILD_*` env flag introduced (constraint satisfied) — only a Python-level tuple listing already-existing flag names.
|
||||
|
||||
* **`tests/unit/runtime_root/test_az619_pre_constructed_phase_a.py`**:
|
||||
* New autouse fixture `_stub_c7_inference_builder` monkeypatches `_build_c7_inference` to an opaque sentinel — keeps AZ-619 tests focused on Phase A keys without entangling them with the new Phase C integration path (and avoids requiring `BUILD_TENSORRT_RUNTIME=ON` + real GPU for the AZ-619 cases).
|
||||
|
||||
* **`tests/unit/runtime_root/test_az620_pre_constructed_phase_b.py`**:
|
||||
* New autouse fixture `_stub_c7_inference_builder` (same pattern as the AZ-619 file) — keeps AZ-620 tests focused on Phase B C6 wiring and error-translation, independent of Phase C.
|
||||
|
||||
* **`tests/unit/runtime_root/test_az621_pre_constructed_phase_c.py`** (new):
|
||||
* `test_ac_621_1_adds_c7_inference` — stubs `build_inference_runtime` to an identifiable mock; asserts the bootstrap dict contains `c7_inference` referencing the stub return, and AZ-619 + AZ-620 keys are still present. Covers AC-621.1.
|
||||
* `test_ac_621_2_both_build_flags_off_with_configured_consumer_raises_named_error` — stubs `build_inference_runtime` to raise `RuntimeNotAvailableError` (mirroring real `BUILD_*=OFF` behaviour); builds a config with `c3_matcher.strategy="disk_lightglue"`; asserts `AirborneBootstrapError` is raised whose message contains `c7_inference`, BOTH `BUILD_TENSORRT_RUNTIME` and `BUILD_PYTORCH_FP16_RUNTIME` (sourced from the new constant), and `c3_matcher`. Verifies the original error is preserved as `__cause__`. Covers AC-621.2.
|
||||
* `test_ac_621_2_no_configured_consumer_still_raises_with_full_set` — defence-in-depth (mirrors the AZ-620 same-name pattern): even with no consumer configured, the bootstrap still fails loudly and the message lists the FULL theoretical consumer set (`c2_vpr`, `c3_matcher`, `c3_5_adhop`). Strengthens AC-621.2's operator-facing contract.
|
||||
|
||||
## File Ownership
|
||||
|
||||
* OWNED (component `runtime_root`):
|
||||
* `src/gps_denied_onboard/runtime_root/airborne_bootstrap.py`
|
||||
* `tests/unit/runtime_root/test_az619_pre_constructed_phase_a.py`
|
||||
* `tests/unit/runtime_root/test_az620_pre_constructed_phase_b.py`
|
||||
* `tests/unit/runtime_root/test_az621_pre_constructed_phase_c.py`
|
||||
* READ-ONLY: imports from `runtime_root.inference_factory` (Layer-5 sibling within same package), `runtime_root.errors`, plus the AZ-619/620 modules already in scope (`storage_factory`, `clock`, `fdr_client`, `config`).
|
||||
* No FORBIDDEN-zone writes.
|
||||
|
||||
## Test Results
|
||||
|
||||
* New unit tests: **3** under `tests/unit/runtime_root/test_az621_pre_constructed_phase_c.py` covering AC-621.1 + AC-621.2 (twice, defence-in-depth).
|
||||
* Updated unit tests: AZ-619 (5/5 passing) and AZ-620 (3/3 passing) — both files extended with one autouse stub fixture each; no behavioural changes to existing assertions.
|
||||
* AZ-591 regression (`test_az591_airborne_bootstrap.py`): **7/7 passing** — `AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS` table consistency + airborne registration semantics + tier isolation all unchanged.
|
||||
* Combined: `pytest tests/unit/runtime_root/ -q` → **18/18 passed in 0.68 s**.
|
||||
* No new ruff errors on touched files.
|
||||
|
||||
## Out of scope (deferred)
|
||||
|
||||
* Remaining keys in `AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS` — AZ-622 (`c3_lightglue_runtime` + `c3_feature_extractor`), AZ-623 (`c282_ransac_filter` + c5 helpers), AZ-624 (`main()` integration + AC-1..AC-5).
|
||||
* Real GPU model load / TensorRT engine deserialization — consolidated at AZ-624's Tier-2 Jetson AC-5 run per `_docs/02_document/tests/tier2-jetson-testing.md`. This subtask stubs at the factory boundary, exactly mirroring the AZ-620 pattern for C6 storage.
|
||||
* Cumulative review for batches 85-92 (8 batches deferred — K=3 default missed). Recorded as outstanding work; the previous session intentionally chose AZ-621 as the next single-task batch and did not run the cumulative review at the start. Next session should run cumulative review (scope: union of files changed since `cumulative_review_batches_82-84_cycle1_report.md`) BEFORE starting batch 93 (AZ-622) or batch 94 (AZ-623) — both are still in the AZ-618 remediation cycle and the architecture surface around `airborne_bootstrap.py` deserves a cross-batch sweep before the umbrella's `main()` wiring (AZ-624) lands.
|
||||
|
||||
## State
|
||||
|
||||
* Spec moved: `_docs/02_tasks/todo/AZ-621_pre_constructed_phase_c_c7_inference.md` → `_docs/02_tasks/done/`.
|
||||
* Tracker: AZ-621 transitioned **To Do → In Progress** at batch start; will transition **In Progress → In Testing** after this commit (per implement skill Step 12).
|
||||
* `_docs/_autodev_state.md` advanced to `last_completed_batch: 92`.
|
||||
|
||||
## Next Batch
|
||||
|
||||
* **Batch 93**: AZ-622 (3 cp) — Phase D of AZ-618 umbrella. Adds `c3_lightglue_runtime` + `c3_feature_extractor` to `build_pre_constructed`. Depends on AZ-619 + AZ-620 + AZ-621 (all will be in `done/` after this commit) + AZ-278 (already in `done/`).
|
||||
* **Alternative Batch 93**: AZ-623 (3 cp) — Phase E. Independent of AZ-621; could batch alongside AZ-622 (combined 6 cp, well under the 20-cp cap) if next session prefers throughput over single-phase review focus.
|
||||
* **Recommended sequencing for next session**: (1) run the deferred cumulative review for batches 85-92; (2) then batch 93 — single task (AZ-622) to maintain the AZ-618 single-phase-per-batch pattern, OR a two-task batch (AZ-622 + AZ-623) if cumulative review surfaces no architecture drift around the bootstrap surface.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Code Review Report — Batch 92 (AZ-621 Phase C: c7_inference)
|
||||
|
||||
**Batch**: 92
|
||||
**Tasks**: AZ-621 (3 cp) — AZ-618 Phase C
|
||||
**Date**: 2026-05-19
|
||||
**Verdict**: PASS
|
||||
|
||||
## Findings
|
||||
|
||||
None.
|
||||
|
||||
## Phase Notes
|
||||
|
||||
- **Phase 1 (Context)**: Loaded `_docs/02_tasks/todo/AZ-621_pre_constructed_phase_c_c7_inference.md` and the AZ-618 umbrella spec. Mapped batch changes to AZ-621 exclusively.
|
||||
- **Phase 2 (Spec Compliance)**:
|
||||
- AC-621.1: `_build_c7_inference` is called from `build_pre_constructed`; `test_ac_621_1_adds_c7_inference` asserts the key and identity to the factory return.
|
||||
- AC-621.2: `_build_c7_inference` wraps `RuntimeNotAvailableError` into `AirborneBootstrapError` whose message includes both `BUILD_TENSORRT_RUNTIME` and `BUILD_PYTORCH_FP16_RUNTIME` (sourced from the new `C7_AIRBORNE_BUILD_FLAGS` const) plus the configured consumer slug. Defence-in-depth test covers the no-consumer-configured path.
|
||||
- AC-621.3: `tests/unit/runtime_root/test_az621_pre_constructed_phase_c.py` exists and contains the AC-621.1 / AC-621.2 cases.
|
||||
- Constraints: no new `BUILD_*` env flag introduced (only a Python-level `tuple` listing already-existing flag names). Implementation is strictly additive — AZ-619 and AZ-620 keys still populated; AZ-619/620 test suites green after stubbing the new `_build_c7_inference` symbol via the same autouse-fixture pattern AZ-620 introduced for `_build_c6_*`.
|
||||
- **Phase 3 (Code Quality)**: `_build_c7_inference` is ~25 lines, single responsibility (wrap factory + translate error), preserves cause chain via `raise ... from exc`, mirrors the established `_build_c6_descriptor_index` pattern verbatim. Tests use AAA, monkeypatch at the bootstrap module boundary (no real GPU/TensorRT load), assert message content per AC-621.2's explicit naming requirement.
|
||||
- **Phase 4 (Security)**: no external inputs, no subprocess/eval/exec, no secrets. The new error message includes operator-facing flag names and component slugs only.
|
||||
- **Phase 5 (Performance)**: composition-time only; one factory call per `build_pre_constructed` invocation. No hot-path change.
|
||||
- **Phase 6 (Cross-Task Consistency)**: single-task batch — N/A.
|
||||
- **Phase 7 (Architecture Compliance)**:
|
||||
- `runtime_root.airborne_bootstrap` (Layer 5) imports `runtime_root.inference_factory` (Layer 5 sibling). Per the Allowed Dependencies table, Layer 5 may import from Layers 1–4 plus its own sibling composition modules. No layer violation.
|
||||
- The imported symbol `build_inference_runtime` is part of `inference_factory.__all__` — Public API respected.
|
||||
- No new module-level cycle introduced (graph remains `airborne_bootstrap → inference_factory → errors`, no back-edge).
|
||||
- No duplicate symbol introduced across components.
|
||||
- No cross-cutting concern locally re-implemented — error translation is bootstrap-specific (mirrors c6 path) and not a candidate for `shared/`.
|
||||
|
||||
## Counts
|
||||
|
||||
- Critical: 0
|
||||
- High: 0
|
||||
- Medium: 0
|
||||
- Low: 0
|
||||
@@ -6,10 +6,10 @@ step: 7
|
||||
name: Implement
|
||||
status: in_progress
|
||||
sub_step:
|
||||
phase: 14
|
||||
phase: 16
|
||||
name: batch-loop
|
||||
detail: "next batch = AZ-621 (Phase C: c7_inference, 3pt); AZ-623 (Phase E, 3pt) also unblocked. AZ-622/AZ-624 still chained. Fresh session recommended — AZ-621 touches GPU runtimes."
|
||||
detail: "batch 92 done (AZ-621 Phase C). Next: cumulative review for batches 85-92 (8 batches overdue, K=3 default missed), THEN batch 93 = AZ-622 (Phase D, 3pt) or AZ-622+AZ-623 (6pt). Fresh session recommended."
|
||||
retry_count: 0
|
||||
cycle: 1
|
||||
tracker: jira
|
||||
last_completed_batch: 91
|
||||
last_completed_batch: 92
|
||||
|
||||
@@ -57,6 +57,7 @@ from gps_denied_onboard.clock.wall_clock import WallClock
|
||||
from gps_denied_onboard.fdr_client.client import make_fdr_client
|
||||
from gps_denied_onboard.runtime_root import register_strategy
|
||||
from gps_denied_onboard.runtime_root.errors import RuntimeNotAvailableError
|
||||
from gps_denied_onboard.runtime_root.inference_factory import build_inference_runtime
|
||||
from gps_denied_onboard.runtime_root.matcher_factory import build_matcher_strategy
|
||||
from gps_denied_onboard.runtime_root.pose_factory import build_pose_estimator
|
||||
from gps_denied_onboard.runtime_root.refiner_factory import build_refiner_strategy
|
||||
@@ -75,6 +76,7 @@ if TYPE_CHECKING:
|
||||
__all__ = [
|
||||
"AIRBORNE_MAIN_PRODUCER_ID",
|
||||
"AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS",
|
||||
"C7_AIRBORNE_BUILD_FLAGS",
|
||||
"FAISS_BUILD_FLAG",
|
||||
"AirborneBootstrapError",
|
||||
"build_pre_constructed",
|
||||
@@ -92,6 +94,26 @@ flag is OFF but a consuming component still requires the index.
|
||||
"""
|
||||
|
||||
|
||||
C7_AIRBORNE_BUILD_FLAGS: Final[tuple[tuple[str, str], ...]] = (
|
||||
("tensorrt", "BUILD_TENSORRT_RUNTIME"),
|
||||
("pytorch_fp16", "BUILD_PYTORCH_FP16_RUNTIME"),
|
||||
)
|
||||
"""Airborne-buildable C7 inference runtimes paired with their gating env flags.
|
||||
|
||||
Production-default for the airborne binary is ``tensorrt`` (TensorRT FP16);
|
||||
``pytorch_fp16`` is the Tier-0 / workstation fallback (per
|
||||
``module-layout.md`` build-time exclusion table and the AZ-621 task spec).
|
||||
``onnx_trt_ep`` is deliberately omitted — it is research-only and not built
|
||||
into the airborne binary, even though
|
||||
:mod:`gps_denied_onboard.runtime_root.inference_factory` supports the label.
|
||||
|
||||
Surfaced here so :func:`_build_c7_inference` can name BOTH airborne flags
|
||||
in an :class:`AirborneBootstrapError` (AC-621.2) — the operator sees which
|
||||
flag must be flipped ON to enable the configured runtime AND which fallback
|
||||
flag would unblock the bootstrap with a different runtime selection.
|
||||
"""
|
||||
|
||||
|
||||
AIRBORNE_MAIN_PRODUCER_ID: Final[str] = "airborne_main"
|
||||
"""Producer ID for the per-binary shared FdrClient placed under
|
||||
``pre_constructed['c13_fdr']``.
|
||||
@@ -474,22 +496,71 @@ def _build_c6_tile_store(config: Config) -> Any:
|
||||
return build_tile_store(config)
|
||||
|
||||
|
||||
def _build_c7_inference(config: Config) -> Any:
|
||||
"""Build ``pre_constructed['c7_inference']`` via the C7 factory.
|
||||
|
||||
Wraps :func:`inference_factory.build_inference_runtime` so a
|
||||
:class:`RuntimeNotAvailableError` (raised when the configured
|
||||
runtime's ``BUILD_*`` flag is OFF) surfaces as an
|
||||
:class:`AirborneBootstrapError` naming:
|
||||
|
||||
* the missing key (``c7_inference``);
|
||||
* BOTH airborne-buildable runtimes and their gating flags
|
||||
(:data:`C7_AIRBORNE_BUILD_FLAGS`), so the operator sees the
|
||||
production-default (``tensorrt`` /
|
||||
``BUILD_TENSORRT_RUNTIME``) AND the Tier-0 fallback
|
||||
(``pytorch_fp16`` / ``BUILD_PYTORCH_FP16_RUNTIME``);
|
||||
* the consuming component slug(s) — narrowed to configured
|
||||
consumers when available, else the full theoretical set.
|
||||
|
||||
The original factory error is preserved via ``raise ... from`` so
|
||||
operators still see the upstream cause (e.g., "runtime 'tensorrt'
|
||||
requires BUILD_TENSORRT_RUNTIME=ON in this binary; the flag is
|
||||
OFF.").
|
||||
|
||||
AC-621.2: this is the path the test exercises when both airborne C7
|
||||
flags are OFF and a configured consumer (c2_vpr / c3_matcher /
|
||||
c3_5_adhop) still needs ``c7_inference``.
|
||||
"""
|
||||
try:
|
||||
return build_inference_runtime(config)
|
||||
except RuntimeNotAvailableError as exc:
|
||||
consumers = _configured_consumers_of_pre_constructed_key(
|
||||
config, "c7_inference"
|
||||
)
|
||||
flag_options = ", ".join(
|
||||
f"{flag}=ON for runtime {runtime!r}"
|
||||
for runtime, flag in C7_AIRBORNE_BUILD_FLAGS
|
||||
)
|
||||
raise AirborneBootstrapError(
|
||||
f"airborne_bootstrap: cannot construct "
|
||||
f"pre_constructed['c7_inference'] because no airborne C7 "
|
||||
f"inference runtime is buildable. Consuming components: "
|
||||
f"{list(consumers)}. Set one of: {flag_options}, and ensure "
|
||||
f"config.components['c7_inference'].runtime matches the "
|
||||
f"enabled flag. Upstream error: {exc}"
|
||||
) from exc
|
||||
|
||||
|
||||
def build_pre_constructed(config: Config) -> dict[str, Any]:
|
||||
"""Build the airborne ``pre_constructed`` dict for :func:`compose_root`.
|
||||
|
||||
AZ-619 (Phase A) seeded ``c13_fdr`` and ``clock``. AZ-620 (Phase B)
|
||||
adds the two C6 storage entries (``c6_descriptor_index`` +
|
||||
``c6_tile_store``). Phases C..F (AZ-621..AZ-624) will extend this
|
||||
function to populate the remaining keys in
|
||||
added the two C6 storage entries (``c6_descriptor_index`` +
|
||||
``c6_tile_store``). AZ-621 (Phase C) adds ``c7_inference``
|
||||
(PyTorch FP16 vs. TensorRT, gated by
|
||||
:data:`C7_AIRBORNE_BUILD_FLAGS`). Phases D..F (AZ-622..AZ-624) will
|
||||
extend this function to populate the remaining keys in
|
||||
:data:`AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS`.
|
||||
|
||||
Returns a fresh dict on each call. The ``c13_fdr`` instance is cached
|
||||
inside :func:`make_fdr_client` (per-producer cache) so two calls within
|
||||
the same process return dicts where ``pre_constructed['c13_fdr']`` is
|
||||
the SAME object — AC-619.2. ``clock`` is a fresh :class:`WallClock`
|
||||
each call (stateless; the cache would be a no-op). The C6 entries are
|
||||
constructed via the existing :mod:`storage_factory` builders without
|
||||
additional caching at this layer.
|
||||
each call (stateless; the cache would be a no-op). The C6 + C7
|
||||
entries are constructed via the existing :mod:`storage_factory` and
|
||||
:mod:`inference_factory` builders without additional caching at this
|
||||
layer.
|
||||
|
||||
Replay-mode override: :func:`compose_root` merges ``replay_components``
|
||||
over ``pre_constructed`` so the :class:`WallClock` here is replaced by
|
||||
@@ -500,14 +571,19 @@ def build_pre_constructed(config: Config) -> dict[str, Any]:
|
||||
AirborneBootstrapError: if ``BUILD_FAISS_INDEX`` is OFF and any
|
||||
configured consumer (per
|
||||
:data:`AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS`) requires
|
||||
``c6_descriptor_index`` — surfaces with the consuming
|
||||
component slug(s) and the gating flag.
|
||||
``c6_descriptor_index``; OR if no airborne C7 inference
|
||||
runtime is buildable (both ``BUILD_TENSORRT_RUNTIME`` and
|
||||
``BUILD_PYTORCH_FP16_RUNTIME`` OFF, or the configured
|
||||
runtime's matching flag is OFF) and any configured consumer
|
||||
requires ``c7_inference``. The message names the consuming
|
||||
component slug(s) and the relevant gating flag(s).
|
||||
"""
|
||||
return {
|
||||
"c13_fdr": make_fdr_client(AIRBORNE_MAIN_PRODUCER_ID, config),
|
||||
"clock": WallClock(),
|
||||
"c6_descriptor_index": _build_c6_descriptor_index(config),
|
||||
"c6_tile_store": _build_c6_tile_store(config),
|
||||
"c7_inference": _build_c7_inference(config),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -67,6 +67,19 @@ def _stub_c6_builders(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _stub_c7_inference_builder(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange: stub the AZ-621 Phase C C7 inference builder so the AZ-619
|
||||
# tests stay focused on Phase A. Without this the bare Config() below
|
||||
# would hit KeyError inside inference_factory's
|
||||
# config.components["c7_inference"] lookup, AND the airborne BUILD_*
|
||||
# flags are typically unset in the test env. The sentinel is opaque
|
||||
# on purpose — AZ-619 assertions never inspect it.
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap, "_build_c7_inference", lambda _config: object()
|
||||
)
|
||||
|
||||
|
||||
def test_ac_619_1_default_config_seeds_c13_fdr_and_clock() -> None:
|
||||
# Arrange
|
||||
config = Config()
|
||||
|
||||
@@ -60,6 +60,20 @@ def _isolated_fdr_cache() -> Iterator[None]:
|
||||
fdr_client_module._reset_for_tests()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _stub_c7_inference_builder(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange: stub the AZ-621 Phase C C7 inference builder so AZ-620
|
||||
# tests stay focused on the Phase B contract. Without this the
|
||||
# configs used below would hit KeyError inside inference_factory's
|
||||
# config.components["c7_inference"] lookup. The sentinel is opaque
|
||||
# on purpose — AZ-620 assertions never inspect it.
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"_build_c7_inference",
|
||||
lambda _config: MagicMock(name="InferenceRuntime"),
|
||||
)
|
||||
|
||||
|
||||
def test_ac_620_1_adds_c6_descriptor_index_and_c6_tile_store(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
"""AZ-621 — Phase C of AZ-618: ``build_pre_constructed`` seeds c7_inference.
|
||||
|
||||
Verifies the contract at
|
||||
``_docs/02_tasks/todo/AZ-621_pre_constructed_phase_c_c7_inference.md``:
|
||||
|
||||
* AC-621.1: with a default airborne ``Config`` (and one of the airborne
|
||||
C7 ``BUILD_*`` flags ON in spirit — here stubbed at the factory
|
||||
boundary), ``build_pre_constructed(config)`` additionally contains
|
||||
``c7_inference`` (InferenceRuntime instance) on top of AZ-619 + AZ-620.
|
||||
* AC-621.2: when BOTH ``BUILD_TENSORRT_RUNTIME=OFF`` AND
|
||||
``BUILD_PYTORCH_FP16_RUNTIME=OFF`` AND a config selects a C2 / C3
|
||||
strategy that needs c7 (here: ``c3_matcher.strategy="disk_lightglue"``),
|
||||
``build_pre_constructed`` raises ``AirborneBootstrapError`` whose
|
||||
message names both airborne flags AND the consuming component slug.
|
||||
|
||||
AC-621.3 (this file exists with the above tests) is satisfied by the
|
||||
existence of this module.
|
||||
|
||||
The tests stub the heavy ``build_inference_runtime`` factory at the
|
||||
airborne_bootstrap import boundary — exactly mirroring the AZ-620
|
||||
pattern for the C6 factories. The factory itself has its own
|
||||
component-level unit tests under ``tests/unit/c7_inference/`` for the
|
||||
real engine-load paths; real GPU model load is Tier-2 only (consolidated
|
||||
under AZ-624 per the umbrella's Tier-2 testing strategy).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from gps_denied_onboard.config import Config
|
||||
from gps_denied_onboard.fdr_client import client as fdr_client_module
|
||||
from gps_denied_onboard.runtime_root import airborne_bootstrap
|
||||
from gps_denied_onboard.runtime_root.airborne_bootstrap import (
|
||||
C7_AIRBORNE_BUILD_FLAGS,
|
||||
AirborneBootstrapError,
|
||||
build_pre_constructed,
|
||||
)
|
||||
from gps_denied_onboard.runtime_root.errors import RuntimeNotAvailableError
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _C3MatcherBlock:
|
||||
"""Minimal c3_matcher config block — only the field the test needs."""
|
||||
|
||||
strategy: str = "disk_lightglue"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolated_fdr_cache() -> Iterator[None]:
|
||||
# Arrange: every test starts with an empty FdrClient cache so the
|
||||
# bootstrap's make_fdr_client call doesn't accidentally pick up a
|
||||
# stale instance from a prior test in the same process.
|
||||
fdr_client_module._reset_for_tests()
|
||||
yield
|
||||
fdr_client_module._reset_for_tests()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _stub_c6_builders(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange: stub the AZ-620 Phase B C6 builders so AZ-621 stays
|
||||
# focused on the Phase C contract. The configs used below either
|
||||
# omit c6 entirely or use bare Config()-derived blocks.
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"_build_c6_descriptor_index",
|
||||
lambda _config: MagicMock(name="DescriptorIndex"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"_build_c6_tile_store",
|
||||
lambda _config: MagicMock(name="TileStore"),
|
||||
)
|
||||
|
||||
|
||||
def test_ac_621_1_adds_c7_inference(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange: stub build_inference_runtime to a sentinel so we can
|
||||
# assert wiring without standing up real GPU/TensorRT/PyTorch.
|
||||
inference_runtime_sentinel = MagicMock(name="InferenceRuntime")
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"build_inference_runtime",
|
||||
lambda _config: inference_runtime_sentinel,
|
||||
)
|
||||
config = Config()
|
||||
|
||||
# Act
|
||||
pre_constructed = build_pre_constructed(config)
|
||||
|
||||
# Assert: AZ-621 key is present and references the exact factory
|
||||
# return. AZ-619 + AZ-620 keys are still present (additivity contract).
|
||||
assert "c7_inference" in pre_constructed
|
||||
assert pre_constructed["c7_inference"] is inference_runtime_sentinel
|
||||
assert {"c13_fdr", "clock", "c6_descriptor_index", "c6_tile_store"}.issubset(
|
||||
pre_constructed.keys()
|
||||
)
|
||||
|
||||
|
||||
def test_ac_621_2_both_build_flags_off_with_configured_consumer_raises_named_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
# Arrange: simulate both BUILD_TENSORRT_RUNTIME=OFF and
|
||||
# BUILD_PYTORCH_FP16_RUNTIME=OFF by making build_inference_runtime
|
||||
# raise RuntimeNotAvailableError the same way inference_factory does
|
||||
# in production when neither flag is ON.
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"build_inference_runtime",
|
||||
_raise_no_c7_runtime_available,
|
||||
)
|
||||
config = Config.with_blocks(
|
||||
c3_matcher=_C3MatcherBlock(strategy="disk_lightglue")
|
||||
)
|
||||
|
||||
# Act + Assert
|
||||
with pytest.raises(AirborneBootstrapError) as excinfo:
|
||||
build_pre_constructed(config)
|
||||
|
||||
message = str(excinfo.value)
|
||||
assert "c7_inference" in message
|
||||
# Both airborne C7 BUILD_* flags must be named in the message so the
|
||||
# operator sees the production-default AND the Tier-0 fallback flag.
|
||||
for _runtime, flag in C7_AIRBORNE_BUILD_FLAGS:
|
||||
assert flag in message, f"{flag} missing from error: {message!r}"
|
||||
# Consuming component slug is named.
|
||||
assert "c3_matcher" in message
|
||||
# The original factory error is preserved as the cause chain so the
|
||||
# operator can still see the upstream reason.
|
||||
assert isinstance(excinfo.value.__cause__, RuntimeNotAvailableError)
|
||||
|
||||
|
||||
def test_ac_621_2_no_configured_consumer_still_raises_with_full_set(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Defence-in-depth: even with no consumer configured, AZ-621 still
|
||||
fails loudly when no airborne C7 runtime is buildable.
|
||||
|
||||
Mirrors the AZ-620 defence-in-depth test. ``build_pre_constructed``
|
||||
is the airborne contract's single seam — silently returning a dict
|
||||
without ``c7_inference`` would let a later AZ-622..AZ-624 phase or a
|
||||
downstream caller misread the state. The error message lists the
|
||||
FULL theoretical consumer set so the operator still gets actionable
|
||||
information.
|
||||
"""
|
||||
# Arrange
|
||||
monkeypatch.setattr(
|
||||
airborne_bootstrap,
|
||||
"build_inference_runtime",
|
||||
_raise_no_c7_runtime_available,
|
||||
)
|
||||
config = Config() # empty components
|
||||
|
||||
# Act + Assert
|
||||
with pytest.raises(AirborneBootstrapError) as excinfo:
|
||||
build_pre_constructed(config)
|
||||
|
||||
message = str(excinfo.value)
|
||||
assert "c7_inference" in message
|
||||
for _runtime, flag in C7_AIRBORNE_BUILD_FLAGS:
|
||||
assert flag in message
|
||||
# When no consumer is configured, ALL theoretical consumers of
|
||||
# c7_inference are surfaced — per AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS
|
||||
# these are c2_vpr, c3_matcher, c3_5_adhop.
|
||||
for slug in ("c2_vpr", "c3_matcher", "c3_5_adhop"):
|
||||
assert slug in message, f"{slug} missing from error: {message!r}"
|
||||
|
||||
|
||||
def _raise_no_c7_runtime_available(_config: Config) -> None:
|
||||
# Mirrors the message that inference_factory.build_inference_runtime
|
||||
# raises when the configured runtime's BUILD_* flag is OFF — except
|
||||
# phrased to cover the "both flags off, nothing left" scenario the
|
||||
# bootstrap is supposed to wrap into an AirborneBootstrapError.
|
||||
raise RuntimeNotAvailableError(
|
||||
"InferenceRuntime runtime 'tensorrt' requires "
|
||||
"BUILD_TENSORRT_RUNTIME=ON in this binary; the flag is OFF."
|
||||
)
|
||||
Reference in New Issue
Block a user