mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 08:41:13 +00:00
[AZ-331] C1 VioStrategy: Protocol + DTOs + factory + C5 migration
Freezes the c1_vio Public API per _docs/02_document/contracts/c1_vio/vio_strategy_protocol.md v1.0.0: - VioStrategy Protocol (4 methods: process_frame, reset_to_warm_start, health_snapshot, current_strategy_label) in components/c1_vio/interface.py. - DTOs (VioOutput, VioHealth, FeatureQuality, WarmStartPose) + VioState enum in _types/nav.py — L1 placement so C5 + C13 consume them without crossing the components.* boundary (AZ-270 AC-6). The new VioOutput shape (frame_id: str, relative_pose_T: gtsam.Pose3, pose_covariance_6x6, imu_bias, feature_quality, emitted_at_ns) replaces the AZ-263 scaffolding in _types/vio.py, which is now deleted. - VioError family (VioInitializingError / VioDegradedError / VioFatalError) in components/c1_vio/errors.py. Documented rationale: the degraded-operation path returns a VioOutput with inflated covariance + VioHealth.state=DEGRADED rather than raising VioDegradedError — the error type exists only for the rare degraded->fatal transition. - C1VioConfig per-component config block (strategy enum, lost_frame_threshold default 9, warm_start_max_frames default 5) with constructor-time validation rejecting unknown strategy labels. - StrategyNotAvailableError added to runtime_root/errors.py; composition-time error distinct from the VioError family. - Composition-root factory build_vio_strategy in runtime_root/vio_factory.py with three BUILD_* gates (BUILD_OKVIS2, BUILD_VINS_MONO, BUILD_KLT_RANSAC). Concrete strategy modules are imported lazily via __import__ AFTER the flag check — Tier-0 workstation builds with the flag OFF MUST NOT load the strategy module (Risk-2 / I-5; verifiable via sys.modules). - 36 conformance tests cover all 9 ACs + NFR-perf-factory (p99 build under 200 ms x 1000 calls) + NFR-reliability-error-family. AC-8 introspects the contract file's Shape table and asserts method parity against the runtime Protocol; AC-9 asserts the frame_id annotation is 'str' (PEP-563 stringified). C5 migration (consumers of the new VioOutput shape): - gtsam_isam2_estimator.py + eskf_baseline.py: replaced vio.timestamp -> vio.emitted_at_ns (drops _datetime_to_ns on the VIO path), vio.pose_se3 -> vio.relative_pose_T (gtsam.Pose3 direct; drops _pose_se3_to_gtsam / _pose_se3_to_array), vio.covariance_6x6 -> vio.pose_covariance_6x6 (rename). - key_for_frame signature widened to UUID | int | str to accept the new str frame_id. - 4 C5 test files migrated to the new VioOutput shape with helper fixtures producing ImuBias + FeatureQuality + str frame_id. - c5_state/interface.py TYPE_CHECKING import path updated. Bootstrap healthcheck + test_types_importable updated to drop the deleted _types/vio module and pick up _types/inference (AZ-297) in the same sweep. Full unit-test sweep: 884 passed, 2 pre-existing environment skips (cmake, actionlint). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
These are raised at composition time (``build_*`` factory entry) and
|
||||
NOT during the running flight. Components own their per-runtime error
|
||||
families; this module owns the cross-component selection error.
|
||||
families; this module owns the cross-component selection errors.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -22,3 +22,15 @@ class RuntimeNotAvailableError(RuntimeError):
|
||||
The message MUST name the requested runtime label so the operator can
|
||||
correlate against ``.env``'s ``BUILD_*`` matrix without guessing.
|
||||
"""
|
||||
|
||||
|
||||
class StrategyNotAvailableError(RuntimeError):
|
||||
"""Raised when ``build_vio_strategy`` is asked for a VIO strategy whose
|
||||
compile-time ``BUILD_*`` flag is OFF (AZ-331).
|
||||
|
||||
Distinct from :class:`RuntimeNotAvailableError` because the C1
|
||||
contract names this error type explicitly (AC-5). The message
|
||||
MUST name both the requested strategy label and the missing
|
||||
``BUILD_*`` flag so the operator can correlate against the
|
||||
binary's compile matrix.
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
"""C1 VIO strategy composition-root factory (AZ-331).
|
||||
|
||||
:func:`build_vio_strategy` selects exactly one strategy by
|
||||
``config.components['c1_vio'].strategy`` and respects compile-time
|
||||
``BUILD_*`` gating: requesting a strategy whose flag is OFF raises
|
||||
:class:`StrategyNotAvailableError` at composition time (NOT at first
|
||||
frame).
|
||||
|
||||
Concrete strategy modules (``okvis2``, ``vins_mono``, ``klt_ransac``)
|
||||
are imported lazily — a Tier-0 workstation build with
|
||||
``BUILD_OKVIS2=OFF`` MUST NOT load ``c1_vio.okvis2`` (Risk-2 / I-5;
|
||||
verifiable via ``sys.modules``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from gps_denied_onboard.runtime_root.errors import StrategyNotAvailableError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gps_denied_onboard.components.c1_vio import (
|
||||
C1VioConfig,
|
||||
VioStrategy,
|
||||
)
|
||||
from gps_denied_onboard.components.c13_fdr import FdrClient
|
||||
from gps_denied_onboard.config.schema import Config
|
||||
|
||||
__all__ = ["build_vio_strategy"]
|
||||
|
||||
|
||||
_STRATEGY_TO_BUILD_FLAG: dict[str, str] = {
|
||||
"okvis2": "BUILD_OKVIS2",
|
||||
"vins_mono": "BUILD_VINS_MONO",
|
||||
"klt_ransac": "BUILD_KLT_RANSAC",
|
||||
}
|
||||
|
||||
_STRATEGY_TO_MODULE: dict[str, tuple[str, str]] = {
|
||||
"okvis2": ("gps_denied_onboard.components.c1_vio.okvis2", "Okvis2Strategy"),
|
||||
"vins_mono": (
|
||||
"gps_denied_onboard.components.c1_vio.vins_mono",
|
||||
"VinsMonoStrategy",
|
||||
),
|
||||
"klt_ransac": (
|
||||
"gps_denied_onboard.components.c1_vio.klt_ransac",
|
||||
"KltRansacStrategy",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _is_build_flag_on(flag_name: str) -> bool:
|
||||
"""Read a compile-time ``BUILD_*`` flag from the environment.
|
||||
|
||||
``ON`` / ``1`` / ``true`` / ``yes`` (case-insensitive) → ``True``;
|
||||
anything else (including unset) → ``False``. Defaults to OFF so
|
||||
test environments must opt-in explicitly per strategy.
|
||||
"""
|
||||
raw = os.environ.get(flag_name, "")
|
||||
return raw.strip().lower() in {"on", "1", "true", "yes"}
|
||||
|
||||
|
||||
def _c1_config(config: "Config") -> "C1VioConfig":
|
||||
"""Pull the registered C1 config block.
|
||||
|
||||
``c1_vio.__init__`` registers it on import; a missing
|
||||
registration is a developer error and surfaces as ``KeyError``
|
||||
rather than a silent fallback.
|
||||
"""
|
||||
return config.components["c1_vio"]
|
||||
|
||||
|
||||
def build_vio_strategy(
|
||||
config: "Config",
|
||||
*,
|
||||
fdr_client: "FdrClient",
|
||||
) -> "VioStrategy":
|
||||
"""Construct the :class:`VioStrategy` impl selected by config.
|
||||
|
||||
1. Reads ``config.components['c1_vio'].strategy``.
|
||||
2. Checks the matching ``BUILD_*`` flag — if OFF, raises
|
||||
:class:`StrategyNotAvailableError` BEFORE any import.
|
||||
3. Lazily imports the concrete strategy module.
|
||||
4. Constructs and returns the strategy instance, passing
|
||||
``config`` and ``fdr_client``.
|
||||
|
||||
Raises :class:`StrategyNotAvailableError` when the compile-time
|
||||
flag is OFF (canonical Tier-0 path) or when the concrete strategy
|
||||
module has not been built yet (AZ-332 / AZ-333 / AZ-334 pending).
|
||||
"""
|
||||
block = _c1_config(config)
|
||||
strategy = block.strategy
|
||||
flag_name = _STRATEGY_TO_BUILD_FLAG.get(strategy)
|
||||
module_info = _STRATEGY_TO_MODULE.get(strategy)
|
||||
if flag_name is None or module_info is None:
|
||||
raise StrategyNotAvailableError(
|
||||
f"VioStrategy {strategy!r} is not buildable in this binary."
|
||||
)
|
||||
if not _is_build_flag_on(flag_name):
|
||||
raise StrategyNotAvailableError(
|
||||
f"VioStrategy {strategy!r} requires {flag_name}=ON in this "
|
||||
"binary; the flag is OFF."
|
||||
)
|
||||
module_name, class_name = module_info
|
||||
try:
|
||||
module = __import__(module_name, fromlist=[class_name])
|
||||
except ModuleNotFoundError as exc:
|
||||
raise StrategyNotAvailableError(
|
||||
f"VioStrategy {strategy!r} is configured but its concrete impl "
|
||||
f"module {module_name!r} has not been built into this binary "
|
||||
"yet (AZ-332 / AZ-333 / AZ-334 pending)."
|
||||
) from exc
|
||||
strategy_cls = getattr(module, class_name)
|
||||
return strategy_cls(config, fdr_client=fdr_client)
|
||||
Reference in New Issue
Block a user