FTS5 auto-heal is unreachable when the serving interpreter's SQLite cannot detect the fault
Version: 3.7.0 (also present in 3.5.0)
Platform: Windows 11, uv tool install mempalace
Backend: chroma, default minilm embedder
Palace: 322,848 drawers, chroma.sqlite3 ~1.95 GB, 325,451 embedding_fulltext_search_content rows
Summary
repair.maybe_autoheal_fts5_index only runs when repair.sqlite_integrity_errors
returns a non-empty list. That function asks the SQLite bundled with whichever
interpreter is running MemPalace. On this install that is SQLite 3.47.1, and
3.47.1 does not detect the FTS5 fault present in this palace, while SQLite
3.50.4 reports it on the same file through the same mode=ro URI.
The result is that a palace can carry a malformed FTS5 inverted index
indefinitely while every MemPalace-run check — the startup integrity probe,
repair's preflight, and the post-mine validation in
palace._validate_palace_fts5_after_mine — reports it clean. The auto-heal
added for exactly this condition can never fire.
Separately, and more surprising: a 'rebuild' issued by 3.47.1 does not clear
the fault, while the same statement issued by 3.50.4 does. So even if
detection were fixed, the heal would still no-op on this interpreter.
Evidence
Same file, same URI, both pragma forms, minutes apart:
| SQLite |
PRAGMA quick_check(1) |
PRAGMA quick_check |
| 3.50.4 (CPython 3.14.6) |
malformed inverted index for FTS5 table main.embedding_fulltext_search |
same |
| 3.47.1 (CPython 3.12.9 — MemPalace's uv venv) |
ok |
ok |
FTS5's own command, from 3.50.4:
INSERT INTO embedding_fulltext_search(embedding_fulltext_search) VALUES('integrity-check');
-- DatabaseError: database disk image is malformed
Table definition:
CREATE VIRTUAL TABLE embedding_fulltext_search USING fts5(string_value, tokenize='trigram')
-- embedding_fulltext_search_config: version = 4
Rebuild behaviour, on a byte copy of the live database so the result is
attributable to the rebuild alone:
'rebuild' issued by |
duration |
3.50.4 quick_check afterwards |
| 3.47.1 |
76s |
still malformed |
| 3.50.4 |
85s |
clean, and integrity-check passes |
After rebuilding the live palace with 3.50.4 under mine_palace_lock,
quick_check is clean, integrity-check passes, embedding_fulltext_search_content
is unchanged at 325,451 rows, and MemPalace (still on 3.47.1) returns identical
search results with identical BM25 scores — so a 3.50.4-written index reads back
correctly on 3.47.1 at this scale.
What I could not reproduce
A synthetic fts5(body, tokenize='trigram') table of 4,000 rows round-trips
clean in both directions between 3.47.1 and 3.50.4, with and without an
explicit 'rebuild'. So this is not simply "3.50.4 rejects any trigram index
3.47.1 writes" — something about this palace's data or scale is required, and I
do not have a minimal reproduction. Candidates I did not chase: multi-segment
indexes at ~2 GB, and content predating the embedded-NUL sanitisation in the
3.6.0 line (#1716, #1872, #1878, #1927, #1928).
I am reporting it without that minimal case because the detection asymmetry
stands on its own and is straightforward to check.
Impact
Nothing is visibly broken while MemPalace stays on SQLite < 3.50: BM25 search
returns sensible scores, because the index is self-consistent for the build that
wrote it. The problem is latent and surfaces on interpreter upgrade — at which
point quick_check starts failing, repair aborts at the preflight with
"ABORT: SQLite-layer corruption detected before repair rebuild", and the operator
is told their palace is corrupt. On this palace the underlying content was intact
the whole time.
It also means the several auto-heal paths added in the 3.6.0/3.7.0 line are
silently inert on any install whose Python bundles an older SQLite, which
includes the documented uv tool install route.
Suggestions
- Log the SQLite version alongside integrity verdicts. A "clean" from a
build that cannot see this class of fault is not the same claim as a clean
from one that can, and today they are indistinguishable in the output.
- Consider a floor for the integrity check, or a note in
docs/write-routing-policy.md that integrity results are only as good as the
linked SQLite.
- Worth confirming independently that FTS5
'rebuild' on 3.47.1 can leave a
large trigram index in a state 3.50.4 rejects. If that holds generally it
affects the heal path regardless of detection.
Happy to run further diagnostics against this palace — it reproduces reliably
here and I have a verified pre-change backup.
FTS5 auto-heal is unreachable when the serving interpreter's SQLite cannot detect the fault
Version: 3.7.0 (also present in 3.5.0)
Platform: Windows 11,
uv tool install mempalaceBackend: chroma, default
minilmembedderPalace: 322,848 drawers,
chroma.sqlite3~1.95 GB, 325,451embedding_fulltext_search_contentrowsSummary
repair.maybe_autoheal_fts5_indexonly runs whenrepair.sqlite_integrity_errorsreturns a non-empty list. That function asks the SQLite bundled with whichever
interpreter is running MemPalace. On this install that is SQLite 3.47.1, and
3.47.1 does not detect the FTS5 fault present in this palace, while SQLite
3.50.4 reports it on the same file through the same
mode=roURI.The result is that a palace can carry a malformed FTS5 inverted index
indefinitely while every MemPalace-run check — the startup integrity probe,
repair's preflight, and the post-mine validation inpalace._validate_palace_fts5_after_mine— reports it clean. The auto-healadded for exactly this condition can never fire.
Separately, and more surprising: a
'rebuild'issued by 3.47.1 does not clearthe fault, while the same statement issued by 3.50.4 does. So even if
detection were fixed, the heal would still no-op on this interpreter.
Evidence
Same file, same URI, both pragma forms, minutes apart:
PRAGMA quick_check(1)PRAGMA quick_checkmain.embedding_fulltext_searchFTS5's own command, from 3.50.4:
Table definition:
Rebuild behaviour, on a byte copy of the live database so the result is
attributable to the rebuild alone:
'rebuild'issued byquick_checkafterwardsintegrity-checkpassesAfter rebuilding the live palace with 3.50.4 under
mine_palace_lock,quick_checkis clean,integrity-checkpasses,embedding_fulltext_search_contentis unchanged at 325,451 rows, and MemPalace (still on 3.47.1) returns identical
search results with identical BM25 scores — so a 3.50.4-written index reads back
correctly on 3.47.1 at this scale.
What I could not reproduce
A synthetic
fts5(body, tokenize='trigram')table of 4,000 rows round-tripsclean in both directions between 3.47.1 and 3.50.4, with and without an
explicit
'rebuild'. So this is not simply "3.50.4 rejects any trigram index3.47.1 writes" — something about this palace's data or scale is required, and I
do not have a minimal reproduction. Candidates I did not chase: multi-segment
indexes at ~2 GB, and content predating the embedded-NUL sanitisation in the
3.6.0 line (#1716, #1872, #1878, #1927, #1928).
I am reporting it without that minimal case because the detection asymmetry
stands on its own and is straightforward to check.
Impact
Nothing is visibly broken while MemPalace stays on SQLite < 3.50: BM25 search
returns sensible scores, because the index is self-consistent for the build that
wrote it. The problem is latent and surfaces on interpreter upgrade — at which
point
quick_checkstarts failing,repairaborts at the preflight with"ABORT: SQLite-layer corruption detected before repair rebuild", and the operator
is told their palace is corrupt. On this palace the underlying content was intact
the whole time.
It also means the several auto-heal paths added in the 3.6.0/3.7.0 line are
silently inert on any install whose Python bundles an older SQLite, which
includes the documented
uv tool installroute.Suggestions
build that cannot see this class of fault is not the same claim as a clean
from one that can, and today they are indistinguishable in the output.
docs/write-routing-policy.mdthat integrity results are only as good as thelinked SQLite.
'rebuild'on 3.47.1 can leave alarge trigram index in a state 3.50.4 rejects. If that holds generally it
affects the heal path regardless of detection.
Happy to run further diagnostics against this palace — it reproduces reliably
here and I have a verified pre-change backup.