# m12p2 — ANN candidate generation in RETRIEVE (✅ COMPLETE 2026-06-14) Landed in `bb21e69`; cache-invalidation fix `da5d2d4`. Changelog: [CHANGELOG.md](../../../CHANGELOG.md). Milestone index: [README.md](README.md). Backfilled record. This is the **G1 unblock**. ## What shipped 1. **`CandidateStrategy::Ann` is real.** It previously fell back to a scan with a warning. The db layer now resolves the query vector — the user's **preference vector** for `for_you`, the **seed item's embedding** (`similar_to`) for `related` — and Stage 1 runs an `O(ef_search)` HNSW search over the item content slot instead of scanning an arbitrary low-id slice of the universe (`tidal/src/query/executor/candidate_gen.rs`, `tidal/src/query/executor/pipeline.rs`). `for_you` and `related` are now `Ann` profiles. 2. **Graceful, not conditional-on-luck.** No vector registry, no preference vector, or no seed ⇒ it degrades to a scan. Anonymous reads and cold-start users still serve; every embedding-less schema and every pre-m12p2 caller is unchanged. 3. **`trending` stopped being an O(N) ledger scan.** `SignalRanked` is now a cached per-signal-type top-K (`tidal/src/signals/ledger/hot_top_k.rs`): O(K) on the served path, with a bounded O(N) rebuild only when stale. The cache is sound because decay preserves relative order at a fixed λ, so it stays valid until the next write. Small ledgers rebuild on any write (always fresh); large ledgers throttle the rebuild off the read hot path (1 s). `trending` now uses `SignalRanked(view)`, so it ranks the actually-viewed corpus at any id — not whatever happened to sit in the low-id scan slice. 4. **`related` over HTTP.** `GET /feed?profile=related&similar_to=` resolves the seed's embedding and runs ANN — "more like this" on the read surface (`similar_to` on `FeedQuery`, threaded through all three feed handlers). 5. **Harness support for measuring it.** `tidal-stress --feed-profile ` forces every feed read to one profile (per-profile retrieve p99) and `--seed-preferences` builds a preference vector per user so `for_you` exercises ANN rather than the scan fallback. 6. **Cache correctness under replication** (`da5d2d4`): the `SignalRanked` top-K cache is invalidated on CRDT reconciliation. Without that, a follower could serve a top-K assembled before reconciliation merged remote signal state. ## Evidence - **trending retrieve p99 = 3.5–7.7 ms** under concurrent writes against a real 1536-D standalone server — inside the 10 ms G1 target — via the cached top-K. - `for_you` retrieve is ANN-backed at p99 ≈ 24 ms, dominated by the Stage-3 preference-boost recompute (a per-candidate embedding read). Recorded, not hidden, and flagged forward to m12p3's index/score tuning as the risk register's "materialized-score layer". - ANN candidate recall is the m12p1 `/vector_search` probe: 0.9997. - `tidal/tests/m12p2_ann_retrieve.rs` (5) — proves ANN, `related`, and `trending` reach relevant items at high ids that a scan can never reach, plus cache freshness.