recall: a re-tapped fact drops its superseded vector (V-493)

The fact vector id carries a timestamp, so tapping the same key twice added a
row instead of replacing one and recall then scored the old value against the
current one. CorrectValue and VoidLatestFact already prune the key; an ordinary
re-tap is the third way a value is superseded and it did not.

actionFact now prunes fact:<key>: before inserting, so exactly one vector
survives per key. InMemoryStore gained the matching DeletePrefix, because a
test double that quietly kept both rows would pass a test the daemon fails.

The prune is best-effort and silent on a store that cannot do it: the fact row
is the truth, and a stale vector costs a wrong recall, not a lost fact.
This commit is contained in:
2026-08-05 02:32:03 +04:00
parent 0560684b35
commit dd91c6961c
3 changed files with 121 additions and 0 deletions
+20
View File
@@ -132,6 +132,26 @@ func (s *InMemoryStore) Delete(_ context.Context, id string) error {
return nil
}
// DeletePrefix removes every row whose id starts with prefix and returns how
// many went. It matches store.MemoryStore's method so a test double and the
// real index agree about superseding a fact (Vikunja #493) — a double that
// silently kept the old vectors would pass a test the daemon fails.
func (s *InMemoryStore) DeletePrefix(_ context.Context, prefix string) (int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
kept := s.items[:0]
var n int64
for _, it := range s.items {
if strings.HasPrefix(it.id, prefix) {
n++
continue
}
kept = append(kept, it)
}
s.items = kept
return n, nil
}
func (s *InMemoryStore) Search(_ context.Context, vec []float32, topK int) ([]Result, error) {
s.mu.RLock()
defer s.mu.RUnlock()