diff --git a/docs/待审查.md b/docs/待审查.md index e590869..6f3df83 100644 --- a/docs/待审查.md +++ b/docs/待审查.md @@ -965,6 +965,61 @@ --- +### CR-260619-10 消息级溯源 P0 地基(ChatMessage.id + ai_messages 拆表 + V21 迁移 + AiMessageRepo + audit message_id 列·commit e981c14) — ✅ 已审(PASS) + +- **结论(2026-06-19·独立 grep/read 核验 commit e981c14 + cargo test 复跑)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 +- **验证**: `cargo test -p df-storage` 45 单测 + 11 集成全 passed(V21 5 测:新库空跑/老库迁移/坏数据/幂等重跑/列补建 + message_repo 5 测全绿,无漂移)。git show e981c14 10 文件 diff 逐文件核验。 + +**5 维度逐项核验**: + +| # | 维度 | 判定 | +|---|------|------| +| 1 | 迁移正确性(migrate_v21 migrations.rs:374) | ✅ 幂等三重防护(COUNT 探测 :400-407 / INSERT OR IGNORE :472/:489/:505 / schema_version PK IGNORE)+ 坏数据 warn+continue :432-435 + 全量逐条 :438-483 + 新库空跑(V9_SQL :727-744 同步建 ai_messages 表 + message_id 列)+ msg_migrated_{conv_id}_{seq} :442 填 ai_messages.id 正确(V21 测 v21_legacy_db_migrates_messages 覆盖老库迁移 + 字段提取 + seq 递增) | +| 2 | id 格式(new_message_id types.rs:134) | ✅ AtomicU64 COUNTER + ts 并发安全 + 时钟回拨单调(计数器兜底)+ serde `#[serde(default)]` 老 JSON → None round-trip + 6 构造器(provider.rs:45 system/48 user/51 assistant/54 assistant_with_tools/57 tool_result/63 user_parts)全覆盖 `id: Some(new_message_id())` | +| 3 | AiMessageRepo(message_repo.rs) | ✅ 5 方法 SQL/参数/返回正确:insert_batch INSERT OR IGNORE 幂等 :80 / list_by_conversation ORDER BY seq :115 / delete_range 左闭右开 + None 末尾 :145-153 / update_status :179 / update_content_by_tool_call_id 定点 :207。baseline_tests(mod.rs:263)断言 ai_messages 白名单 None 合理(专用 Repo 不进通用 query 路径) | +| 4 | P0 边界(未误接 P1/P2) | ✅ AiMessageRepo 仅 df-storage 自身注册 + test,**0 外部调用方**(grep crates/src-tauri 业务侧 0 命中)/ ContextManager 读写路径未改 / save_conversation 未改双写 / audit message_id None 占位仅 finalize.rs:40 单点(P1 接真值) | +| 5 | 迁移耦合点(types + migrations) | ✅ types.rs:75-77 ChatMessage 定义注释「改字段名必须同步更新 migrate_v21」+ migrations.rs:370-373 反向标注「同步标注已在 types.rs 加注释」,双向闭环 | + +**主代核查·审查防污染印证**: +- 迁移数据完整性逻辑独立走查:messages JSON → Vec 逐条 :430,seq `messages.iter().enumerate()` :438(0 起递增),status 归一化 None/空→"active" :458-461(列 NOT NULL DEFAULT 'active' 对齐),created_at timestamp 优先 fallback conv_created_at :466-469。 +- audit 14 处 message_id 说法:范围描述措辞,实际 grep src-tauri 仅 finalize.rs:40 单点构造 AiToolExecutionRecord(AiToolExecutionRecord { 仅 1 处),14 处指未来调用覆盖面。单点 None 占位符合 P0 边界(列已建、值待 P1),非缺陷。 +- workspace 自验 EXIT 0(commit message 自报)+ 本会话 cargo test 45+11 复跑印证无漂移(memory [[review-batching-worktree-transient]] P0 与 MCP 并行警惕快照漂移,以实际磁盘内容为准已落实)。 + +- **⚪ WATCH-1**: V21_SQL(migrations.rs:498)与 V9_SQL(migrations.rs:727)两处 ai_messages 建表 SQL 镜像,改动须两边同步——已在两处加注释 :496-497 / :724-726 标注,后续加列/改约束需双向维护(低风险,有注释护栏) +- **待修项回流 todo**: **无** 🔴/🟡 项 + +### CR-260619-11 MCP Server(df-mcp crate + stdio 协议 + CLI 子命令·commit f217e39) — ✅ 已审(PASS·⚪2) + +- **结论(2026-06-19·独立 grep/read 核验 commit f217e39 + cargo test df-mcp 8 passed 复跑)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 +- **验证**: cargo test -p df-mcp 8 passed 0 failed 复跑确认(initialize/tools_list 高风险过滤/read_only/路由/High 拒/create+list 往返/visible 谓词全过)。HEAD vs f217e39 在 df-mcp 范围零 diff,源码形态即 commit 形态。19 工具逐条核验 vs tool_registry + df-storage Repo 方法全部存在。 + +**6 维度逐一核验**: + +| # | 维度 | 判定 | +|---|------|------| +| 1 | JSON-RPC 协议正确性 | ✅ protocol.rs:109-127 路由 initialize/initialized/tools/list/tools/call/ping 5 method + 错误码常量 -32700/-32600/-32601/-32602/-32603 齐全。server.rs:60-106 stdin 逐行 read_line + write_response 单行 \n framing 正确。Notification(id=None)不回(:92-102)。initialize 返 serverInfo+capabilities.tools(:117-130)。tools/list 返 `{tools:[{name,description,inputSchema}]}` 符合 MCP Tool schema(:136-143)。tools/call 返 `content:[{type:"text"}]` + isError 区分业务错/协议错(protocol.rs:185-201) | +| 2 | **安全降级三层严密性(本 CR 重点)** | ✅ **严密无漏** — 层①tools/list visible 谓词(server.rs:183-189:`risk!=High` 非ro / `risk==Low` ro)过滤 4 个 High(delete_project/delete_task/delete_idea/run_workflow)永不暴露。层②dispatch 兜底(server.rs:163 `if spec.risk==RiskLevel::High` 拒)+ read-only 下 Medium 也拒(:155 `spec.risk!=RiskLevel::Low`)。层③handler 二次拒(tools.rs:305/489/590/651 四个 High handler 体内无条件 return error,连 ctx/args 都不读)。**对抗核验**:purge_project df-mcp 未注册(物理删比软删更危险,数据层 MCP 故意只暴露 delete_project 软删,设计正确非遗漏);方法路由仅 5 个无其他 method 触发工具;文件工具(read/write/patch/list/search/command)0 命中。测试 tools_call_high_risk_is_rejected + tools_call_read_only_rejects_medium + tools_list_excludes_high_risk_by_default 全覆盖 | +| 3 | 不暴露文件系统 | ✅ grep `read_file\|write_file\|patch_file\|list_directory\|search_files\|run_command\|delete_file\|rename_file\|append_file\|http_request` 全 df-mcp/src **0 命中**。19 工具全为数据层(项目/任务/灵感/工作流/回收站),防绕过应用内路径校验 | +| 4 | 工具复用正确性 | ✅ 19 工具逐条核验:list_projects/get_project/create_project/update_project/delete_project/bind_directory/list_tasks/create_task/update_task/advance_task/delete_task/list_ideas/create_idea/update_idea/delete_idea/evaluate_idea/run_workflow/list_trash/restore_project。复用 df-storage Repo(ProjectRepo/TaskRepo/IdeaRepo)零重复 CRUD。advance_task 走 advance_status_atomic CAS(task_repo.rs:151 WHERE id AND status=expected + deleted_at IS NULL 兜底)。bind_directory 走 find_path_conflict(project_repo.rs:190)+ df-mcp 自实现 normalize_path(tools.rs:695 与 df-project scan.rs:24 逐字一致)。evaluate_idea 本地启发式(tools.rs:623 纯函数,不调 LLM) | +| 5 | CLI 子命令 | ✅ main.rs:16 首参 == "mcp-server" 路由 + run_mcp_server(:28)解析 --db/--read-only/--print-config/-h/--help。default_db_path(:112)Windows %APPDATA%/devflow/devflow[-dev].db(cfg!(debug_assertions)→devflow-dev.db,release→devflow.db)与 GUI 同库。--print-config 输出 `{"mcpServers":{"devflow":{"command":"devflow","args":["mcp-server"[,"--read-only"]]}}}` (:66-70)。windows_subsystem="windows" 保留(main.rs:2) | +| 6 | 并发安全 | ✅ db.rs:22 `PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;` 确认启用(run_server server.rs:38 复用 Database::open)。仅 stdio/本地无认证,依赖本地隔离(Tauri 桌面应用非网络服务,门槛可接受,与 tool_registry Path Traversal 同评估口径 CR-260618-11 #8) | + +**⚪ 可选优化 (2)**: + +| # | file:line | 问题 | 建议 | +|---|-----------|------|------| +| 1 | `tools.rs:664-665` | `list_trash` 用 `unwrap_or_default()` 吞 DB 错误返空列表。MCP 对外暴露,DB 故障时客户端收 `{projects:[],tasks:[],...count:0}` 误判「回收站为空」(与 CR-260618-13 SMELL-P0-1 同款降级模式,但 MCP 对外场景误导性更高) | 改 `match { Ok(v)→v, Err(e)→return err_str(e) }` 让客户端感知 DB 故障 | +| 2 | `tools.rs:616` | `evaluate_idea` `let _ = repo.update_full(&rec).await;` 丢弃写回结果。scores 写回失败时客户端仍收旧 idea + 新 scores(响应用写回前的 idea clone :617)轻微数据不一致 | 写回失败时 err_str 返回,或响应标注「scores 未持久化」(只读评估场景影响小,可接受现状) | + +**主代核验印证(防污染)**: +- agent 自报 8 passed,主代独立 cargo test -p df-mcp 复跑 8 passed 0 failed 一致 ✅ +- 登记项维度2 把 purge_project 列为 High 风险工具之一,独立 grep 核验 df-mcp **未注册 purge_project**(仅 delete_project 软删),描述偏差非 df-mcp 遗漏 — df-mcp 设计正确(数据层不暴露物理删,tool_registry.rs 有 purge_project 但 MCP 不暴露) +- 三层安全降级源码佐证齐:visible 谓词 server.rs:183 / dispatch 兜底 server.rs:163 / handler 二次拒 tools.rs:305+489+590+651,四 High 工具三层全覆盖 + +- **待修项回流 todo**: **无** 🔴/🟡 项(⚪2 可选:list_trash 吞 DB 错误返空列表对外误导 / evaluate_idea 丢弃写回结果,非阻塞) + +--- + ## 已审归档 > 已审 CR 段迁独立文件: [待审查归档/2026-06.md](./07-项目管理/待审查归档/2026-06.md)