chore: checkpoint turso and ai runtime work

This commit is contained in:
Agent Board
2026-07-03 23:20:16 +08:00
parent a75b3d11f9
commit 36d027a4a1
67 changed files with 4224 additions and 914 deletions
@@ -22,11 +22,14 @@
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` 继续优先使用 `libsql-local`,覆盖本地 store、migration脚本纪律 SQLite fallback。
- `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。
@@ -56,7 +59,7 @@ Turso 的收益不在于“SQLite 语法换个驱动”本身,而在于把控
- external conversation bindings
- `mnote-web` 对 control-plane 的构造、注入和测试 helper。
- 所有直接 `sqlite3` 写 control-plane 的 smoke / 测试脚本。
- 启动脚本和部署脚本里的 `MNOTE_CONTROL_PLANE_DB_PATH` 默认路径逻辑。
- 启动脚本和部署脚本里的 `MNOTE_CONTROL_PLANE_DB_PATH` 默认路径逻辑必须移除;运行时只读取 libSQL/Turso env
### 2.2 第一阶段不迁移
@@ -88,7 +91,7 @@ Rust mnote-web :3000
v
ControlPlaneStore trait
|
+-- SqliteControlPlaneStore 本地 fallback / 回滚 / 测试
+-- SqliteControlPlaneStore 仅限 admin 迁移 / 导出 / Rust 测试隔离
|
+-- TursoControlPlaneStore 默认目标后端
```
@@ -96,8 +99,8 @@ ControlPlaneStore trait
推荐环境变量:
```text
MNOTE_CONTROL_PLANE_BACKEND=sqlite|turso|turso-local-replica
MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.db
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
@@ -105,10 +108,10 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
默认策略建议:
- 当前已落地:`dev-hot` 默认 `libsql-local``desktop-hot` 仍保守默认 `sqlite`
- 当前已落地:`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。
- 稳定后才评估:普通运行默认 `turso-local-replica`,保留 `sqlite` 作为离线和回滚模式
- 稳定后才评估:普通运行是否从 `libsql-local` 提升为 `turso-local-replica`;不恢复 SQLite runtime fallback
本轮新增云端边界:
@@ -141,7 +144,7 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
- `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。
- 历史状态:`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`
@@ -156,7 +159,7 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
当前至少有两类脚本必须改:
- 直接调用 `sqlite3` CLI 写库。
- 通过临时 `control-plane.sqlite3``MNOTE_CONTROL_PLANE_DB_PATH` 绕过真实 store。
- 通过临时 `control-plane.sqlite3``MNOTE_CONTROL_PLANE_DB_PATH` 绕过真实 storehard cutover 后运行时启动链不得再接受该方式
这些脚本如果不改,即使 runtime 切 Turso,测试和开发链路仍会制造第二套写入口。
@@ -165,14 +168,14 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
## 5. Phase 0:风险登记和依赖确认
- [x] 确认 Turso / libSQL 的当前 Beta 风险:API 稳定性、SLA、区域、限流、连接数、备份能力、最大数据库大小。
- 当前记录为运行手册中的准入风险:真实 remote / replica / synced 作为云端同步/备份/多设备准备层后续启用;默认保留 `sqlite` fallback,不把本地文件真相迁入 Turso。
- 当前记录为运行手册中的准入风险:真实 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] 决定第一阶段是否允许离线写入。当前策略:`sqlite` fallback 允许离线写入`turso-local-replica` 模式下本地 replica 可离线读、上线后同步;纯 `turso` remote 模式禁止离线写入,返回 `ServiceUnavailable`
- [x] 决定第一阶段是否允许离线写入。当前策略:`libsql-local` 允许本地离线写入`turso-local-replica` 模式下本地 replica 可离线读、上线后同步;纯 `turso-remote` 模式依赖网络,失败时返回明确错误
验收:
@@ -208,17 +211,17 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
- [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` 可正常启动
- [x] `MNOTE_CONTROL_PLANE_BACKEND=sqlite npm run desktop:hot` 已改为拒绝启动,防止运行时回落到 SQLite
---
## 7. Phase 2:新增 TursoControlPlaneStore
目标:新增实现,不先删除 SQLite fallback。
目标:新增 Turso/libSQL 实现,并在 mnote-web runtime 中移除 SQLite fallback。
- [x] `rust/crates/control-plane/Cargo.toml`
- [x] 增加 `libsql` 依赖。
- [x] 暂时保留 `rusqlite`,直到 SQLite fallback 和测试策略明确
- [x] 保留 `rusqlite` 仅服务 control-plane-admin 迁移/导出和 Rust 测试隔离
- [x] `rust/crates/control-plane/src/turso.rs`
- [x] 新增 `TursoControlPlaneStore`
- [x] 支持 remote URL + token。
@@ -290,7 +293,7 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
- [x] 支持 `MNOTE_CONTROL_PLANE_BACKEND` 和 Turso env。
- [x] `scripts/desktop-hot.js`
- [x] 明确 dev 默认后端。
- [x] 防止多个 hot 进程同时写同一个 SQLite fallback
- [x] 防止 hot/prod runtime 再次写 SQLite control-plane 文件
- [x] `scripts/dev-hot.js`
- [x] 同步 backend 环境变量策略。
@@ -324,7 +327,7 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
- [ ] 后续云端验证:空 Turso dev DB 可通过初始化命令启动 MNote(缺 `MNOTE_TURSO_DATABASE_URL` / `MNOTE_TURSO_AUTH_TOKEN`)。
- [ ] 初始化后的测试账号 `mnote.e2e@example.com` 可登录。
- 后续云端验证:同 Turso dev DB/token 缺
- [x] 本地 SQLite fallback 可从 libSQL local 导出恢复;真实 Turso remote 导出待 dev DB/token
- [x] libSQL/Turso 可导出为 SQLite 备份;SQLite 备份不再作为 mnote-web runtime fallback
---
@@ -365,13 +368,13 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
## 11. Phase 6:默认切换
> dev-hot.js 已默认使用 `MNOTE_CONTROL_PLANE_BACKEND=libsql-local``dev-hot.js:75`);desktop-hot.js 默认 `sqlite`。remote / local-replica / synced 更接近云端同步、备份和多设备准备层,不影响当前 local-first 本地功能测试;它们作为后续云端验收项,不再阻塞本轮本地 libSQL cutover。
> 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、migrationscript discipline SQLite fallback。
- [x] 不依赖 Turso dev DB/token,覆盖本地 store、migrationscript discipline,不保留 runtime SQLite fallback。
- [x] Turso dev DB/token 已配置为本机私有 env
- [x] `/mnt/Data1T/Mnote_data/control-plane/turso.env`
- [x] 文件权限 `0600`
@@ -395,7 +398,7 @@ MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plan
默认切换验收:
- [x] 本地 cutover`libsql-local` store 行为、migration、admin roundtrip、seed helperSQLite fallback 已验证。
- [x] 本地 cutover`libsql-local` store 行为、migration、admin roundtrip、seed helperSQLite runtime 拒绝路径已验证。
- [ ] 云端 cutover`npm run desktop:hot` 使用 `turso-local-replica` 启动后可登录测试账号。
- [ ] 云端 cutover`/api/auth/session` 返回真实 Turso control-plane session。
- [x] 文件树和文档页仍读取本地工作区文件。
@@ -530,36 +533,36 @@ OpenHub 切换门槛:
## 13. Phase 8:文档和运行手册
- [x] 更新 `ARCHITECTURE.md`
- [x] control-plane 从 `Rust SQLite` 改为 `Turso/libSQL + SQLite fallback`
- [x] control-plane 从 `Rust SQLite` 改为 `Turso/libSQL-only runtime`
- [x] 明确本地 `.md` 文件仍是正文真相。
- [x] 更新 `CURRENT_ARCHITECTURE.md`
- [x] 当前默认后端、fallback、回滚方式。
- [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] 本地 fallback 备份。
- [x] SQLite 导出备份。
- [x] Turso 到 SQLite 回滚。
- [x] 故障排查。
---
## 14. 回滚策略
## 14. 备份与恢复策略
必须保留一键回滚:
hard cutover 后不再保留 mnote-web runtime SQLite 一键回滚;SQLite 只作为导出备份格式和迁移源。
```text
MNOTE_CONTROL_PLANE_BACKEND=sqlite
MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.db
```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 成为默认后需要完整执行):
恢复 checklist(仅在 remote / replica / synced 成为默认后需要完整执行):
- [ ] 切回前从 Turso 导出最新 control-plane dump
- [ ] 备份当前本地 SQLite
- [ ] 导入 Turso 数据到本地 SQLite 或接受 dev 数据重置。
- [ ] 从 Turso/libSQL 导出最新 control-plane 备份
- [ ] 保留当前 libSQL/Turso 目标库快照
- [ ] 将备份重新导入新的 libSQL/Turso 目标库,或接受 dev 数据重置。
- [ ] 重启 `npm run desktop:hot`
- [ ] 验证登录、workspace、授权、sidebar、navigation、AI policy。
- [ ] Turso DB 保留至少 30 天用于审计和补导出。
@@ -580,7 +583,7 @@ MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.d
8. 补并发写和故障注入测试。
9. 再决定是否迁 OpenHub 会话库。
建议一开始就删除 `SqliteControlPlaneStore`。它仍是本地 fallback、回滚、测试隔离和故障排查的必要后门
不删除 `SqliteControlPlaneStore` 实现本身,但它不再是 mnote-web runtime fallback;仅作为迁移源、导出备份格式和 Rust 测试隔离
---
@@ -611,14 +614,14 @@ MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.d
| `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 作为本地 fallbackstore 行为测试覆盖完整 |
| `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`,默认 `sqlite` |
| `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,支持全部后端模式 |
@@ -626,7 +629,7 @@ MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.d
| `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 + SQLite fallback` |
| `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 |
@@ -648,10 +651,10 @@ MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.d
- 真实 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 已通过
**回滚命令(保持 Phase 1 前行为):**
```text
MNOTE_CONTROL_PLANE_BACKEND=sqlite
MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.db
**备份命令(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 不迁移边界(已验证未踏入):**
@@ -684,7 +687,7 @@ MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.d
- `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` 可正常启动 ✅
- `MNOTE_CONTROL_PLANE_BACKEND=sqlite npm run desktop:hot` 拒绝启动 ✅
**已知失败:**
@@ -697,13 +700,9 @@ MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.d
- `desktop:hot` 默认后端切换到 `turso-local-replica` 暂缓;当前本地开发默认已可用 `libsql-local`
- OpenHub SQLite 会话库迁移(Phase 7)属于 OpenHub 独立评估,不绑定 Rust control-plane 切换
**SQLite fallback / rollback 环境:**
**SQLite 导出备份 / admin-only 环境:**
```text
# 回滚到纯本地 SQLite(保持 Phase 1 前行为)
MNOTE_CONTROL_PLANE_BACKEND=sqlite
MNOTE_CONTROL_PLANE_DB_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane.db
# 本地 libSQL 模式(无远程依赖,推荐开发测试)
MNOTE_CONTROL_PLANE_BACKEND=libsql-local