# 插件暂存环境测试约束(真实隔离环境验证) 本文档是 dsh-devforge 及本机 DSH 插件交付流程的**项目约束**:插件改动在进入生产 Web Host 之前,必须先通过「隔离 HOME 暂存实例」完成真实环境验证。该方法于 0.7.7 首次验证通过,0.10.0(2026-09-01)复验有效。 ## 一、约束目标 - 单元测试覆盖不了宿主集成环节(面板渲染、设置注册、协议握手、浏览器通道、备份调度),必须有一个**真实运行**的宿主来验收。 - 生产实例(端口 3080)承载真实凭据、飞书桥长连接、会话库与任务 ledger,绝不允许当试验田。 - 暂存实例要做到"真实但隔离":跑的是与生产同一份已安装插件(真实集成),但凭据、会话、调度器状态全部落在独立 HOME(零污染)。 ## 二、硬性约束(红线) 1. **交付流程必须完整走完**:源仓库测试通过 → 中文提交并推送 CNB → 安装到 web profile → **起暂存实例人工过目 → 用户明确确认 → 才允许重启生产** → 关停暂存实例。 2. 禁止跳过暂存验收直接重启生产;`devforge_restart` 只能在用户明确要求后执行。 3. **每次改完代码必须提交并推送 CNB(与远端同步)**:源仓库任何改动(源码、脚本、文档、配置)落地后,同一轮就要完成「中文提交 → 推送 CNB → `cnb_status` 复核无 ahead」。只提交不推送视为本次交付未完成,不得进入安装、暂存验收或重启生产等后续阶段(做法见 3.6)。 4. 暂存实例必须使用独立 HOME(隔离凭据与会话库);**禁止与生产共用 HOME 直接 `--patch` 起实例**——loader 只向插件传 schema 内字段,同 HOME 打补丁改插件配置无效,飞书桥仍会连生产凭据。暂存 profile 目录必须是真实目录、只软链 `node_modules`,**禁止把整个 `profiles` 目录软链到生产**(插件设置写在 `/cordis.patch.yml`,整目录软链会让暂存的启动自愈直接改写生产配置)。 5. 暂存 overlay 必须显式禁用:桌面宠物(防双实例重复弹窗)和 devforge 飞书桥(同 appId 双 WSClient 长连接会分流事件,属铁律)。 6. 暂存实例固定用 3081 端口,与生产 3080 区分;启动前确认端口空闲。 7. 需要预览飞书卡片效果时不得依赖暂存实例(其无生产凭据),用一次性 node 脚本走 REST 直发预览卡。 8. **「真实测试」的边界(禁止乱测试)**:真实测试 = 对**本次改动本身**做可复现的验证——新写或更新断言并证明「改动前失败、改动后通过」,或按可复现步骤在隔离环境人工验证。它**不是**"随手试一下":不得在生产实例、生产数据库、生产接口上执行任何写操作式验证(含调用写接口、执行写 SQL、运行直连生产库的测试方法),生产只做只读核对;测试数据与测试环境必须隔离,禁止任何向生产库写数据的"测试"入口。 9. **源码只在本地仓库改,服务器只做部署目标(禁止直接改线上代码)**:源码唯一真源是本机项目目录(如 `D:\项目`),服务器 / 远程主机只是部署目标与运行环境,**不是源码库**。禁止在服务器上编辑、新建、覆盖源码文件(`sed -i`、`tee`、`cat >`、`vim/nano`、PowerShell `Set-Content`/`Add-Content`、直接改线上工作区文件);禁止把线上目录当源码读改,禁止「直接改线上凑效果」。任何修复先落回本地源仓库 → 测试 → 中文提交推送 → 再部署;远程通道(`ssh_exec` / `winrm_exec` / 上传下载)只用于部署产物覆盖、重启服务、查看日志与配置、只读核对。发现线上与仓库不一致,先回本地仓库对比提交历史(漏部署 or 线上手改),再决定回补或重新发布。 ## 三、标准操作流程 ### 3.1 一次性准备(已就绪则跳过) 暂存 profile 目录**必须是真实目录,只把 `node_modules` 软链到生产**。禁止把整个 `profiles` 目录软链到生产:插件设置(`llm-pi-ai` 的模型声明、各能力开关等)落在 `/cordis.patch.yml`,整目录软链等于让暂存实例直接改写生产配置(2026-10-10 实测踩到,见第四节教训表)。 ```bash # macOS / Linux mkdir -p ~/.dsh-staging/.dsh/profiles/web ln -s ~/.dsh/bin ~/.dsh-staging/.dsh/bin ln -s ~/.dsh/profiles/web/node_modules ~/.dsh-staging/.dsh/profiles/web/node_modules # 复用同款已安装插件 cp ~/.dsh/profiles/web/{cordis.patch.yml,cordis.yml,package.json,pnpm-workspace.yaml,pnpm-lock.yaml,.npmrc} \ ~/.dsh-staging/.dsh/profiles/web/ # 配置拷贝副本 ``` ```powershell # Windows:直接用仓库脚本(自动完成上面的目录布局与副本刷新) pwsh -NoProfile -File D:\项目\scripts\start-staging-3081.ps1 ``` 脚本默认每次启动从生产刷新一次配置副本(生产是只读来源,暂存写坏只坏副本);加 `-KeepProfile` 则保留暂存侧上次设置,用于重复验收或“先种旧值、再看自愈是否改写”的定向验证。 overlay 补丁固定放在 `~/.dsh/staging.patch.yml`(内容见 3.2)。 ### 3.2 overlay 补丁(staging.patch.yml) ```yaml # 暂存实例 overlay(配合隔离 HOME 使用):禁用桌面宠物,避免双实例重复弹窗 - id: web-ui-pet disabled: true # 硬教训:devforge 飞书桥 schema 默认启用,暂存实例必须显式禁飞书, # 否则同一 appId 双实例长连接分流事件 - id: devforge config: feishu: enabled: false ``` ### 3.3 启动暂存实例 ```bash HOME=/Users/andyfan/.dsh-staging \ DSH_HOME=/Users/andyfan/.dsh-staging/.dsh \ dsh --profile web --patch /Users/andyfan/.dsh/staging.patch.yml --port 3081 --no-open ``` 原理:`os.homedir()` 跟随 `HOME`,全局凭据(dsh-feishu.json 等)、会话库、任务 ledger 全部落进隔离 HOME,绝不触碰生产;`node_modules` 软链保证跑的就是生产同款已安装插件,而配置文件是副本,暂存里的设置写入只落副本。 ### 3.4 验收 ```bash # 确认实例存活与插件版本 curl -s --noproxy '*' http://127.0.0.1:3081/api/dsh-devforge/meta # 期望返回 {"ok":true,"version":"<目标版本>"} ``` 若这里返回 401 或「超时」,先分清是**两种不同失败**,不要一律当"没起来"(2026-10-19 实测): - **401** = 宿主正常,只是 CLI 宿主的面板 API 要登录态:先 `GET /?token=<启动日志里打印的 token>` 拿到 `dsh-auth-*` cookie,再带 cookie 调接口(生产桌面壳的接口不需要 token)。 - **404**(带 cookie 后仍 404)= 宿主起来了但 dsh-devforge **没被激活**,原因与替代验收方式见第四节对应教训行;此时不要再在此环境反复尝试面板/工具验收。 上下文压缩改动还必须确认隔离 HOME 中存在可挂载的用户预设(profiles/bin 软链不会同步它): ```text /Users/andyfan/.dsh-staging/.dsh/.agent-presets/cordis-250k/agent.cordis.yml ``` 用暂存实例的 `agentPresets.list()`/`standingKeyFor('cordis-250k')` 确认该条目存在且没有 `broken`,再实际创建一条 `devforge_jobs` 任务。核对任务对应会话的 `agentPreset` 为 `cordis-250k`,并确认 preset mount 成功后才发送首条消息;任务状态/最近消息应能看到实际预设。若预设缺失而回退 `cordis`,必须看到明确告警,不得把回退当作 250k 验收通过。 浏览器打开 `http://127.0.0.1:3081`,人工过目本次改动涉及的面板与功能。暂存 GUI 无生产模型配置属正常现象,不影响验收。 ### 3.5 收尾 用户明确确认后:重启生产 Host(`devforge_restart`)→ 确认生产版本 → 关停 3081 暂存实例。 ### 3.6 代码提交与远端同步(每次改完必做,红线 3) 改动一旦落地(源码、脚本、文档、配置),当轮就提交并推送到 CNB,绝不允许「本地已改、远端落后」的状态跨轮存在: ```text 1) cnb_status 看工作区:确认本次改了哪些文件,远端是否已有新提交 2) cnb_pull 远端有新提交时先拉取,避免分叉 3) cnb_commit 只提交本次任务文件(指定文件,禁止 git add -A 混入无关改动) 4) cnb_push 推送到 cnb.cool 对应分支(默认 main) 5) cnb_status 复核不再是 ahead,工作区只剩可解释的未跟踪文件 ``` - 提交说明用中文,写清类型(修复/新增/文档)与实际变更,一次任务一次提交。 - **只提交不推送 = 本次交付未完成**:不得进入安装、暂存验收、重启生产等后续阶段;回复里也不能宣称"已完成"。 - 推送失败(网络、权限、令牌失效、CNB 未开 Allow push)必须当轮排查并如实说明原因,不能静默跳过,也不能用"稍后一起推"搪塞。 - 不要把多次无关改动攒成一次大提交;也不要在验证失败时强行提交(红线 3 要求的是"改完即同步",不是"失败即同步")。 - CNB 推送默认关闭,需在天工造梦设置的 CNB 配置里打开 Allow push;关掉时 `cnb_push` 会直接报错,此时应在回复中提示用户去打开,而不是改用 GitHub 或其他远端冒充同步。 ## 四、已验证的教训 | 教训 | 后果 | 对策 | |------|------|------| | 同 HOME 直接 `--patch` 改插件配置 | 配置不生效,飞书桥仍连生产凭据 | 必须隔离 HOME(红线 3) | | 暂存忘禁飞书桥 | 同 appId 双 WSClient 分流事件 | overlay 显式 `feishu.enabled: false` | | 暂存忘禁桌面宠物 | 双实例重复弹窗 | overlay 显式 `web-ui-pet.disabled` | | 离线验证补丁是否命中 | 起实例才知道错了 | 先 `dsh --profile web --patch X.yml --dump-config` 离线核对 | | 端口被占 | 起在未知端口或失败 | 启动前 `lsof -i :3081 -sTCP:LISTEN` 检查 | | 隔离 HOME 下 pnpm store 错位 | 暂存实例内执行 `dsh plugin add` 报 ERR_PNPM_UNEXPECTED_STORE(store 归属真实用户主目录) | 暂存实例内不要跑插件安装/升级——「插件更新」的 apply 在暂存不可用属预期边界;装包一律在真实用户环境执行,暂存只验 check/面板 | | 暂存忘禁 CNB 加密备份自动同步(0.26.x 新增,2026-09-08 暂存验收发现) | 暂存按 15m 间隔把隔离 store.db 加密推到与生产相同的备份仓库;生产侧「从远端同步(跳过本机)」会取到暂存机器的最新提交,造成跨实例数据污染 | 起暂存后进入「代码仓库 → CNB → 加密备份」点「停用自动同步」(备份配置存于插件自身 store.db settings 域,不走 cordis overlay,patch 覆盖不到);确认横幅变为「自动同步未启用」后再验收,配置中 repo/interval 原样保留不影响生产 | | 依赖 web-ui-pet overlay 判断宠物风险(2026-09-08 复核确认已过时) | web-ui-* 全家桶已不再经 profile 挂载,组合树中不存在该插件;overlay 里的 web-ui-pet 条目变为惰性,照旧书写会造成「已防护」的错觉 | 暂存防宠物以「未挂载」为准:`--dump-config` 全文无任何 web-ui-* 条目即无宠物弹窗风险;overlay 中的 web-ui-pet 条目仅作历史保留,不再是生效防线 | | 暂存 HOME 用户预设过期(0.31.1 暂存验收发现) | 早期复制的 `cordis-250k` 预设用已废弃的 `text` 字段(缺 `prefix`),新版 schema 校验挂载失败;Forge 任务如实 failed 并暴露完整错误 | 验收涉及预设挂载时,先 `diff` 生产与暂存的 `.agent-presets//agent.cordis.yml`,不一致从生产同步(profiles/bin 软链不会同步用户预设);插件的失败路径会把 mount 错误完整写进任务 lastMessage,验收时应先核对失败原因再判定 | | 暂存 profile 整目录软链到生产(2026-10-10 暂存验收发现) | 插件设置写在 `/cordis.patch.yml`,暂存实例的启动自愈/设置写入直接落到生产配置文件(实测把 `llm-pi-ai` 的 tiangong 声明 262144 改成 200000,并刷新了生产文件时间戳),另一实例内存中的旧设置还会在后续写入时把改动盖回去 | profile 用真实目录 + 只软链 `node_modules` + 配置文件用生产副本(红线 3);启动脚本每次刷新副本,`-KeepProfile` 保留暂存侧设置 | | 改完只提交不推送(旧口径"除非明确要求禁止 push"导致,2026-10-18 修正) | 本地 main 领先远端多个提交,远端与流水线/其他机器拿到的还是旧代码,跨机接手直接在旧版本上排查,问题被反复"复现" | 改完当轮 `cnb_push` 并 `cnb_status` 复核无 ahead(红线 3、3.6);未推送不得宣称交付完成 | | 把"测试"理解成"随手试一下"(2026-10-19 均辉月杂事故暴露) | 有会话为验证自己对接口的理解,反复执行直连生产库的测试清单方法(`[TestMethod]` 直接调业务控制器写生产库),把 278 条月杂重复写入生产库,事后需备份、删除、按审计回滚 AWB 字段并删除整个测试工程 | 红线 8:真实测试只针对本次改动、可复现、断言可证;验证性动作不得落在生产库/生产接口上;会写数据的测试必须改用测试库或隔离环境,禁止任何直连生产库的"测试"入口 | | 同一写库调用点存在多份副本(2026-10-19 同案) | 只注释业务带里的 `Controllers/FanTestController.cs`,对独立测试程序集 `WEBAPITEST/FanTest.cs` 完全无效,误以为"已经改掉就不会再跑",事故继续发生 | 排查写库来源必须全仓库搜索关键调用点(含未被 sln 引用的工程);加固或删除要覆盖所有副本,而不是只看被修改的那个文件 | | 暂存宿主用 `Start-Process` 启动(2026-10-19 实测) | 宿主成为调用方 pwsh 的子进程,DSH 的 pwsh 工具调用超时收进程树时把宿主一起杀掉;现象极具迷惑性:`meta` 已返回正确版本号,几十秒后 3081 不再监听、日志里没有任何错误 | 用 `Win32_Process.Create` 脱离父子关系启动(脚本已改),父进程为 WMI 服务,调用方超时收树伤不到它 | | 以为 `Win32_Process.Create` 会继承环境变量(2026-10-19 实测,同轮踩到) | **它不继承**:`$env:USERPROFILE`/`$env:DSH_HOME` 的修改到不了新进程,宿主以**生产 HOME** 启动并直接打开生产的 `store.db`(实测 `store.db-wal` mtime 被打上时间戳、诊断日志落到生产 `.dsh\logs`)。当天逐项核对后确认未产生数据写入(rag.doc 篇数与最后创建时间未变、无新增会话、备份为 disabled),但属于**红线 4 的实质违反**,极其危险 | 隔离 HOME 必须在**被创建的命令行里**显式 `set`:`cmd /c "set USERPROFILE=… && set HOME=… && set DSH_HOME=… && node bin.js …"`;每次启动后都用「生产 `.dsh\logs` 是否新增 `startup-*.log`」这条外部证据复核隔离是否真的生效,不要只看端口通不通 | | 认为「CLI 起 `--profile web` 宿主一定装不起 dsh-devforge、做不了面板/工具验收」(2026-10-19 二次实测修正了上一条的前提) | 上一条教训的前提已过期:启动脚本自 2026-10-18 起把**配置副本基线从 web 改成 desktop**(生产同构),此后 CLI 暂存宿主能正常激活插件。2026-10-19 14:01 实测:`meta` 返回目标版本 `0.61.0`,面板「开发 → 项目 → 产出公约」完整渲染出新增的二级布局/命名硬约束/规则,`/api/dsh-devforge/backup/status` 等端点带 cookie 后 200 | 暂存面板验收**照常做**(按 3.1 起 3081,访问日志里的 `/?token=…` 拿 `dsh-auth-*` cookie 后调接口与看面板);只有基线仍停留在 web profile 时才会重演「静默不激活」,此时才退到上一条的替代核验 | | 改完 `src` 忘记重建 `lib`(link 安装的插件入口是产物,2026-10-19 实测) | 运行实例继续跑旧构建:当天 `src` 于 14:49 已改回 `openai-completions` 并 14:51 提交推送,而 `lib/index.js` 停在 13:59 构建仍是 `anthropic-messages`,desktop 的 `llm-pi-ai` 声明也就一直是 Anthropic;更隐蔽的是**旧构建的自愈判据正好相反**(认 `anthropic-messages` 才是正确组合),所以静止状态下永远不会自我纠正,重启也不会切协议 | 交付前用「`grep -o 'const TIANGONG_PROTOCOL = "[^"]*"' lib/index.js`」核对**产物**协议,不要只看 `src`;link 安装的 profile 改完 `src` 必须 `pnpm build` 重建 `lib` 再重启宿主才生效;排查「配置改了没生效」先确认运行实例加载的是哪份产物 | | 模型跑到服务器上找源码、甚至直接改线上文件(2026-10-19 辉哥指出) | 线上是部署副本不是源码库:直接改线上会让仓库与线上分叉、下次部署被覆盖、问题反复;模型也无从判断线上那份是否可靠。更深的根因是注入层从未说过源码在哪——默认开发路径清单只写了 macOS 路径且 `resolveAgentCwd` 只认 `/` 开头,Windows 会话永远停在 7 条摘要层,全文红线(含「禁止改生产凑效果」)根本没进过上下文 | 红线 9:源码只在本机仓库改,改完测试 → 提交推送 → 再部署;注入摘要/全文、项目卡、`ssh_*`/`winrm_*` 工具描述四处同口径声明;默认清单补 `D:\项目` 并支持盘符/反斜杠路径(0.61.2) | ## 五、上下文注入(0.11.0 起自动生效) 本文档的约束不再依赖模型自觉读取:dsh-devforge 0.11.0 起内置**三层上下文注入**(`src/constraints.ts`,可在天工造梦设置中关闭或调整路径清单)—— 1. **常驻保底**:所有会话无条件注入红线摘要与路由指令(含「每次改完必须提交并推送 CNB」与「源码只在本机改、服务器只做部署」); 2. **仓库信号**:会话工作目录命中开发仓库路径清单(默认含本仓库工作区:Windows `D:\项目` 与 macOS 路径并列,盘符/反斜杠路径同样识别)→ 从第一轮起注入本文档全文; 3. **行为信号**:会话出现写文件/bash/git 提交等开发工具动作 → 后续轮次自动升级注入全文。 注入级别只升不降;涉及开发时全文自动在上下文中,模型无需检索。注入文案与本文档冲突时以本文档为准,并同步修正 `src/constraints.ts`。