Batch 45 (AZ-341 C2 FAISS retrieve wiring) post-commit bookkeeping: - Move AZ-341 task spec to done/ (implement skill step 13). - Write batch_45_cycle1_report.md (test results, AC coverage, architectural decisions, findings carried into cumulative review). - Bump state.last_completed_batch 44 → 45. Co-authored-by: Cursor <cursoragent@cursor.com>
7.7 KiB
Batch 45 — Cycle 1 Report
Date: 2026-05-13 Batch: 45 Tasks: AZ-341 (C2 FAISS HNSW Retrieve Wiring, 3pt) Status: complete; ticket transitioned to "In Testing".
Scope
AZ-341 delivers FaissBridge — the cross-strategy shared plumbing
that connects every concrete C2 VprStrategy.retrieve_topk to C6's
FAISS HNSW DescriptorIndex.search_topk surface. The bridge owns the
defended-in-depth INV-4 check (exactly K results, distance-ascending),
the WARN-threshold check on distances[0], the optional per-frame
DEBUG top10_distances log, and the vpr.retrieve_topk FDR record
emission with monotonic-ns latency measurement.
Companion deliverables in the same batch:
DescriptorIndexCutProtocol — the AZ-507 consumer-side structural cut that keepsc2_vpr/*.pyimport-free ofc6_tile_cache.*.C2VprConfiggains two operator-tunable knobs:warn_top1_threshold(default 0.30 — task-spec Risk 3 conservative placeholder pending FT-P-19 telemetry) anddebug_per_frame_distances(default OFF — Risk 2 mitigation against journald flood).fdr_client.recordsregistersvpr.retrieve_topkinKNOWN_PAYLOAD_KEYSand the AZ-272 roundtrip schema test gains a fixture for the new kind (mirroring the Batch-44c12.reloc.requestedpattern).
Unblocks each future C2 strategy (AZ-337 UltraVPR, AZ-338 NetVLAD,
AZ-339 MegaLoc+MixVPR, AZ-340 SelaVPR+EigenPlaces+SALAD): every
strategy's retrieve_topk shrinks to a one-line delegation
return self._faiss_bridge.retrieve(query, k, backbone_label=...),
eliminating the seven-way duplication risk the task spec called out.
Architectural Decisions
1. The bridge is stricter than the c6 contract — by design
C6's descriptor_index.md Invariant I-2 says search_topk "MAY
return fewer than K results when the corpus has fewer than K
vectors". The AZ-341 task spec asks the bridge to enforce == K
and raise IndexUnavailableError on undersized returns. The two
contracts are complementary, not contradictory:
- C6's I-2 is a structural invariant (the return-shape upper bound, not a guarantee of full results).
- AZ-341's INV-4 is an operational invariant (a corpus with fewer than K vectors is operationally degraded; the system can not function — downstream RANSAC matching needs the full top-K spread).
The bridge's module docstring documents this on-purpose strictness. The defended-in-depth posture is the task-spec's explicit Risk-1 mitigation against silent C6 regressions.
2. The bridge propagates the c6 IndexUnavailableError unchanged
AC-4 is explicit: "the bridge does not catch and re-raise". The
bridge has no try/except IndexUnavailableError. When the c6
stale-handle / sidecar / dim-mismatch defence fires, the original
c6 exception escapes directly to the strategy and onwards.
This conflicts with the c6_tile_cache.errors.IndexUnavailableError
module docstring's expectation that "the C2 family is the closed
envelope a C5/C2.5 consumer sees; the C6 family is the storage-layer
error a concrete strategy is responsible for rewrapping". The
implementation follows the task-spec letter; surfacing the gap as
Finding F2 of the per-batch review for spec/code-comment alignment.
3. _iso_ts_from_clock is duplicated — accepted for this batch
The helper is six lines that appear inline in c2 / c5 / c11 / c12.
Each component avoids importing the others' helpers (per AZ-507
shape). A future shared helper would live in shared/helpers/
(L1) but factoring is deferred to AZ-508 (the hygiene-only PBI
already in todo/ covering ISO-timestamp consolidation across
the suite). Captured as Finding F1.
4. The dropped normaliser constructor parameter
The task spec's Outcome § Constructor lists
normaliser: DescriptorNormaliser but the documented retrieve
body never calls it — the VprStrategy Protocol INV-3 already
requires VprQuery.embedding to arrive L2-normalised. The bridge
omits the parameter (no behaviour change vs the documented body).
Captured as Finding F2 for spec hygiene.
Files Changed
Production source (new)
src/gps_denied_onboard/components/c2_vpr/_faiss_bridge.py—FaissBridgeclass (~250 LOC),_verify_invariants/_log_invariant_violation/_emit_fdr_record/_iso_ts_from_clockhelpers, module-level FDR / logkindconstants.src/gps_denied_onboard/components/c2_vpr/descriptor_index_cut.py—DescriptorIndexCutProtocol +TileIdTuplealias (~50 LOC).
Production source (modified)
src/gps_denied_onboard/components/c2_vpr/config.py—C2VprConfiggainswarn_top1_threshold: float = 0.30anddebug_per_frame_distances: bool = Falseplus their validators.src/gps_denied_onboard/components/c2_vpr/__init__.py— re-exportsDescriptorIndexCutandTileIdTuplefor the composition root's c6 adapter.src/gps_denied_onboard/fdr_client/records.py— registersvpr.retrieve_topkinKNOWN_PAYLOAD_KEYSwith payload field set{frame_id, backbone_label, top10_distances, latency_us}.
Tests (new)
tests/unit/c2_vpr/test_faiss_bridge.py— 22 tests covering AC-1..AC-11, NFR-perf microbench (p95 ≤ 0.5 ms), constructor validation, retrieve-argument validation. Uses_FakeDescriptorIndex,_StubClock, and a realFdrClientwith power-of-two capacity 8 to verify both the success enqueue pattern and the OVERRUN warn path.
Tests (modified)
tests/unit/test_az272_fdr_record_schema.py— adds avpr.retrieve_topkpayload to the_make_fdr_payloadfixture so the schema roundtrip + envelope tests cover the new kind.
Docs / process
_docs/03_implementation/reviews/batch_45_review.md— per-batch code review (verdict PASS_WITH_WARNINGS, 2 Low findings)._docs/_autodev_state.md— sub_step bumped through the batch loop.
Test Results
1565 passed, 80 skipped in 69.9s
Versus pre-batch (Batch 44 final): 1543 passed, 80 skipped.
Delta: +22 new tests (all in tests/unit/c2_vpr/test_faiss_bridge.py),
zero regressions, zero new skips.
Code Review
Verdict: PASS_WITH_WARNINGS — see
_docs/03_implementation/reviews/batch_45_review.md.
Two Low-severity findings:
| # | Severity | Category | Title |
|---|---|---|---|
| F1 | Low | Maintainability | Duplicated _iso_ts_from_clock helper across c2/c5/c11/c12 — defer to AZ-508 |
| F2 | Low | Spec-Gap | Task spec lists unused normaliser parameter — surface to user / spec hygiene |
No Critical, no High, no Medium. The Auto-Fix Gate accepts the verdict without intervention.
AC Coverage Summary
All 11 acceptance criteria + NFR-perf have at least one covering
test. Detailed AC ↔ test mapping in
_docs/03_implementation/reviews/batch_45_review.md § Phase 2.
Tracker
- AZ-341 transitioned
To Do→In Progressat batch start. - AZ-341 transitioned
In Progress→In Testingafter the commit landed.
Cumulative Review (batches 43-45)
Last cumulative review was for batches 40-42. With Batch 45 closed (43, 44, 45 since the last cumulative), the implement skill's Step 14.5 cumulative review fires next at K=3.
Next Batch
Several C-component slices are now unblocked:
- C1: AZ-333 (VINS-Mono strategy, research), AZ-334 (KLT/RANSAC mandatory baseline). Then AZ-335 (warm-start) once 333+334 land.
- C2: AZ-337 (UltraVPR primary, 5pt), AZ-338 (NetVLAD baseline, 3pt), AZ-339 (MegaLoc+MixVPR research), AZ-340 (SelaVPR+ EigenPlaces+SALAD research).
- C3: AZ-345 / AZ-346 / AZ-347 — pending the C2.5 ReRanker tasks.
A natural Batch-46 candidate would be a C1 mandatory simple-baseline (AZ-334) — single 5pt task, OpenCV-based, no model weights — but the next-batch selection will go through the implement skill's Step 3 (Compute Next Batch) afresh.