Persist PageTree expand state via control-plane view-state and align chevron/DOM with restored expansion; keep Sidex-style shallow page-tree scan and drop the unused recursive scanner that only added cargo noise. Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi into a module package, and retire Hermes/ACP/OpenHub recycle + root harness evidence from the index while gitignoring recycle and local diag dumps. Archive superseded design/bugs docs under old/, point architecture at ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor regressions so the working tree can stay clean.
44 KiB
2-9 Turso / libSQL 控制面切换 checklist v1
创建时间:2026-07-02
当前状态:
process目标:在接受 Turso / libSQL Beta 与云服务风险的前提下,把 MNote 当前 Rust SQLite 控制面切换到 Turso / libSQL 后端,降低多个进程直接写同一个 SQLite 文件带来的损坏风险,并为后续云同步、审计、权限和 AI 控制面维护提供更稳的存储边界。
上位依据:
/mnt/Data1T/mnote/ARCHITECTURE.md(CURRENT_ARCHITECTURE.md为兼容指针)/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-8-convex-replace-with-rust-sqlite-control-plane-v1.md
1. 结论
当前可以开始转向 Turso,但迁移边界必须收紧:
第一阶段只切 MNote control-plane,不迁移 local-first 文件真相,不迁移页面正文、附件、mindmap、Office 文件,不迁移已经退役的
evidence.sqlite。
Turso 的收益不在于“SQLite 语法换个驱动”本身,而在于把控制面写入统一收口到受控 store / API 后端,避免脚本、测试、进程和运行时同时直接写同一个 SQLite 文件。若继续保留脚本直接 sqlite3 写库、测试临时覆盖 MNOTE_CONTROL_PLANE_DB_PATH、OpenHub 直接写自己的 SQLite 文件,那么换 Turso 只能解决一部分问题,不能解决当前最致命的并发写入纪律问题。
2026-07-03 hard cutover 更新:用户已确认不再保留 mnote-web runtime SQLite fallback。当前策略改为 Turso/libSQL-only runtime:dev-hot、desktop-hot、prod 和 mnote-web 默认 libsql-local,显式 MNOTE_CONTROL_PLANE_BACKEND=sqlite 必须拒绝启动;SQLite 仅保留给 control-plane-admin 迁移/导出和 Rust 测试隔离。
2026-07-03 补充决策:已经获得真实 Turso mnote dev DB URL/token,并完成 remote dry-run 连接验证;但分叉讨论与本轮评估结论一致,Turso Cloud 对当前 MNote 的意义不是“立刻把产品变成云同步应用”。当前阶段仍以 local-first 文件真相和本机开发稳定性为主,云端能力只作为 control-plane 的受控验证、备份、迁移演练和后续多设备准备层。
因此默认路线调整为:
dev-hot、desktop-hot和 prod runtime 默认使用libsql-local,覆盖本地 store、migration 和脚本纪律;不再保留 runtime SQLite fallback。desktop-hot不立即切到turso-remote或turso-local-replica。- Turso remote 只做 dev DB dry-run、迁移演练、回滚演练和后续受保护 smoke,不作为当前本地功能测试的阻塞项。
- local-first 文件正文、附件、mindmap、OnlyOffice 仍完全留在本地文件系统,不迁入 Turso。
推荐执行顺序:
- 先把
ControlPlaneStore后端抽象和脚本直写清掉。 - 再新增 Turso / libSQL store。
- 本地默认先切
libsql-local,真实 Turso remote 只做受控验证。 - 等密码哈希、remote smoke、回滚导出和 token 管理都稳定后,再评估是否让
turso-local-replica成为普通默认。 - 最后评估 OpenHub 会话库是否单独迁移。
2. 范围
2.1 必迁移
- Rust
control-planecrate 当前的 SQLite store:- users / password identities / sessions
- workspaces / workspace members
- directory grants / share links
- sync state / outbox / audit log
- sidebar shortcuts / UI preferences / navigation recent
- AI policy / AI runtime run 和 event
- AI agent profile access
- external conversation bindings
mnote-web对 control-plane 的构造、注入和测试 helper。- 所有直接
sqlite3写 control-plane 的 smoke / 测试脚本。 - 启动脚本和部署脚本里的
MNOTE_CONTROL_PLANE_DB_PATH默认路径逻辑必须移除;运行时只读取 libSQL/Turso env。
2.2 第一阶段不迁移
- 本地
.md页面正文。它仍是 local-first 正文真相。 - 本地附件、图片、mindmap、OnlyOffice 文件。
.mnote/index/evidence.sqlite。该库已从当前主线退役,本轮不迁移。- LightRAG 自身存储。知识库/RAG provider 存储不在 control-plane 切换范围内。
- OpenHub 自身 SQLite 会话库。它可以作为第二阶段单独迁移,不应和 Rust control-plane 首次切换绑死。
- WeKnora / RAGFlow。当前不作为主线迁移目标。
- 浏览器直连 Turso。所有读写仍必须经过 Rust / OpenHub API。
3. 目标架构
Browser / Desktop Shell / Agent
|
v
Rust mnote-web :3000
- auth/session
- workspace/profile
- access policy
- share/invite
- sync state
- AI policy/runtime
- audit/outbox
|
v
ControlPlaneStore trait
|
+-- SqliteControlPlaneStore 仅限 admin 迁移 / 导出 / Rust 测试隔离
|
+-- TursoControlPlaneStore 默认目标后端
推荐环境变量:
MNOTE_CONTROL_PLANE_BACKEND=libsql-local|turso-remote|turso-local-replica|turso-synced
MNOTE_TURSO_LOCAL_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane-libsql.db
MNOTE_TURSO_DATABASE_URL=libsql://...
MNOTE_TURSO_AUTH_TOKEN=...
MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane-replica.db
默认策略建议:
- 当前已落地:
dev-hot、desktop-hot和 prod runtime 默认libsql-local。 - 已配置真实 Turso dev DB:仅用于 remote dry-run、迁移演练、回滚演练和受保护 smoke,不改变 local-first 主路径。
- Turso store 全测试通过后:CI 先跑
sqlite+libsql-local;turso-dev作为需要 secret 的受保护 job。 - 稳定后才评估:普通运行是否从
libsql-local提升为turso-local-replica;不恢复 SQLite runtime fallback。
本轮新增云端边界:
- remote / replica / synced 更接近“控制面云备份、迁移演练、多设备准备层”,不是本地文件网盘。
- control-plane 可以进入 Turso;正文、附件、mindmap、OnlyOffice、OpenHub 会话库和 RAG 存储不能因为这次 cutover 顺带迁入。
- remote 上线前必须先完成密码哈希从
sha256-v1升级到 Argon2id,避免把弱哈希带入云端控制面。 - token 只允许存放在本机私有 env 或 secret manager,不写入仓库、日志、截图和设计文档。
4. 当前代码事实
4.1 Rust control-plane
rust/crates/control-plane/src/store.rsControlPlaneStore是同步 trait,当前是正确抽象边界,应保持接口稳定。
rust/crates/control-plane/src/sqlite.rs- 唯一实现是
SqliteControlPlaneStore。 - 内部是
Mutex<rusqlite::Connection>。 - 当前配置
journal_mode=WAL、synchronous=NORMAL、foreign_keys=ON、busy_timeout=5000。
- 唯一实现是
rust/crates/control-plane/src/migrations.rs- 直接依赖
rusqlite::Connection。
- 直接依赖
rust/crates/control-plane/src/error.rs- 直接实现
From<rusqlite::Error>。
- 直接实现
rust/crates/control-plane/Cargo.toml- 当前声明
rusqlite = { version = "0.34", features = ["bundled"] }。
- 当前声明
4.2 Rust mnote-web
rust/crates/mnote-web/src/app.rs- 当前直接导入
SqliteControlPlaneStore。 AppState.control_plane当前是Arc<SqliteControlPlaneStore>,应改成Arc<dyn ControlPlaneStore>。- 历史状态:
open_control_plane_store()曾读取MNOTE_CONTROL_PLANE_DB_PATH并打开 SQLite;hard cutover 后该路径已删除。
- 当前直接导入
rust/crates/mnote-web/src/local_folder_watcher_registry.rs- 字段已经是
Arc<dyn ControlPlaneStore>,但new()参数仍偏向具体 SQLite 类型,需要收口。
- 字段已经是
rust/crates/mnote-web/src/routes/local_search_index.rs- 历史上使用过
rusqlite处理evidence.sqlite;该库已退役,不能作为 Turso 迁移目标。
- 历史上使用过
rust/crates/mnote-web/src/routes/local_folder_source.rs- 部分 best-effort 索引刷新路径会自行打开 control-plane store;Phase 1 已适配 trait object,后续可评估复用
AppState中的 store。
- 部分 best-effort 索引刷新路径会自行打开 control-plane store;Phase 1 已适配 trait object,后续可评估复用
rust/crates/mnote-web/src/ssr/pages/admin.rs- admin 页面存在 control-plane DB 路径展示逻辑;Turso 默认后端切换时需要改成后端状态展示,不应继续只显示 SQLite 文件路径。
4.3 脚本和 smoke
当前至少有两类脚本必须改:
- 直接调用
sqlite3CLI 写库。 - 通过临时
control-plane.sqlite3和MNOTE_CONTROL_PLANE_DB_PATH绕过真实 store;hard cutover 后运行时启动链不得再接受该方式。
这些脚本如果不改,即使 runtime 切 Turso,测试和开发链路仍会制造第二套写入口。
5. Phase 0:风险登记和依赖确认
- 确认 Turso / libSQL 的当前 Beta 风险:API 稳定性、SLA、区域、限流、连接数、备份能力、最大数据库大小。
- 当前记录为运行手册中的准入风险:真实 remote / replica / synced 作为云端同步/备份/多设备准备层后续启用;不保留 mnote-web runtime SQLite fallback,也不把本地文件真相迁入 Turso。
- 固定 Rust
libsqlcrate 版本,不使用浮动主分支依赖。 - 确认当前 8 个 migration SQL 在 libSQL 上可执行。
- 明确 remote-only、local replica、pure local 三种模式的行为差异。
- 明确 token 管理规则:只走环境变量或本机 secret 文件,不写入仓库。
- 记录故障边界:认证失败、网络超时、Turso 5xx、限流、schema migration 失败、local replica 同步失败。
- 代码已覆盖 missing/empty URL/token、timeout/rate-limit error mapping、invalid local path;真实 5xx/限流/同步失败属于后续云端验证,不阻塞本地 control-plane 功能测试。
- 决定第一阶段是否允许离线写入。当前策略:
libsql-local允许本地离线写入;turso-local-replica模式下本地 replica 可离线读、上线后同步;纯turso-remote模式依赖网络,失败时返回明确错误。
验收:
- 已形成 Turso dev 数据库和 dev token,用于云端同步/备份链路验收。
- 本机私有 env:
/mnt/Data1T/Mnote_data/control-plane/turso.env,权限0600,不入仓库。 - 数据库 URL host 已验证为
mnote-liaibo.aws-ap-northeast-1.turso.io,token 不写入设计文档。
- 本机私有 env:
- 后续
libsql最小连接 demo 能跑通 dev 数据库。- 2026-07-03 已执行
control-plane-admin init --backend turso-remote --dry-run,返回ok=true。 - 2026-07-03 已执行
control-plane-admin migrate-sqlite-to-target --backend turso-remote --dry-run,返回源库表计数和targetBackend=TursoRemote。
- 2026-07-03 已执行
- 本机无 token 时不会 panic,返回
InvalidInput;真实断网场景作为后续 Turso dev DB 验证。
6. Phase 1:先加固 ControlPlaneStore 边界
目标:在不改变数据库后端的情况下,先把 runtime 从具体 SQLite 类型中解耦。
rust/crates/mnote-web/src/app.rsAppState.control_plane: Arc<SqliteControlPlaneStore>改为Arc<dyn ControlPlaneStore>。open_control_plane_store()改为返回 trait object。- 新增
MNOTE_CONTROL_PLANE_BACKENDfactory 入口,已实现 sqlite/libsql-local/turso-remote/turso-local-replica/turso-synced 全部分支。 - 当前默认 SQLite 行为与现状一致。
rust/crates/mnote-web/src/local_folder_watcher_registry.rsnew()参数改为Arc<dyn ControlPlaneStore>。- watcher 测试通过 trait object 构造器编译与运行。
rust/crates/mnote-web/src/routes/local_search_index.rs- 已复核:该文件中的 SQLite 写入仅限
#[cfg(test)]的evidence.sqlite/ local search legacy 测试,不是 control-plane DB 直写;本轮不迁移。
- 已复核:该文件中的 SQLite 写入仅限
rust/crates/control-plane/src/lib.rs- 保持
SqliteControlPlaneStore导出,新增后端前不破坏现有测试。
- 保持
验收:
cargo test --manifest-path rust/Cargo.toml -p control-planecargo test --manifest-path rust/Cargo.toml -p mnote-web已运行并确认本轮 control-plane 相关路径无新增失败。- 全量结果为 855 passed, 1 failed;唯一失败是既存 CSS 体积阈值
ssr::styles::tests::mnote_css_is_reasonably_sized,与 control-plane cutover 无关。
- 全量结果为 855 passed, 1 failed;唯一失败是既存 CSS 体积阈值
MNOTE_CONTROL_PLANE_BACKEND=sqlite npm run desktop:hot已改为拒绝启动,防止运行时回落到 SQLite。
7. Phase 2:新增 TursoControlPlaneStore
目标:新增 Turso/libSQL 实现,并在 mnote-web runtime 中移除 SQLite fallback。
rust/crates/control-plane/Cargo.toml- 增加
libsql依赖。 - 保留
rusqlite仅服务 control-plane-admin 迁移/导出和 Rust 测试隔离。
- 增加
rust/crates/control-plane/src/turso.rs- 新增
TursoControlPlaneStore。 - 支持 remote URL + token。
- 支持 local replica path + remote sync URL。
- 支持 pure local libSQL 模式,用于无云测试。
- 实现
ControlPlaneStore全部方法。
- 新增
rust/crates/control-plane/src/migrations.rs- 从
rusqlite::Connection绑定改为后端无关迁移入口,或新增 libSQL 迁移入口。 - PRAGMA 只在本地 SQLite / local replica 下执行;remote Turso 跳过 WAL 类配置。
- 从
rust/crates/control-plane/src/error.rs- 增加
From<libsql::Error>。 - 区分 constraint、not found、auth、network、timeout、rate limit。
- 增加
rust/crates/control-plane/src/lib.rs- 导出
TursoControlPlaneStore。
- 导出
设计注意:
- 当前 trait 是同步接口。第一阶段建议保持同步边界,避免把所有 route 改成异步 store trait。
- 如果
libsqlAPI 只能异步访问 remote,应在 store 内部集中处理 async runtime 包装,不要让每个 route 自己 block。 - 多语句写入必须明确事务边界;不能把原来 SQLite 单连接串行行为误拆成多个独立远程请求。
验收:
cargo test --manifest-path rust/Cargo.toml -p control-plane --features turso-unit-tests -- --test-threads=1- 同一套 store 行为测试已在本地可测后端运行:
sqlite✅ /libsql-local✅ / feature-gated local Turso store tests ✅。 - 后续云端验证:同一套 store 行为测试在
turso-dev后端运行。turso-dev需要 dev DB/token,不阻塞本地功能测试。
- migration idempotent 测试在 Turso dev 数据库通过。
libsql-localmigration idempotent ✅;真实 Turso dev DB 为后续云端验证项(缺 dev DB/token)
8. Phase 3:移除脚本直写数据库
目标:所有测试和 smoke 通过 API / helper seed 数据,不再直接写 control-plane DB 文件。
8.1 直接 sqlite3 CLI 用户
以下脚本必须改为调用 Rust API、测试 seed endpoint,或统一 helper:
scripts/task557-page-ai-run-resume-smoke.jsscripts/task558-page-ai-reasonix-live-session-context-smoke.jsscripts/task559-page-ai-terminal-status-reconciliation-smoke.jsscripts/task560-page-ai-hermes-load-replay-smoke.jsscripts/task561-page-ai-session-dashboard-smoke.jsscripts/task562-page-ai-reasonix-approval-plan-smoke.jsscripts/task512-chatonly-doubao-sync-smoke.jsscripts/task513-chatonly-provider-sync-smoke.jsscripts/task527-chatonly-api-provider-smoke.jsscripts/task530-knowledge-rag-page-ai-final-answer-smoke.js
8.2 临时 DB path 覆盖用户
以下脚本必须改为使用统一 test control-plane backend,不再自己创建临时 SQLite 文件后覆盖环境变量:
scripts/task492-sidebar-starred-shortcuts-smoke.jsscripts/task494-filetree-lazy-loading-dedup-smoke.jsscripts/task496-editor-open-parallel-runtime-aggregate-smoke.jsscripts/task497-local-page-tree-filetree-open-performance-smoke.jsscripts/task498-starred-page-tree-scope-and-local-edit-smoke.jsscripts/task499-sidebar-tree-view-state-smoke.jsscripts/task500-navigation-page-route-guard-smoke.jsscripts/task167-local-markdown-title-body-options-no-convex-smoke.js
8.3 启动和部署脚本
scripts/prod-build-start.js- 不再只设置
MNOTE_CONTROL_PLANE_DB_PATH。 - 支持
MNOTE_CONTROL_PLANE_BACKEND和 Turso env。
- 不再只设置
scripts/desktop-hot.js- 明确 dev 默认后端。
- 防止 hot/prod runtime 再次写 SQLite control-plane 文件。
scripts/dev-hot.js- 同步 backend 环境变量策略。
验收:
rg -n "sqlite3|MNOTE_CONTROL_PLANE_DB_PATH|control-plane\\.sqlite3|control-plane\\.db" scripts rust/crates --glob '!target/**' --glob '!recycle/**'中不再出现未解释的直写入口。- 需要 seed 的脚本统一走
scripts/lib/control-plane-dev-seed.js/scripts/lib/control-plane-test-env.js或测试 API。 - smoke 失败时不再留下孤立临时 control-plane DB(
scripts/task-control-plane-admin-libsql-roundtrip-smoke.js使用/tmp前缀并在 finally 清理)。
9. Phase 4:数据初始化和迁移
当前仍是测试期,不需要保留正式业务数据,但仍需要迁移工具来保证以后可运维。
- 新增 control-plane 初始化命令:
- 创建 schema。
- 创建默认 admin / e2e 用户。
- 创建默认 workspace / membership。
- 写入必要 AI policy / UI preference 默认值。
- 新增本地 SQLite 到 Turso 的一次性迁移工具:
- 支持
--dry-run。 - 输出源表行数、目标表行数、跳过项、失败项。
- 支持目标清空后重建 dev DB。
- 支持
- 新增 Turso 到 SQLite 的回滚导出工具:
- 回滚前自动备份本地 DB。
- 支持只导出 control-plane schema。
验收:
- 后续云端验证:空 Turso dev DB 可通过初始化命令启动 MNote(缺
MNOTE_TURSO_DATABASE_URL/MNOTE_TURSO_AUTH_TOKEN)。 - 初始化后的测试账号
mnote.e2e@example.com可登录。- 后续云端验证:同 Turso dev DB/token 缺
- libSQL/Turso 可导出为 SQLite 备份;SQLite 备份不再作为 mnote-web runtime fallback。
10. Phase 5:并发、损坏和故障测试
目标:证明切换确实解决当前痛点,而不是只换驱动名。
- 并发写测试:
- 多线程同时创建 session(libSQL local)。
- 多线程同时 upsert sidebar shortcut(libSQL local)。
- 多线程同时写 AI runtime events(libSQL local)。
- 多线程同时 append audit/outbox(libSQL local)。
- 故障注入:
- Turso token 缺失返回
InvalidInput且不 panic。 - Turso URL 不可达。
- 后续云端验证:缺 Turso dev DB(libSQL local 模式下无远程连接)
- 网络超时。
- 后续云端验证:缺 Turso dev DB
- 远端返回 5xx。
- 后续云端验证:缺 Turso dev DB
- 远端限流。
- 后续云端验证:缺 Turso dev DB
- local replica 文件不可写。
- 后续云端验证:缺 Turso dev DB
- Turso token 缺失返回
- 数据一致性:
- unique constraint 冲突仍返回可处理错误。
- session 创建和查询一致。
- outbox 不重复投递。
- AI runtime event 顺序可恢复。
验收:
- 新增
cargo test -p control-plane并发测试。 - 新增 Node smoke 覆盖 admin init / SQLite→libSQL local / libSQL local→SQLite roundtrip,且失败时清理临时库。
- 已覆盖缺 token 时不 panic;远端网络/5xx/限流作为后续真实 Turso dev DB 验证。
11. Phase 6:默认切换
dev-hot.js 已默认使用
MNOTE_CONTROL_PLANE_BACKEND=libsql-local(dev-hot.js:75);desktop-hot.js 已默认libsql-local。remote / local-replica / synced 更接近云端同步、备份和多设备准备层,不影响当前 local-first 本地功能测试;它们作为后续云端验收项,不再阻塞本轮本地 libSQL cutover。2026-07-03 决策更新:真实 Turso dev DB/token 已可用,但云端价值对当前阶段有限;本轮只把它接入为“可验证但不默认”的控制面 dev target,不把 remote / replica 作为 local-first 功能测试前置条件。
- dev 环境先切到本地 libSQL:
MNOTE_CONTROL_PLANE_BACKEND=libsql-local- 不依赖 Turso dev DB/token,覆盖本地 store、migration 和 script discipline,不保留 runtime SQLite fallback。
- Turso dev DB/token 已配置为本机私有 env:
/mnt/Data1T/Mnote_data/control-plane/turso.env- 文件权限
0600 - repo 内未出现 JWT/token 明文命中。
- Turso remote 最小验证:
control-plane-admin init --backend turso-remote --dry-run返回ok=true。control-plane-admin migrate-sqlite-to-target --backend turso-remote --dry-run成功读取本地 SQLite 源库并规划导入 remote。
- 后续云端同步/备份链路切换(低优先级,非当前默认):
MNOTE_CONTROL_PLANE_BACKEND=turso-local-replica- 先完成 Argon2id password migration。
- 观察登录、workspace、授权、分享、sidebar、navigation、AI runtime。
- 验证 replica initial sync、
MNOTE_TURSO_SYNC_INTERVAL_MS、断网和 token 失效边界。
- CI 增加后端矩阵:
sqlitelibsql-localturso-dev作为后续受保护云端 job,需要 secret 时才跑;失败不阻塞本地 local-first smoke。
- 本机正式默认切换到 remote / replica 前(后续云端验证):
- 备份当前
/mnt/Data1T/Mnote_data/control-plane/control-plane.db。 - 记录当前 schema version。
- 跑完整 smoke 基线。
- 备份当前
默认切换验收:
- 本地 cutover:
libsql-localstore 行为、migration、admin roundtrip、seed helper 和 SQLite runtime 拒绝路径已验证。 - 云端 cutover:
npm run desktop:hot使用turso-local-replica启动后可登录测试账号。 - 云端 cutover:
/api/auth/session返回真实 Turso control-plane session。 - 文件树和文档页仍读取本地工作区文件。
- 新建/重命名/打开页面不依赖 Turso 存正文。
- 云端 cutover:断开 Turso 后错误边界符合 Phase 0 决策。
11.1 当前不做的 Turso 云端事项
- 不把 remote Turso 当成“网盘备份”替代本地文件系统;它只保存 control-plane 元数据。
- 不把 remote / replica / synced 设为
desktop-hot默认。 - 不把 OpenHub SQLite、local search、RAG provider 数据跟随 control-plane 一起迁移。
- 不在 repo 内保存真实 token;
tmp/turso.md和turso.env都属于本机私有文件。
11.2 下一轮可执行 checklist
rust/crates/mnote-web/src/app.rs:解析MNOTE_TURSO_SYNC_INTERVAL_MS,传入 remote replica / synced builder。rust/crates/control-plane/src/turso.rs:remote replica 建库时启用 explicit initial sync;必要时启用read_your_writes(true)。rust/crates/mnote-web/src/routes:新增安全的 control-plane health endpoint,暴露 backend/mode/schema version/sync status,不暴露 token。rust/crates/control-plane:补真实 remote dev DB 的 store contract smoke,作为 secret-gated 测试。rust/crates/control-plane:补 Argon2id password hash migration,再允许 remote 成为长期候选默认。docs/operations/control-plane-turso.md:记录本机私有 env、remote dry-run、低优先级云端边界和 token 防泄露检查。
11.3 Turso / libSQL 优势落地状态校准
2026-07-03 校准:上一轮 P0/P1/P2 是“最值得利用的能力清单”,不是全部已实现清单。当前已完成的是 local cutover、脚本直写清理、local libSQL 并发防损坏测试和 remote dry-run;读写并发性能设计、sync health、Argon2id、CDC/Tantivy 都尚未落地。
| 优先级 | 能力 | 当前状态 | 结论 |
|---|---|---|---|
| P0 | 唯一写入口 / 脚本不直写 control-plane DB | 已完成 | 继续保持所有 seed / smoke 走 API、helper 或 control-plane-admin。这是降低 SQLite 损坏风险的核心收益。 |
| P0 | libsql-local store + migration + browser QA |
已完成 | 当前可作为本地测试主线;不依赖 Turso Cloud。 |
| P0 | 并发防损坏测试 | 已完成 local 版 | rust/crates/control-plane/tests/turso_store.rs 已用多线程同时创建 session、shortcut、AI runtime events、audit/outbox,验证 local libSQL store 在当前单进程并发下不损坏。 |
| P0 | 真实 Turso remote 凭据 dry-run | 已完成 non-destructive 版 | 已验证 URL/token 可用,init --dry-run 与 migrate-sqlite-to-target --dry-run 成功;未把 remote 设为默认。 |
| P0 | MNOTE_TURSO_SYNC_INTERVAL_MS / initial sync |
已完成:env var 解析与传递 | mnote-web/src/app.rs 已解析 MNOTE_TURSO_SYNC_INTERVAL_MS 并传给 remote replica / synced;explicit initial sync 仍待后续验证。 |
| P0 | control-plane health endpoint | 未完成 | 需要新增只读诊断端点,暴露 backend/mode/schema/sync 状态,不暴露 token。 |
| P0 | Argon2id password hash migration | 未完成 | remote 成为长期默认前必须完成,避免把 sha256-v1 占位哈希带入云端。 |
| P1 | 读写并发性能设计 | 仅完成安全基线,未实现性能优化 | 当前 TursoControlPlaneStore 是 Mutex<TursoConnection>,读写全部串行;这能保证简单安全,但不能发挥 remote / replica 的读并发优势。 |
| P2 | CDC / change data capture | 未采用 | 当前 control-plane 已有显式 audit_log 和 outbox_events,CDC 只适合作为后续审计/同步 spike,不进入本轮。 |
| P2 | Tantivy-powered full-text search | 未采用 | 当前 control-plane 表主要是元数据,全文搜索收益有限;正文和知识库搜索仍属于 OpenHub/WeKnora/local search 方向,不跟 control-plane 绑死。 |
| P3 | Browser WASM / OPFS database | 未采用 | 对纯 Web 离线笔记有价值,但当前 MNote 主形态是本地文件夹 + Rust mnote-web + 文件系统 watcher;浏览器内 SQL 不能替代本地 .md 真相,也不适合作为 control-plane 权限真源。 |
11.4 读写并发设计原则
当前实现优先解决“不要损坏”和“不要绕过写入口”,不是追求最大吞吐:
TursoControlPlaneStore现在用单个Mutex<TursoConnection>串行化所有 control-plane 操作。- 这能避免本轮最致命的问题:多个运行时、脚本或测试直接写同一个 SQLite 文件导致损坏。
- 这也意味着 remote / replica 模式下,读操作暂时不能和写操作并行,属于保守实现。
后续若要优化读写并发,必须按以下顺序推进:
- 先把
ControlPlaneStore方法按只读 / 写入 / 多表事务分类,形成文件级清单。 - 写入路径继续保持单写者队列或独占写锁;多表写入必须显式事务,不允许拆成多个独立锁周期。
- 只读路径可以引入独立 read connection pool 或
RwLock,但必须先证明 libSQL remote / replica 连接在当前 Rust SDK 版本下可安全共享。 - remote replica 只把读放在本地副本;写仍以 remote primary 为准,不把 embedded replica 当成高并发本地写主路径。
- 在改
Mutex -> RwLock/read pool前,先补 benchmark 和压力测试:并发 session lookup、grant resolve、navigation recent、AI event append、audit/outbox drain。 - 在同步 trait 未改 async 前,不扩大
block_in_place使用面;否则大量并发请求可能耗尽 Tokio worker。
推荐下一步不是直接把 Mutex 改成 RwLock,而是先加:
MNOTE_TURSO_SYNC_INTERVAL_MS- explicit initial sync
- control-plane health endpoint
- secret-gated remote store contract smoke
这些完成后,再评估读连接池或 async store trait。
11.5 CDC 与 Tantivy FTS 评估
CDC / Change Data Capture
Turso 文档中的 CDC 通过 PRAGMA capture_data_changes_conn(...) 按连接开启,把 insert/update/delete/DDL 变更写入 CDC 表;它尊重事务边界,rollback 不产生 CDC 记录。当前本地文档还明确 CDC 与 MVCC 在同一连接上互斥。
对 MNote 的判断:
- 当前不作为 P0:control-plane 已有业务语义明确的
audit_log和outbox_events,比通用 CDC 更适合作为权限、分享、AI policy 的审计与事件源。 - 可作为 P2 spike:如果后续要把 control-plane 变更桥接到 OpenHub、实时分析、外部消息队列或审计归档,可以评估 CDC 作为补充。
- 不替代业务审计:CDC 记录的是表级变更,不知道“用户授权目录”“AI policy 变更”“分享撤销”等业务意图,仍需要应用层 audit/outbox。
- 不进首轮 remote:CDC 仍是早期能力,且可能捕获敏感字段;必须先定义字段脱敏、保留期、权限和导出路径。
结论:P2 / research only,不进入当前 Turso control-plane 默认切换。
Tantivy-powered Full-text Search
Turso 文档显示 FTS 通过 CREATE INDEX ... USING fts 和 fts_match / fts_score / fts_highlight 使用 Tantivy,不是 SQLite FTS5 的完全同语法替换。
对 MNote 的判断:
- 当前 control-plane 不需要:users、workspaces、grants、recent、shortcuts、policy 都是轻量元数据,
LIKE或精确索引足够。 - 不迁正文:页面正文
.md仍在本地文件系统,知识库问答主线是 OpenHub/WeKnora,不把正文全文塞进 control-plane。 - 可作为 P2/P3:如果以后要做轻量“标题 / 文件路径 / 页面摘要 / AI 会话标题”的本地快速搜索,可以单独设计 Turso FTS 索引。
- 不替代 WeKnora/OpenHub RAG:Tantivy FTS 是关键词检索,不等于知识库引用、OCR、语义检索和 agent citation 链。
结论:对当前 control-plane 价值低;对未来轻量元数据搜索有价值;不进入本轮。
Browser WASM / OPFS
Turso / libSQL 的 Browser WASM + OPFS 路线适合“纯浏览器应用也要持久化 SQL 数据库”的场景。官方资料也能看到 browser WASM 示例、@tursodatabase/sync-wasm 和本地浏览器数据库方向;Context7 返回的 sync 文档强调 synced database 可做离线写入再同步。
对 MNote 的判断:
- 当前不作为 P0/P1:MNote 的主形态不是纯浏览器笔记应用,而是
3000 Rust SSR + local-first workspace + 本地文件系统。本地.md、附件、mindmap、OnlyOffice 已经有真实文件真相和 watcher,同步到浏览器 OPFS 反而会制造第三份数据真相。 - 不替代 control-plane:auth、session、grant、AI policy 仍必须在 Rust control-plane 中受控;浏览器 OPFS 数据库不能成为权限真源,否则会弱化服务端校验和 agent 访问边界。
- 不适合当前桌面链路:浏览器 WASM/OPFS 往往受浏览器 storage quota、origin 隔离、清站点数据、cross-origin isolation / SharedArrayBuffer 等运行条件影响;这些不如当前本地文件系统路径可审计、可备份、可由 agent 直接 patch。
- 可作为 P3 独立 spike:如果未来要做“无桌面端 / 无本地服务”的纯 Web 离线阅读、临时草稿、移动端 PWA、离线收集箱,可以评估 WASM OPFS 存储轻量 cache、草稿或最近访问索引。
- 不存敏感真相:即使做 P3,也只应存可重建 cache / draft / projection,不存长期 auth token、分享授权真相、完整知识库和不可恢复正文唯一副本。
结论:对当前网页中的笔记系统不是主线优化;对未来纯 Web/PWA 离线模式有研究价值,归 P3 spike。
12. Phase 7:OpenHub 会话库单独评估
OpenHub 属于当前 AI 主线,但它不是 Rust control-plane。不能把 Rust control-plane 切换和 OpenHub SQLite 切换混成一个不可回滚的大改。
当前 Phase 7 为独立后续评估:OpenHub 不属于 Rust control-plane,其 SQLite 会话库迁移不绑定 Rust control-plane 切换。OpenHub 侧直写 SQLite 的文件路径和 schema 需单独审计(属于 OpenHub 自身治理范围);Rust control-plane 本地 libSQL cutover 已完成,remote / replica / synced 仅作为后续云端同步/备份验收项,不影响 OpenHub 单独评估。
- 先确认 OpenHub 当前数据库文件、schema 和写入路径。
- 确认哪些数据属于可丢弃会话缓存,哪些属于用户需要保留的 AI 历史。
- 新增
OPENHUB_DB_BACKEND=sqlite|turso,默认先保持sqlite。 - OpenHub Python 侧增加 Turso/libSQL client abstraction。
- 会话创建、消息写入、会话恢复、MNote 嵌入 Page AI 全链路验证。
- 若 OpenHub 会话写入频率高于 control-plane,单独做性能和限流评估。
OpenHub 切换门槛:
- Rust control-plane Turso 已稳定。
- OpenHub 不再通过脚本直接改 SQLite。
- Page AI / OpenHub smoke 可在 Turso 后端通过。
13. Phase 8:文档和运行手册
- 更新
ARCHITECTURE.md:- control-plane 从
Rust SQLite改为Turso/libSQL-only runtime。 - 明确本地
.md文件仍是正文真相。
- control-plane 从
- 更新
CURRENT_ARCHITECTURE.md:- 当前默认后端、SQLite admin-only 边界和备份恢复方式。
- 更新
AGENTS.md:- 常用命令增加 Turso backend env。
- smoke 基线说明不得直写 control-plane DB。
- 新增运行手册:
docs/operations/control-plane-turso.md- dev token 配置。
- dev DB reset / init。
- SQLite 导出备份。
- Turso 到 SQLite 回滚。
- 故障排查。
14. 备份与恢复策略
hard cutover 后不再保留 mnote-web runtime SQLite 一键回滚;SQLite 只作为导出备份格式和迁移源。
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- \
export-target-to-sqlite --backend libsql-local --output /tmp/mnote-control-plane-backup.sqlite --backup-existing
恢复 checklist(仅在 remote / replica / synced 成为默认后需要完整执行):
- 从 Turso/libSQL 导出最新 control-plane 备份。
- 保留当前 libSQL/Turso 目标库快照。
- 将备份重新导入新的 libSQL/Turso 目标库,或接受 dev 数据重置。
- 重启
npm run desktop:hot。 - 验证登录、workspace、授权、sidebar、navigation、AI policy。
- Turso DB 保留至少 30 天用于审计和补导出。
15. 最小落地顺序
如果只追求最快降低 SQLite 损坏风险,按这个顺序做:
AppState.control_plane改成Arc<dyn ControlPlaneStore>。- 增加 control-plane factory 和 backend env。
- 改掉 17 个直写/临时 DB 脚本。
- 增加
TursoControlPlaneStore,先跑 libSQL local。 - 跑 control-plane store 行为测试矩阵。
- 接 Turso dev DB。
- 切
desktop:hotdev 默认后端。 - 补并发写和故障注入测试。
- 再决定是否迁 OpenHub 会话库。
不删除 SqliteControlPlaneStore 实现本身,但它不再是 mnote-web runtime fallback;仅作为迁移源、导出备份格式和 Rust 测试隔离。
16. 执行记录
2026-07-02 Phase 1 起步
- 已将
rust/crates/mnote-web/src/app.rs的AppState.control_plane解耦为Arc<dyn ControlPlaneStore>。 - 已将
open_control_plane_store()改为返回 trait object,内部仍使用SqliteControlPlaneStore,未引入 Turso 实现。 - 已新增
MNOTE_CONTROL_PLANE_BACKENDfactory 入口,Phase 2 已完成 sqlite/libsql-local/turso-remote/turso-local-replica/turso-synced 全部分支(详见下文全 Phase 同步记录)。 - 已将
rust/crates/mnote-web/src/local_folder_watcher_registry.rs构造器参数改为Arc<dyn ControlPlaneStore>。 - 已适配
rust/crates/mnote-web/src/routes/local_folder_source.rs中 best-effort 索引刷新调用。 - 已复核脚本直写入口:P0 为 10 个生产 control-plane
sqlite3直写 smoke;P1/P2 为 Rust 初始化路径和 8 个临时 DB smoke。 - 验证通过:
cargo check --manifest-path rust/Cargo.toml -p mnote-web。 - 验证通过:
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder_watcher -- --test-threads=1。 - 验证通过:
cargo test --manifest-path rust/Cargo.toml -p control-plane。 - 已验证(已知失败):
cargo test --manifest-path rust/Cargo.toml -p mnote-web全量为 855 passed, 1 failed(既有 CSS 体积阈值ssr::styles::tests::mnote_css_is_reasonably_sized,断言MNOTE_CSS.len() < 190000,与控制面切换无关)。
2026-07-02 全 Phase 同步与收尾
本轮同步完成了 Phase 2/3/5/6/8 的大幅推进,并更新 checklist 以反映实际代码状态。
已实现文件(本轮变更范围):
| 文件 | 变更 |
|---|---|
rust/crates/control-plane/Cargo.toml |
新增 libsql、tokio 依赖与 turso-unit-tests feature |
rust/crates/control-plane/src/turso.rs |
4738 行完整 TursoControlPlaneStore 实现:Local/Remote/RemoteReplica/Synced 四种模式 |
rust/crates/control-plane/src/migrations.rs |
新增 run_libsql_migrations() async 迁移入口 |
rust/crates/control-plane/src/error.rs |
新增 From<libsql::Error>,区分 constraint/unauthorized/storage |
rust/crates/control-plane/src/lib.rs |
导出 TursoControlPlaneConfig、TursoControlPlaneMode、TursoControlPlaneStore |
rust/crates/control-plane/src/sqlite.rs |
保持 SqliteControlPlaneStore 作为 admin 迁移/导出与测试隔离实现,store 行为测试覆盖完整 |
rust/crates/control-plane/src/model.rs |
新增字段 |
rust/crates/mnote-web/src/app.rs |
AppState.control_plane: Arc<dyn ControlPlaneStore> 解耦;5 种后端 factory |
rust/crates/mnote-web/src/local_folder_watcher_registry.rs |
构造器改为 Arc<dyn ControlPlaneStore> |
rust/crates/mnote-web/src/routes/local_folder_source.rs |
best-effort 索引刷新适配 trait object |
rust/crates/mnote-web/src/ssr/pages/admin.rs |
适配 control-plane 后端状态展示 |
docs/operations/control-plane-turso.md |
新增 Turso/libSQL control-plane 初始化、迁移、回滚、验证和故障边界运行手册 |
scripts/desktop-hot.js |
支持 MNOTE_CONTROL_PLANE_BACKEND,默认 libsql-local,拒绝 sqlite |
scripts/dev-hot.js |
支持 MNOTE_CONTROL_PLANE_BACKEND,默认 libsql-local |
scripts/prod-build-start.js |
支持 MNOTE_CONTROL_PLANE_BACKEND |
scripts/lib/control-plane-test-env.js |
新增统一 test env builder,支持全部后端模式 |
scripts/lib/control-plane-dev-seed.js |
新增 dev seed API helper,替代 smoke 脚本直接写库 |
scripts/task-control-plane-admin-libsql-roundtrip-smoke.js |
新增 admin CLI SQLite → libSQL local → SQLite roundtrip smoke |
rust/crates/mnote-web/src/routes/dev_seed.rs |
新增 /api/dev/seed gated endpoint 与 targeted tests |
| 17 个 smoke 脚本 | 从直接 sqlite3 CLI 写库改为 API POST /api/dev/seed 和 buildControlPlaneTestEnv |
ARCHITECTURE.md |
control-plane 从 Rust SQLite 改为 Turso/libSQL-only runtime |
CURRENT_ARCHITECTURE.md |
记录 ControlPlaneStore trait 架构、后端选择、env vars |
AGENTS.md |
常用命令增加 Turso backend env;脚本纪律不得直写 control-plane DB |
已通过测试验证:
cargo check --manifest-path rust/Cargo.toml -p mnote-web✅cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder_watcher -- --test-threads=1✅cargo test --manifest-path rust/Cargo.toml -p mnote-web app_state_initializes_sqlite_control_plane_store✅cargo test --manifest-path rust/Cargo.toml -p mnote-web dev_seed -- --test-threads=1✅cargo test --manifest-path rust/Cargo.toml -p control-plane✅(28 unit + 3 integration)cargo test --manifest-path rust/Cargo.toml -p control-plane --features turso-unit-tests -- --test-threads=1✅(64 unit + 3 integration)node --test scripts/desktop-hot.test.js✅node scripts/task-dev-hot-plan-test.js✅node scripts/task-control-plane-admin-libsql-roundtrip-smoke.js✅node --check覆盖 control-plane helper 与已迁移 smoke 脚本 ✅cargo test --manifest-path rust/Cargo.toml -p mnote-web❌ 855 passed, 1 failed(既有 CSS 体积阈值MNOTE_CSS.len() < 190000,与控制面切换无关)
后续云端验证项:
MNOTE_TURSO_DATABASE_URL与MNOTE_TURSO_AUTH_TOKEN未配置 → 暂不测试 Turso remote / local-replica / synced 模式;不影响当前本地功能测试- 真实 Turso dev DB / token 未配置,remote、local-replica、synced、真实 5xx/限流/断网/同步失败作为后续云端验收
cargo test --manifest-path rust/Cargo.toml -p mnote-web全量 855 passed, 1 failed(既有 CSS 体积阈值失败);本轮 control-plane 相关 targeted tests 已通过
备份命令(SQLite 只作为导出格式,不作为 runtime rollback):
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- \
export-target-to-sqlite --backend libsql-local --output /tmp/mnote-control-plane-backup.sqlite --backup-existing
local-first 不迁移边界(已验证未踏入):
- 本地
.md文件正文 → 未迁移 - 本地附件、图片、mindmap、OnlyOffice 文件 → 未迁移
.mnote/index/evidence.sqlite(已退役)→ 未迁移- LightRAG 自身存储 → 未迁移
- OpenHub 自身 SQLite 会话库 → 未迁移(Phase 7 推迟)
- WeKnora / RAGFlow → 未迁移
- 浏览器直连 Turso → 未实现(所有读写经过 Rust API)
2026-07-03 最终执行记录
本轮为 stale 状态修正与最终记录归档,不修改代码。
已修正的 stale 状态:
cargo test --manifest-path rust/Cargo.toml -p control-plane✅(28 unit + 3 integration)cargo test --manifest-path rust/Cargo.toml -p control-plane --features turso-unit-tests -- --test-threads=1✅(64 unit + 3 integration)cargo test --manifest-path rust/Cargo.toml -p mnote-web全量 855 passed, 1 failed(既有 CSS 体积阈值MNOTE_CSS.len() < 190000,与控制面切换无关)- Phase 7 状态修正:OpenHub 不属于 Rust control-plane,其 SQLite 会话库迁移不绑定 Rust control-plane 切换;本地 libSQL cutover 已完成,remote / replica / synced 仅作为后续云端同步/备份验收项
已通过验证(截至 2026-07-03):
cargo check --manifest-path rust/Cargo.toml -p mnote-web✅cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder_watcher -- --test-threads=1✅cargo test --manifest-path rust/Cargo.toml -p control-plane✅(28 unit + 3 integration)cargo test --manifest-path rust/Cargo.toml -p control-plane --features turso-unit-tests -- --test-threads=1✅(64 unit + 3 integration)cargo test --manifest-path rust/Cargo.toml -p mnote-web dev_seed -- --test-threads=1✅node --test scripts/desktop-hot.test.js✅node scripts/task-dev-hot-plan-test.js✅node scripts/task-control-plane-admin-libsql-roundtrip-smoke.js✅MNOTE_CONTROL_PLANE_BACKEND=sqlite npm run desktop:hot拒绝启动 ✅
已知失败:
cargo test --manifest-path rust/Cargo.toml -p mnote-web❌ 855 passed, 1 failed(既有 CSS 体积阈值MNOTE_CSS.len() < 190000,与控制面切换无关,不属于本轮范围)
后续云端验证项:
MNOTE_TURSO_DATABASE_URL与MNOTE_TURSO_AUTH_TOKEN未配置 → 暂不测试 Turso remote / local-replica / synced 模式;不影响当前本地功能测试- 真实 Turso dev DB / token 未配置,remote、local-replica、synced、真实 5xx/限流/断网/同步失败作为后续云端验收
desktop:hot默认后端切换到turso-local-replica暂缓;当前本地开发默认已可用libsql-local- OpenHub SQLite 会话库迁移(Phase 7)属于 OpenHub 独立评估,不绑定 Rust control-plane 切换
SQLite 导出备份 / admin-only 环境:
# 本地 libSQL 模式(无远程依赖,推荐开发测试)
MNOTE_CONTROL_PLANE_BACKEND=libsql-local
# Turso remote(需配置 dev DB/token 后启用)
MNOTE_CONTROL_PLANE_BACKEND=turso-remote
MNOTE_TURSO_DATABASE_URL=libsql://...
MNOTE_TURSO_AUTH_TOKEN=...
# Turso local replica(需配置 remote URL/token 后启用)
MNOTE_CONTROL_PLANE_BACKEND=turso-local-replica
MNOTE_TURSO_DATABASE_URL=libsql://...
MNOTE_TURSO_AUTH_TOKEN=...
MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane-replica.db