dev-manager-tauri/docs/tasks/2026-10-03-p8-onboarding-oneclick-design.md
lanrtop f8dc95c6ee
All checks were successful
Push & PR Check / check (push) Successful in 1m2s
docs(blueprint/agent-client-parity): P8 一键接入设计定稿,拆为 P8-0、P8a–P8g 与 P4-0、P4a–P4e,A1 改写为三段(P8-0)
三角度方案(最小编排 / 收敛流水线 / 本体收口)并行设计 + 综合评审,设计文档入库
docs/tasks/2026-10-03-p8-onboarding-oneclick-design.md(16 条已核实代码事实、风险
R1–R18、决策 Q1–Q11)。用户以 /goal 授权按推荐方向代为推进,11 项全部采纳推荐项。

- 主方案:同步、零模型的最小编排本体 + 可选异步探针;嫁接 plan/digest 写前确认、
  经真实 shim 的认证链重放、「已接入」单一判定、客户端适配器注册表
- A1 不前置:P8/P4 载体都在炼境进程内,离线写入会绕过进程内锁且有 MSIX 重定向
  风险,真正耦合是全局 pool() 而非 crate 边界;A1 改为 A1a(随 P8b 的适应度测试)、
  A1b(按需 CLI 薄客户端)、A1c(触发式拆 crate,四个触发条件)
- P4 分 L0 静态 / L1 零模型 / L3 无头会话;P8 只依赖 L1(P4a)
- 验收口径变更(Q1,报备):P8 由「三端无头 A–F 矩阵」改为「L1 零模型矩阵 + 可选
  L3 结果及是否过期」,理由写在 P8 卡
- P6 依赖 P8c(Q3);P4b 排在 A4 之后(Q8);manifest 清除「P4/P8 依赖 A1」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:35:53 +09:00

443 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# P8 一键接入 与 P4 三端探针:设计决策(2026-10-03)
> **决策状态**:2026-10-03 由三角度方案(最小编排 / 收敛流水线 / 本体收口)并行设计加综合评审产出。用户以 /goal 授权「按推荐方向代为推进」,§9 的 11 个问题**全部采纳推荐项**(已勾选),用户可随时推翻。其中 Q1 属于修改验收口径,理由见 §9 Q1 与 agent-client-parity P8 卡。
> 蓝图落点:agent-client-parity(P8-0、P8a–P8g、P4-0、P4a–P4e)、architecture-evolution(A1a/A1b/A1c)。下文「本会话」指设计评审会话(只读调研,未运行客户端)。
> 这次是只读调研,没有改任何文件,也没有提交。本会话 lian-jing MCP 连接失败(ECONNREFUSED),所以没调 get_diagnostics。下文结论都来自读代码、读蓝图和读本机探针素材,凡是没实测过的地方都标了「待实测」。
## 0. 评审结论
- **主方案选方案一**(最小编排本体:先做同步、零模型的接入,探针做成可选的异步任务,A1 不前置)。它离现有代码最近,能最快交付,而且不改接入包模板,所以不会触发全部子项目重新分发。
- **嫁接方案二**:写入前的 plan + digest 确认;用真实稳定入口重放认证链,做不调模型的检查;判定以客观证据为准,对失败做归因;把 bypassPermissions 默认值和 Codex 信任单列为人确认点;先做 spike 再写代码;断言探针无副作用。
- **嫁接方案三**:统一「已接入」的判定;客户端适配器注册表;架构适应度测试(让 A1-ready 由测试校验);矩阵每格标证据等级;L3 只在 canary 项目、指纹变化时跑,并按指纹缓存;A1 拆成三段。
- **不采纳**:
- 方案二的 onboarding_runs 表和心跳锁。收敛式设计不需要运行状态,v1 用 runtime_logs 就够。
- 方案二「首次接入默认跑 L3」。
- 方案三「每天对全部项目跑 L1」,因为每个项目都要起一次 claude CLI,噪声大。
- 方案三「Rust console CLI + skill 降级 + 接入包升版」,范围膨胀,要求全部子项目重新分发。
- 方案一的「只靠 --allowedTools 收窄 Claude」,因为项目 settings 默认是 bypassPermissions,见风险 R8。
## 1. 已核实的代码事实(三份方案的关键论断逐条核过,另有补充)
| # | 事实 | 位置 |
|---|---|---|
| F1 | `inject_project_mcp` 返回 `Result<(),String>`。`sync_claude_local` 返回 `()`,`refresh_dsh_patch` 失败只写日志。Claude 和 DSH 两端的成败没有结构化结果 | mcp_inject.rs:591, 1053, 1254 |
| F2 | `ensure_standalone_group` 只在项目还不在任何组里时才注入。已在组内的项目重跑接入包,不会修复本机注册 | groups.rs:147-198 |
| F3 | 「已接入」有三套判定:`reinject_all_mcp` 按组成员(`project_group_map`),DSH 白名单和根目录迁移按 token,`list_all_projects` 按 `.claude/liangjing.json` 是否存在 | mcp_inject.rs:1417, 806, 1476;mcp_tools.rs:2058 |
| F4 | `apply_onboarding_pack` 是同步的 `#[tauri::command]`,内部直接调用 buff,再到建组、注入,可能起 claude CLI(单次超时 20 秒),会卡界面 | onboarding.rs:471 |
| F5 | `create_project`、`batch_create_projects`、`create_project_from_template` 只写 DB,不会触发接入。MCP 里也没有登记项目的工具,所以 agent 复刻不了「新建 → 接入」这条人的操作路径 | projects.rs:182 |
| F6 | MCP 路径下 `verify_onboarding` 的运行态(`server_observed_runtime_status`)把 reachable/handshake 写死为 Ok,工具名取的是全局清单。UI 路径的 `collect_runtime_evidence` 会真做一次 loopback 握手,但不带认证、也不经过 shim,覆盖不到 F 项 | settings.rs:561-584, 469-558 |
| F7 | `verify_onboarding` 包含「origin 指向 Gitea」「CI workflow 存在」「飞轮 commit 数据流入」三项,新项目必然不通过。所以一键接入的「完成」不能定义为 verify 全过 | onboarding.rs:1093-1151 |
| F8 | 注入用 `or_insert` 往项目 `.claude/settings.json`(受跟踪文件)写 `permissions.defaultMode=bypassPermissions`。一键接入会让这个默认值扩散到每个新项目 | mcp_inject.rs:1281 |
| F9 | `setup_gitea_repo` 的 `private` 默认是 false,即创建公开仓库 | mcp_tools.rs:388 |
| F10 | `mcp_server.rs` 已有 `INITIALIZED_PROJECT_CLIENTS`,记录项目路由的 initialize,并排除 `lian-jing-readiness`。这是服务端调用轨迹的雏形 | mcp_server.rs:36-66 |
| F11 | `run_claude_cli` 超时只杀直接子进程,不杀进程树 | mcp_inject.rs:505 |
| F12 | release 主 exe 是 `windows_subsystem="windows"`,`--lianjing-mcp-header` 只能经管道使用 | main.rs:2 |
| F13 | 内部用到 `pool()` 的地方约 293 处,分布在 43 个文件。真正的耦合在全局连接池和进程内静态锁(`MCP_OP_LOCK`、`DSH_PATCH_LOCK`),拆 crate 解决不了 | grep 统计 |
| F14 | P1 中 Codex 用的是 `workspace-write + approval never`。P7 中 `-s read-only` 下的 MCP 调用被 approval 拦截。DSH 经 `app.asar` 内的 `cli.js` 加 `ELECTRON_RUN_AS_NODE` 启动,Codex 在带哈希的 bin 目录里 | %TEMP%\probe2、lj-p7-probe |
| F15 | %TEMP% 下已有约 20 个手工探针目录(probe、probe2、probe3、lj-p7-probe、lj-p9-* 等),说明「每次重写探针」这种重复摩擦确实存在 | 本机 |
| F16 | `inject_mcp` 和 `apply_onboarding_pack` 的 schema 里有 project_id 参数,因此在项目路由上可见(子项目 agent 能对本项目调用) | mcp_tools.rs:445 |
## 2. 三方案比较
| 维度 | 方案一 最小编排 | 方案二 收敛流水线 + 验证金字塔 | 方案三 本体收口 + A1 三段 |
|---|---|---|---|
| 管家准入 | 好:只组合现有本体,不改模板 | 好,但 L3 首次默认跑,额度消耗偏多 | 中:Rust CLI 加 skill 降级要升接入包版本,全部子项目重新分发 |
| 先本体后投影 | 好:onboard_project 本体,加 Tauri 和 MCP 两个投影 | 好:commands/onboard.rs 本体 | 最好:onboard/ 模块加适应度测试,由机器校验 |
| 写前确认 | 只有混合文件闸门 | plan、diff、digest | plan、diff、digest |
| P8 的验收证据 | 静态检查加运行态,但 MCP 路径的运行态是写死的(F6),偏弱 | L1 自握手经过真实 shim,强 | L1 配置回放加协议握手,强 |
| 「已接入」不一致 | 只列为风险 | 只列为风险 | 立卡修复 |
| 成本 | 最低,6 张核心卡 | 最高,10 张卡加运行记录表 | 中高,12 张卡加 console 二进制 |
| 与在途工作冲突 | 低 | P8b 与 P6 冲突 | P8-b1 与 P6 冲突,A1b 牵涉打包 |
| 明显缺陷 | --allowedTools 收窄不成立 | 运行记录、心跳过度设计 | 每天全量 L1 有噪声;CLI 范围膨胀 |
## 3. 目标与非目标
**目标**
1. 新建或添加项目后,**一次确认**就完成以下各步,并用**零模型的 L1 矩阵**验收:canonical context,接入包(规则、skill 双投影、Codex 托管块、蓝图规则),本机三端注册(token、shim、Claude local、DSH 白名单和会话插件)。
2. 收敛式幂等:重跑等于修复;稳态下零写入、文件 mtime 不变。
3. 每一步都返回结构化状态;`unavailable` 和 `unknown` 不当作成功报出。
4. 项目文件改动只报告、不提交;写入前可以预览(见 Q2)。
5. P4:三端兼容性表可以自动重跑。L3 按需触发、结果可比对、带指纹。
6. 一个本体、两个投影(Tauri async command 和 MCP),同源同边界。
**非目标**
- 不自动 commit 或 push。不自动建 Gitea 仓库、webhook、CI,不自动装工具链,这些只列进 next_actions。
- 不修改客户端的全局安全设置(Codex 信任表、Claude 全局 permissions),不覆盖手写的同名 MCP 配置。
- L3 不作为接入成功的门槛,也不作为 CI 闸门。不替 agent 判定完成,不替 agent 选模型。
- 不做离线 CLI 写入,不拆 core crate。
- P8 不改接入包模板。
- GUI 形态(DSH 桌面、Codex Desktop、Claude 桌面)不在自动验证范围,归 human_verify。
- 批量登记不自动接入。
## 4. 一键接入流程
入口分三种:
- UI 新建或模板创建项目成功后,弹出「接入预览」。`create_project` 本身保持没有副作用。
- 已有项目点「一键接入」。
- agent 在用户确认目标项目后,调用 `plan_onboarding(project_id)`。
**0【自动·只读,约 1 秒】预检。** 任一项不过就 `failed`,零写入。
- 项目已登记;根目录是存在的绝对路径;记录是否为 git 仓库以及 HEAD。
- `.lian-jing/context.json` 已有不同的 project_id 时,标 `blocked(identity_conflict)`。
- 控制面项目(`mcp_scope=global`):Claude local 跳过,DSH 写停止哨兵。
- WSL 项目:Claude local 和 DSH 显式标 `unsupported`,不静默跳过。
- 探测客户端:claude 加 git-bash、codex(ClientLocator,支持设置里覆盖路径)、`~/.dsh`;记录各端版本。
- 检查 MCP `/health` 可达。
- 检测炼境是否运行在 MSIX 包上下文中。
**1【自动·只读,约 3 秒】生成计划**(Q2 选 A 时)。
- 项目文件逐项给出动作(create/update/append/noop/skip)、有上限的 diff,以及混合文件预警(P10 检测前移到写入之前)。
- 本机变更清单:token、shim、Claude local(只读快路径判定,计划阶段只允许 `claude mcp get`)、DSH 托管块和插件、产品组和 buff、`.git/hooks`。
- **单列**两项:
- bypassPermissions 默认值(F8);
- Codex 是否已信任该目录(取决于 P4-0 的 H-P8-1)。
- 生成 `digest`,覆盖项目文件新内容的哈希和本机动作清单。
**【人确认 C1】应用前确认。**
- UI:点「确认接入」。agent:必须先把计划给用户看,得到明确同意后,再带 `expected_digest` 加 `confirmed=true` 调用。
- 有 `blocked` 项(混合文件、身份冲突、手写同名条目、共享 Claude 项目键)时,确认按钮禁用,需要人逐项选「跳过 / 中止」。
- 混合文件只能「先在子项目提交或丢弃块外改动,再重跑」,禁止字符串手术。
**2【自动·写】应用。**
- 先重算计划;digest 不符就 `refused`,零写入。
- 按依赖图执行,每步状态为 `converged / applied / skipped / not_applicable / blocked(dep=…) / failed / needs_human`:
1. 身份:context.json 和 liangjing.json 兼容投影。
2. 项目文件:模板、蓝图规则、Codex 托管块、settings.json 兼容条目,都经 WriteSink 写入,内容不变不写。
3. `lefthook install`;缺可执行文件时记 `needs_human`。
4. 产品组和 buff,作为兼容步骤保留。
5. token,然后 shim,然后各客户端适配器:Claude local(经 claude CLI)、DSH 托管块和会话插件。手写同名配置记 `manual_kept`,不覆盖。
- 某步失败时,只把依赖它的步骤标 blocked,其他照常执行,整体记 `partial`。不做自动回滚:项目文件靠 git 撤销,本机配置由下次收敛修复,整体撤销走现有的 `remove_project_mcp`。
**3【自动·只读,约 5 到 10 秒】验收。**
- L0:`verify_onboarding` 静态检查,加上新的 `clients` 字段。
- L1:零模型三端矩阵,见 §6。
- 「完成(done)」只按**三端可用子集**判定:身份、托管块版本、两份 skill、Codex 块、Claude local、DSH 白名单、lefthook、L1 适用格全部 ready。origin、CI、飞轮三项进入 next_actions,不算失败(F7)。
**4【自动·只读】报告。**
- 内容:分步状态,L1 矩阵(带证据等级),`git status --porcelain` 和 `diff --stat` 给出的待提交文件,断言 HEAD 没变,附一条建议的 commit message。
- 提示哪些已打开的会话需要重开。
- 给出最近一次 L3 结果,以及它是否已过期。
**【人确认 C2,可选】L3 无头探针。** 会消耗三端模型额度,约 2 到 3 分钟,默认不勾选。确认后以异步任务运行。
**【人确认 C3】提交。** 由人,或者经用户同意的子项目 agent,按子项目的 git 工作流提交。炼境不 add、不 commit。
**【人确认 C4】next_actions。** 机器只列出,不执行:
- Gitea 建仓:要提醒「工具默认建公开仓库」,再配 webhook。
- CI:`plan_cicd_workflow` 后由人确认,再 `apply_cicd_workflow`。
- 工具链:由人确认后调用 `prepare_toolchain(confirmed=true)`。
- lefthook 可执行文件。
- 在 Codex 里信任该目录(如果 H-P8-1 成立)。
- 托管块变化后重启 DSH 桌面 GUI。
- Claude 交互模式首次打开目录时的 trust 确认。
**【human_verify】** 在 Claude Code、Codex、DSH 桌面 GUI 各新开一个会话,调用 `read_project_test_account(name=LJ_PROBE_NONEXISTENT)`,返回「开发需求未登记」即为通过。
**目标耗时(估算,未实测)**:plan 不超过 3 秒;首次 apply 不超过 30 秒(包含 claude CLI 写入);收敛后重跑不超过 10 秒(L1 里包含 `claude mcp get` 和 `codex mcp get`)。
## 5. A1 是否前置:结论是不前置,按触发条件后置
**理由**
1. P8 和 P4 的验收载体是 Tauri command 和 MCP,本来就跑在炼境进程里。探针测的是「客户端连上运行中的炼境 MCP」,炼境离线时没有东西可测。所以「界面不运行也能用的 CLI」对它们没有用例。
2. 离线写入有实际危害:
- `MCP_OP_LOCK`、`DSH_PATCH_LOCK` 和 credential store 的锁只在进程内有效,`~/.claude.json`(经 claude CLI)、`~/.dsh/cordis.patch.yml`、Credential Manager、SQLite 会被两个进程竞争写;
- agent 从 MSIX 客户端里调用时,AppData 写入可能被重定向(P3 已实证)。
3. 真正的耦合是全局 `pool()`(F13)和进程内锁,拆 crate 解决不了。A1 是 L 级,会和进行中的 A4、P6 大面积冲突,把 P8 推迟数周。
4. 复用主 exe 做 CLI 还要处理控制台挂接和 single-instance(F12),那是 A1 自己要解决的设计问题。
**P8 需要的是 A1 的原则,不是 crate 拆分。** 建议把 A1 改写为三段:
- **A1a(随 P8 落地)**:新代码统一放进 `src-tauri/src/onboard/`。约束如下:
- 不 import `tauri::`,不调用 `db::pool(`,不写 `#[tauri::command]`;
- 连接、CredentialStore、ProcessRunner、ClientLocator 都由调用方注入;
- 结果一律是 serde 结构体。
这些约束由 cargo 架构适应度测试 `onboard_module_is_projection_free` 机器校验。存量代码不扩散旧写法,旧入口按绞杀者模式逐步改成薄包装。
- **A1b(CLI 薄客户端,按需)**:P4e 只做 `scripts/probe-clients.mjs`,作为全局 `/mcp` 的 MCP 客户端,不新增协议面。Rust console 二进制 `lianjing.exe` 和 skill 降级等真实需求出现再做,比如「会话启动时 MCP 没连上,需要第二通道」被证明是常态。
- **A1c(core crate 拆分,触发式,L 级,届时先走 /architect)**:满足以下任一条就启动:
1. 需要在没有 Tauri/WebView 的环境(例如 Linux CI)里跑本体;
2. 确实需要离线写入,并且跨进程锁和包上下文检测已经具备;
3. cargo test 的编译时延成为主要的反馈延迟;
4. 出现第三个独立的消费者。
需要同步修正的蓝图位置:manifest 第 516 行「P4 依赖 A1」、第 525 行「P4/P8 依赖」、第 866 行注释,以及 A1「为什么」段里「P4/P8 可直接做成命令」的表述。这些改动归卡 P8-0。
## 6. P4 探针:定位与成本控制
**定位。** P4 是三端兼容性表(Can I Use)的自动重跑器,也是 P8 的验收判官。分三层,越便宜的越先跑,前一层失败就不跑后一层:
| 层 | 内容 | 成本 | 何时运行 |
|---|---|---|---|
| L0 静态 | `verify_onboarding`,加上 clients 静态状态:读 `~/.claude.json` 快路径、解析 DSH 块 | 毫秒级 | 每次 verify |
| L1 零模型 | ① 客户端自省:Claude 在项目目录执行 `claude mcp get lian-jing`(Local 条目、本项目地址、Connected 状态);Codex 执行 `codex mcp get lian-jing --json`(待 H-P8-1 确认,不可用时退回解析配置);DSH 解析白名单并校验插件哈希。② 认证链重放:按各客户端的方式执行稳定 shim(Claude/Codex 经 `cmd /C`,DSH 直接执行),拿到认证头后在项目路由上依次调用 initialize、tools/list(与 `tools_list_result_for_project` 自省值比对,不写死数字)、get_diagnostics(D)、list_project_dev_variables(E)、`read_project_test_account(LJ_PROBE_<nonce>)`(F)。A 和 B 用静态证据 | 约 5–10 秒,零额度 | 每次接入,以及显式调用 |
| L3 无头会话 | 三端同一只读任务、同一断言,判定以客户端事件流和服务端轨迹为准 | 2–3 分钟,消耗额度 | 只在显式触发时运行;建议在 canary(enterprise-system)项目上、指纹变化时跑 |
**矩阵。** A–F 乘三端,另加全局项 G:无凭据泄露,且探针前后 `git status` 一致、没有非白名单的写调用。
**证据等级。** 每格标注证据等级,从弱到强为 `static < client_introspection < protocol < session_event/server_trace`;`self_report` 只作辅证,避免把 L1 的绿当成真实会话的绿。
**格子状态。** 取值为 `pass / fail / unknown / not_installed / not_applicable / blocked / timeout`。
**按项目画像给期望值。**
- 控制面项目:E、F 为 not_applicable,C 期望全局工具集。
- WSL 项目:DSH 为 unsupported。
**L3 判定规则**(来自方案二)。
- C–F 只认客观信号:
- Claude:init 事件里的 tools 和 skills;
- Codex:`--json` 输出中的 mcp_tool_call 事件;
- DSH:`--json` 事件;
- 服务端:轨迹中的 clientInfo、认证结果,以及 LJ_PROBE nonce。
- A:模型在不调用工具的前提下逐字摘录一句,摘录必须是规则文件磁盘文本的子串。
- 模型自报的工具数和条目数只记作 model_fidelity,不影响格子结论。
**失败归因**按三分定位,从环境到投影到本体:
- 环境:`client_not_installed`、`client_auth_or_quota`(记 unknown,不记 fail)、`timeout`、`env_package_context`。
- 投影:`route_global`、`not_connected`、`codex_project_untrusted`、`rules_not_loaded`、`skill_not_visible`、`client_policy_blocked`。
- 适配器:`adapter_drift`,即客户端输出格式变了,需要升级炼境适配器。
- 模型:`model_noncompliant`。
**成本控制**
1. 从不自动运行。MCP 调用必须带 `confirmed=true`(与 prepare_toolchain 同一模式),描述里写明会消耗额度、需要 2–3 分钟。只在全局路由开放;项目路由只提供只读的 `get_client_parity`。
2. `clients` 参数可以只选部分端(例如只跑 DSH,约 15 秒)。L1 已判定未注册的一端直接跳过 L3。
3. 串行执行,顺序 DSH → Claude → Codex,快的先跑,便于早失败,也便于按时间窗归属。每端用独立的 nonce。
4. 超时(P4-0 实测后校准):DSH 60 秒、Claude 210 秒(基线出现过 138 秒)、Codex 120 秒。超时后用 **Windows Job Object** 杀整个进程树(F11 证明只杀子进程不够)。
5. 全机单飞锁:重复调用 start 返回同一个 run_id,这也防止探针会话里的 agent 递归调用。
6. 缓存:缓存键由客户端版本、接入包版本、项目工具目录 digest、注入配置哈希、路由类型、probe_spec_version 组成。7 天内命中直接复用,`force=true` 时重跑。
7. 不自动重试。
8. 模型:默认用各客户端自己的默认模型;用户可以在设置里为探针单独指定更便宜的模型(这是用户的决定,不是炼境选模型)。
9. 子进程卫生:
- 清掉 `CLAUDECODE` 和 `CLAUDE_CODE_*` 环境变量,stdin 置空,设 `MCP_CONNECTION_NONBLOCKING=false` 和 `CLAUDE_CODE_GIT_BASH_PATH`;
- Claude 的收窄参数组合和 Codex 的沙箱/审批组合,都由 P4-0 实测后写死;
- 炼境运行在 MSIX 包上下文中时,报告标注「结果可能不代表真实客户端环境」。
10. 产物:脱敏后存到 `%USERPROFILE%\.lian-jing\probe-runs\<run_id>\`,每个项目保留最近 5 次,不放 AppData。泄露扫描在炼境进程内用真实 token 比对,token 不经过 CLI 或 agent。
11. 已知开销:Codex 单次约 3.06 万 token(P1 实测)。三端合计估计 4–8 万 token(**估算,未实测**)。
**准入与退出。** 五条准入都满足;F15 的约 20 个临时目录就是「消除重复」的证据。退出条件:三端都原生支持标准、且连续多个版本矩阵不变时,降低 L3 频率或撤销。
## 7. 任务卡清单(全部为 S/M)
### 依赖顺序
```
P8-0 ──> P8a ──> P8b ──> P8c ──┐
└────> P4-0 ─────────> P4a ──┴─> P8d ──> P8e ──> P8g
(P4a 也依赖 P8b) ^
P8f(可选,独立;P8e 端到端验收前完成)─────────┘
A4 合并 ──> P4b ─┐
P4-0 ──> P4c ────┼─> P4d ──> P4e(含 UI 探针按钮)
P4a ─────────────┘
P6 与 P8c 的先后见 Q3
```
P8 的关键路径不依赖 L3:P8-0 → P8a → P8b → P8c → P4a → P8d → P8e → P8g。
### 卡片
**P8-0 蓝图与依赖修正**(S)
- files:.blueprint/modules/agent-client-parity.md,.blueprint/modules/architecture-evolution.md,.blueprint/manifest.yaml
- acceptance:
- agent_verify:[YAML 解析] manifest.yaml 可解析;第 516、525 行的 note 和第 866 行注释不再写「P4/P8 依赖 A1」。[读文件] P8 的 depends 改为 P4a;acceptance 按 Q1 改写,并记录改口径的理由;P4 拆为 P4-0、P4a–P4e;A1 拆为 A1a/A1b/A1c,A1c 写明四个触发条件。
- human_verify:用户确认 Q1、Q2、Q10。
- 注:工作区里这两个模块文件、notes-archive、usage.json 有未提交改动(P6/P8/A4 的状态改为 in_progress),动手前先确认归属。
**P4-0 探针前置实测**(S,spike,零凭据,只能在主会话或后台任务里执行,不交给 Workflow 子 agent)
- files:.blueprint/modules/agent-client-parity.md(假设实测结果节),src-tauri/tests/fixtures/client_probe/(脱敏夹具)
- acceptance:agent_verify:[主会话 bash] 逐项给出结论并附原始日志路径。
- H-P8-1:在新建的未信任目录里,Codex 是否加载项目 `.codex/config.toml`;`codex mcp get lian-jing --json` 能否反映项目配置和信任状态。
- H-P8-2:项目 settings 为 bypassPermissions 时,哪组参数(`--permission-mode` / `--tools` / `--allowedTools` / `--disallowedTools`)能把可调用工具收窄到 lian-jing 只读三件加 Skill,同时 MCP 仍可调用。
- H-P8-3:Codex 在 `-s read-only` 下 MCP 调用是否被拦截;如果被拦截,记录可用的最窄参数组合。
- H-P8-4:`claude -p` 读到 system/init 后立即终止,是否零模型调用。可行的话,Claude 的 B、C 两项并入 L1。
- H-P8-5:Codex MCP 工具调用的默认超时。
- H-P8-6:DSH headless 入口的稳定定位方法,以及 `--json` 事件字段。
- 每端一份夹具(事件流、stderr、版本、参数);grep 夹具不含 `Bearer ` 和项目 token。任一假设不成立就回到设计,不硬编码绕过。
**P8a 「已接入」单一判定**(S)
- files:src-tauri/src/mcp_inject.rs,src-tauri/src/mcp_tools.rs(list_all_projects)
- acceptance:agent_verify:
- [cargo test mcp_inject] 用 in-memory DB 加假 CredentialStore:有 token、不在任何组的项目进入 `reinject_all_mcp` 的目标集合;组成员仍在;token 已删的项目不在(锁内复查语义不变)。新函数的连接由调用方传入。
- [cargo test mcp_tools] `list_all_projects` 的「已注入」与 DSH 白名单、根目录迁移调用同一个函数:删除 `.claude/liangjing.json` 不改变判定,删除 token 后判为未注入。
**P8b 客户端适配器注册表 + 结构化注入结果 + A1a 适应度测试**(M)
- files:src-tauri/src/onboard/mod.rs(新增),src-tauri/src/onboard/clients.rs(新增,ClientAdapter trait 和 claude/codex/dsh 注册表),src-tauri/src/mcp_inject.rs(sync_claude_local 和 ensure_dsh_patch 改为返回结果枚举,保留日志,公开函数签名不变),src-tauri/src/commands/onboarding.rs(verify 增加 `clients` 附加字段),src-tauri/src/mcp_tools.rs(inject_mcp 文案改为按实际结果),src-tauri/src/lib.rs,src/lib/commands.ts
- acceptance:agent_verify:
- [cargo test onboard::clients] 注册表恰好列出三端,每端声明 detect、static_status、apply。sync_claude_local 的每条分支映射到一种 ClientOutcome(converged / applied / refreshed / manual_kept / other_project / unavailable{reason} / skipped{control_plane|wsl})。DSH 未安装记 not_installed;块外有手写条目记 manual_kept;缺 claude CLI 时 claude 记 unavailable,其余照常。原有 log_event 的类别和文案不变。
- [cargo test] `onboard_module_is_projection_free`:src/onboard/ 下的源码不含 `tauri::`、`db::pool(`、`#[tauri::command]`。
- [cargo test onboarding] 新增的 `clients` 字段不改变 checks 计数和 VerifyState;控制面和 WSL 记 not_applicable,不拉低 pass。
- [MCP] 对 enterprise 连续两次 inject_mcp:第二次三端都是 converged,耗时不到 1 秒(未起 claude CLI),四个项目文件的 mtime 不变。
**P8c 写入前计划:WriteSink + plan_onboarding**(M,Q2 选 A 时做;选 B 时改为「混合文件闸门 needs_confirmation」,S)
- files:src-tauri/src/onboard/plan.rs(新增),src-tauri/src/lib.rs(WriteSink trait,以 write_if_content_changed 作为真实写入汇),src-tauri/src/commands/onboarding.rs(dispatch_* 改经写入汇;MixedFileGuard 前移),src-tauri/src/commands/blueprint.rs(sync_blueprint_rules 改经写入汇),src-tauri/src/mcp_inject.rs(context、Codex 块、settings、liangjing.json 改经写入汇)
- acceptance:agent_verify:
- [cargo test onboard::plan] 在临时 git 仓库里:
- 空仓库做 plan,全部是 create,目录快照前后字节一致;
- 已接入的仓库连续两次 plan,全部 noop、digest 相同;任一目标文件变化,digest 就变;
- 块外有未提交改动时,该文件带预警,且 `plan.blocked` 非空;
- 内容相同但行尾为 CRLF,判 noop;超长 diff 截断并标注;
- context.json 是别的 project_id 时,记 blocked(identity_conflict);
- `machine_changes` 列出各本机步骤的预计动作,计划阶段假 claude CLI 只收到 `mcp get`。
- [cargo test] 原有的 onboarding、blueprint、mcp_inject 测试全部通过,行为不变。
**P4a L1 零模型三端检查**(M)
- files:src-tauri/src/onboard/probe_l1.rs(新增),src-tauri/src/onboard/clients.rs(各适配器的 probe_l1),src-tauri/src/mcp_server.rs(识别 `lian-jing-l1-probe` 并排除出 has_initialized_project_client),src-tauri/src/mcp_tools.rs(`probe_client_parity`,tier=l1),src-tauri/src/commands/onboarding.rs(async command),src/lib/commands.ts
- acceptance:agent_verify:
- [cargo test onboard::probe_l1] 用进程内桩 MCP 服务加哑 shim:适配器取配置,按客户端方式执行 helper,再依次 initialize、tools/list、tools/call,C/D/E/F 均为 ready,每格带证据等级。shim 输出错误 token 时 F 记 auth_failed;shim 缺失记 shim_missing;端口不可达记 unreachable;客户端未安装记 not_installed,不计失败。期望工具集取自 `tools_list_result_for_project`。结果 JSON 和日志里不含桩 token。
- [MCP 全局] `probe_client_parity(enterprise, tier=l1)` 在 10 秒内返回,三端适用格 ready,并附版本号;对 dev-manager-tauri 调用时,Claude 和 DSH 记 not_applicable(control_plane)。
**P8d onboard_project 编排本体**(M)
- files:src-tauri/src/onboard/apply.rs、report.rs(新增),src-tauri/src/commands/onboarding.rs(存量的 apply_onboarding_pack 一并改为 `#[tauri::command(async)]`)
- acceptance:agent_verify:[cargo test onboard::apply] 用临时 git 仓库、in-memory DB,以及假的 CredentialStore、ClientAdapter、ProcessRunner:
1. plan 后 apply(expected_digest, confirmed=true):status 为 done,HEAD 不变,repo_changes 与 `git status --porcelain` 一致且都未提交。
2. 立即再次 plan:全部 noop;再次 apply:全部 converged,mtime 不变。
3. digest 不符或 confirmed=false:refused,零写入。
4. Claude 适配器失败:partial,Codex 和 DSH 照常完成,L1 的 Claude 列记 blocked(dep=claude_local);修复后重跑得到 done。
5. 未登记的 project_id、空路径或相对路径:failed,零写入。
6. 非 git 目录:repo_changes 为 unavailable,status 不是 done。
7. 新项目没有 origin、CI、飞轮数据:仍可以是 done,这三项进入 next_actions;Gitea 一项注明「默认公开」。
8. 同一项目并发 apply:第二个返回 busy。
**P8e 投影收口与契约同步**(S)
- files:src-tauri/src/mcp_tools.rs(`plan_onboarding`、`onboard_project` 的 schema,单独成函数以避开 json! 的递归上限;dispatch 里只留一行委托),src-tauri/src/lib.rs,src/lib/commands.ts,docs/ai-context/project-context.yaml,docs/ai-context/agent-button-system.md,AGENTS.md(架构速查补 `src-tauri/src/onboard/`)
- acceptance:agent_verify:
- [cargo test mcp_tools] 全局和项目路由的 tools/list 都包含这两个工具;在项目路由上强制本项目,传跨项目参数返回 scope_violation;`onboard_project` 的 required 包含 `expected_digest` 和 `confirmed`;annotations 中 plan 为 readOnly、apply 为 idempotent。
- [pnpm typecheck] 通过。
- [MCP 全局] 对 enterprise 做 plan,全部 noop;做 apply,结果 done 且各步都是 converged。
- [读文件] project-context.yaml 已补上 plan、digest 和状态语义(T1)。
**P8f register_project 控制面投影**(S,可选,见 Q7)
- files:src-tauri/src/commands/projects.rs(抽出 create_project 的本体,连接由调用方注入),src-tauri/src/mcp_tools.rs(只开放在全局路由,并在 is_project_scoped_tool 中排除)
- acceptance:agent_verify:
- [cargo test] 空路径、相对路径、不存在的路径、重复登记都被拒绝,不建目录,不执行 git init。
- [MCP 全局] 对临时 git 仓库依次调用 `register_project`、`plan_onboarding`、`onboard_project`,结果为 done;项目路由的 tools/list 不含 register_project。
**P8g UI 一键接入**(M)
- files:src/components/dashboard/OnboardPanel.tsx(新增),src/components/dashboard/onboardView.ts 及其 .test.ts(新增,报告到视图的纯函数),ProjectModal.tsx,BatchAddModal.tsx(只提供批量入口,不自动接入),src/components/manage/NewProjectModal.tsx,BlueprintModal.tsx(「应用接入包」改为先 plan 再 apply),src/lib/commands.ts
- acceptance:
- agent_verify:[pnpm typecheck && pnpm test] 视图纯函数:blocked 非空时确认按钮禁用,并列出文件和原因;not_installed 和 not_applicable 显示为灰色说明,不显示为红色失败;bypassPermissions 和 Codex 信任单独列出;确认时携带 expected_digest。
- human_verify:
1. 用一个临时 git 仓库新建项目,确认弹出预览。
2. 确认后 30 秒内看到分步结果、L1 矩阵和「待你提交的 N 个文件」。
3. 另试一次在预览里取消,确认目录零改动。
4. 在该目录新开 Claude Code、Codex、DSH 桌面 GUI 会话,调用 LJ_PROBE_NONEXISTENT,确认得到「开发需求未登记」。
**P4b MCP 服务端调用轨迹环**(S,在 A4 合并后做,见 Q8)
- files:src-tauri/src/mcp_observe.rs(新增),src-tauri/src/mcp_server.rs(单一挂点,扩展 F10 中已有的记录点)
- acceptance:agent_verify:[cargo test mcp_observe] 在项目路由上依次 initialize(codex-mcp-client)、tools/list、调用 `read_project_test_account(name=LJ_PROBE_A1B2C3D4)`,分别用无认证、错误认证、正确认证:
- 依次记录 auth=none/invalid/valid、route、method、tool、截断后的 clientInfo/UA,以及 nonce;
- Authorization 的值、bearer、其他参数值(例如 SECRET_X)都不进入记录;
- 容量上限按 FIFO 淘汰;
- readiness 和 l1-probe 的请求不进入轨迹;
- 没有任何工具暴露原始轨迹。
**P4c 探针判定本体**(M,纯函数)
- files:src-tauri/src/onboard/probe/{evaluate,adapters,redact}.rs(新增),src-tauri/src/envoy.rs(把敏感模式抽成共享函数)
- acceptance:agent_verify:[cargo test onboard::probe]
- P4-0 的真实夹具解析出的矩阵与期望一致。
- 合成夹具逐一命中对应归因:route_global、auth_header_missing、token_mismatch、adapter_drift(判 unknown)、model_noncompliant、client_auth_or_quota(判 unknown)、rules_not_loaded,以及 credential_leak(落盘文本已脱敏)。
- 控制面画像下 E、F 为 not_applicable。
- compare 只列出变化的格子;probe_spec_version 不同时标记为不可比。
- 判定函数没有任何 IO。
**P4d 探针执行器 + 异步任务 + 持久化**(M)
- files:src-tauri/src/onboard/probe/runner.rs(ProcessRunner 和 ClientLocator trait、Job Object、环境清理、包上下文检测),src-tauri/src/onboard/probe/job.rs,src-tauri/src/db.rs(`migrate()` 新增 `client_parity_runs` 表)
- acceptance:agent_verify:[cargo test onboard::probe::runner] 用假 ProcessRunner:
- 超时:进程树全部退出,该端记 timeout,其他端照常。
- 未安装:记 not_installed,耗时不到 1 秒。
- L1 判定未注册的一端:零进程。
- 子进程环境不含 `CLAUDECODE` 和 `CLAUDE_CODE_*`,含 `MCP_CONNECTION_NONBLOCKING=false`,stdin 为空;参数等于 P4-0 写死的组合。
- 运行中再次 start:返回同一个 run_id;启动时遗留的 running 记录改标 interrupted。
- 同一指纹 7 天内命中缓存;`force=true` 时重跑。
- `migrate()` 在旧库上重复执行是幂等的。
- 探针前后 `git status` 不一致时,记 probe_side_effect,整次失败。
**P4e 探针投影 + CLI 薄客户端 + UI 按钮**(M)
- files:src-tauri/src/mcp_tools.rs(`start_parity_probe` 和 `get_parity_probe` 只在全局路由;`get_client_parity` 在项目路由只读),scripts/probe-clients.mjs,package.json(`probe:clients`),src/components/dashboard/OnboardPanel.tsx(探针按钮和矩阵),src/lib/commands.ts,docs/ai-context/agent-button-system.md,docs/ai-context/project-context.yaml,AGENTS.md(「炼境自己的 Agent 按钮清单」加一行)
- acceptance:
- agent_verify:
- [MCP 全局] `confirmed=false` 时拒绝,且不起任何子进程;`confirmed=true` 时 1 秒内返回 run_id。
- [MCP 全局] 轮询 enterprise,6 分钟内 done,三端 B–F 通过、G 通过,与 P1 基线一致;不做任何改动,用 force 立即重跑,diff 为空。
- [MCP 全局] `clients=["dsh"]` 时 60 秒内完成。
- [MCP 项目路由] 看不到 start_parity_probe。
- [CLI] `pnpm probe:clients <id> --wait`:全部适用格通过时 exit 0,否则 exit 1;炼境不可达时 exit 3。
- human_verify:人看一次矩阵,确认证据等级和回退标注可读。
## 8. 风险
| # | 风险 | 应对 |
|---|---|---|
| R1 | 与在途工作冲突。P6 要改 onboarding.rs 的分发逻辑,和 P8c 改同一处;A4(rmcp)要改 mcp_server.rs 和 mcp_tools.rs,和 P4b、P8e、P4e 冲突 | 按 Q3、Q8 排序。新工具在 dispatch 里只留一行委托。开工前查 origin 和 Codex worktree 分支(目前没有 P6 或 A4 的代码分支) |
| R2 | Codex 可能不加载未信任目录的项目配置,结果是「配置写了但不生效」 | 先由 P4-0 实证;成立的话列为 next_actions,炼境不写 `~/.codex` 的信任表(Q5) |
| R3 | bypassPermissions 默认值随一键接入扩散到每个新项目(F8) | plan 中单列,默认值交给 Q4 决定 |
| R4 | P8b 之前直接编排会掩盖失败,把 Claude 或 DSH 的失败报成成功(F1) | P8d 必须排在 P8b 之后 |
| R5 | 隐式副作用链拆平后行为会变:已在组内的项目也会刷新本机注册,失败会显式暴露 | 有意为之,在 PR 中声明;独立产品组作为兼容步骤保留 |
| R6 | L1 只是配置回放,可能 L1 全绿而真实会话失败 | 每格标证据等级;版本变化时提示跑 L3;GUI 形态归 human_verify |
| R7 | L3 的额度消耗和随机性;模型自报不可靠 | C–F 只认客观证据;不自动重试;不作为 CI 闸门;额度或登录问题记 unknown |
| R8 | 项目 settings 默认 bypassPermissions,`--allowedTools` 在这种模式下大概率不构成限制(待 H-P8-2 实测确认),探针会话可能调用写工具 | P4-0 写死收窄参数组合;外加副作用断言和服务端轨迹兜底 |
| R9 | Codex 在 read-only 下 MCP 调用被拦截,回退到 workspace-write 后模型可能写文件 | 比对 `git status` 前后,检查非白名单调用,违反即整次失败 |
| R10 | 环境污染:MSIX 包上下文、继承 `CLAUDECODE`、GUI 进程的 PATH 不全 | 检测并在报告中标注;客户端路径允许在设置里覆盖 |
| R11 | 杀进程树不彻底(F11),`.cmd` 包装下的 node 进程会成为孤儿 | 使用 Job Object |
| R12 | MCP 单次调用超时:apply 最坏情况下 claude CLI 要跑 4 次,每次最多 20 秒,可能超过 Codex 的默认工具超时 | 主要调用方是 async 的 Tauri command 和控制面 Claude 会话;等 H-P8-5 实测后再决定是否把 apply 也改成异步任务 |
| R13 | L3 并发归属:用户同时开着的会话可能混进时间窗 | F 项用 nonce 区分;C–E 靠串行、时间窗和 clientInfo;记为已知限制 |
| R14 | 轨迹环越界,存进了参数值或请求头 | 只存元数据,用测试钉死 |
| R15 | 依赖客户端私有实现(DSH 的 app.asar、Codex 的哈希 bin 目录,本机已有两个) | 通过 ClientLocator 和设置覆盖定位;格式变化归因为 adapter_drift;适配器升级需要重建炼境 |
| R16 | T1 契约变更:新 MCP 工具、新 Tauri command、`OnboardingVerification.clients`、`client_parity_runs` 表 | 同步 commands.ts、project-context.yaml,并在 `migrate()` 中补 migration |
| R17 | 真实客户端验收超过 180 秒,会被 Workflow 子 agent 判为卡死 | P4-0、P4e、P8g 的端到端验收只在主会话或后台任务里跑 |
| R18 | 新项目的 verify 必然不全过(F7) | 「完成」只按三端可用子集判定 |
## 9. 需要用户决定的问题([推荐] 为推荐项)
**Q1 P8 验收口径**
- [x] [推荐] 改为「L1 零模型矩阵(带证据等级)+ 可选 L3 结果及是否过期」。P8 只依赖 P4a,可以先于 L3 交付。
- [ ] 保持原口径「三端无头 A–F 矩阵」。P8 要等 P4 全部完成。
**Q2 写入前预览**
- [x] [推荐] A:plan → digest 确认 → apply。通过 WriteSink 实现,与 CI 的 plan/apply 同一模式,并关闭 P10 遗留问题。
- [ ] B:只做混合文件闸门 + 写后的 git diff 报告。少一张 M 卡,但写入前看不到具体改动。
**Q3 P6 与 P8c 的先后**
- [x] [推荐] P8c 先落地 WriteSink,P6 基于它实现。
- [ ] P6 先合并,P8c 再适配。
- [ ] 并行开发(不推荐,onboarding.rs 必然冲突)。
**Q4 bypassPermissions 默认值**
- [x] [推荐] 保持现状,但在 plan 和报告中单列,允许按项目取消勾选。
- [ ] 新项目默认不写(这是行为变更,需要另写理由)。
- [ ] 维持现在的静默写入。
**Q5 Codex 信任**(H-P8-1 成立时)
- [x] [推荐] 列入 next_actions,由用户在 Codex 里自己信任。
- [ ] 逐项目确认后,由炼境写 `~/.codex/config.toml` 的信任表(这是在改另一个工具的安全设置,不推荐)。
**Q6 L3 什么时候跑**
- [x] [推荐] 只在显式触发时跑;建议在 canary 项目上、指纹变化时跑;新项目接入默认不勾选。
- [ ] 首次接入默认勾选。
- [ ] 另外加每周金丝雀定时探针(默认关闭)。
**Q7 register_project 的 MCP 投影**(让 agent 能复刻「新建 → 接入」)
- [x] [推荐] 做。只开放在全局路由,写操作必须经用户确认。
- [ ] 不做,新建项目只走 UI;agent 只能对已登记的项目验收。
**Q8 P4b 与 A4 的先后**
- [x] [推荐] A4 先合并,P4b 挂在 rmcp 的 dispatch/认证边界上(P8 不受影响)。
- [ ] P4b 先合并,A4 迁移时顺带处理挂点。
**Q9 CLI 形态**
- [x] [推荐] P4e 只做 `pnpm probe:clients` 薄脚本(放在炼境仓库,调用全局 MCP)。
- [ ] 现在就做 Rust console 版 `lianjing.exe` + skill 降级说明(要升接入包版本,全部子项目重新分发)。
- [ ] 不做 CLI,P4 验收载体改为 MCP。
**Q10 A1 改写**
- [x] [推荐] 拆成三段:A1a 随 P8 落地(适应度测试),A1b 按需做 CLI 薄客户端,A1c 触发式拆 crate。
- [ ] 维持原 A1 卡,并把 P8 排在它之后。
**Q11 探针用的模型**
- [x] [推荐] 用各客户端默认模型,设置里允许用户为探针单独指定更便宜的模型。
- [ ] 固定用便宜模型。
## 10. 没验什么
- lian-jing MCP 在本会话中没有连上,所以没有用 get_diagnostics 或 verify_onboarding 核对 enterprise 当前的运行态基线。实施 P8-0 之前,应该在炼境在线时复核一次。
- 本次没有运行任何客户端。以下都只是依据蓝图记录或推断,没有实测:
- H-P8-1 至 H-P8-6;
- `claude mcp get` 的 Connected 状态是否意味着 helper 已被执行(依据 P1/H2 的记录推断);
- bypassPermissions 下 `--allowedTools` 不构成限制(依据对 Claude Code 权限模型的理解,需要实测);
- 各项耗时目标和三端合计 token 估算。
- 所有代码结论都来自 master(10434d4)的只读阅读,没有跑 cargo test。工作区里的未提交改动没有动过,也没有判定归属。