Files
Jeff Emanuel d66d05d1c5 test(search): reproduce false exact completeness after initial-window deduplication
Prepare two real-FSVI RED/GREEN cases: a short first window hiding later messages and a filled multi-shard page with the wrong second result. Require the parent to fail at the intended completeness assertions, then require all 43 component tests to pass after the narrow production patch before source publication.

Use the retained exact readers, unchanged engine pin and same temporary dependency lock. No full-CASS qualification is claimed; no archive or dependency manifests are modified.
2026-09-18 09:38:49 -04:00

75 lines
3.9 KiB
Diff

diff --git a/src/search/query.rs b/src/search/query.rs
--- a/src/search/query.rs
+++ b/src/search/query.rs
@@ -5114,8 +5114,11 @@
Self::record_fs_semantic_hit(&mut best_by_message, hit);
}
let collapsed = Self::collapse_semantic_results(best_by_message, candidate_limit);
- let has_more_candidates =
- fs_hits.len() >= candidate_limit && candidate_limit < record_count;
+ // FSVI deduplicates document IDs after raw top-k. A short nonempty
+ // batch can therefore hide later messages even before our first
+ // refill. Only an empty batch or a full-record window proves that
+ // no candidates remain; retain the score bound in every other case.
+ let has_more_candidates = !fs_hits.is_empty() && candidate_limit < record_count;
let max_omitted_score = if has_more_candidates {
fs_hits.last().map(|hit| hit.score)
} else {
@@ -5154,8 +5157,7 @@
let fs_hits = index
.search_top_k(embedding, shard_limit, fs_filter)
.map_err(|err| anyhow!("frankensearch sharded semantic search failed: {err}"))?;
- if fs_hits.len() >= shard_limit
- && shard_limit < shard_record_count
+ if shard_limit < shard_record_count
&& let Some(last_hit) = fs_hits.last()
{
has_more_candidates = true;
diff --git a/src/search/query/message_topk_integration.rs b/src/search/query/message_topk_integration.rs
--- a/src/search/query/message_topk_integration.rs
+++ b/src/search/query/message_topk_integration.rs
@@ -292,3 +292,43 @@
.unwrap();
assert_eq!(ranked(&actual), ranked(&reference));
}
+
+#[test]
+fn short_deduplicated_initial_window_does_not_hide_later_messages() {
+ let temp = tempfile::tempdir().unwrap();
+ let path = temp.path().join("duplicate-docs.fsvi");
+ let mut records = vec![(doc(1, 0, 3), [1.0, 0.0]); 40];
+ records.extend([(doc(2, 0, 3), [0.8, 0.6]), (doc(3, 0, 3), [0.6, 0.8])]);
+ let ctx = context(vec![artifact(&path, &records)]);
+ let before = std::fs::read(&path).unwrap();
+ let (initial, state) = SearchClient::search_exact_semantic_indexes_initial_window(
+ &ctx, &[1.0, 0.0], 2, None,
+ ).unwrap();
+ assert_eq!(initial.len(), 1, "FSVI deduplicates the raw candidate window");
+ assert!(state.has_more_candidates, "short deduplication is not exhaustion");
+ assert!(state.exact_window_may_omit_competitor);
+ let (hits, state) = SearchClient::search_exact_semantic_indexes(&ctx, &[1.0, 0.0], 2, None).unwrap();
+ assert_eq!(hits.iter().take(2).map(|hit| hit.message_id).collect::<Vec<_>>(), vec![1, 2]);
+ assert!(!state.exact_window_may_omit_competitor);
+ assert_eq!(std::fs::read(&path).unwrap(), before);
+}
+
+#[test]
+fn short_deduplicated_shard_cannot_certify_an_incorrect_global_ranking() {
+ let temp = tempfile::tempdir().unwrap();
+ let mut first = vec![(doc(1, 0, 3), [1.0, 0.0]); 40];
+ first.push((doc(2, 0, 3), [0.9, 0.4358899]));
+ let second = [(doc(3, 0, 3), [0.6, 0.8]), (doc(4, 0, 3), [0.0, 1.0])];
+ let ctx = context(vec![
+ artifact(&temp.path().join("first.fsvi"), &first),
+ artifact(&temp.path().join("second.fsvi"), &second),
+ ]);
+ let (initial, state) = SearchClient::search_exact_semantic_indexes_initial_window(
+ &ctx, &[1.0, 0.0], 2, None,
+ ).unwrap();
+ assert_eq!(initial.iter().take(2).map(|hit| hit.message_id).collect::<Vec<_>>(), vec![1, 3]);
+ assert!(state.exact_window_may_omit_competitor, "a filled page is not a proof after raw deduplication");
+ let (hits, state) = SearchClient::search_exact_semantic_indexes(&ctx, &[1.0, 0.0], 2, None).unwrap();
+ assert_eq!(hits.iter().take(2).map(|hit| hit.message_id).collect::<Vec<_>>(), vec![1, 2]);
+ assert!(!state.exact_window_may_omit_competitor);
+}