mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 13:51:13 +00:00
[AZ-620] Phase B: build_pre_constructed seeds c6_descriptor_index + c6_tile_store
Second of six subtasks of AZ-618. Extends airborne_bootstrap.build_pre_constructed(config) additively with the two C6 storage entries on top of AZ-619's c13_fdr + clock contract: - c6_descriptor_index: via storage_factory.build_descriptor_index - c6_tile_store: via storage_factory.build_tile_store When BUILD_FAISS_INDEX=OFF, the lower-level RuntimeNotAvailableError from the descriptor index factory is translated into an AirborneBootstrapError that names the missing key (c6_descriptor_index), the gating flag (BUILD_FAISS_INDEX), and the consuming component slug(s) drawn from AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS. The original error is preserved as __cause__ so operators still see the upstream reason. Tests: 3 new unit tests cover AC-620.1 + AC-620.2 (twice, with and without a configured consumer, so the bootstrap fails loudly in either branch). AZ-619 tests updated to add an autouse stub for the Phase B builders (keeps them focused on Phase A keys) and to relax the "exactly two keys" assertion to "AZ-619 keys remain present under AZ-620 additivity" per the original test's own forward-pointer. Bonus: ruff --fix removed 12 pre-existing UP037 quoted-annotation warnings in airborne_bootstrap.py (covered by `from __future__ import annotations`). All in modified-area scope per quality-gates.mdc. Run: pytest tests/unit/runtime_root/ -q -> 15/15 passed in 1.06s. Spec moved to _docs/02_tasks/done/ in the previous commit (audit-trail backfill of batch_90 also landed there). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -56,11 +56,16 @@ from typing import TYPE_CHECKING, Any, Final
|
||||
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.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
|
||||
from gps_denied_onboard.runtime_root.rerank_factory import build_rerank_strategy
|
||||
from gps_denied_onboard.runtime_root.state_factory import build_state_estimator
|
||||
from gps_denied_onboard.runtime_root.storage_factory import (
|
||||
build_descriptor_index,
|
||||
build_tile_store,
|
||||
)
|
||||
from gps_denied_onboard.runtime_root.vio_factory import build_vio_strategy
|
||||
from gps_denied_onboard.runtime_root.vpr_factory import build_vpr_strategy
|
||||
|
||||
@@ -70,12 +75,23 @@ if TYPE_CHECKING:
|
||||
__all__ = [
|
||||
"AIRBORNE_MAIN_PRODUCER_ID",
|
||||
"AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS",
|
||||
"FAISS_BUILD_FLAG",
|
||||
"AirborneBootstrapError",
|
||||
"build_pre_constructed",
|
||||
"register_airborne_strategies",
|
||||
]
|
||||
|
||||
|
||||
FAISS_BUILD_FLAG: Final[str] = "BUILD_FAISS_INDEX"
|
||||
"""Env flag gating the FAISS-backed ``DescriptorIndex`` impl.
|
||||
|
||||
Mirrors :func:`gps_denied_onboard.runtime_root.storage_factory.build_descriptor_index`
|
||||
which reads the same flag at composition time. Surfaced here so the airborne
|
||||
bootstrap can name the flag in an :class:`AirborneBootstrapError` when the
|
||||
flag is OFF but a consuming component still requires the index.
|
||||
"""
|
||||
|
||||
|
||||
AIRBORNE_MAIN_PRODUCER_ID: Final[str] = "airborne_main"
|
||||
"""Producer ID for the per-binary shared FdrClient placed under
|
||||
``pre_constructed['c13_fdr']``.
|
||||
@@ -176,12 +192,12 @@ def _require(
|
||||
return constructed[key]
|
||||
|
||||
|
||||
def _c1_vio_wrapper(config: "Config", constructed: Mapping[str, Any]) -> Any:
|
||||
def _c1_vio_wrapper(config: Config, constructed: Mapping[str, Any]) -> Any:
|
||||
fdr_client = _require(constructed, "c13_fdr", "c1_vio")
|
||||
return build_vio_strategy(config, fdr_client=fdr_client)
|
||||
|
||||
|
||||
def _c2_vpr_wrapper(config: "Config", constructed: Mapping[str, Any]) -> Any:
|
||||
def _c2_vpr_wrapper(config: Config, constructed: Mapping[str, Any]) -> Any:
|
||||
descriptor_index = _require(constructed, "c6_descriptor_index", "c2_vpr")
|
||||
inference_runtime = _require(constructed, "c7_inference", "c2_vpr")
|
||||
return build_vpr_strategy(
|
||||
@@ -192,7 +208,7 @@ def _c2_vpr_wrapper(config: "Config", constructed: Mapping[str, Any]) -> Any:
|
||||
|
||||
|
||||
def _c2_5_rerank_wrapper(
|
||||
config: "Config", constructed: Mapping[str, Any]
|
||||
config: Config, constructed: Mapping[str, Any]
|
||||
) -> Any:
|
||||
tile_store = _require(constructed, "c6_tile_store", "c2_5_rerank")
|
||||
lightglue_runtime = _require(
|
||||
@@ -214,7 +230,7 @@ def _c2_5_rerank_wrapper(
|
||||
|
||||
|
||||
def _c3_matcher_wrapper(
|
||||
config: "Config", constructed: Mapping[str, Any]
|
||||
config: Config, constructed: Mapping[str, Any]
|
||||
) -> Any:
|
||||
lightglue_runtime = _require(
|
||||
constructed, "c3_lightglue_runtime", "c3_matcher"
|
||||
@@ -234,7 +250,7 @@ def _c3_matcher_wrapper(
|
||||
|
||||
|
||||
def _c3_5_adhop_wrapper(
|
||||
config: "Config", constructed: Mapping[str, Any]
|
||||
config: Config, constructed: Mapping[str, Any]
|
||||
) -> Any:
|
||||
ransac_filter = _require(constructed, "c282_ransac_filter", "c3_5_adhop")
|
||||
inference_runtime = _require(constructed, "c7_inference", "c3_5_adhop")
|
||||
@@ -249,7 +265,7 @@ def _c3_5_adhop_wrapper(
|
||||
)
|
||||
|
||||
|
||||
def _c4_pose_wrapper(config: "Config", constructed: Mapping[str, Any]) -> Any:
|
||||
def _c4_pose_wrapper(config: Config, constructed: Mapping[str, Any]) -> Any:
|
||||
ransac_filter = _require(constructed, "c282_ransac_filter", "c4_pose")
|
||||
wgs_converter = _require(constructed, "c5_wgs_converter", "c4_pose")
|
||||
se3_utils = _require(constructed, "c5_se3_utils", "c4_pose")
|
||||
@@ -269,7 +285,7 @@ def _c4_pose_wrapper(config: "Config", constructed: Mapping[str, Any]) -> Any:
|
||||
)
|
||||
|
||||
|
||||
def _c5_state_wrapper(config: "Config", constructed: Mapping[str, Any]) -> Any:
|
||||
def _c5_state_wrapper(config: Config, constructed: Mapping[str, Any]) -> Any:
|
||||
imu_preintegrator = _require(
|
||||
constructed, "c5_imu_preintegrator", "c5_state"
|
||||
)
|
||||
@@ -295,7 +311,7 @@ def _c5_state_wrapper(config: "Config", constructed: Mapping[str, Any]) -> Any:
|
||||
return estimator
|
||||
|
||||
|
||||
def _ensure_state_strategy_registered(config: "Config") -> None:
|
||||
def _ensure_state_strategy_registered(config: Config) -> None:
|
||||
"""Register the c5_state strategy module if its build flag is on.
|
||||
|
||||
state_factory does NOT have a lazy-import fallback (unlike pose_factory).
|
||||
@@ -382,27 +398,116 @@ _AIRBORNE_REGISTRATIONS: tuple[
|
||||
)
|
||||
|
||||
|
||||
def build_pre_constructed(config: "Config") -> dict[str, Any]:
|
||||
def _consumers_of_pre_constructed_key(key: str) -> tuple[str, ...]:
|
||||
"""Return component slugs that require ``key`` in ``pre_constructed``.
|
||||
|
||||
Reads :data:`AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS` — the single
|
||||
source of truth for which slot consumes which infrastructure dep.
|
||||
"""
|
||||
return tuple(
|
||||
sorted(
|
||||
slug
|
||||
for slug, required in AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS.items()
|
||||
if key in required
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _configured_consumers_of_pre_constructed_key(
|
||||
config: Config, key: str
|
||||
) -> tuple[str, ...]:
|
||||
"""Return consumers of ``key`` that are present in ``config.components``.
|
||||
|
||||
Used to narrow an error message from "every theoretical consumer of
|
||||
``c6_descriptor_index``" to "the consumer(s) you actually configured".
|
||||
Falls back to the full theoretical set when config carries no component
|
||||
blocks (e.g., bare ``Config()`` in early-bootstrap tests).
|
||||
"""
|
||||
consumers = _consumers_of_pre_constructed_key(key)
|
||||
components = getattr(config, "components", None) or {}
|
||||
if not isinstance(components, Mapping):
|
||||
return consumers
|
||||
configured = tuple(slug for slug in consumers if slug in components)
|
||||
return configured if configured else consumers
|
||||
|
||||
|
||||
def _build_c6_descriptor_index(config: Config) -> Any:
|
||||
"""Build ``pre_constructed['c6_descriptor_index']`` via the C6 factory.
|
||||
|
||||
Wraps :func:`storage_factory.build_descriptor_index` so a
|
||||
:class:`RuntimeNotAvailableError` (typically raised when
|
||||
``BUILD_FAISS_INDEX`` is OFF) surfaces as an
|
||||
:class:`AirborneBootstrapError` naming the missing key, the gating
|
||||
build flag, and the component(s) that need the index. The original
|
||||
factory error is preserved via ``raise ... from``.
|
||||
|
||||
AC-620.2: this is the path the test exercises when
|
||||
``BUILD_FAISS_INDEX=OFF`` and a C2 strategy needing the index is
|
||||
configured.
|
||||
"""
|
||||
try:
|
||||
return build_descriptor_index(config)
|
||||
except RuntimeNotAvailableError as exc:
|
||||
consumers = _configured_consumers_of_pre_constructed_key(
|
||||
config, "c6_descriptor_index"
|
||||
)
|
||||
raise AirborneBootstrapError(
|
||||
f"airborne_bootstrap: cannot construct "
|
||||
f"pre_constructed['c6_descriptor_index'] because "
|
||||
f"{FAISS_BUILD_FLAG} is OFF (or the FAISS impl module is "
|
||||
f"unavailable). Consuming components: {list(consumers)}. "
|
||||
f"Set {FAISS_BUILD_FLAG}=ON to enable the FAISS DescriptorIndex, "
|
||||
f"or reconfigure the consuming components to use a strategy "
|
||||
f"that does not require the index."
|
||||
) from exc
|
||||
|
||||
|
||||
def _build_c6_tile_store(config: Config) -> Any:
|
||||
"""Build ``pre_constructed['c6_tile_store']`` via the C6 factory.
|
||||
|
||||
Thin pass-through to :func:`storage_factory.build_tile_store`. There
|
||||
is no ``BUILD_*`` flag for the tile store (the Postgres + filesystem
|
||||
backend is always built when c6 is configured); failures here surface
|
||||
as :class:`RuntimeNotAvailableError` with the operator-actionable
|
||||
message provided by the factory itself.
|
||||
"""
|
||||
return build_tile_store(config)
|
||||
|
||||
|
||||
def build_pre_constructed(config: Config) -> dict[str, Any]:
|
||||
"""Build the airborne ``pre_constructed`` dict for :func:`compose_root`.
|
||||
|
||||
AZ-619 (Phase A) lands the foundational keys ``c13_fdr`` and ``clock``.
|
||||
Phases B..F (AZ-620..AZ-624) extend this function to populate the
|
||||
remaining 10 keys in :data:`AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS`.
|
||||
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
|
||||
: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).
|
||||
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.
|
||||
|
||||
Replay-mode override: :func:`compose_root` merges ``replay_components``
|
||||
over ``pre_constructed`` so the :class:`WallClock` here is replaced by
|
||||
the replay branch's :class:`TlogDerivedClock`. That's intentional and
|
||||
matches the contract in :func:`compose_root`'s docstring.
|
||||
|
||||
Raises:
|
||||
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.
|
||||
"""
|
||||
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),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user