2026-07-03 13:24:20 +08:00
|
|
|
|
# 2-9 Turso / libSQL 控制面切换 checklist v1
|
|
|
|
|
|
|
|
|
|
|
|
> 创建时间:2026-07-02
|
|
|
|
|
|
>
|
|
|
|
|
|
> 当前状态:`process`
|
|
|
|
|
|
>
|
|
|
|
|
|
> 目标:在接受 Turso / libSQL Beta 与云服务风险的前提下,把 MNote 当前 Rust SQLite 控制面切换到 Turso / libSQL 后端,降低多个进程直接写同一个 SQLite 文件带来的损坏风险,并为后续云同步、审计、权限和 AI 控制面维护提供更稳的存储边界。
|
|
|
|
|
|
>
|
|
|
|
|
|
> 上位依据:
|
2026-07-21 05:13:05 +08:00
|
|
|
|
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`(`CURRENT_ARCHITECTURE.md` 为兼容指针)
|
2026-07-03 13:24:20 +08:00
|
|
|
|
> - `/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 23:20:16 +08:00
|
|
|
|
|
|
|
|
|
|
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 13:24:20 +08:00
|
|
|
|
2026-07-03 补充决策:已经获得真实 Turso `mnote` dev DB URL/token,并完成 remote dry-run 连接验证;但分叉讨论与本轮评估结论一致,**Turso Cloud 对当前 MNote 的意义不是“立刻把产品变成云同步应用”**。当前阶段仍以 local-first 文件真相和本机开发稳定性为主,云端能力只作为 control-plane 的受控验证、备份、迁移演练和后续多设备准备层。
|
|
|
|
|
|
|
|
|
|
|
|
因此默认路线调整为:
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- `dev-hot`、`desktop-hot` 和 prod runtime 默认使用 `libsql-local`,覆盖本地 store、migration 和脚本纪律;不再保留 runtime SQLite fallback。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- `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 / 测试脚本。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- 启动脚本和部署脚本里的 `MNOTE_CONTROL_PLANE_DB_PATH` 默认路径逻辑必须移除;运行时只读取 libSQL/Turso env。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
### 2.2 第一阶段不迁移
|
|
|
|
|
|
|
|
|
|
|
|
- 本地 `.md` 页面正文。它仍是 local-first 正文真相。
|
|
|
|
|
|
- 本地附件、图片、mindmap、OnlyOffice 文件。
|
|
|
|
|
|
- `.mnote/index/evidence.sqlite`。该库已从当前主线退役,本轮不迁移。
|
|
|
|
|
|
- LightRAG 自身存储。知识库/RAG provider 存储不在 control-plane 切换范围内。
|
|
|
|
|
|
- OpenHub 自身 SQLite 会话库。它可以作为第二阶段单独迁移,不应和 Rust control-plane 首次切换绑死。
|
2026-07-28 17:04:27 +08:00
|
|
|
|
- RAGFlow。当前不作为主线迁移目标。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- 浏览器直连 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
|
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
+-- SqliteControlPlaneStore 仅限 admin 迁移 / 导出 / Rust 测试隔离
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
+-- TursoControlPlaneStore 默认目标后端
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
推荐环境变量:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
2026-07-03 23:20:16 +08:00
|
|
|
|
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
|
2026-07-03 13:24:20 +08:00
|
|
|
|
MNOTE_TURSO_DATABASE_URL=libsql://...
|
|
|
|
|
|
MNOTE_TURSO_AUTH_TOKEN=...
|
|
|
|
|
|
MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane-replica.db
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
默认策略建议:
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- 当前已落地:`dev-hot`、`desktop-hot` 和 prod runtime 默认 `libsql-local`。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- 已配置真实 Turso dev DB:仅用于 remote dry-run、迁移演练、回滚演练和受保护 smoke,不改变 local-first 主路径。
|
|
|
|
|
|
- Turso store 全测试通过后:CI 先跑 `sqlite` + `libsql-local`;`turso-dev` 作为需要 secret 的受保护 job。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- 稳定后才评估:普通运行是否从 `libsql-local` 提升为 `turso-local-replica`;不恢复 SQLite runtime fallback。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
本轮新增云端边界:
|
|
|
|
|
|
|
|
|
|
|
|
- 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>`。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- 历史状态:`open_control_plane_store()` 曾读取 `MNOTE_CONTROL_PLANE_DB_PATH` 并打开 SQLite;hard cutover 后该路径已删除。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- `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 写库。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- 通过临时 `control-plane.sqlite3` 和 `MNOTE_CONTROL_PLANE_DB_PATH` 绕过真实 store;hard cutover 后运行时启动链不得再接受该方式。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
这些脚本如果不改,即使 runtime 切 Turso,测试和开发链路仍会制造第二套写入口。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. Phase 0:风险登记和依赖确认
|
|
|
|
|
|
|
|
|
|
|
|
- [x] 确认 Turso / libSQL 的当前 Beta 风险:API 稳定性、SLA、区域、限流、连接数、备份能力、最大数据库大小。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- 当前记录为运行手册中的准入风险:真实 remote / replica / synced 作为云端同步/备份/多设备准备层后续启用;不保留 mnote-web runtime SQLite fallback,也不把本地文件真相迁入 Turso。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [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 功能测试。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] 决定第一阶段是否允许离线写入。当前策略:`libsql-local` 允许本地离线写入;`turso-local-replica` 模式下本地 replica 可离线读、上线后同步;纯 `turso-remote` 模式依赖网络,失败时返回明确错误。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
验收:
|
|
|
|
|
|
|
|
|
|
|
|
- [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 无关。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] `MNOTE_CONTROL_PLANE_BACKEND=sqlite npm run desktop:hot` 已改为拒绝启动,防止运行时回落到 SQLite。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7. Phase 2:新增 TursoControlPlaneStore
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
目标:新增 Turso/libSQL 实现,并在 mnote-web runtime 中移除 SQLite fallback。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
- [x] `rust/crates/control-plane/Cargo.toml`
|
|
|
|
|
|
- [x] 增加 `libsql` 依赖。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] 保留 `rusqlite` 仅服务 control-plane-admin 迁移/导出和 Rust 测试隔离。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [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 默认后端。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] 防止 hot/prod runtime 再次写 SQLite control-plane 文件。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [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 缺
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] libSQL/Turso 可导出为 SQLite 备份;SQLite 备份不再作为 mnote-web runtime fallback。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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:默认切换
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
> 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 13:24:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
> 2026-07-03 决策更新:真实 Turso dev DB/token 已可用,但云端价值对当前阶段有限;本轮只把它接入为“可验证但不默认”的控制面 dev target,不把 remote / replica 作为 local-first 功能测试前置条件。
|
|
|
|
|
|
|
|
|
|
|
|
- [x] dev 环境先切到本地 libSQL:
|
|
|
|
|
|
- [x] `MNOTE_CONTROL_PLANE_BACKEND=libsql-local`
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] 不依赖 Turso dev DB/token,覆盖本地 store、migration 和 script discipline,不保留 runtime SQLite fallback。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [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 基线。
|
|
|
|
|
|
|
|
|
|
|
|
默认切换验收:
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] 本地 cutover:`libsql-local` store 行为、migration、admin roundtrip、seed helper 和 SQLite runtime 拒绝路径已验证。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [ ] 云端 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,不进入本轮。 |
|
2026-07-28 17:04:27 +08:00
|
|
|
|
| P2 | Tantivy-powered full-text search | 未采用 | 当前 control-plane 表主要是元数据,全文搜索收益有限;正文和知识库搜索仍属于 OpenHub/LightRAG/local search 方向,不跟 control-plane 绑死。 |
|
2026-07-03 13:24:20 +08:00
|
|
|
|
| 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` 或精确索引足够。
|
2026-07-28 17:04:27 +08:00
|
|
|
|
- 不迁正文:页面正文 `.md` 仍在本地文件系统,知识库问答主线是 OpenHub/LightRAG,不把正文全文塞进 control-plane。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- 可作为 P2/P3:如果以后要做轻量“标题 / 文件路径 / 页面摘要 / AI 会话标题”的本地快速搜索,可以单独设计 Turso FTS 索引。
|
2026-07-28 17:04:27 +08:00
|
|
|
|
- 不替代 LightRAG/OpenHub RAG:Tantivy FTS 是关键词检索,不等于知识库引用、OCR、语义检索和 agent citation 链。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
结论:**对当前 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`:
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] control-plane 从 `Rust SQLite` 改为 `Turso/libSQL-only runtime`。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [x] 明确本地 `.md` 文件仍是正文真相。
|
|
|
|
|
|
- [x] 更新 `CURRENT_ARCHITECTURE.md`:
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] 当前默认后端、SQLite admin-only 边界和备份恢复方式。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [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。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [x] SQLite 导出备份。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [x] Turso 到 SQLite 回滚。
|
|
|
|
|
|
- [x] 故障排查。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
## 14. 备份与恢复策略
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
hard cutover 后不再保留 mnote-web runtime SQLite 一键回滚;SQLite 只作为导出备份格式和迁移源。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
```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
|
2026-07-03 13:24:20 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
恢复 checklist(仅在 remote / replica / synced 成为默认后需要完整执行):
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- [ ] 从 Turso/libSQL 导出最新 control-plane 备份。
|
|
|
|
|
|
- [ ] 保留当前 libSQL/Turso 目标库快照。
|
|
|
|
|
|
- [ ] 将备份重新导入新的 libSQL/Turso 目标库,或接受 dev 数据重置。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- [ ] 重启 `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 会话库。
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
不删除 `SqliteControlPlaneStore` 实现本身,但它不再是 mnote-web runtime fallback;仅作为迁移源、导出备份格式和 Rust 测试隔离。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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` |
|
2026-07-03 23:20:16 +08:00
|
|
|
|
| `rust/crates/control-plane/src/sqlite.rs` | 保持 SqliteControlPlaneStore 作为 admin 迁移/导出与测试隔离实现,store 行为测试覆盖完整 |
|
2026-07-03 13:24:20 +08:00
|
|
|
|
| `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 初始化、迁移、回滚、验证和故障边界运行手册 |
|
2026-07-03 23:20:16 +08:00
|
|
|
|
| `scripts/desktop-hot.js` | 支持 `MNOTE_CONTROL_PLANE_BACKEND`,默认 `libsql-local`,拒绝 `sqlite` |
|
2026-07-03 13:24:20 +08:00
|
|
|
|
| `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` |
|
2026-07-03 23:20:16 +08:00
|
|
|
|
| `ARCHITECTURE.md` | control-plane 从 `Rust SQLite` 改为 `Turso/libSQL-only runtime` |
|
2026-07-03 13:24:20 +08:00
|
|
|
|
| `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 已通过
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
**备份命令(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
|
2026-07-03 13:24:20 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**local-first 不迁移边界(已验证未踏入):**
|
|
|
|
|
|
- 本地 `.md` 文件正文 → 未迁移
|
|
|
|
|
|
- 本地附件、图片、mindmap、OnlyOffice 文件 → 未迁移
|
|
|
|
|
|
- `.mnote/index/evidence.sqlite`(已退役)→ 未迁移
|
|
|
|
|
|
- LightRAG 自身存储 → 未迁移
|
|
|
|
|
|
- OpenHub 自身 SQLite 会话库 → 未迁移(Phase 7 推迟)
|
2026-07-28 17:04:27 +08:00
|
|
|
|
- RAGFlow → 未迁移
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- 浏览器直连 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` ✅
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- `MNOTE_CONTROL_PLANE_BACKEND=sqlite npm run desktop:hot` 拒绝启动 ✅
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
**已知失败:**
|
|
|
|
|
|
|
|
|
|
|
|
- `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 切换
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
**SQLite 导出备份 / admin-only 环境:**
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
```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
|
|
|
|
|
|
```
|