store: repair fact vectors against the facts they name (V-493)

Every write-path fix leaves the rows already stored wrong, and a box in that
state looks fine: recall answers with the wrong text and nothing logs an error.
That is how the original poison survived four restarts.

RepairFactVectors resolves each fact vector against the fact it names,
re-embeds the ones whose text is stale, and deletes the voided, superseded and
orphaned ones. Marker-guarded and idempotent, so it runs once per box and a run
that dies partway is simply redone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 22:36:54 +04:00
parent dbdab2d570
commit 1528697287
2 changed files with 192 additions and 6 deletions
+15 -6
View File
@@ -498,12 +498,21 @@ rejects `https://api.openai.com`, and forget really deletes
(`internal/store/memory.go:145` is a real `DELETE`, not a tombstone). Vision is
19/19, speaker 22/22, media 16/16.
**470 got worse.** Both poisoned facts show `voided` on `/history`, and the
defect survives. Re-measured at 15:42, after four restarts: `почему небо синее?`
still answers `какая последняя версия языка Go?` with no `search:` line. What
comes back is the question he typed, not the value the fact held. So the poison
is a vector in the memory index, and `revert` does not remove it. There is
currently no documented way to repair a poisoned box.
**470 got worse, then closed.** Both poisoned facts showed `voided` on
`/history` and the defect survived. Re-measured at 15:42, after four restarts:
`почему небо синее?` still answered `какая последняя версия языка Go?` with no
`search:` line. What came back was the question he typed, not the value the fact
held. So the poison was a vector in the memory index, and `revert` did not
remove it.
Repaired in two parts. 470 stopped the writes: a question is never a fact, and a
void drops the key's vectors. 493 fixed what the index holds. A fact is indexed
as the fact and not as the utterance, and a correction drops its superseded
vector too.
A poisoned box now repairs itself on the next start. `RepairFactVectors`
re-embeds every fact vector from the fact it names, and deletes the voided and
superseded ones. It runs once, guarded by a marker, and logs what it did.
---