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.
719 lines
44 KiB
Markdown
719 lines
44 KiB
Markdown
# 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。
|
||
|
||
推荐执行顺序:
|
||
|
||
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. 目标架构
|
||
|
||
```text
|
||
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 默认目标后端
|
||
```
|
||
|
||
推荐环境变量:
|
||
|
||
```text
|
||
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.rs`
|
||
- `ControlPlaneStore` 是同步 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。
|
||
- `rust/crates/mnote-web/src/ssr/pages/admin.rs`
|
||
- admin 页面存在 control-plane DB 路径展示逻辑;Turso 默认后端切换时需要改成后端状态展示,不应继续只显示 SQLite 文件路径。
|
||
|
||
### 4.3 脚本和 smoke
|
||
|
||
当前至少有两类脚本必须改:
|
||
|
||
- 直接调用 `sqlite3` CLI 写库。
|
||
- 通过临时 `control-plane.sqlite3` 和 `MNOTE_CONTROL_PLANE_DB_PATH` 绕过真实 store;hard cutover 后运行时启动链不得再接受该方式。
|
||
|
||
这些脚本如果不改,即使 runtime 切 Turso,测试和开发链路仍会制造第二套写入口。
|
||
|
||
---
|
||
|
||
## 5. Phase 0:风险登记和依赖确认
|
||
|
||
- [x] 确认 Turso / libSQL 的当前 Beta 风险:API 稳定性、SLA、区域、限流、连接数、备份能力、最大数据库大小。
|
||
- 当前记录为运行手册中的准入风险:真实 remote / replica / synced 作为云端同步/备份/多设备准备层后续启用;不保留 mnote-web runtime SQLite fallback,也不把本地文件真相迁入 Turso。
|
||
- [x] 固定 Rust `libsql` crate 版本,不使用浮动主分支依赖。
|
||
- [x] 确认当前 8 个 migration SQL 在 libSQL 上可执行。
|
||
- [x] 明确 remote-only、local replica、pure local 三种模式的行为差异。
|
||
- [x] 明确 token 管理规则:只走环境变量或本机 secret 文件,不写入仓库。
|
||
- [x] 记录故障边界:认证失败、网络超时、Turso 5xx、限流、schema migration 失败、local replica 同步失败。
|
||
- 代码已覆盖 missing/empty URL/token、timeout/rate-limit error mapping、invalid local path;真实 5xx/限流/同步失败属于后续云端验证,不阻塞本地 control-plane 功能测试。
|
||
- [x] 决定第一阶段是否允许离线写入。当前策略:`libsql-local` 允许本地离线写入;`turso-local-replica` 模式下本地 replica 可离线读、上线后同步;纯 `turso-remote` 模式依赖网络,失败时返回明确错误。
|
||
|
||
验收:
|
||
|
||
- [x] 已形成 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 不写入设计文档。
|
||
- [x] 后续 `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`。
|
||
- [x] 本机无 token 时不会 panic,返回 `InvalidInput`;真实断网场景作为后续 Turso dev DB 验证。
|
||
|
||
---
|
||
|
||
## 6. Phase 1:先加固 ControlPlaneStore 边界
|
||
|
||
目标:在不改变数据库后端的情况下,先把 runtime 从具体 SQLite 类型中解耦。
|
||
|
||
- [x] `rust/crates/mnote-web/src/app.rs`
|
||
- [x] `AppState.control_plane: Arc<SqliteControlPlaneStore>` 改为 `Arc<dyn ControlPlaneStore>`。
|
||
- [x] `open_control_plane_store()` 改为返回 trait object。
|
||
- [x] 新增 `MNOTE_CONTROL_PLANE_BACKEND` factory 入口,已实现 sqlite/libsql-local/turso-remote/turso-local-replica/turso-synced 全部分支。
|
||
- [x] 当前默认 SQLite 行为与现状一致。
|
||
- [x] `rust/crates/mnote-web/src/local_folder_watcher_registry.rs`
|
||
- [x] `new()` 参数改为 `Arc<dyn ControlPlaneStore>`。
|
||
- [x] watcher 测试通过 trait object 构造器编译与运行。
|
||
- [x] `rust/crates/mnote-web/src/routes/local_search_index.rs`
|
||
- [x] 已复核:该文件中的 SQLite 写入仅限 `#[cfg(test)]` 的 `evidence.sqlite` / local search legacy 测试,不是 control-plane DB 直写;本轮不迁移。
|
||
- [x] `rust/crates/control-plane/src/lib.rs`
|
||
- [x] 保持 `SqliteControlPlaneStore` 导出,新增后端前不破坏现有测试。
|
||
|
||
验收:
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p control-plane`
|
||
- [x] `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 无关。
|
||
- [x] `MNOTE_CONTROL_PLANE_BACKEND=sqlite npm run desktop:hot` 已改为拒绝启动,防止运行时回落到 SQLite。
|
||
|
||
---
|
||
|
||
## 7. Phase 2:新增 TursoControlPlaneStore
|
||
|
||
目标:新增 Turso/libSQL 实现,并在 mnote-web runtime 中移除 SQLite fallback。
|
||
|
||
- [x] `rust/crates/control-plane/Cargo.toml`
|
||
- [x] 增加 `libsql` 依赖。
|
||
- [x] 保留 `rusqlite` 仅服务 control-plane-admin 迁移/导出和 Rust 测试隔离。
|
||
- [x] `rust/crates/control-plane/src/turso.rs`
|
||
- [x] 新增 `TursoControlPlaneStore`。
|
||
- [x] 支持 remote URL + token。
|
||
- [x] 支持 local replica path + remote sync URL。
|
||
- [x] 支持 pure local libSQL 模式,用于无云测试。
|
||
- [x] 实现 `ControlPlaneStore` 全部方法。
|
||
- [x] `rust/crates/control-plane/src/migrations.rs`
|
||
- [x] 从 `rusqlite::Connection` 绑定改为后端无关迁移入口,或新增 libSQL 迁移入口。
|
||
- [x] PRAGMA 只在本地 SQLite / local replica 下执行;remote Turso 跳过 WAL 类配置。
|
||
- [x] `rust/crates/control-plane/src/error.rs`
|
||
- [x] 增加 `From<libsql::Error>`。
|
||
- [x] 区分 constraint、not found、auth、network、timeout、rate limit。
|
||
- [x] `rust/crates/control-plane/src/lib.rs`
|
||
- [x] 导出 `TursoControlPlaneStore`。
|
||
|
||
设计注意:
|
||
|
||
- 当前 trait 是同步接口。第一阶段建议保持同步边界,避免把所有 route 改成异步 store trait。
|
||
- 如果 `libsql` API 只能异步访问 remote,应在 store 内部集中处理 async runtime 包装,不要让每个 route 自己 block。
|
||
- 多语句写入必须明确事务边界;不能把原来 SQLite 单连接串行行为误拆成多个独立远程请求。
|
||
|
||
验收:
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p control-plane --features turso-unit-tests -- --test-threads=1`
|
||
- [x] 同一套 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:
|
||
|
||
- [x] `scripts/task557-page-ai-run-resume-smoke.js`
|
||
- [x] `scripts/task558-page-ai-reasonix-live-session-context-smoke.js`
|
||
- [x] `scripts/task559-page-ai-terminal-status-reconciliation-smoke.js`
|
||
- [x] `scripts/task560-page-ai-hermes-load-replay-smoke.js`
|
||
- [x] `scripts/task561-page-ai-session-dashboard-smoke.js`
|
||
- [x] `scripts/task562-page-ai-reasonix-approval-plan-smoke.js`
|
||
- [x] `scripts/task512-chatonly-doubao-sync-smoke.js`
|
||
- [x] `scripts/task513-chatonly-provider-sync-smoke.js`
|
||
- [x] `scripts/task527-chatonly-api-provider-smoke.js`
|
||
- [x] `scripts/task530-knowledge-rag-page-ai-final-answer-smoke.js`
|
||
|
||
### 8.2 临时 DB path 覆盖用户
|
||
|
||
以下脚本必须改为使用统一 test control-plane backend,不再自己创建临时 SQLite 文件后覆盖环境变量:
|
||
|
||
- [x] `scripts/task492-sidebar-starred-shortcuts-smoke.js`
|
||
- [x] `scripts/task494-filetree-lazy-loading-dedup-smoke.js`
|
||
- [x] `scripts/task496-editor-open-parallel-runtime-aggregate-smoke.js`
|
||
- [x] `scripts/task497-local-page-tree-filetree-open-performance-smoke.js`
|
||
- [x] `scripts/task498-starred-page-tree-scope-and-local-edit-smoke.js`
|
||
- [x] `scripts/task499-sidebar-tree-view-state-smoke.js`
|
||
- [x] `scripts/task500-navigation-page-route-guard-smoke.js`
|
||
- [x] `scripts/task167-local-markdown-title-body-options-no-convex-smoke.js`
|
||
|
||
### 8.3 启动和部署脚本
|
||
|
||
- [x] `scripts/prod-build-start.js`
|
||
- [x] 不再只设置 `MNOTE_CONTROL_PLANE_DB_PATH`。
|
||
- [x] 支持 `MNOTE_CONTROL_PLANE_BACKEND` 和 Turso env。
|
||
- [x] `scripts/desktop-hot.js`
|
||
- [x] 明确 dev 默认后端。
|
||
- [x] 防止 hot/prod runtime 再次写 SQLite control-plane 文件。
|
||
- [x] `scripts/dev-hot.js`
|
||
- [x] 同步 backend 环境变量策略。
|
||
|
||
验收:
|
||
|
||
- [x] `rg -n "sqlite3|MNOTE_CONTROL_PLANE_DB_PATH|control-plane\\.sqlite3|control-plane\\.db" scripts rust/crates --glob '!target/**' --glob '!recycle/**'` 中不再出现未解释的直写入口。
|
||
- [x] 需要 seed 的脚本统一走 `scripts/lib/control-plane-dev-seed.js` / `scripts/lib/control-plane-test-env.js` 或测试 API。
|
||
- [x] smoke 失败时不再留下孤立临时 control-plane DB(`scripts/task-control-plane-admin-libsql-roundtrip-smoke.js` 使用 `/tmp` 前缀并在 finally 清理)。
|
||
|
||
---
|
||
|
||
## 9. Phase 4:数据初始化和迁移
|
||
|
||
当前仍是测试期,不需要保留正式业务数据,但仍需要迁移工具来保证以后可运维。
|
||
|
||
- [x] 新增 control-plane 初始化命令:
|
||
- [x] 创建 schema。
|
||
- [x] 创建默认 admin / e2e 用户。
|
||
- [x] 创建默认 workspace / membership。
|
||
- [x] 写入必要 AI policy / UI preference 默认值。
|
||
- [x] 新增本地 SQLite 到 Turso 的一次性迁移工具:
|
||
- [x] 支持 `--dry-run`。
|
||
- [x] 输出源表行数、目标表行数、跳过项、失败项。
|
||
- [x] 支持目标清空后重建 dev DB。
|
||
- [x] 新增 Turso 到 SQLite 的回滚导出工具:
|
||
- [x] 回滚前自动备份本地 DB。
|
||
- [x] 支持只导出 control-plane schema。
|
||
|
||
验收:
|
||
|
||
- [ ] 后续云端验证:空 Turso dev DB 可通过初始化命令启动 MNote(缺 `MNOTE_TURSO_DATABASE_URL` / `MNOTE_TURSO_AUTH_TOKEN`)。
|
||
- [ ] 初始化后的测试账号 `mnote.e2e@example.com` 可登录。
|
||
- 后续云端验证:同 Turso dev DB/token 缺
|
||
- [x] libSQL/Turso 可导出为 SQLite 备份;SQLite 备份不再作为 mnote-web runtime fallback。
|
||
|
||
---
|
||
|
||
## 10. Phase 5:并发、损坏和故障测试
|
||
|
||
目标:证明切换确实解决当前痛点,而不是只换驱动名。
|
||
|
||
- [x] 并发写测试:
|
||
- [x] 多线程同时创建 session(libSQL local)。
|
||
- [x] 多线程同时 upsert sidebar shortcut(libSQL local)。
|
||
- [x] 多线程同时写 AI runtime events(libSQL local)。
|
||
- [x] 多线程同时 append audit/outbox(libSQL local)。
|
||
- [ ] 故障注入:
|
||
- [x] 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
|
||
- [x] 数据一致性:
|
||
- [x] unique constraint 冲突仍返回可处理错误。
|
||
- [x] session 创建和查询一致。
|
||
- [x] outbox 不重复投递。
|
||
- [x] AI runtime event 顺序可恢复。
|
||
|
||
验收:
|
||
|
||
- [x] 新增 `cargo test -p control-plane` 并发测试。
|
||
- [x] 新增 Node smoke 覆盖 admin init / SQLite→libSQL local / libSQL local→SQLite roundtrip,且失败时清理临时库。
|
||
- [x] 已覆盖缺 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 功能测试前置条件。
|
||
|
||
- [x] dev 环境先切到本地 libSQL:
|
||
- [x] `MNOTE_CONTROL_PLANE_BACKEND=libsql-local`
|
||
- [x] 不依赖 Turso dev DB/token,覆盖本地 store、migration 和 script discipline,不保留 runtime SQLite fallback。
|
||
- [x] Turso dev DB/token 已配置为本机私有 env:
|
||
- [x] `/mnt/Data1T/Mnote_data/control-plane/turso.env`
|
||
- [x] 文件权限 `0600`
|
||
- [x] repo 内未出现 JWT/token 明文命中。
|
||
- [x] Turso remote 最小验证:
|
||
- [x] `control-plane-admin init --backend turso-remote --dry-run` 返回 `ok=true`。
|
||
- [x] `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 基线。
|
||
|
||
默认切换验收:
|
||
|
||
- [x] 本地 cutover:`libsql-local` store 行为、migration、admin roundtrip、seed helper 和 SQLite runtime 拒绝路径已验证。
|
||
- [ ] 云端 cutover:`npm run desktop:hot` 使用 `turso-local-replica` 启动后可登录测试账号。
|
||
- [ ] 云端 cutover:`/api/auth/session` 返回真实 Turso control-plane session。
|
||
- [x] 文件树和文档页仍读取本地工作区文件。
|
||
- [x] 新建/重命名/打开页面不依赖 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
|
||
|
||
- [x] `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:文档和运行手册
|
||
|
||
- [x] 更新 `ARCHITECTURE.md`:
|
||
- [x] control-plane 从 `Rust SQLite` 改为 `Turso/libSQL-only runtime`。
|
||
- [x] 明确本地 `.md` 文件仍是正文真相。
|
||
- [x] 更新 `CURRENT_ARCHITECTURE.md`:
|
||
- [x] 当前默认后端、SQLite admin-only 边界和备份恢复方式。
|
||
- [x] 更新 `AGENTS.md`:
|
||
- [x] 常用命令增加 Turso backend env。
|
||
- [x] smoke 基线说明不得直写 control-plane DB。
|
||
- [x] 新增运行手册:`docs/operations/control-plane-turso.md`
|
||
- [x] dev token 配置。
|
||
- [x] dev DB reset / init。
|
||
- [x] SQLite 导出备份。
|
||
- [x] Turso 到 SQLite 回滚。
|
||
- [x] 故障排查。
|
||
|
||
---
|
||
|
||
## 14. 备份与恢复策略
|
||
|
||
hard cutover 后不再保留 mnote-web runtime SQLite 一键回滚;SQLite 只作为导出备份格式和迁移源。
|
||
|
||
```bash
|
||
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 起步
|
||
|
||
- [x] 已将 `rust/crates/mnote-web/src/app.rs` 的 `AppState.control_plane` 解耦为 `Arc<dyn ControlPlaneStore>`。
|
||
- [x] 已将 `open_control_plane_store()` 改为返回 trait object,内部仍使用 `SqliteControlPlaneStore`,未引入 Turso 实现。
|
||
- [x] 已新增 `MNOTE_CONTROL_PLANE_BACKEND` factory 入口,Phase 2 已完成 sqlite/libsql-local/turso-remote/turso-local-replica/turso-synced 全部分支(详见下文全 Phase 同步记录)。
|
||
- [x] 已将 `rust/crates/mnote-web/src/local_folder_watcher_registry.rs` 构造器参数改为 `Arc<dyn ControlPlaneStore>`。
|
||
- [x] 已适配 `rust/crates/mnote-web/src/routes/local_folder_source.rs` 中 best-effort 索引刷新调用。
|
||
- [x] 已复核脚本直写入口:P0 为 10 个生产 control-plane `sqlite3` 直写 smoke;P1/P2 为 Rust 初始化路径和 8 个临时 DB smoke。
|
||
- [x] 验证通过:`cargo check --manifest-path rust/Cargo.toml -p mnote-web`。
|
||
- [x] 验证通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder_watcher -- --test-threads=1`。
|
||
- [x] 验证通过:`cargo test --manifest-path rust/Cargo.toml -p control-plane`。
|
||
- [x] 已验证(已知失败):`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):**
|
||
```bash
|
||
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 状态:**
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p control-plane` ✅(28 unit + 3 integration)
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p control-plane --features turso-unit-tests -- --test-threads=1` ✅(64 unit + 3 integration)
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web` 全量 855 passed, 1 failed(既有 CSS 体积阈值 `MNOTE_CSS.len() < 190000`,与控制面切换无关)
|
||
- [x] 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 环境:**
|
||
|
||
```text
|
||
# 本地 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
|
||
```
|