Files
mnote/design/02-convex-rust-long-term-architecture/process/2-9-turso-control-plane-cutover-v1.md
T
Agent Board b798f628ee chore: land tree view-state, vault, Pi module split, and repo hygiene
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.
2026-07-21 05:13:05 +08:00

44 KiB
Raw Blame History

2-9 Turso / libSQL 控制面切换 checklist v1

创建时间:2026-07-02

当前状态:process

目标:在接受 Turso / libSQL Beta 与云服务风险的前提下,把 MNote 当前 Rust SQLite 控制面切换到 Turso / libSQL 后端,降低多个进程直接写同一个 SQLite 文件带来的损坏风险,并为后续云同步、审计、权限和 AI 控制面维护提供更稳的存储边界。

上位依据:

  • /mnt/Data1T/mnote/ARCHITECTURE.mdCURRENT_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 runtimedev-hotdesktop-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-hotdesktop-hot 和 prod runtime 默认使用 libsql-local,覆盖本地 store、migration 和脚本纪律;不再保留 runtime SQLite fallback。
  • desktop-hot 不立即切到 turso-remoteturso-local-replica
  • Turso remote 只做 dev DB dry-run、迁移演练、回滚演练和后续受保护 smoke,不作为当前本地功能测试的阻塞项。
  • local-first 文件正文、附件、mindmap、OnlyOffice 仍完全留在本地文件系统,不迁入 Turso。

推荐执行顺序:

  1. 先把 ControlPlaneStore 后端抽象和脚本直写清掉。
  2. 再新增 Turso / libSQL store。
  3. 本地默认先切 libsql-local,真实 Turso remote 只做受控验证。
  4. 等密码哈希、remote smoke、回滚导出和 token 管理都稳定后,再评估是否让 turso-local-replica 成为普通默认。
  5. 最后评估 OpenHub 会话库是否单独迁移。

2. 范围

2.1 必迁移

  • Rust control-plane crate 当前的 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-hotdesktop-hot 和 prod runtime 默认 libsql-local
  • 已配置真实 Turso dev DB:仅用于 remote dry-run、迁移演练、回滚演练和受保护 smoke,不改变 local-first 主路径。
  • Turso store 全测试通过后:CI 先跑 sqlite + libsql-localturso-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.rs
    • ControlPlaneStore 是同步 trait,当前是正确抽象边界,应保持接口稳定。
  • rust/crates/control-plane/src/sqlite.rs
    • 唯一实现是 SqliteControlPlaneStore
    • 内部是 Mutex<rusqlite::Connection>
    • 当前配置 journal_mode=WALsynchronous=NORMALforeign_keys=ONbusy_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 并打开 SQLitehard 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 storePhase 1 已适配 trait object,后续可评估复用 AppState 中的 store。
  • rust/crates/mnote-web/src/ssr/pages/admin.rs
    • admin 页面存在 control-plane DB 路径展示逻辑;Turso 默认后端切换时需要改成后端状态展示,不应继续只显示 SQLite 文件路径。

4.3 脚本和 smoke

当前至少有两类脚本必须改:

  • 直接调用 sqlite3 CLI 写库。
  • 通过临时 control-plane.sqlite3MNOTE_CONTROL_PLANE_DB_PATH 绕过真实 storehard cutover 后运行时启动链不得再接受该方式。

这些脚本如果不改,即使 runtime 切 Turso,测试和开发链路仍会制造第二套写入口。


5. Phase 0:风险登记和依赖确认

  • 确认 Turso / libSQL 的当前 Beta 风险:API 稳定性、SLA、区域、限流、连接数、备份能力、最大数据库大小。
    • 当前记录为运行手册中的准入风险:真实 remote / replica / synced 作为云端同步/备份/多设备准备层后续启用;不保留 mnote-web runtime SQLite fallback,也不把本地文件真相迁入 Turso。
  • 固定 Rust libsql crate 版本,不使用浮动主分支依赖。
  • 确认当前 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.iotoken 不写入设计文档。
  • 后续 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
  • 本机无 token 时不会 panic,返回 InvalidInput;真实断网场景作为后续 Turso dev DB 验证。

6. Phase 1:先加固 ControlPlaneStore 边界

目标:在不改变数据库后端的情况下,先把 runtime 从具体 SQLite 类型中解耦。

  • rust/crates/mnote-web/src/app.rs
    • AppState.control_plane: Arc<SqliteControlPlaneStore> 改为 Arc<dyn ControlPlaneStore>
    • open_control_plane_store() 改为返回 trait object。
    • 新增 MNOTE_CONTROL_PLANE_BACKEND factory 入口,已实现 sqlite/libsql-local/turso-remote/turso-local-replica/turso-synced 全部分支。
    • 当前默认 SQLite 行为与现状一致。
  • rust/crates/mnote-web/src/local_folder_watcher_registry.rs
    • new() 参数改为 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 直写;本轮不迁移。
  • rust/crates/control-plane/src/lib.rs
    • 保持 SqliteControlPlaneStore 导出,新增后端前不破坏现有测试。

验收:

  • cargo test --manifest-path rust/Cargo.toml -p control-plane
  • cargo 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 无关。
  • 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。
  • 如果 libsql API 只能异步访问 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-local migration 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.js
  • scripts/task558-page-ai-reasonix-live-session-context-smoke.js
  • scripts/task559-page-ai-terminal-status-reconciliation-smoke.js
  • scripts/task560-page-ai-hermes-load-replay-smoke.js
  • scripts/task561-page-ai-session-dashboard-smoke.js
  • scripts/task562-page-ai-reasonix-approval-plan-smoke.js
  • scripts/task512-chatonly-doubao-sync-smoke.js
  • scripts/task513-chatonly-provider-sync-smoke.js
  • scripts/task527-chatonly-api-provider-smoke.js
  • scripts/task530-knowledge-rag-page-ai-final-answer-smoke.js

8.2 临时 DB path 覆盖用户

以下脚本必须改为使用统一 test control-plane backend,不再自己创建临时 SQLite 文件后覆盖环境变量:

  • scripts/task492-sidebar-starred-shortcuts-smoke.js
  • scripts/task494-filetree-lazy-loading-dedup-smoke.js
  • scripts/task496-editor-open-parallel-runtime-aggregate-smoke.js
  • scripts/task497-local-page-tree-filetree-open-performance-smoke.js
  • scripts/task498-starred-page-tree-scope-and-local-edit-smoke.js
  • scripts/task499-sidebar-tree-view-state-smoke.js
  • scripts/task500-navigation-page-route-guard-smoke.js
  • scripts/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 DBscripts/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:并发、损坏和故障测试

目标:证明切换确实解决当前痛点,而不是只换驱动名。

  • 并发写测试:
    • 多线程同时创建 sessionlibSQL local)。
    • 多线程同时 upsert sidebar shortcutlibSQL local)。
    • 多线程同时写 AI runtime eventslibSQL local)。
    • 多线程同时 append audit/outboxlibSQL local)。
  • 故障注入:
    • Turso token 缺失返回 InvalidInput 且不 panic。
    • Turso URL 不可达。
      • 后续云端验证:缺 Turso dev DBlibSQL local 模式下无远程连接)
    • 网络超时。
      • 后续云端验证:缺 Turso dev DB
    • 远端返回 5xx。
      • 后续云端验证:缺 Turso dev DB
    • 远端限流。
      • 后续云端验证:缺 Turso dev DB
    • local replica 文件不可写。
      • 后续云端验证:缺 Turso dev DB
  • 数据一致性:
    • 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-localdev-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 增加后端矩阵:
    • sqlite
    • libsql-local
    • turso-dev 作为后续受保护云端 job,需要 secret 时才跑;失败不阻塞本地 local-first smoke。
  • 本机正式默认切换到 remote / replica 前(后续云端验证):
    • 备份当前 /mnt/Data1T/Mnote_data/control-plane/control-plane.db
    • 记录当前 schema version。
    • 跑完整 smoke 基线。

默认切换验收:

  • 本地 cutoverlibsql-local store 行为、migration、admin roundtrip、seed helper 和 SQLite runtime 拒绝路径已验证。
  • 云端 cutovernpm 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 内保存真实 tokentmp/turso.mdturso.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.rsremote 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-runmigrate-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 / syncedexplicit initial sync 仍待后续验证。
P0 control-plane health endpoint 未完成 需要新增只读诊断端点,暴露 backend/mode/schema/sync 状态,不暴露 token。
P0 Argon2id password hash migration 未完成 remote 成为长期默认前必须完成,避免把 sha256-v1 占位哈希带入云端。
P1 读写并发性能设计 仅完成安全基线,未实现性能优化 当前 TursoControlPlaneStoreMutex<TursoConnection>,读写全部串行;这能保证简单安全,但不能发挥 remote / replica 的读并发优势。
P2 CDC / change data capture 未采用 当前 control-plane 已有显式 audit_logoutbox_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 的判断:

  • 当前不作为 P0control-plane 已有业务语义明确的 audit_logoutbox_events,比通用 CDC 更适合作为权限、分享、AI policy 的审计与事件源。
  • 可作为 P2 spike:如果后续要把 control-plane 变更桥接到 OpenHub、实时分析、外部消息队列或审计归档,可以评估 CDC 作为补充。
  • 不替代业务审计:CDC 记录的是表级变更,不知道“用户授权目录”“AI policy 变更”“分享撤销”等业务意图,仍需要应用层 audit/outbox。
  • 不进首轮 remote:CDC 仍是早期能力,且可能捕获敏感字段;必须先定义字段脱敏、保留期、权限和导出路径。

结论:P2 / research only,不进入当前 Turso control-plane 默认切换。

Turso 文档显示 FTS 通过 CREATE INDEX ... USING ftsfts_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 RAGTantivy 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-planeauth、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 7OpenHub 会话库单独评估

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 文件仍是正文真相。
  • 更新 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 损坏风险,按这个顺序做:

  1. AppState.control_plane 改成 Arc<dyn ControlPlaneStore>
  2. 增加 control-plane factory 和 backend env。
  3. 改掉 17 个直写/临时 DB 脚本。
  4. 增加 TursoControlPlaneStore,先跑 libSQL local。
  5. 跑 control-plane store 行为测试矩阵。
  6. 接 Turso dev DB。
  7. desktop:hot dev 默认后端。
  8. 补并发写和故障注入测试。
  9. 再决定是否迁 OpenHub 会话库。

不删除 SqliteControlPlaneStore 实现本身,但它不再是 mnote-web runtime fallback;仅作为迁移源、导出备份格式和 Rust 测试隔离。


16. 执行记录

2026-07-02 Phase 1 起步

  • 已将 rust/crates/mnote-web/src/app.rsAppState.control_plane 解耦为 Arc<dyn ControlPlaneStore>
  • 已将 open_control_plane_store() 改为返回 trait object,内部仍使用 SqliteControlPlaneStore,未引入 Turso 实现。
  • 已新增 MNOTE_CONTROL_PLANE_BACKEND factory 入口,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 直写 smokeP1/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 新增 libsqltokio 依赖与 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 导出 TursoControlPlaneConfigTursoControlPlaneModeTursoControlPlaneStore
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/seedbuildControlPlaneTestEnv
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_URLMNOTE_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_URLMNOTE_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