[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
@@ -281,18 +281,26 @@ def test_ac7_passthrough_resolution() -> None:
)
def test_ac7_adhop_resolution_stops_at_module_lookup() -> None:
"""Task spec: the full-success path for "adhop" belongs to the
AdHoP task (AZ-349); this assertion verifies the factory reaches
the import step but the module does not exist yet.
def test_ac7_adhop_resolution_loads_module_and_rejects_missing_weights() -> None:
"""AZ-348 wrote this test as a "module not yet built" stop-gap.
AZ-349 landed the concrete :class:`AdHoPRefiner`; the resolution
now reaches the strategy's ``create`` factory. With no weights
path configured, that factory raises :class:`RefinerConfigError`
— which is the desired "engine load fails fast at composition"
behaviour the AdHoP task documents.
"""
from gps_denied_onboard.components.c3_5_adhop.errors import RefinerConfigError
config = _config_with_strategy("adhop")
with pytest.raises(ModuleNotFoundError):
with pytest.raises(RefinerConfigError):
build_refiner_strategy(
config,
ransac_filter=_FakeRansacFilter(),
inference_runtime=_FakeInferenceRuntime(),
)
assert (
"gps_denied_onboard.components.c3_5_adhop.adhop_refiner" in sys.modules
)
# ----------------------------------------------------------------------