chore: align sqlite control plane architecture

- replace default Convex control-plane wording with Rust SQLite control-plane across architecture, AGENTS, Reasonix, and design docs

- retire root Convex functions source and deploy script into recycle while keeping explicit cloud/compat/sync-replica boundaries

- add control-plane migration guard/docs and keep CodeGraph refreshed after the SQLite control-plane cutover
This commit is contained in:
lix-2026
2026-05-22 17:45:22 +08:00
parent 531e845600
commit 47e224d419
79 changed files with 7634 additions and 2600 deletions
@@ -4,6 +4,8 @@
>
> 当前状态:`DONE`
>
> 2026-05-22 口径补充:本文是页面块编辑运行时 Actor 的历史设计记录,当时仍以 Convex-backed 写入为默认持久化底座。当前默认数据面已切到 local-first `.md` / Page Aggregate,默认控制面已由 Rust SQLite control-plane 承接;Convex 只保留历史迁移源、显式 cloud source / compat / sync replica 边界。下文中的 Convex 持久化描述仅按历史阶段理解。
>
> 本稿目的:在 7-12 已排除第二套 AI runtime 的前提下,补上 Hermes tool execution → Convex 持久化之间缺失的 Rust 编辑运行时中继层,实现「内存态 apply → 编辑器就地 patch → Convex 异步持久化 → 事件增量通知」的四步闭环。
>
> 关联文档:
@@ -25,7 +27,7 @@
- 编辑器只能全量 reload snapshot,不能就地 patch
- tree event stream 收到 `resync_required` 而非增量 delta
**正确方向不是绕开 Convex(禁止项,Convex 保留为自托管存储底座),而是在 Rust mnote-web 进程中新增一个轻量 EditorRuntimeActor,作为写操作的本地缓冲层。**
**历史阶段的正确方向不是绕开当时的 Convex-backed 持久化,而是在 Rust mnote-web 进程中新增一个轻量 EditorRuntimeActor,作为写操作的本地缓冲层。当前默认持久化已经继续演进为 local-first `.md` / SQLite control-planeConvex 不再是默认底座。**
EditorRuntimeActor 不是 agent runtime(遵从 7-12 禁止项),它只负责:
@@ -79,9 +81,9 @@ EditorRuntimeActor 不维护:
它只是 Rust-owned 的命令执行 + diff 分发层。
### 3.2 Convex 仍是唯一的持久化底座
### 3.2 历史阶段:Convex-backed 持久化
EditorRuntimeActor 的内存态允许异步写入 Convex,但不绕过 Convex。进程重启后从 Convex 恢复
EditorRuntimeActor 的内存态在本文历史阶段允许异步写入 Convex,但不绕过当时的 Convex-backed 持久化。当前默认恢复源已改为本地 `.md` / Rust SQLite control-planeConvex 只用于显式 cloud / compat source
### 3.3 编辑器 patch 是增量,非全量
@@ -2,6 +2,8 @@
> 更新时间:2026-05-17
> 参考:`hermes-vscode-main` (ACP client) / `hermes-web-ui-0.5.18` (HTTP API)
>
> 2026-05-22 口径补充:本文是 ACP session runtime 增强的历史执行记录,当时曾选择根 `convex/` 作为 ACP-local runtime store。后续 `2-8` 已把默认 auth / ACP-Hermes runtime session / share / sync / AI policy 控制面替换为 Rust SQLite `control-plane`;根 `convex/` functions 源码已软删除到 `recycle/20260522-convex-runtime-retirement/convex/``scripts/run-convex-deploy.js` 已归档到同批 recycle 目录。下文中的 Convex store / 根 `convex/` 部署描述仅作为历史记录,不再是当前实现方向。
## 1. 当前状态
@@ -34,7 +36,7 @@
- mnote 自己产生的 tool audit、业务写入结果、artifact / page / tree 事实;
- 经单独架构决策确认后的 ACP 会话缓存或索引。
如果要把 mnote 的存储升级为跨 Hermes HTTP / ACP 的长期会话真源,必须先更新 `7-5` / `7-8` 的边界,而不能作为本计划的隐含前提。并且 mnote 是多用户系统,AI session 必须按用户、workspace、document 进行隔离;需要数据库时使用当前 Convex 底座,不新增 SQLite。
如果要把 mnote 的存储升级为跨 Hermes HTTP / ACP 的长期会话真源,必须先更新 `7-5` / `7-8` 的边界,而不能作为本计划的隐含前提。并且 mnote 是多用户系统,AI session 必须按用户、workspace、document 进行隔离;当前默认数据库边界已改为 Rust SQLite control-plane,不新增 Convex functions 作为默认 runtime store。
## 2. 参考实现分析
@@ -83,7 +85,7 @@
1. **会话持久化存储**
- 默认先建设 ACP-local runtime store / TTL cache,不改 Hermes HTTP 会话真相归属
- 持久化如需数据库,必须落到 Convex;不在 mnote-web 内新增 SQLite
- 持久化如需数据库,默认落到 Rust SQLite control-plane;不新增根 Convex functions 作为默认 runtime store
- 表结构先服务 `ACP_RUN_PAYLOADS` / `ACP_ACTIVE_RUNS` 的可靠生命周期,并且所有记录必须带 `userId` / `workspaceId` / `documentId` 作用域
- `sessions/messages` 如要保存完整聊天历史,必须先完成架构决策并标明只覆盖 ACP 路径还是统一覆盖 Hermes HTTP + ACP
@@ -157,20 +159,20 @@
## 4. 技术方案
### 4.0 当前 Convex 源目录口径
### 4.0 历史 Convex 源目录口径
当前仓库状态下:
- `infra/convex/` 只承载自托管 Convex backend/dashboard 的 Docker 与说明,不是 functions/schema 源目录;
- `wolai-frontend/convex/` 已不在当前工作区可读路径中,不能作为新的实现落点;
- `recycle/wolai-frontend/convex/` 只作为历史参考;
- 本轮执行在仓库根 `convex/` 建立 ACP-local runtime store 的最小 functions/schema,并让 `scripts/run-convex-deploy.js` 优先以仓库根作为 Convex CLI cwd。
- 本轮历史执行在仓库根 `convex/` 建立 ACP-local runtime store 的最小 functions/schema,并让 `scripts/run-convex-deploy.js` 优先以仓库根作为 Convex CLI cwd;当前这些源码和脚本已退役到 `recycle/20260522-convex-runtime-retirement/`
后续若要恢复完整业务 Convex functions,需要把历史 `recycle/wolai-frontend/convex/` 中仍需要的 documents/workspaces/media 等 functions 正式迁移到`convex/`,不能继续依赖已删除的 `wolai-frontend/` 路径
后续若要恢复 Convex cloud / compat source,必须先明确新的 cloud/compat 部署边界,再从 recycle 恢复必要 functions;不能把`convex/` 重新作为默认 active deploy source
### 4.1 存储方案
注意:以下结构是 Convex-backed ACP-local store 候选形状,不默认推翻 Hermes 持有聊天真相的既有边界。mnote-web 不新增 SQLite;多用户隔离字段是硬要求。
注意:以下结构是历史 Convex-backed ACP-local store 候选形状,不默认推翻 Hermes 持有聊天真相的既有边界。当前默认 store 已转向 Rust SQLite control-plane;多用户隔离字段是硬要求。
```rust
// sessions 记录
@@ -208,11 +210,11 @@ struct MessageRow {
### 4.2 现有代码改动范围
- **新增/修改** `convex/` 相关 schema / functions ACP-local session/runtime 记录、用户隔离查询、TTL 清理;`infra/convex/` 只负责自托管服务部署
- **历史项**:曾新增/修改根 `convex/` 相关 schema / functions,承接 ACP-local session/runtime 记录、用户隔离查询、TTL 清理;当前默认应改 Rust SQLite control-plane schema/API`infra/convex/` 只负责显式 cloud / compat 服务部署
- **修改** `acp_session_manager.rs` — 按架构决策写入 ACP-local 消息或只写 runtime/event 索引
- **修改** `hermes_client.rs` — 新增 session 管理 route handlers
- **修改** `routes/mod.rs` — 注册新 route
- **修改** Convex bridge / transport 调用点 — mnote-web 通过已有 Convex 底座读写 session/runtime 索引
- **修改** control-plane / compat transport 调用点 — mnote-web 默认通过 SQLite control-plane 读写 session/runtime 索引,Convex 只作为显式 legacy compat
### 4.3 与现有 bug 的关系
@@ -269,11 +271,11 @@ Phase CP2 用户体验)
### 6.2 先做后端持久化骨架
- [x] 为 ACP-local session/runtime store 设计 Convex schema,不新增 SQLite。
- [x] 在 Convex 中建 `sessions``messages` 或更小的 `runtimeRuns` / `runtimeEvents` 记录,补齐索引和权限过滤。
- 当前实现为根 `convex/schema.ts``acp_runtime_runs` / `acp_runtime_events`,以及 `convex/aiSessions.ts``upsertRuntimeRun/getRuntimeRun/listRuntimeRuns`
- [x] 历史阶段曾为 ACP-local session/runtime store 设计 Convex schema;当前默认已改为 SQLite control-plane。
- [x] 历史阶段曾在 Convex 中建 `sessions``messages` 或更小的 `runtimeRuns` / `runtimeEvents` 记录,补齐索引和权限过滤。
- 历史实现为根 `convex/schema.ts``acp_runtime_runs` / `acp_runtime_events`,以及 `convex/aiSessions.ts``upsertRuntimeRun/getRuntimeRun/listRuntimeRuns`;当前根 `convex/` 已退役
- [x]`ACP_RUN_PAYLOADS` / `ACP_ACTIVE_RUNS` 中必须保留的数据拆到持久化层或短 TTL 层。
- 当前 `create_run` 会把 ACP payload/runtime 写入 Convex内存 map 仍作为热路径保留,`stream_events` 不再消费删除 payload
- 历史阶段 `create_run` 会把 ACP payload/runtime 写入 Convex当前默认写入 SQLite control-plane,旧 Convex store 仅在显式 legacy compat 下使用
- [x]`create_run` 产出的 `sessionId``profile``traceId``runtime` 写入 `sessions` 记录。
- 当前写入目标为更小的 `acp_runtime_runs`,而不是完整聊天 `sessions` 真相表。
- [x]`stream_events` 的 ACP 路径里,按架构决策写入 `messages` 或 runtime/event 索引,不能把 Hermes HTTP 聊天真相复制进 mnote。
@@ -334,18 +336,18 @@ Phase CP2 用户体验)
### 2026-05-18
- 新增根 `convex/schema.ts``acp_runtime_runs` / `acp_runtime_events` 表和索引。
- 新增根 `convex/aiSessions.ts``upsertRuntimeRun``getRuntimeRun``listRuntimeRuns`,按 `userId` 校验 identity 作用域。
- 修改 `scripts/run-convex-deploy.js`:优先以仓库根 `convex/` 作为 Convex deploy cwd,避免继续指向已不存在的 `wolai-frontend/`
- 后续修正:根 `convex/`只包含 ACP session runtime store,不能直接覆盖完整本地 Convex 部署;`scripts/run-convex-deploy.js` 改为只有根 `convex/` 具备完整 schema 时才用根目录,否则继续使用现有完整 Convex 源。为保持本机 `3210` 部署完整,已将 `aiSessions.ts``acp_runtime_*` schema 同步到现有完整 Convex 源并重新部署
- 新增根 `convex/schema.ts``acp_runtime_runs` / `acp_runtime_events` 表和索引。(历史记录;当前已退役到 recycle)
- 新增根 `convex/aiSessions.ts``upsertRuntimeRun``getRuntimeRun``listRuntimeRuns`,按 `userId` 校验 identity 作用域。(历史记录;当前已退役到 recycle)
- 修改 `scripts/run-convex-deploy.js`:优先以仓库根 `convex/` 作为 Convex deploy cwd,避免继续指向已不存在的 `wolai-frontend/`(历史记录;当前脚本已退役到 recycle,`desktop:hot` / `dev:hot` 不部署 Convex
- 后续历史修正:根 `convex/`只包含 ACP session runtime store,不能直接覆盖完整本地 Convex 部署;`scripts/run-convex-deploy.js` 改为只有根 `convex/` 具备完整 schema 时才用根目录,否则继续使用现有完整 Convex 源。当前根 `convex/` 和该 deploy 脚本均已退役到 recycle
- 修改 `rust/crates/mnote-web/src/routes/hermes_client.rs`
- ACP `create_run` 写入 `aiSessions:upsertRuntimeRun`
- ACP `GET /client/sessions` 读取 `aiSessions:listRuntimeRuns`
- ACP `create_run` 写入 `aiSessions:upsertRuntimeRun`(历史 Convex store;当前默认 SQLite
- ACP `GET /client/sessions` 读取 `aiSessions:listRuntimeRuns`(历史 Convex store;当前默认 SQLite
- ACP `GET /client/sessions/{session_id}` 读取 runs/events
- ACP `POST /client/sessions/{session_id}/resume` 返回 Convex runtime history 恢复来源;
- ACP `DELETE /client/sessions/{session_id}``rename``auto-title` 写入 Convex store
- ACP `GET /client/sessions/search` 返回 Convex store 搜索摘要和 snippet
- ACP `stream_events` 将 SSE event 写入 `aiSessions:appendRuntimeEvent`
- ACP `POST /client/sessions/{session_id}/resume` 返回 runtime history 恢复来源;
- ACP `DELETE /client/sessions/{session_id}``rename``auto-title` 写入 runtime store
- ACP `GET /client/sessions/search` 返回 runtime store 搜索摘要和 snippet
- ACP `stream_events` 将 SSE event 写入 runtime event store
- `stream_events` 使用 clone lookup 保留 ACP payload,不再 `.remove(run_id)`
- 验证:
- `cd rust && cargo test -p mnote-web hermes_client_acp -- --nocapture`10 passed。
@@ -353,8 +355,8 @@ Phase CP2 用户体验)
- `cd rust && cargo test -p mnote-web acp_permission -- --nocapture`1 passed。
- `cd rust && cargo test -p mnote-web page_ai_uses_backend_acp_session_runtime_store -- --nocapture`1 passed。
- `cd rust && cargo test -p mnote-web acp_thought_delta_does_not_emit_message_delta -- --nocapture`1 passed。
- `node -c scripts/run-convex-deploy.js`通过
- `node scripts/run-convex-deploy.js`:部署入口已避免用根最小 `convex/` 覆盖完整本地 Convex schema`aiSessions.ts` 同步补到完整 Convex 源并部署到 `http://127.0.0.1:3210`,最终脚本验证为 `No indexes are deleted by this push`
- `node -c scripts/run-convex-deploy.js`历史验证通过;当前脚本已退役到 recycle
- `node scripts/run-convex-deploy.js`历史验证通过;当时部署入口已避免用根最小 `convex/` 覆盖完整本地 Convex schema。当前默认启动链路不再部署 Convex
- `node` 提取并 `new Function(SIDEBAR_TREE_JS)`:通过。
- `npx convex --version`1.39.1。
- `CONVEX_TMPDIR=/mnt/Data1T/mnote/.convex-tmp npx convex codegen --dry-run --typecheck try`:通过。
@@ -198,7 +198,7 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-tool-sm
执行步骤:
- [x] 启动当前前后端,确保测试入口可访问。
- [x] 用真实 Convex Auth 测试账号登录。
- [x] 用真实测试账号登录。历史执行时使用旧 Auth 后端;当前默认应使用 SQLite control-plane Auth / session
- [x] 打开任意测试页面,点击右下角页面 AI。
- [x] 发送一条只读问题,例如“概括当前页面标题和第一段”。
- [x] 记录当前请求是否仍命中旧 `/api/ai-agent/run`
@@ -763,7 +763,7 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-tool-sm
总体验收标准:
- [x] 所有 smoke 在真实 `http://127.0.0.1:3000` 上通过。
- [x] 使用默认 Convex Auth 测试账号,不使用 `MNOTE_DEV_AUTH=1` 伪登录作为正式验收。
- [x] 使用默认真实测试账号,不使用 `MNOTE_DEV_AUTH=1` 伪登录作为正式验收。历史执行时使用旧 Auth 后端;当前默认应使用 SQLite control-plane Auth / session。
- [x] smoke 只操作本轮新建测试数据,测试数据前缀统一为 `TEST-HERMES-AI-<timestamp>`
- [x] 测试结束后可定位创建的页面、artifact、edge、audit。
- [x] 任一 smoke 失败时,必须记录到 `bugs/` 对应分类,而不是在 checklist 中直接标 GREEN。
@@ -1054,7 +1054,7 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-tool-sm
### M1:把正式 `3000` 的 E2E smoke 跑成最终验收矩阵
**目标:** 临时 `3019` 证据不能作为最终完成;必须在正式 `3000` + 真实 Convex Auth 上复跑全矩阵
**目标:** 临时 `3019` 证据不能作为最终完成;必须在正式 `3000` + 真实 SQLite control-plane Auth / session 上复跑全矩阵。历史记录中的 Convex Auth 只作为当时验收背景
**必须参考 Hermes Web UI 的位置:**
@@ -1068,7 +1068,7 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-tool-sm
**顺序执行 checklist**
- [x] M1.1 确认主入口 `http://127.0.0.1:3000` 运行的是包含本轮改动的 `mnote-web`,不是旧进程。
- [x] M1.2 使用默认 Convex Auth 测试账号 `mnote.e2e@example.com` 登录,不使用 `MNOTE_DEV_AUTH=1` 伪登录作为正式验收。
- [x] M1.2 使用默认 SQLite control-plane 测试账号 `mnote.e2e@example.com` 登录,不使用 `MNOTE_DEV_AUTH=1` 伪登录作为正式验收;旧 Convex Auth 仅作 compat 回归
- [x] M1.3 运行 `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-smoke.js`
- [x] M1.4 运行 `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-tool-smoke.js`
- [x] M1.5 运行 `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-writeback-smoke.js`
@@ -6,6 +6,8 @@
>
> 归档说明(2026-05-21):本文冻结 ACP session 与控制面分享/账号作用域远期合同;当前只作为 future reference,不作为 MVP 后阶段 active implementation checklist。
>
> 2026-05-22 口径补充:本文标题中的 Convex 是历史控制面语境。当前默认控制面已由 Rust SQLite `control-plane` 承接;ACP/Hermes runtime session、auth、share grants、AI policy 默认不再依赖 Convex。下文涉及 Convex store 的内容只作为历史 / compat / sync replica 对照。
>
> 本稿目的:
> 1. 明确 ACP session 必须绑定当前账号权限与控制面作用域,不能以内存态绕过权限。
> 2. 冻结后续“复制会话 / 只读分享 / 多人共同会话”的产品语义,避免当前实现误扩。
@@ -26,7 +28,7 @@ ACP 默认主链已经替代旧 Hermes HTTP 主链,但这不意味着 ACP runt
当前必须成立的规则:
- **控制面持有产品层 AI session 的账号作用域真相;在当前实现里它可以暂时落在 Convex,但长期不应把“Convex”写死成唯一真相。**
- **Rust SQLite control-plane 持有产品层 AI session 的账号作用域真相;Convex 只作为历史 / compat / sync replica 对照,不应被写死成唯一真相。**
- **ACP runtime session 只是执行层会话**,可以被控制面 session 绑定或索引,但不能直接作为跨用户共享对象。
- **ACP session 记录必须带 `userId / workspaceId / documentId / sessionId / runId / actorId / acpRuntime / profile`**。
- **写入控制面 session store 失败时,不允许静默降级为可继续写入的内存 session**。这会破坏账号隔离、审计和后续分享语义。
@@ -38,7 +40,7 @@ ACP 默认主链已经替代旧 Hermes HTTP 主链,但这不意味着 ACP runt
### 1.1 MNote AI Session
MNote AI Session 是产品层会话对象,长期应该由控制面持有;当前实现可以暂存在 Convex,但目标不是把消息全文和权限真相永久绑死在 Convex。
MNote AI Session 是产品层会话对象,长期应该由控制面持有;当前默认控制面是 Rust SQLite control-plane目标不是把消息全文和权限真相永久绑死在 Convex。
它回答:
@@ -77,8 +79,8 @@ Tool Audit 是 mnote 侧工具执行与写入结果的审计记录,必须以
当前代码可接受的最小边界:
- `POST /api/hermes/client/sessions` 在 ACP 默认路径下创建 Convex 侧 runtime session 索引
- `POST /api/hermes/client/runs` 创建 run 并持久化到 Convex runtime store。
- `POST /api/hermes/client/sessions` 在 ACP 默认路径下创建 SQLite control-plane runtime session 索引;Convex 仅保留显式 legacy compat
- `POST /api/hermes/client/runs` 创建 run 并默认持久化到 SQLite control-plane runtime store。
- `GET /api/hermes/client/events/{runId}` 将 ACP event 转成页面 SSE,并追加 runtime event。
- `GET /api/hermes/client/gateway/health` 在 ACP 默认路径下返回 `transport=acp`,不再探测旧 Hermes HTTP gateway。
@@ -209,7 +211,7 @@ Runtime event 和 tool event 应保留原始结构,但查询时必须做权限
规则:
- 创建 session 时使用当前 Convex Auth 解析出的真实 user id。
- 创建 session 时使用当前 SQLite control-plane auth/session 解析出的真实 user / actor id。
- `userId` 只可作为服务端派生字段,不可由浏览器任意指定。
- run / event / tool audit 都必须与同一 actor 绑定。
- 当前请求无法解析用户时,应返回 `401` 或稳定错误,而不是创建匿名共享 session。
@@ -410,7 +412,7 @@ DELETE /api/hermes/client/sessions/{sessionId}/members/{userId}
状态:当前主线。
- ACP 默认主链可用。
- session/run/event 必须账号作用域写入 Convex
- session/run/event 必须账号作用域写入 Rust SQLite control-planeConvex 只作为显式 compat / sync replica
- 旧 Hermes HTTP 主链进入 `recycle`
- 不实现复制、分享、多人会话。
- 文档与测试明确禁止内存降级绕过权限。
@@ -421,7 +423,7 @@ DELETE /api/hermes/client/sessions/{sessionId}/members/{userId}
- 增加 `ai_sessions` / `ai_session_members` / `ai_runtime_bindings`
- 将现有 `acp_runtime_runs` 从“运行态索引”升级为 session 下的 run 记录。
- UI 从 localStorage 历史逐步迁到 Convex session 列表。
- UI 从 localStorage 历史逐步迁到 Rust control-plane session 列表。
### Phase C:复制与只读分享