Skip to content

v0.9.2 — NaN-safe recall

Choose a tag to compare

@spranab spranab released this 10 Jul 22:50

v0.9.2 — patch

Fixes #60: a NaN-valued embedding could panic the process during recall() / procedure(action="surface").

Root cause (two bugs compounding)

  1. hnsw.rs cosine_distance()''s zero-norm guard used norm == 0.0, which misses NaN (NaN == 0.0 is false) — so a NaN embedding produced a NaN score with no interception.
  2. Sort comparators used partial_cmp(..).unwrap_or(Ordering::Equal), which is not a valid total order once NaN mixes with finite scores. Rust ≥ 1.81''s driftsort detects the broken transitivity and panics ("user-provided comparison function does not correctly implement a total order"), killing the call and leaving MCP sessions returning -32602.

Fix

  • hnsw norm guard is now !(norm > 0.0) — catches NaN, zero, and negatives; distance is always finite.
  • Crate-wide sweep: every float sort comparator (117 sites across 40 files, including the recall_profiled profiling-gated duplicates and the hnsw heap Ord impls) now uses f64::total_cmp — IEEE totalOrder, never panics, order-identical to partial_cmp for all finite values.
  • Regression tests: NaN/zero-norm distance guard + end-to-end recall with a NaN embedding mixed into finite candidates.

Credit: reported by @blue-mtn-dog with an exceptional root-cause analysis — traced to exact source lines against the v0.9.1 tag, including the driftsort total-order precondition.

Published to PyPI (pip install -U yantrikdb) and crates.io (yantrikdb).