# Code Review Report — Batch 51 **Batch**: 51 **Tasks**: AZ-340 (C2 SelaVPR + EigenPlaces + SALAD Secondary Backbones — Research-only, 5 pts) **Date**: 2026-05-14 **Verdict**: **PASS_WITH_WARNINGS** (2 Low findings — one is a carry-over already tracked by AZ-527, the other mirrors the documented AZ-337/338/339 spec-drift precedent) ## Phase 1 — Context Loading Reviewed: - `_docs/02_tasks/todo/AZ-340_c2_selavpr_eigenplaces_salad.md` — task spec, ACs, NFRs, scope, constraints, runtime completeness section. - `_docs/02_document/contracts/c2_vpr/vpr_strategy_protocol.md` (referenced; behavioural Protocol contract). - `_docs/02_document/components/02_c2_vpr/description.md` § 1, § 5, § 6 (referenced; secondary backbone designation, list of strategies including SALAD, preprocessor-duplication policy). - `_docs/02_document/module-layout.md` — Component: c2_vpr Internal entry already pre-declares `sela_vpr.py`, `eigen_places.py`, `salad.py`; `BUILD_VPR_SELAVPR` / `BUILD_VPR_EIGENPLACES` / `BUILD_VPR_SALAD` rows already in the build-flag matrix. - Existing AZ-339 implementation (`mega_loc.py` / `mix_vpr.py` / `_preprocessor_*.py`) as the architectural template — AZ-340 is the explicit twin task (research-only secondary backbones via TRT, no PyTorch architecture registry). - Composition root `runtime_root/vpr_factory.py` — `_STRATEGY_TO_BUILD_FLAG` and `_STRATEGY_TO_MODULE` already include `sela_vpr` / `eigen_places` / `salad` rows (pre-wired at AZ-336 land time). - C2 config `KNOWN_STRATEGIES` already includes the three names. Files mapped to AZ-340: | Task | Files | |------|-------| | AZ-340 | `src/gps_denied_onboard/components/c2_vpr/sela_vpr.py` (new), `eigen_places.py` (new), `salad.py` (new), `_preprocessor_sela_vpr.py` (new), `_preprocessor_eigen_places.py` (new), `_preprocessor_salad.py` (new), `tests/unit/c2_vpr/test_az340_sela_vpr_eigen_places_salad.py` (new) | ## Phase 2 — Spec Compliance Review | AC | Status | Test reference | Notes | |----|--------|----------------|-------| | AC-1 (each) | PASS | `test_ac1_protocol_conformance[sela_vpr|eigen_places|salad]` | `isinstance(..., VprStrategy)` returns True for all three. | | AC-2 (each) | PASS | `test_ac2_embed_query_returns_unit_norm_fp16_correct_dim[*]` + `test_ac2_single_stage_l2_no_intra_cluster_call[*]` | Shape (512,) / (2048,) / (8448,); FP16 dtype; ‖x‖₂ ≈ 1.0 ± 1e-3. Single-stage L2 confirmed by spy normaliser; no `intra_cluster_normalise` call. | | AC-3 (each) | PASS | `test_ac3_embed_query_deterministic_for_same_frame[*]` | Bit-exact embeddings across 3 calls on same frame. | | AC-4 (each) | PASS | `test_ac4_retrieve_topk_returns_exactly_k_with_correct_label[*]` | `len == 10`, ascending distances, correct `backbone_label`, correct `descriptor_dim`. | | AC-5 (each) | PASS | `test_ac5_descriptor_dim_stable[*]` | 100 calls, returns 512 / 2048 / 8448. | | AC-6 (each) | PASS | `test_ac6_create_rejects_engine_output_shape_mismatch[*]` + `test_ac6_create_rejects_missing_embedding_key[*]` | `ConfigError` raised at create-time; the dry-run probe matches each preprocessor's `input_shape()`. | | AC-7 (each) | PASS | `test_ac7_runtime_error_yields_vpr_backbone_error[*]` + `test_ac7_wrong_forward_output_shape_yields_vpr_backbone_error[*]` | `RuntimeError` / wrong-shape outputs raise `VprBackboneError`; ERROR log + FDR record emitted. | | AC-8 (each) | PASS | `test_ac8_corrupt_image_yields_vpr_preprocess_error[*]` | Non-array `frame.image` raises `VprPreprocessError`; ERROR log + FDR record emitted. | | AC-9 (each) | PASS | `test_ac9_create_emits_ready_log_with_correct_label_and_dim[*]` | INFO log `kind="c2.vpr.ready"` with correct `{strategy, descriptor_dim}`. | | AC-10 (each) | PASS with drift | `test_ac10_runtime_label_mismatch_raises_config_error[*]` | See **F2** below — same documented precedent as AZ-337 / AZ-338 / AZ-339. | | AC-11 (each) | PASS | `test_ac11_preprocessor_input_shape[*]` + `test_preprocess_output_is_nchw_fp16[*]` | `(224, 224)` / `(480, 480)` / `(322, 322)`. | **Constraint compliance**: - Each strategy ships its own concrete preprocessor (✓). - Centre-crop logic duplicated, NOT shared (✓ — explicit duplication, comment references description.md § 6). - All three use TensorRT runtime via `InferenceRuntimeCut` (✓). - No engine compilation in this task (✓ — `inference_runtime.compile_engine` is the local TRT runtime; the actual `.trt` file source is C10 / AZ-321). - All three hold engine handles, not engines (✓). - No GPU operations in `__init__` beyond engine load (✓ — engine load is in `create()`, not in `__init__`). - SALAD's 8448-d output is non-negotiable, no PCA at strategy-level (✓ — DESCRIPTOR_DIM = 8448 is a Final constant; PCA gating left for a future `BUILD_VPR_SALAD_PCA` flag, out of scope). - Logging + FDR records mirror UltraVPR / MegaLoc / MixVPR pattern (✓). ## Phase 3 — Code Quality Review - **SOLID**: each strategy is a single class with one responsibility (one backbone). Constructor injection throughout. Module-level `create()` factory keeps composition concerns out of `__init__`. - **Error handling**: explicit `try` / `raise` for backbone failures; `_wrap_backbone_error` rewraps general `Exception` into the typed envelope. No bare `except`. ERROR logs emitted with structured `extra={...}`. - **Naming**: `SelaVprStrategy` / `EigenPlacesStrategy` / `SaladStrategy`, `_BACKBONE_LABEL`, `DESCRIPTOR_DIM`, `_OUTPUT_KEY`, `_LOG_KIND_*`, `_FDR_KIND_*` — consistent with the existing C2 strategy modules. - **Complexity**: `embed_query` ≈ 50 lines per strategy (matches MegaLoc / MixVPR; under the 50-line / cyc-10 threshold). `create()` ≈ 60 lines but mostly orchestration; mirrors the AZ-339 baseline. - **DRY**: significant duplication across three strategies + three preprocessors. Documented as intentional per `description.md` § 6 + module-level docstrings + tracked by AZ-527 (the planned hygiene PBI scoped at AZ-339 land). See **F1** below. - **Test quality**: parametrised across the three strategies; each test asserts meaningful behaviour (shape, dtype, norm, label, error type, log kind, FDR record kind). 54/54 PASS. - **Dead code**: none — `ruff check` clean across all six new modules and the test file. ## Phase 4 — Security Quick-Scan - No SQL, no `subprocess`, no `eval` / `exec`, no `pickle` deserialisation. - No hardcoded secrets / credentials / API keys. - No external input ingested without validation (`_coerce_to_rgb_uint8` rejects non-numpy / wrong-dtype / wrong-shape image bytes). - Sensitive data in logs: only `frame_id`, `backbone_label`, `descriptor_dim`, `error_type`, error-message string truncated to 512 chars. No PII / no calibration intrinsics / no embedding payloads. ✓ ## Phase 5 — Performance Scan - No O(n²) algorithms in the new code paths. FAISS retrieval is delegated to `FaissBridge` (AZ-341) and runs once per query. - No N+1 query patterns (no DB access in c2_vpr). - No unbounded data fetching. - No blocking I/O in async contexts (codebase is synchronous; the strategies are explicitly single-threaded per INV-1). - Memory copies: `np.ascontiguousarray(...)` on `raw[0]` produces one copy of the (D,) embedding before normalisation; `preprocessor.preprocess` produces one (1, 3, H, W) FP16 NCHW buffer. Both are intrinsic to the per-frame VPR contract; no avoidable allocations in the hot path. - SALAD's 8448-d output and 322×322 input are acknowledged in the task spec NFR-perf budget (≤ 120 ms / ≤ 1200 MB GPU; ≤ 6 ms FAISS lookup); no performance regression introduced by the strategy code itself — the cost is intrinsic to the chosen backbone. ## Phase 6 — Cross-Task Consistency Batch 51 contains a single task (AZ-340), so cross-task drift inside the batch is not applicable. Inter-batch consistency with the prior C2 batches (45 / 46 / 47 / 50): - All four C2 secondary strategy modules (mega_loc, mix_vpr, sela_vpr, eigen_places, salad — five total now) share the identical TRT-only contract and embed_query / retrieve_topk / descriptor_dim shapes. ✓ - All four use `iso_ts_from_clock` from the AZ-526 helper from day one. ✓ - All four use the local `_faiss_bridge.FaissBridge` and the AZ-507 `inference_runtime_cut` / `descriptor_index_cut` Protocol cuts. ✓ - All four use ImageNet mean/std normalisation in their preprocessor. ✓ ## Phase 7 — Architecture Compliance 1. **Layer direction**: every import in the six new files resolves to a Layer-1 (`_types`, `helpers`, `config`, `logging`, `fdr_client`, `clock`) or same-component (`c2_vpr.*`) target. Verified via `from ... import` survey of each new file. ✓ No layer-direction violations. 2. **Public API respect**: no `from gps_denied_onboard.components.c6_tile_cache import ...` and no `from gps_denied_onboard.components.c7_inference import ...` in any of the new files. The c6 / c7 surfaces are reached only via the constructor-injected `DescriptorIndexCut` / `InferenceRuntimeCut` Protocols (AZ-507 pattern). ✓ 3. **No new cyclic module dependencies**: the strategy modules are leaf modules within `c2_vpr/`. No new cycles introduced. ✓ 4. **Duplicate symbols across components**: all new symbols (`SelaVprStrategy`, `EigenPlacesStrategy`, `SaladStrategy`, `*BackbonePreprocessor`, `DESCRIPTOR_DIM`, `*_INPUT_HW`) live exclusively under `src/gps_denied_onboard/components/c2_vpr/`. No duplication into other components. Within `c2_vpr/` itself: the `_assert_engine_output_dim` helper is now duplicated 7-way (was 4-way after AZ-339, becomes 7-way after AZ-340). This is **F1** below; the duplication is intentional and tracked by AZ-527. 5. **Cross-cutting concerns not locally re-implemented**: no local `_iso_ts_from_clock`; no local logger setup; no local config loading; no local FDR record schema. All cross-cutting work goes through the established shared modules. ✓ AZ-526 regression guard (the AST scan in `test_az508_iso_timestamps.py`) confirms no new module-level `_iso_ts_from_clock` / `_iso_ts_now` definitions. ## Findings | # | Severity | Category | File:Line | Title | |---|----------|----------|-----------|-------| | F1 | Low | Maintainability | `c2_vpr/{sela_vpr,eigen_places,salad}.py` (3 new copies) + `c2_vpr/{ultra_vpr,net_vlad,mega_loc,mix_vpr}.py` (4 pre-existing copies) | `_assert_engine_output_dim` now 7-way duplicated across c2_vpr strategies | | F2 | Low | Spec-Gap (Documentation drift) | `c2_vpr/{sela_vpr,eigen_places,salad}.py` `create()` runtime-label guard | AZ-340 AC-10 literally names `ConfigurationError`; implementation raises `ConfigError` (matching AZ-337 / AZ-338 / AZ-339 precedent) | ### Finding Details **F1: `_assert_engine_output_dim` 7-way duplicated across c2_vpr strategies (Low / Maintainability)** - **Locations** (3 new + 4 pre-existing copies): - `src/gps_denied_onboard/components/c2_vpr/sela_vpr.py:_assert_engine_output_dim` (new) - `src/gps_denied_onboard/components/c2_vpr/eigen_places.py:_assert_engine_output_dim` (new) - `src/gps_denied_onboard/components/c2_vpr/salad.py:_assert_engine_output_dim` (new) - `src/gps_denied_onboard/components/c2_vpr/ultra_vpr.py:_assert_engine_output_dim` (pre-existing) - `src/gps_denied_onboard/components/c2_vpr/net_vlad.py:_assert_engine_output_dim` (pre-existing) - `src/gps_denied_onboard/components/c2_vpr/mega_loc.py:_assert_engine_output_dim` (pre-existing) - `src/gps_denied_onboard/components/c2_vpr/mix_vpr.py:_assert_engine_output_dim` (pre-existing) - **Description**: Each copy is the same ~30-line helper that runs a zero-init dry-run inference, asserts the engine's output dict has an `"embedding"` key, and asserts the output ndarray shape is `(1, DESCRIPTOR_DIM)`. The only thing that differs across the 7 copies is the local `DESCRIPTOR_DIM` constant. The duplication is **intentional** and **tracked**: each copy carries an in-line comment referencing AZ-527 (the hygiene PBI scoped in parallel with AZ-339 land). - **Suggestion**: extract to a c2-internal helper module `c2_vpr/_engine_dim_assertion.py` (or similar) under AZ-527. The helper would take `descriptor_dim` and `_assert_engine_output_dim(inference_runtime, handle, preprocessor, descriptor_dim)` could be a single shared function. Each strategy keeps its own `DESCRIPTOR_DIM` constant; the helper reads the dim from the parameter. - **Why Low (not Medium)**: this finding is the carry-over of cumulative review batches 46-48 F2 (also Low) — the duplication has been growing since AZ-337 / AZ-338. AZ-527 is already on the backlog; AZ-340 was scoped to land the three secondary strategies, and pulling the consolidation forward would have expanded AZ-340's scope past the spec. - **Cumulative-review handoff**: this finding will surface in the cumulative review for batches 49–51 (about to run after batch 51 commits). AZ-527 closes it. - **Task**: AZ-340 **F2: AC-10 spec drift — `ConfigError` raised instead of literally-named `ConfigurationError` (Low / Spec-Gap, documentation drift)** - **Locations**: - `src/gps_denied_onboard/components/c2_vpr/sela_vpr.py` `create()` runtime-label guard (line ~365) - `src/gps_denied_onboard/components/c2_vpr/eigen_places.py` `create()` runtime-label guard (line ~365) - `src/gps_denied_onboard/components/c2_vpr/salad.py` `create()` runtime-label guard (line ~365) - **Description**: AZ-340 AC-10 literally specifies `ConfigurationError` for the build-flag-OFF case. The implementation raises `StrategyNotAvailableError` (composition root, env-flag check; thrown BEFORE `create()` is reached) and `ConfigError` (strategy module's own runtime-label guard). Both are composition-time fail-fast errors. AZ-337 / AZ-338 / AZ-339 follow this same pattern; AZ-340 mirrors them. - **Suggestion**: amend the AC-10 wording in a future spec pass (`AZ-340_c2_selavpr_eigenplaces_salad.md` already archived; the documentation drift will be cleared by a single shared spec-pass that touches all five secondary backbones). No code change required. The AC-10 test (`test_ac10_runtime_label_mismatch_raises_config_error`) carries an in-line docstring that documents the precedent. - **Task**: AZ-340 ## Baseline Delta `_docs/02_document/architecture_compliance_baseline.md` does not exist for this project — Phase 7 ran without baseline-delta partitioning. No tables to emit. ## Test Results - `tests/unit/c2_vpr/test_az340_sela_vpr_eigen_places_salad.py` — **54 / 54 PASS** (3 strategies × 18 test cases). - `tests/unit/c2_vpr/` (full directory: faiss_bridge + net_vlad + ultra_vpr + AZ-339 + AZ-340 + protocol_conformance) — **180 / 180 PASS**. - `tests/unit/test_az270_compose_root.py` — **8 / 8 PASS** (no composition-root regressions; the factory routing for `sela_vpr` / `eigen_places` / `salad` was pre-wired at AZ-336 land time). - `tests/unit/test_az508_iso_timestamps.py` — **18 / 18 PASS** (AZ-526 regression guard confirms no new `_iso_ts_from_clock` / `_iso_ts_now` duplicates introduced by AZ-340). - `ruff check` on all 7 new files — **clean** (one auto-fixable `RUF022 __all__ not sorted` in `_preprocessor_eigen_places.py` was caught and fixed before commit). ## Auto-Fix Eligibility | Finding | Severity | Eligible? | Action | |---------|----------|-----------|--------| | F1 | Low / Maintainability | Yes (per the auto-fix matrix) | NOT auto-fixed in batch 51 — it requires creating an `AZ-527` hygiene PBI and is scoped as a separate batch. Logged as a follow-on for AZ-527. The cumulative review for batches 49–51 will re-surface it. | | F2 | Low / Spec-Gap | No (Spec-Gap is escalate per matrix) | Documentation drift — no code change required. Logged for the next spec-pass. | ## Verdict **PASS_WITH_WARNINGS** — no Critical or High findings. The two Low findings are both pre-existing patterns (F1 is a 4 → 7 escalation of an already-tracked duplication; F2 is the same precedent as AZ-337 / 338 / 339). The implement skill auto-fix gate proceeds to commit without user intervention. ## Follow-on Work - **AZ-527** (Hygiene — consolidate `_assert_engine_output_dim` into a c2-internal helper). 2 points. Now must consolidate 7 copies, not 4. Depends on AZ-340. To be created and prioritised by the cumulative review for batches 49–51. - **Spec-pass** to align AC-10 wording across AZ-337 / AZ-338 / AZ-339 / AZ-340 task specs with the implemented `ConfigError` / `StrategyNotAvailableError` envelope. Out of scope for the implement skill; should be scheduled in a documentation-sync batch.