[AZ-345] [AZ-346] [AZ-347] [AZ-349] C3 matchers + C3.5 AdHoP refiner

Implement the three concrete C3 CrossDomainMatcher strategies plus the
C3.5 production-default AdHoPRefiner.

C3 (AZ-345/346/347):
- DiskLightGlueMatcher + AlikedLightGlueMatcher share a single shared
  _pipeline.run_lightglue_pipeline orchestrator (decode -> query
  extract -> per-candidate loop -> RANSAC sort -> health update ->
  FDR emit) so the only per-backbone delta is the keypoint+descriptor
  extractor closure. ALIKED adds a create-time engine output-schema
  probe (AC-special-1).
- XFeatMatcher owns its own per-candidate loop (single forward fuses
  extraction + matching); it re-uses the shared FDR emission helpers
  to keep telemetry byte-identical across strategies. lightglue_runtime
  parameter accepted by factory but discarded (AC-special-1).
- All three consume the shared LightGlueRuntime / RansacFilter /
  RollingHealthWindow helpers; no helper forks. InferenceRuntimeCut
  consumer-side Protocol added per AZ-507.

C3.5 (AZ-349):
- AdHoPRefiner implements the <= conditional gate, runs the OrthoLoC
  AdHoP TRT engine over best-candidate correspondences, re-runs RANSAC
  on the perspective-preconditioned set, and emits an enriched
  MatchResult with refinement_label="adhop".
- Invariant 4 passthrough fall-through: any RefinerBackboneError (TRT
  failure, OOM, NaN, bad shape) is caught, logged ERROR, FDR-emitted
  with error: true, and converted to passthrough that still counts
  against the rolling invocation-rate window. MemoryError and other
  non-listed exceptions propagate by design (AC-5 closed-set
  semantics).
- Rolling 60-s invocation-rate window + rate-limited WARN log
  (configurable via ratelimited_warn_window_ns; default 60 s).

Shared changes:
- C3MatcherConfig + C3_5RefinerConfig extended with the new
  weights/threshold/window fields.
- matcher_factory + refiner_factory optionally forward clock +
  fdr_client to the strategy's create(); backward-compatible.
- fdr_client.records registers five new kinds: matcher.frame_done,
  matcher.backbone_error, matcher.insufficient_inliers,
  matcher.all_failed, refiner.frame_done.

Tests: 66 new (43 C3 parametrised + 23 AdHoP) covering 47/47 ACs;
focused suite green; full project test suite green except for one
pre-existing flaky CLI cold-start timing test unrelated to this batch.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 04:09:22 +03:00
parent 06f655d8fb
commit a1185d0a28
19 changed files with 4855 additions and 6 deletions
@@ -38,12 +38,14 @@ from gps_denied_onboard.components.c3_matcher._health_window import RollingHealt
from gps_denied_onboard.runtime_root.errors import StrategyNotAvailableError
if TYPE_CHECKING:
from gps_denied_onboard.clock import Clock
from gps_denied_onboard.components.c3_matcher import (
C3MatcherConfig,
CrossDomainMatcher,
)
from gps_denied_onboard.components.c7_inference import InferenceRuntime
from gps_denied_onboard.config.schema import Config
from gps_denied_onboard.fdr_client import FdrClient
from gps_denied_onboard.helpers.lightglue_runtime import LightGlueRuntime
from gps_denied_onboard.helpers.ransac_filter import RansacFilter
@@ -106,6 +108,8 @@ def build_matcher_strategy(
lightglue_runtime: "LightGlueRuntime",
ransac_filter: "RansacFilter",
inference_runtime: "InferenceRuntime",
clock: "Clock | None" = None,
fdr_client: "FdrClient | None" = None,
) -> "CrossDomainMatcher":
"""Construct the :class:`CrossDomainMatcher` impl selected by config.
@@ -168,6 +172,15 @@ def build_matcher_strategy(
"yet (AZ-345 / AZ-346 / AZ-347 pending)."
) from exc
create_fn = getattr(module, "create", None)
# ``clock`` and ``fdr_client`` were added in AZ-345 / AZ-346 / AZ-347;
# the AZ-344 fakes do not accept them. Only forward when the factory
# has been wired with them so the existing protocol tests continue
# to pass.
extra_kwargs: dict[str, object] = {}
if clock is not None:
extra_kwargs["clock"] = clock
if fdr_client is not None:
extra_kwargs["fdr_client"] = fdr_client
if create_fn is None:
strategy_cls = getattr(module, class_name)
instance = strategy_cls(
@@ -176,6 +189,7 @@ def build_matcher_strategy(
ransac_filter=ransac_filter,
inference_runtime=inference_runtime,
health_window=health_window,
**extra_kwargs,
)
else:
instance = create_fn(
@@ -184,6 +198,7 @@ def build_matcher_strategy(
ransac_filter=ransac_filter,
inference_runtime=inference_runtime,
health_window=health_window,
**extra_kwargs,
)
_LOG.info(
"c3.matcher.strategy_loaded",
@@ -28,12 +28,14 @@ from typing import TYPE_CHECKING
from gps_denied_onboard.components.c3_5_adhop.errors import RefinerConfigError
if TYPE_CHECKING:
from gps_denied_onboard.clock import Clock
from gps_denied_onboard.components.c3_5_adhop import (
C3_5RefinerConfig,
ConditionalRefiner,
)
from gps_denied_onboard.components.c7_inference import InferenceRuntime
from gps_denied_onboard.config.schema import Config
from gps_denied_onboard.fdr_client import FdrClient
from gps_denied_onboard.helpers.ransac_filter import RansacFilter
__all__ = ["build_refiner_strategy"]
@@ -71,6 +73,8 @@ def build_refiner_strategy(
*,
ransac_filter: "RansacFilter",
inference_runtime: "InferenceRuntime",
clock: "Clock | None" = None,
fdr_client: "FdrClient | None" = None,
) -> "ConditionalRefiner":
"""Construct the :class:`ConditionalRefiner` impl selected by config.
@@ -120,17 +124,26 @@ def build_refiner_strategy(
module_name, class_name = module_info
module = __import__(module_name, fromlist=[class_name])
create_fn = getattr(module, "create", None)
# ``clock`` and ``fdr_client`` were added with AZ-349; the AZ-348
# passthrough refiner does not accept them. Only forward when wired.
extra_kwargs: dict[str, object] = {}
if clock is not None:
extra_kwargs["clock"] = clock
if fdr_client is not None:
extra_kwargs["fdr_client"] = fdr_client
if create_fn is None:
strategy_cls = getattr(module, class_name)
instance = strategy_cls(
ransac_filter=ransac_filter,
inference_runtime=inference_runtime,
**extra_kwargs,
)
else:
instance = create_fn(
config,
ransac_filter=ransac_filter,
inference_runtime=inference_runtime,
**extra_kwargs,
)
_LOG.info(
"c3_5.refiner.strategy_loaded",