xiaowei-system/scripts/fts-stale-watch.py

65 lines
3.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""fts_stale 运行期兜底 watchkanban t_a88ff8ac 方案 b 落地, 2026-09-07
设计决策(为什么选方案 b 只读版):
- 既有测试不变量tests/state/test_fts_runtime_rebuild.py: live write/search 不得
rebuild 全量 FTS, 只有 SessionDB open 才恢复。方案 a(独立进程模拟 reopen) 在本环境
有 P1-1 风险(gateway 在线时第二写入者 → state.db WAL 突破损坏, 本环境反复损坏根因),
方案 c(改源码允许受控时机重建) 风险面最大。
- 方案 b 落地 = 部署层「检测 + 告警排队」: 只读查 fts_stale 标记与 FTS 漂移, 若 stale
持续存在 → 输出告警(no_agent watchdog 模式, 健康静默) 通知人工在停机窗口触发重建
(gateway 停止后 SessionDB open 自然走 _recover_stale_fts)。不引入任何活库写面。
- 该方案保留既有不变量不动, 消灭「fts_stale 后无 open 则无限期 LIKE 降级(无声)」——
改为「有 open 则恢复, 无 open 则 30 分钟内有告警」。
"""
import sqlite3, os, sys
db = os.path.expanduser("~/.hermes/state.db")
def main():
try:
# 只读连接: 绝不写活库P1-1 规避: gateway 在线时第二写入者 = 损坏)
conn = sqlite3.connect(f"file:{db}?mode=ro", uri=True, timeout=15)
cur = conn.cursor()
# 1. fts_stale 持久化标记state_meta key, hermes_state_common.py:778
try:
stale = cur.execute(
"SELECT 1 FROM state_meta WHERE key = 'fts_stale' LIMIT 1"
).fetchone() is not None
except sqlite3.OperationalError:
stale = False # 表不存在(新库/极端情况)不算 stale
# 2. FTS 行数 vs messages 行数漂移(粗略健康信号)
n_msg = n_fts = drift = None
try:
n_msg = cur.execute("SELECT COUNT(*) FROM messages").fetchone()[0]
n_fts = cur.execute("SELECT COUNT(*) FROM messages_fts").fetchone()[0]
drift = n_msg - n_fts
except sqlite3.OperationalError:
pass
# 3. 完整性快检(只读安全)
try:
integrity = cur.execute("PRAGMA quick_check").fetchone()[0]
except sqlite3.OperationalError:
integrity = "err"
conn.close()
stale_ok = "✅ 无 stale 标记" if not stale else "⚠️ fts_stale 标记存在"
drift_ok = ""
if drift is not None and drift > 200:
drift_ok = f"; ⚠️ FTS 漂移大 (messages={n_msg} fts={n_fts} drift={drift})"
healthy = (not stale) and (drift is None or drift <= 200) and integrity == "ok"
if not healthy:
# watchdog 模式: 只有异常才输出cron no_agent 空输出不发送)
print(f"[FTS-WATCH] {stale_ok}{drift_ok} integrity={integrity}")
print("处理: 请勿在线 rebuildlive write 不 rebuild 不变量 + P1-1 第二写入者风险)。")
print(" 在停机窗口gateway 停止后)执行: hermes 下次 SessionDB open 会自动走 _recover_stale_fts 重建。")
print(" 或手动: 停 gateway → sqlite3 重建 FTS → 起 gateway。详见 RECOVERY-RULES.md。")
# 健康时完全静默(无输出 = no_agent cron 不投递)
except Exception as e:
print(f"[FTS-WATCH-ERR] {e}")
if __name__ == "__main__":
main()