Adds two research-only VprStrategy implementations for the IT-12 comparative-study matrix. MegaLocStrategy (D=2048, 322x322) and MixVprStrategy (D=4096, 320x320), both via C7 TensorRT FP16 with their own concrete BackbonePreprocessor. Single-stage global L2 normalisation; retrieval delegated to FaissBridge; FDR records + structured logs identical to UltraVPR. BUILD_VPR_MEGALOC and BUILD_VPR_MIXVPR ON for research/replay-cli only, OFF for airborne and operator-tooling (fail-fast at composition root via existing AZ-336 factory). Uses helpers.iso_ts_from_clock from day 1 — no new timestamp helper duplicates introduced. 36 parametrised AC tests + 25 protocol-conformance + 18 helper regression tests pass; 1690 / 1690 unit tests pass (excluding 1 pre-existing flaky cold-start subprocess test in c12). Verdict: PASS_WITH_WARNINGS — one Medium follow-on (AZ-527 to consolidate 4-way _assert_engine_output_dim) + one Low AC wording drift. Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
Code Review Report
Batch: 50 — AZ-339 (C2 MegaLoc + MixVPR Secondary Backbones) Date: 2026-05-13 Verdict: PASS_WITH_WARNINGS
Findings
| # | Severity | Category | File:Line | Title |
|---|---|---|---|---|
| 1 | Medium | Maintainability/Architecture | c2_vpr/mega_loc.py:438, mix_vpr.py:432, ultra_vpr.py:432, net_vlad.py:494 |
_assert_engine_output_dim now 4-way duplicated — schedule AZ-527 |
| 2 | Low | Scope | AZ-339 task spec § AC-10 | AC-10 names ConfigurationError; precedent + impl raise StrategyNotAvailableError / ConfigError |
Finding Details
F1: _assert_engine_output_dim now 4-way duplicated (Medium / Maintainability + Architecture)
- Locations:
src/gps_denied_onboard/components/c2_vpr/ultra_vpr.py:432src/gps_denied_onboard/components/c2_vpr/net_vlad.py:494src/gps_denied_onboard/components/c2_vpr/mega_loc.py:438src/gps_denied_onboard/components/c2_vpr/mix_vpr.py:432
- Description: Each strategy module ships a near-identical ~22-line
_assert_engine_output_dim(inference_runtime, handle, preprocessor)helper. Bodies vary only on three values:_OUTPUT_KEY(always"embedding"for mega_loc / mix_vpr / ultra_vpr;"vlad_descriptor"for net_vlad),DESCRIPTOR_DIM(per-strategy constant), andpreprocessor.input_shape(). Same drift signature as AZ-508 → AZ-526 (_iso_ts_now/_iso_ts_from_clock). - The cumulative review (batches 46-48) flagged this duplication as F2 and recommended deferring "until a third VPR strategy joins (AZ-339 batch)". That trigger has fired.
- Suggestion: Create AZ-527 (Hygiene — consolidate
_assert_engine_output_diminto a c2-internal helper). Signature:_assert_engine_output_dim(inference_runtime, handle, *, expected_dim, output_key, input_shape). 2 points; depends on AZ-339. - Inline comments in the new mega_loc.py and mix_vpr.py already cite AZ-527 as the planned follow-on so the duplication is intentional, not accidental.
- Task: AZ-339 (carries forward from cumulative-46-48 F2).
F2: AC-10 names ConfigurationError but precedent / implementation raise StrategyNotAvailableError / ConfigError (Low / Scope)
- Location:
_docs/02_tasks/todo/AZ-339_c2_megaloc_mixvpr.md§ AC-10. - Description: AC-10 literally reads "
ConfigurationErroris raised at composition-root time with message containing the missing flag; the binary refuses to start (fail-fast per AZ-336 factory's lazy-import → ImportError →ConfigurationErrormapping)". The existing AZ-336 factory (build_vpr_strategy) raisesStrategyNotAvailableErrorfor theBUILD_VPR_<X>=OFFcase (verified viatests/unit/c2_vpr/test_protocol_conformance.py:268-274for UltraVPR; same pattern auto-extends to MegaLoc / MixVPR via the parametrized_STRATEGY_MODULEStable).StrategyNotAvailableErroris aRuntimeErrorsubclass, NOT aConfigError. AZ-337 / AZ-338 followed this precedent; AZ-339 does the same. The strategy module's own runtime-label guard raisesConfigError(the "wrong C7 runtime label" case), which satisfies AC-10's spirit of "composition-time fail-fast". - Implementation choice: mirrored the existing precedent.
- Suggestion: amend AC-10 to read "
StrategyNotAvailableError(for BUILD flag OFF) orConfigError(for runtime-label mismatch) at composition-root time, with a message naming the missing flag or runtime". Recorded as drift; no code change required. - Task: AZ-339.
Phase Summary
Phase 1 — Context Loading
Read AZ-339 task spec (208 lines, AC-1..AC-11 per strategy + NFRs), the AZ-337 UltraVPR + AZ-338 NetVLAD precedents (ultra_vpr.py, net_vlad.py, _preprocessor_ultra_vpr.py), the AZ-336 composition-root factory (vpr_factory.py), the AZ-336 C2VprConfig + KNOWN_STRATEGIES, and the cumulative_review_batches_46-48_cycle1_report.md F2 finding. Mapped 5 new files (2 strategy, 2 preprocessor, 1 test) to AZ-339.
Phase 2 — Spec Compliance
| AC | Verified by | Status |
|---|---|---|
| AC-1 (Protocol conformance) | test_ac1_protocol_conformance[mega_loc], [mix_vpr] |
PASS |
| AC-2 (L2-norm FP16 correct dim) | test_ac2_embed_query_returns_unit_norm_fp16_correct_dim[*], test_ac2_single_stage_l2_no_intra_cluster_call[*] |
PASS |
| AC-3 (deterministic embeddings) | test_ac3_embed_query_deterministic_for_same_frame[*] |
PASS |
| AC-4 (retrieve_topk k + label) | test_ac4_retrieve_topk_returns_exactly_k_with_correct_label[*] |
PASS |
| AC-5 (descriptor_dim stable) | test_ac5_descriptor_dim_stable[*] |
PASS |
| AC-6 (engine shape mismatch → ConfigError at create) | test_ac6_create_rejects_engine_output_shape_mismatch[*], test_ac6_create_rejects_missing_embedding_key[*] |
PASS |
| AC-7 (VprBackboneError on forward failure) | test_ac7_runtime_error_yields_vpr_backbone_error[*], test_ac7_wrong_forward_output_shape_yields_vpr_backbone_error[*] |
PASS |
| AC-8 (VprPreprocessError on corrupt image) | test_ac8_corrupt_image_yields_vpr_preprocess_error[*] |
PASS |
| AC-9 (compose wiring + INFO ready log) | test_ac9_create_emits_ready_log_with_correct_label_and_dim[*] |
PASS |
| AC-10 (build-flag exclusion fail-fast) | test_ac10_runtime_label_mismatch_raises_config_error[*] + tests/unit/c2_vpr/test_protocol_conformance.py parametrised over _STRATEGY_MODULES (auto-covers mega_loc + mix_vpr) |
PASS with F2 wording drift |
| AC-11 (preprocessor input shape) | test_ac11_preprocessor_input_shape[*], test_preprocess_output_is_nchw_fp16[*] |
PASS |
36 / 36 tests in test_az339_mega_loc_mix_vpr.py pass; 25 / 25 in test_protocol_conformance.py pass (now auto-covering the two new strategies via the existing parametrised module-import table).
Phase 3 — Code Quality
- SRP: Strategy class = embed + retrieve via injected dependencies. Preprocessor class = decode + crop + resize + normalise. Each error handler is a separate helper method. Factory
create()is wiring-only. - Error handling: Every failure path emits a structured ERROR log AND an FDR record before raising. Errors are explicitly re-raised; no swallowed exceptions.
- Naming: Consistent with the UltraVPR precedent —
_BACKBONE_LABEL,_OUTPUT_KEY,_LOG_KIND_*,_FDR_KIND_*,_assert_engine_output_dim.DESCRIPTOR_DIMis module-level Final per strategy (2048 / 4096), matching the AZ-337 / AZ-338 pattern. - Complexity: Strategy class ~310 lines (incl. error handlers);
embed_query~55 lines (within the 50-line guidance; same shape as UltraVPR). Cyclomatic complexity low. - DRY: Strategy-pair duplication (mega_loc vs mix_vpr) is intentional per the task spec § Constraints: "Each strategy ships its own concrete preprocessor — preprocessing parameters per upstream code drop … sharing would couple weights-versions across strategies and let one strategy's upgrade silently break another's preprocessing."
_assert_engine_output_dimduplication is unintentional — see F1. - Test quality: AAA pattern with explicit markers. Parametrised across
_StrategySpecto keep cross-strategy assertions semantically identical. Each AC has at least one parametrised test plus targeted variants for failure modes. - Dead code: None introduced.
Literalimport in strategy modules is used by_BACKBONE_LABEL: Final[Literal["mega_loc"]]/["mix_vpr"]annotations.
Phase 4 — Security Quick-Scan
- No SQL, no shell, no
eval/exec, no dynamic deserialisation. cv2.resizeis the only third-party call; inputs are typednp.ndarrayand validated for dtype / ndim / shape upstream.error_message[:512]truncation prevents pathological log-line / FDR-payload growth on a long backbone error.- No hardcoded secrets, API keys, or paths beyond test-fixture placeholders (
/models/mega_loc.trt,/cache/vpr/index.faiss). - Image inputs are byte-bounded (
uint8only); rejection paths emitVprPreprocessErrornot rawcv2.error.
Phase 5 — Performance Scan
- Construction is O(1) (no GPU ops in
__init__per the task spec § Constraints). embed_queryis O(H·W) for decode / resize / normalise — same algorithmic cost as UltraVPR. The 2048-d / 4096-d FP16 embedding is allocated once per frame.- No N+1 patterns, no unbounded fetching.
- One FDR-record allocation per frame on the success path — same per-frame allocation cost as UltraVPR; sits well below the bounded
capacityof the FdrClient ring. - NFR-perf budgets (MegaLoc ≤ 80 ms p95, MixVPR ≤ 100 ms p95) are research-side guidance per the task spec § NFR; not engine-rule-binding. Cannot be measured in unit tests; deferred to Step 9 / E-BBT against the real engines per the task spec § Risks 1 + 4.
Phase 6 — Cross-Task Consistency
Single-batch with two strategies — they were implemented in lockstep, share the same factory create() shape, the same error envelope, the same FDR record kinds (vpr.embed_query, vpr.backbone_error, vpr.preprocess_error), and the same log kinds. The parametrised test surface verifies behavioural equivalence directly.
Phase 7 — Architecture Compliance
- Layer direction: c2_vpr modules import from
_types(L1),clock(L1),helpers.descriptor_normaliser(L1),helpers.iso_timestamps(L1),config.schema(L1),fdr_client(L2), and internalc2_vprmodules. No upward imports. PASS. - Public API respect: The strategies do NOT import from
c6_tile_cacheorc7_inferencedirectly — they use the consumer-side cuts (DescriptorIndexCut,InferenceRuntimeCut) defined locally in c2_vpr per AZ-507. PASS. - No new cyclic dependencies: New modules sit in c2_vpr leaf positions; no incoming imports from c2_5 / c3 / runtime_root that didn't already exist for ultra_vpr / net_vlad. PASS.
- Duplicate symbols: F1 (above) —
_assert_engine_output_dimis the only new duplication. Strategy class names are unique (MegaLocStrategy,MixVprStrategy). Preprocessor class names are unique. Constants (DESCRIPTOR_DIM,_BACKBONE_LABEL) are module-scoped and intentionally per-strategy. - Cross-cutting concerns not locally re-implemented: The new strategies import
iso_ts_from_clockfromgps_denied_onboard.helpers.iso_timestamps— they do NOT re-introduce a local_iso_ts_from_clockbody (verified bytest_ac4_az526_no_module_level_iso_ts_from_clock_outside_helpercontinuing to pass post-AZ-339). PASS. AZ-526's regression guard worked exactly as designed.
Pre-existing failure noted (not blocking)
tests/unit/c12_operator_orchestrator/test_cli_console_script.py::TestConsoleScript::test_cold_start_under_500ms_p99 — fails on this dev laptop with a subprocess.TimeoutExpired after 5 seconds when running operator-orchestrator --help. Confirmed pre-existing by stashing the AZ-339 changes, running the test against the prior commit 5dfd9a5 (AZ-526), and observing the same failure. Cold-start latency depends on local Python interpreter startup + import time and is unrelated to this batch. Not blocking; logged here for traceability.
Verdict Rationale
One Medium finding (F1: _assert_engine_output_dim 4-way duplication, planned for AZ-527) and one Low finding (F2: AC-10 wording drift, mirroring established AZ-337 / AZ-338 precedent). No Critical, no High. Verdict: PASS_WITH_WARNINGS.