From 6091eabc6c9fb70f08d9f712c450cfcf187eb859 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 3 Nov 2026 10:55:09 +0100 Subject: [PATCH] read-floor: the read-back gate comment says what it proves; equal-score ranks tie-break by ordinal at the cut Co-Authored-By: Claude Opus 5.5 --- crates/huginn-mind/src/query.rs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/crates/huginn-mind/src/query.rs b/crates/huginn-mind/src/query.rs index 623bb53..48e1d09 100644 --- a/crates/huginn-mind/src/query.rs +++ b/crates/huginn-mind/src/query.rs @@ -508,9 +508,12 @@ impl Mind { /// read path `view` uses; every pipeline write a receipt names is stored, /// and every stored document has its receipt. Returns how many documents /// it read back; an empty store is a fault. The held image is first set beside a - /// fresh fold of the store and must equal it, so every upgrade proves the held - /// state against the store. One pass over the mind, O(N). Admits nothing. A fault - /// names the document's key and what failed, never its content. + /// fresh fold of the store and must equal it. That clause proves the holder's advance + /// only for a mind that has committed since it opened; on the just-opened mind the + /// upgrade reads, both sides are one fold of the same bytes, so a wrong fold passes + /// it. What the gate proves today is the per-document read-back below. One pass over + /// the mind, O(N). Admits nothing. A fault names the document's key and what failed, + /// never its content. pub fn read_back(&self) -> Result { self.read_back_through(|reader, id| reader.view(id)) } @@ -2032,6 +2035,29 @@ mod tests { assert_eq!((ids(&page), page.matched), (vec![shelf.r4.clone()], plain_matched(&shelf.mind, &standing(Selection::default()))), "top_k bounds the page too"); } + /// Three equal scores in every arrival order rank by ordinal alone, so a lookup that gives + /// every hit the same row (or the wrong one) is seen. + #[test] + fn equal_scores_rank_by_each_documents_own_ordinal_in_any_arrival_order() { + let shelf = shelf(); + let image = shelf.mind.image().unwrap(); + let named = [(K::Question, shelf.q2.clone()), (K::Ruling, shelf.r4.clone()), (K::Resolution, shelf.withdrawn_resolution.clone())]; + let ordinal = |(kind, key): &(K, String)| image.row(image.id_of(*kind, key).unwrap()).ordinal; + let mut expected: Vec<(K, String)> = named.to_vec(); + expected.sort_by_key(ordinal); + assert!(ordinal(&expected[0]) < ordinal(&expected[1]) && ordinal(&expected[1]) < ordinal(&expected[2]), "the fixture's ordinals are distinct"); + for order in [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] { + let hits: Vec<_> = order.iter().map(|at| hit(named[*at].0, &named[*at].1, 0.5)).collect(); + let page = rank_all(&shelf.mind, &Selection::default(), &asking(10), &hits).page; + assert_eq!(ids(&page), expected.iter().map(|(_, key)| key.clone()).collect::>(), "arrival order {order:?}"); + // The cut is where the tie-break decides: the page keeps the earliest ordinals. + let keys: Vec = expected.iter().map(|(_, key)| key.clone()).collect(); + let limited = Selection { limit: Some(1), ..Selection::default() }; + assert_eq!(ids(&rank_all(&shelf.mind, &limited, &asking(10), &hits).page), keys[..1].to_vec(), "limit 1, arrival order {order:?}"); + assert_eq!(ids(&rank_all(&shelf.mind, &Selection::default(), &asking(2), &hits).page), keys[..2].to_vec(), "top_k 2, arrival order {order:?}"); + } + } + /// Membership is the selection's alone: a kind, a key allowlist and a /// citation predicate each cut the candidates exactly as they cut a plain /// query, and the citation predicate sees the whole mind, not only the