bd2c7e2457
`sqlite3 state.db .recover` re-emits the FTS5 shadow tables (messages_fts_data, _idx, _docsize, _config, _content) as ordinary tables but cannot re-emit the CREATE VIRTUAL TABLE row. The next SessionDB open ran the FTS DDL in _ensure_fts_schema and died with "fts5: error creating shadow table messages_fts_data: table 'messages_fts_data' already exists", so a recovered database was unusable until someone hand-dropped the shadows. _init_fts now runs _drop_orphan_fts_shadow_tables before any FTS DDL. It is per-family and exact-name scoped: a family's shadows are dropped only when its own vtable row is absent from sqlite_master (type='table' AND sql LIKE 'CREATE VIRTUAL TABLE%'), so a healthy messages_fts_trigram survives a base-family repair untouched. A repaired base/trigram family is then treated like a missing-trigger repair and rebuilt from the canonical messages table under the cross-process rebuild admission; the shadows are derived index state, nothing is lost. Live repro: real `sqlite3 x.db .recover | sqlite3 y.db` on sqlite 3.50.4 keeps the vtable rows (the shell emits CREATE VIRTUAL TABLE), so the deterministic fixture removes the vtable row via writable_schema leaving the shadows behind: BEFORE OperationalError on open; AFTER opens, fts_enabled, MATCH returns every message, trigram sqlite_master rowids unchanged. Salvaged from PR #56824 (intent applied onto the current hermes_state_fts / hermes_state_schema siblings). The ownership-safety point (never touch a live family's shadows) was raised by @ggoldani in #103868 / #103840. Refs #103840 Refs #56815 Co-authored-by: ggoldani <ggoldani@users.noreply.github.com>