用户令逐字:「E:/ProgramData/.workbuddy/skills 提交仓库是指的这里」—— 即本目录就是仓库(2026-10-07 已在本目录建仓,见当日日志 §22),本轮把余下未纳管的 9 个技能一并提交。 本次入库(9 个技能,46 个文件): 1、`AI HOT` 2、`draw-ui` 3、`dsh-diagnose` 4、`dsh-knowledge` 5、`dsh-local-env` 6、`dsh-opensource-release` 7、`dsh-workflow` 8、`oil-motion` 9、`skills-security-check` 提交前核对: · **凭据类扫描**(`*.env` / `*token*` / `*.key` / `*secret*` / `*.pem`)⇒ **零命中** ✓; · 体积合计约 20 MB(`draw-ui` 12M + `oil-motion` 6.5M 是大头,形态为配图/素材 —— 仓库 `.gitignore` 里明写「`assets/*.png` 是内容不是产物」⇒ 属刻意入库); · 运行产物仍按既定规则排除(`__pycache__` / `logs/` / `tmp/` / `.venv/` / `*.egg-info` / `uv.lock` / `.workbuddy/`)。
57 KiB
平台改造六阶段 + 红线 R1-R11 原文速查
归属:技能
dsh-workflow· 详情档(主干../SKILL.md) 本档覆盖:原技能dsh-change-workflow全文(正文 288 行 + frontmatter 变更历史) provenance:文档库版(超集:含 R9–R11 与已修订的 R8;本机版 R1–R8 是其真子集) 搬运方式:逐行未改;内部附件链接已改写为dsh-change-workflow/<附件名>(附件在本目录下) ⚠️ 原技能dsh-change-workflow已合并退役 ⇒ 见到该名按本档读。
🔵 作用域(2026-09-26 从 WorkBuddy 用户级技能迁入):本技能描述的是 dsh 平台项目的作业方法,已提升为 DSH 全局技能,供任何会话发现与加载。 ⚠️ 正文里的宿主路径与脚本位置是迁移时的旧值(如
~/.workbuddy、CODEBUDDY.md、07-scripts/、文档库)。在 DSH 上以当前工作区的规则文件与🔴 2026-10-02 纠正:宿主是 WorkBuddy、不是 DSH;$DSH_HOME下的实际落点为准$DSH_HOME本机已悬空 ⇒ 以当前工作区的规则文件 +E:\ProgramData\.workbuddy\skills\下的实际落点为准;⛔ 不要把正文里的旧路径当现行事实。 ⚠️ 冲突时:工作区规则 > 本技能。
🔴 宿主落点对照 —— ⚠️ 本节方向已作废(2026-10-02 纠正)
🔴 左右方向与现状相反,⛔ 不要再照它执行。 下表是 2026-09-26 从 WorkBuddy 迁往 DSH 当时 的形态(让人把
~/.workbuddy/08-skills/<n>/换成$DSH_HOME/skills/<n>/)。 现状:宿主是 WorkBuddy(不是 DSH);技能真身就在E:\ProgramData\.workbuddy\skills\<n>\; 而$DSH_HOME=E:\ProgramDSH\.dsh在本机目录已不存在(悬空老值,只剩环境变量还留着)。 ⇒ 正确方向是 WorkBuddy ← DSH:正文里见到$DSH_HOME/skills/<n>/,换成E:\ProgramData\.workbuddy\skills\<n>\。
下表保留仅为留痕(右列=已作废的旧落点):
| 正文里的旧路径 | |
|---|---|
~/.workbuddy/08-skills/<n>/、.workbuddy/08-skills/<n>/ |
$DSH_HOME/skills/<n>/(技能根,被 watch,改动下一请求生效)E:\ProgramData\.workbuddy\skills\<n>\ |
.workbuddy/memory/MEMORY.md、~/.workbuddy/MEMORY.md |
$DSH_HOME/AGENTS.md(全局,每轮注入)/工作区 AGENTS.md(项目级) |
.workbuddy/memory/YYYY-MM-DD.md(按日记忆) |
无对等物 ⇒ 教训写进本技能 references/ 或项目台账「实测教训」节 |
CODEBUDDY.md(项目常驻规则) |
同一份文件仍生效(已在 preset 覆盖里加入 DSH 的指令候选),无需改名 |
07-scripts/<x>.py(文档库脚本) |
同左,但路径必须写全:D:\github\dsh_shenxian\dsh-server-docs\07-scripts\<x>.py |
~/.workbuddy/settings.json 的 hooks |
$DSH_HOME/hooks/hooks.json + dsh-guard.py |
WorkBuddy automation_* / 积分口径 |
无对等物(见 dsh-auto-handoff-chain 的不可移植清单) |
🔴 表尾收口(2026-10-02 补):上表每一行的右列都属同一"迁往 DSH"的旧形态,一律作废 —— 尤其第 2 行(
$DSH_HOME/AGENTS.md)与第 6 行($DSH_HOME/hooks/hooks.json):本机没有$DSH_HOME。 已实测的现行落点只有三条,照这三条找: ① 技能根 =E:\ProgramData\.workbuddy\skills\<n>\; ② hook 注册面 =E:\ProgramData\.workbuddy\settings.json的hooks段(2026-10-02 实测:本轮就是往那儿加的第 6 条); ③ 记忆 = 用户级~/.workbuddy/MEMORY.md(跨项目)+ 工作区<工作区>/.workbuddy/memory/(按日 + 长期)。 ⛔ 上表左列里凡写$DSH_HOME/…的,不要再当落点用;E:\ProgramDSH\整树已于 2026-09-28 删除 (见dsh-local-env/references/01-环境引导与迁移.md§"同名影子树")。
⚠️ 正文其余处出现的旧路径属历史记录(当时的事故与实测),保持原样不改 —— 改写历史记录等于造假。
dsh-change-workflow — dsh 平台改造工作流
dsh 多租户平台(服务器 47.77.182.89,dshs + dsh 0.1.2-rc.1)功能改造的标准处理方法。把平台运维历史中反复验证过的处理方式固化为可执行流程:先识别边界 → 源码级调研 → 方案对比确认 → 小步开发 → 全链路验证 → 归档清理。
📂 平台速查表(域名 / 生效路径 / 端口 / 存量事实)已下沉 →
dsh-change-workflow/00-平台速查.md⛔ 两条留在这里的判据:① 正域名 =ai1net.com(alotbuy.com是已降级的 301 旧域 —— 抓它只会拿到 301 HTML,别误判为「改动没生效」);② 平台页由门户直出(127.0.0.1:3080),生效副本只有/opt/dshs/web/一份。
六阶段流程
阶段 0 · 需求识别(先盘点再动手,勿跳步)
-
⛔ 开工前置检查(2026-09-11 用户点名要求,血的教训):这是本技能最重要的一步。 病根 = 不先查「已经有什么」就凭直觉装工具/猜机制;出错后要用户追问,才把早就写在文档里的说明翻出来 (最刺眼的一次:我早在技能里写了「勿走 agent-browser」,自己却忘了,绕最远的路)。
任何「装工具 / 写自动化 / 查机制」之前,按序做完这三条:
- 先看可用技能列表(会话开始就在)→ 有对应的立刻加载技能,禁止另起炉灶;
- 先看本机/项目已有资产:
$DSH_HOME\skills\(尤其本技能,技能根被 watch、改动下一请求生效)、MEMORY.md; - 要装任何东西前自问:本机已有的能否满足? 能→不装;不能→先说清楚再装。
→ 固化结论(别再重新发现):浏览器自动化只允许一件 ——
browser-harness(调用姿势 ⇒ 本节末指针); ⛔agent-browser已禁用(2026-09-25 用户明令:「改为 browser-harness,只允许使用这个」); ⛔ 禁止 Playwright /playwright-core(2026-09-13 用户明令); 静态页与 API 取数用curl/WebFetch,别动浏览器。 -
环境盘点:本机/服务器/工具链/权限/DB/实例状态先行(历史教训:未盘点就默认本机装 Docker 走错路线,被用户批评)。
-
需求边界:区分"方案请求"(只输出方案,P0 硬规则:方案与执行分离,未经明确授权不改文件)与"任务明确直接执行"。
-
用户确认:关键分叉用选项表收敛(
ask_user_question:推荐项置首标注「(Recommended)」,2-4 选项,不设「其他」占位)。 -
输出格式:一句话结论先行 + 表格化细节(状态/结果列)。
阶段 1 · 调研(源码级实证优先,禁止只靠文档推断)
-
★ 用户报障第一步:先拿「真实失败请求」,别猜(2026-09-11 档案 51 实证,省掉全部试错)。 用户只会说"报错/没反应/发消息失败",而平台 journal 里有精确的 URL + method + status:
ssh ... 'journalctl -u dshs --since "-30min" --no-pager \ | grep -E "401|403|404|500|502|503|crash-restart|dsh-child|not_running|YAMLException|ERR_MODULE" \ | grep -v "level.:30.*200"' # 顺带看 host / remoteAddress(= 用户真实 IP)定位是谁、连的哪个子域判据速查:
POST /api/session/prompt → 401= 实例侧鉴权(档案 51 的旧 cookie 问题);502= 实例根本没起来 → 翻同时间段的YAMLException/ERR_MODULE_NOT_FOUND/crash-loop-circuit-open(档案 52 就是被这一步牵出来的);- 401 先分辨平台 401(body
{"error":"unauthorized"})还是实例 401 (bodydsh web authentication required…)——改的地方完全不同。
-
只读分析官方包(允许):
/usr/local/lib/node_modules/@deepseek-ai/dsh/...内 bundle 读代码定位机制(slot 声明、inject 契约、fiber 注入)。绝不写官方主程序。 -
服务器实测 > 推断:区分 fence 层 / 应用层错误;浏览器问题抓 console 完整 err 对象(
description/堆栈,勿只看字符串字段——at new apply类堆栈是定位关键)。 -
双端对照:archive 源 ↔ 实例安装副本 md5 对账;官方行为对照第三方插件写法差异逐字段对比(如 register 的
locale/children/label函数形式)。 -
多模型交叉验证:关键结论交叉核对,不轻信单点推断。
阶段 2 · 规划(文档先行,方案确认后动工)
- 方案对比表:≥2 选项 + 各自影响/风险 + 建议(用户偏好:结构表对比+理由)。
- 红线自查(每项改造必过):
- R1 不触发 dsh 自动获取最新版本;版本升级走独立"升级测试→评估→修复"流程(档案 07)
- R2 不改官方 dsh 主程序与缓存;扩展只走 profile 层
dsh plugin remove/add(tgz)官方机制 - R3 client bundle 严禁
exports.default = apply——loader ESM/CJS interop 会取纯函数为插件主体 → 无 inject →ctx.<svc>抛cannot get property ... without inject;官方 bundle 只导出apply+inject - R4 禁止借真实账号(
admin/guest)的会话来跑测试 —— 🔴 理由已于 2026-09-20 更换(序47 落地):"—— last-wins 已被序47 取消(现为多会话并存 + 每用户上限 + 显式登出全部,见auth.ts登录时deleteUserSessions(user.id)是 last-wins 单活跃会话,会当场踢掉用户正在用的浏览器会话"04-调整方案/145-…md与04-调整方案/08头注)⇒ ⛔ 不得再拿"会踢人"当理由(那是本技能 R4 的旧理由, 照旧用会得出相反判断)。现在仍然禁用的三条真实理由: ① 借来的会话办不了该用户自己的事(停实例/回收/重建/看 profile 文件——见下面那条"独立账号"); ② 会在真实账号的会话列表与audit_log里留下不属于该用户的一次动作(观测面被污染); ③ 真实账号的会话被拿去做破坏性验证(logout-all/revoke)时,真会把用户踢下线。 要测登录态一律用专用测试账号(注册 → 审批 → 用完即删)或node /opt/dshs/mksess.cjs(PG 直插)建临时 session- 专用测试账号模板(2026-09-11 实证):
POST /api/auth/register→ DBUPDATE users SET role='active', approved_by=<真实 admin id>。⚠️ 两个坑:①users表没有status列, 审批就是改role,CHECK 只允许admin/pending/active/disabled(写'user'会 CHECK 失败); ②approved_by有 FK 指向users.id,填字符串会FOREIGN KEY constraint failed。 ⚡ 更省事的等效做法(2026-09-20 序47 实跑验证):直接INSERT INTO users (...) VALUES (...),pass_hash用产品自己的hashPassword(lib/web/auth.js)现算(scrypt$salt$hash),role='active',uid取max(uid)+1⇒ 绕开邮箱验证码与 Turnstile(注册页两道门都开着,见档案 134)。 凡要"操作该用户自己的实例"(停实例/回收/重建/看它的 profile 文件)→ 必须用独立账号, 不能借 admin/guest 的 session(借了就没法停实例、也污染真实用户)。用完DELETE sessions + users并rm -rf users/<id>⚠️ 拿新版平台验证登录态时还有一个坑:sidcookie 是Secure+Domain=.ai1net.com⇒ 本机curl -c <jar>不会为127.0.0.1存这条 cookie(jar 文件干脆不生成)⇒ 后续请求全是假 401。 正确做法 = 从set-cookie响应头里直接取sid值,再用-b "sid=$VAL"带上。
- 专用测试账号模板(2026-09-11 实证):
- R5 权限可见面只准收窄,扩大必须先确认(2026-09-11 用户新增)——先判方向:这次改动是"扩大"还是"收窄"?命中「扩大」定义(新增 bwrap 挂载 / 放开遮蔽 / 暴露平台目录或 env / 放宽
ALLOWED_ENV/ 放松 nft / 提高权限档位或放宽 approval / 新增用户可读写路径 / 让 root 执行链路的对象变成用户可控)→ 必须在方案里写出「权限影响评估」四问并等用户明确同意,不得顺手做;改了「触发文件」清单里的文件就一律按 R5 走。收窄可直接做,但遮蔽类必须真启动一次实例验证(档案 42:遮蔽/usr内文件让实例起不来)
- 影响评估:实例重启会换端口(authority 变)→ 旧 WS 断连预期;登录 session 删除预期;数据目录权限影响。若命中 R5,另附「权限影响评估」四问 + 改动前后 diff「实例可访问路径清单(权威版)」。
- 文档占位:
04-调整方案/建编号档案(模板见下),先写需求与改动设计。
阶段 3 · 开发(小步 + 备份 + 逐条验证)
服务器 TS/配置改码标准流程(多文件改动):
-
scp服务器源码 → 本地工作区 → 本地 Edit(读一条→改一条→验证一条,禁止批量读全部文件) -
scp回服务器/tmp/→cp覆盖前先备份bak-<功能>-<YYYYMMDD>/ -
npm run build(tsc 零报错)→systemctl restart dshs -
本机直连验证(绕 DNS/代理):
# SECURE_COOKIES=true → 必须走 TLS;用 --resolve 把域名指到回环 curl -sk --resolve ai1net.com:443:127.0.0.1 https://ai1net.com/api/... # 带登录态:-H "Cookie: sid=<token>"(明文
http://127.0.0.1:3080在 SECURE_COOKIES 下不通)⚠️ 全量覆盖
/opt/dshs/lib/之前,必须先在 HEAD 上npm run build(2026-09-13 22:16 实测事故) 并行会话做「整包 lib/ 覆盖」时用了一份不含他人已提交改动的陈旧构建 ⇒ 把别人的已上线修复整体回退(本例:/api/dsh/status的quota字段凭空消失)。- 指纹:
lib/**一整批文件 mtime 相同 + 服务重启时间对得上 ⇒ 就是整包覆盖,不是点改。 - 判据:部署后立刻
md5sum对账本机构建,或grep -c <你上次加的符号>目标产物。 - 铁律:部署完立刻复验「上一次刚验收过的东西」 —— 本例正是靠这一步才发现的。
- 另:「已部署」≠「已提交」;不能拿服务器状态当版本事实源(服务器曾跑着未 commit 的版本)。
- 指纹:
客户端插件(client bundle)部署流程:
⚠️ 动手改之前先做「基线校验」(2026-09-13 补,改第三方 tgz 插件时尤其必须) 插件以 tgz 铺到实例,本地那个「源码目录」不一定是线上正在跑的那份(可能漂移 / 被别的会话改过 / 是旧快照)。 步骤:把服务器上正在用的 tgz 拉下来解包 → 与本地源码目录做忽略行尾的全量比对(
diff --strip-trailing-cr)→ 逐文件确认「差异只有我准备改的」。否则可能用一套漂移的代码覆盖线上(本仓有过同类事故)。
- 只看文件名/版本号不算校验(
package.json里的版本可能与实际不符;本仓lib/index.js的VERSION常量就长期落后于package.json)。- ⚠️ 行尾会制造假差异:
npm pack不规范化行尾,手工拼装的 bundle 常是混用行尾(实测:线上client.jsCR=5222/LF=5547,本地纯 LF),去掉行尾后整文件的「差异」会瞬间收敛到真实改动。- 反例(我犯的):先改后验。顺序错 = 风险窗口白开一段;改完再验只能证明「没出事」,证明不了「不会出事」。
- archive 源改版(
/opt/dsh/docs/04-调整方案/poc/<plugin>/lib/client.js+ package.json version/description) npm pack→ tgz 拷到用户ws/(chown 用户 uid)→setpriv --reuid=<uid> --regid=<gid> --init-groups env HOME=... DSH_HOME=... dsh plugin --profile web remove/add <tgz>- kill 实例进程 → 门户自动 respawn 新 pid+新端口 →
journalctl -u dshs抓dsh web: http://127.0.0.1:<port>/?token=... - 验证 node_modules 副本 markers(version、关键字符串计数——区分代码 vs 注释行)
阶段 4 · 验证(全链路 + 浏览器实测 + 清理)
-
API 链路:portal enter(sid)→ url token → 页面 200 →
llm/listProviders ok:true- ★ 验证用例必须同时覆盖「有请求体」与「无请求体」两类(2026-09-11 档案 51 事后修正实证):
只测
POST会整条漏掉GET/SSE。本次补丁初版闸门写成mayHaveBody && …→ POST 全绿、GET/SSE 完全不生效,而 SSE 恰恰是"页面已打开、实例被回收"时最早撞 401 的链路。 凡改动含"按 method / 有无 body 分支",GET那条必须单独跑一遍。 - 模拟浏览器语义要测到底:断言不只是状态码,还要只带最初那一个旧 cookie、忽略任何新 cookie
再跑一遍(这才等价于真实浏览器)。修完后新 cookie 是否回写(
Set-Cookie)必须显式断言。
- ★ 验证用例必须同时覆盖「有请求体」与「无请求体」两类(2026-09-11 档案 51 事后修正实证):
只测
-
浏览器验证栈(只走
browser-harness;⛔agent-browser与 Playwright 均已禁用):⛔ 铁律 0:动手前先
list_tabs()确认「你在驱动哪个浏览器」⛔ 不要假定端口号 —— 端口绑定会变,断言端口=把当日观察写成永久结论:
- 2026-09-11 实测:
BU_CDP_URL=9223不被尊重(daemon 常驻共享,BH_RUNTIME_DIR_SHARED=1), 换BU_NAME+ 非共享 runtime 重建 daemon 后list_tabs()仍返回用户的 16 个标签页 (admin.ai1net.com/guest.ai1net.com/portal.html#/plugins/manual)。 - 2026-09-26 实测:9222 被用户浏览器占(PID 25272)· 9223 空闲 —— 与 09-11 的归属相反。
⇒ 判据不是端口号,是
list_tabs()返回的标签页是不是你自己开的。端口每次现测:curl --noproxy '*' http://127.0.0.1:<port>/json/list。
所以:脚本第一步永远是
print([t["url"] for t in list_tabs()])。- 只要看到的不是你自己新开的空浏览器 → 立刻停手,只做只读检查。
- 禁止在未确认隔离前做任何有副作用的动作(登录 / 输入 / 提交 / 关闭标签)。
- 已造成的真实事故:在用户浏览器里
fetch('/api/auth/login')→ 覆盖其.ai1net.com的sid,用户在该浏览器里变成测试账号身份。 补救 = 删掉测试账号(cookie 失效 → 用户重新登录即可)。收尾必须close_tab关掉自己开的标签。
- 2026-09-11 实测:
📂 浏览器验证栈的调用细节已下沉 →
dsh-change-workflow/08-浏览器验证栈详解.md(正确调用 · dsh composer(Lexical)输入难点 · agent-browser 旧实测记录 · 独立 headless 定型做法 · 三个必踩坑) ⛔ 留在这里的判据:只用browser-harness一件(⛔agent-browser+ Playwright 全面禁止);动手前第一步先list_tabs();绝不附着用户日常 Chrome。
-
静态文件 vs 后端改动:改
web/*.html无需重启(静态直出,实测服务启动时间不变);改src/**才npm run build+systemctl restart。别为前端改动重启服务(会打断所有用户实例)。 -
安全面复测:降权 uid 探测敏感目录(/root、/etc/shadow、DB)应 denied;中间目录 711(去 r 留 x)不破坏子进程访问。
-
清理临时物:
poc-*session 用完即删(DB DELETE);测试 tab 关闭;临时 tgz/脚本归档或清。 -
截图留证:每功能验收存 PNG 到本地 tmp + present_files 展示。
-
🔴 交付前必问一句:这份改动「出厂」了吗(2026-09-13 实证 —— 用户第 2 次反馈「样式还是没变」的真因之一): 改完
lib/*.js≠ 用户会看到变化。出厂链固定为 改文件 → bumppackage.json版本 →npm pack→ 上传/opt/dsh/artifacts/<包>-<版本>.tgz→node /opt/dshs/07-scripts/ensure-<包>.cjs --all --restart。- 一秒自检:
ls -la对比「源文件 mtime」与「最新 tgz mtime」—— 源文件更新 ⇒ 还没出厂。 - ⚠️ 打包脚本里带断言时先把锚点核对存在再跑
npm pack:断言若在写文件前抛错,版本号没改但 tgz 已被覆盖 ⇒ 得到「内容是新版、版本号仍写旧版」的假产物(本次已踩,靠手工删该 tgz 兜住)。 - 缓存不是借口:
src/supervisor/proxy.ts已对/plugins/、/assets/下发Cache-Control: no-cache,且模块 URL 自带内容rev=<sha1>⇒ bundle 一变浏览器下次请求就拿到新的(「看不到变化」几乎都是没出厂,不是缓存)。
- 一秒自检:
-
视觉/UI 交付必须三层验收,且要拿到「真正下发到浏览器的那份」(2026-09-13 定型):
层 做什么 能发现什么 ① 单测 07-scripts/verify-*.mjs断言 class 名与结构(不只断言文本),关键结构逐项写死(例:5 个弹窗都必须是.table-wrap + table.tbl)结构走样、分支没改到 ② 实装 md5sum对齐 本地产物 / 各 profile 的node_modules实链,并断言旧标记为 0没铺发、铺了旧版 ③ 端到端 mksess.cjs临时会话 → 取实例首页 → 解出/plugins/??…&rev=<sha1>→curl回 bundle → grep 新/旧标记(用完即删临时会话)bundle 没下发、下发的是旧内容 只做 ① ⇒「脚本全绿但用户说没变」;只做 ①② ⇒ 漏掉「用户拿到的那份」。
-
🔴 用户说「参考门户/某个现成页面的样式」时:抄那个页面的源码,不要抄规范的抽象条目(2026-09-13 第 3 次踩): 正确动作 = 打开
web/portal.html/web/admin.html的<style>(或它 link 的design.css), 把.nav-card/table.tbl/.badge/.btn-sm/.card-h/.page-title的实际取值逐条搬过来, 并在注释里写明「本类名 ← 门户某类名」的对应表(便于复核)。 ⛔ 反面:只引01-规范/06-工作台UI规范 §4.5/§4.7的抽象条目再自行演绎 —— 用户会说「还是没变」。 唯一允许的差异:颜色走 dsh token--dsw-*(嵌在 dsh 面板内须随主题);字号/间距/圆角/动效与门户逐值一致。
阶段 5 · 归档清理(文档同步 + 三层沉淀)
-
★★ 交付门禁:本机改完 ≠ 交付(2026-09-15 加 —— 同类已发生 3 次:09-13「只做到本地打包、没部署」→ 用户「点开看还是和之前一样」;09-14「应用更改是哪里…还是和之前一样」;09-15 会话原话「平台那半我只改了本机,从没部署到服务器 —— 那你看的当然还是旧页面」) 收尾前逐项自问三句:① 这次改的是哪一层?② 这一层的生效链路是什么?③ 最后一步走了吗、在「用户可见面」验了吗?
改动所在层 生效链路(缺一步都不算交付) 用户可见面复验 平台静态页( web/*.html、web/i18n.js、portal.html…)scp到服务器对应路径(如/opt/dshs/web/)→ 注意 CDN/CF 缓存(2026-09-14 实测静态资源被 CF 缓存 31 天)用 curl取线上页面(带?lang=等参数)确认新内容;必要时 cache-busting平台 TS( src/**)npm run build→systemctl restart dshs线上端点 / 页面实测(不是本地 build 通过) 自研插件( poc/*、client bundle / host 面)打 tgz → admin 导入候选池 → 实例「功能管理」启用 → 重启实例(client bundle 在实例启动时加载,见 01-规范/06-工作台UI规范.md §7)取实例页 HTML 里真实 bundle URL 逐串验证( rev已变)文档库 / 技能 docs-sync-check.sh对账 →scp(同相对目录)→ 复跑对账对账「一致」计数回升、且残留项全是别人的 lane 配置(env / nginx / nft / 配额) 改 → reload / restart 对应服务 生效值实测( systemctl show/curl/nft list)⛔ 四条"自我安慰",一条都不算交付:本机改完了 / build 通过了 / 本地打包完成 / 已 commit 了。 ⛔ 也别回头问用户「要不要部署」:不打断在线用户的上线属我的 lane 内执行细节 ⇒ 直接做完( CODEBUDDY.md §3 R7-边界+ 素材库 U20 / X9);只有不可逆破坏性操作与边界外六类才先问。📌 与阶段 4 的分工:阶段 4 验"改对了没有"(三层验收);本门禁验"东西到用户那儿了没有"(链路完整性)。 -
文档落盘(2026-09-13 现状:本地工作树 = 源,服务器 = 只读镜像):
- 源 = 本地
D:\github\dsh_shenxian\dsh-server-docs(目录即 git 工作树),推 Giteadsh_shenxian_doc.git的 main。 - 四件套(顺序固定):
docs-audit.py→docs-index-stats.py --write→docs-manifest.py→docs-sync-check.sh;另有docs-consistency.py。 - 传服务器:用单个
tar管道(逐个scp会因每条一个 SSH 连接而超时):tar -cf - <相对路径…> | ssh bt-server 'cd /opt/dsh/docs && tar -xf -',随后chmod(目录 700 / 文件 600)。 - 对账:
bash 07-scripts/docs-sync-check.sh(服务器镜像无.git,只能靠 md5 对账;要点:core.autocrlf=false+.gitattributes的* -text必须保持)。 - ⛔
docs-status-sync.sh已不存在(旧流程残留,勿再引用/调用);状态摘要仍在/opt/dsh/docs-status/文档库状态备注.md。 - ⚠️ 本库多会话共用 ⇒ 提交前先
git status,只git add自己改的文件;别人在途的改动(2026-09-13 实例:档案 76 md、08-skills/dsh-opensource-release/SKILL.md、docs-manifest.json)一律不提交、不 scp。docs-manifest.json是派生文件:它描述的是整库状态,若别人有未提交改动,跟着一起提交会把别人的半成品「顺手上锁」⇒ 此时宁可让它保持 dirty。
- 源 = 本地
-
档案更新:
04-调整方案/<NN>-*.md写"需求→改动文件→commit→验证记录";README.md 档案清单行同步;版本号 bump。- ⚠️ 档案号必须『原子预留』,不能靠"读一眼 ls 再写"(2026-09-11 两次撞号实证):"取号 + 写档案 + 改
INDEX.md+ 改docs-status同一串行事务"这条规则本身不够 —— 两条并行通道同时ls时都读不到对方的文件,必然撞。第二次撞号(37/38 各两条,4 个文件挤 2 个号)就是这么发生的。- 正确做法(原子锁):写档案前先原子占号,例如
或等价地用
cd /opt/dsh/docs/调整方案 n=$(ls | grep -oE '^[0-9]+' | sort -n | tail -1); n=$((n+1)) mkdir ".lock-$n" 2>/dev/null || { echo "占用,重取"; } # mkdir 是原子的flock /opt/dsh/docs/.numbering.lock -c '...'包住"取号+落文件"整段。占号与落盘之间的窗口必须极短,且不要在中途放开去做别的任务。 - 撞号后处置:保留已 commit 的那个号不动(commit message 已引用它),把自己未提交的那个顺延改号(
mv文件 + 改 INDEX 行 + 改自己的 docs-status 变更行 + 在自己档案内修正交叉引用 + 加一条撞号说明)。改号要在提交前做,否则又制造一次引用错位。
- 正确做法(原子锁):写档案前先原子占号,例如
- ⚠️
docs/README.md的档案清单已落后(止于 28) → 不要只改 README;全量清单在docs/INDEX.md,以它为准。
- ⚠️ 档案号必须『原子预留』,不能靠"读一眼 ls 再写"(2026-09-11 两次撞号实证):"取号 + 写档案 + 改
-
三层沉淀(用户习惯,必做)—— 🔴 DSH 侧落点已改,见下表:
- 治本层:机制/流程固化进 skill(
$DSH_HOME/skills/<name>/)或档案 - 失误层:教训 append 到本技能的
references/实测教训档,或项目台账的「实测教训」节 (DSH 侧没有 WorkBuddy 那种按日记忆文件);⛔ append-only,不改写历史 - 记忆层:长期约定 / 红线写进
$DSH_HOME/AGENTS.md(全局,每轮注入)或工作区AGENTS.md(项目级)
- 治本层:机制/流程固化进 skill(
-
汇总表:带状态/结果列交付,列明未完成项与副作用。排版按
session-mechanism §5.4(首屏给判定 / 层级≤3 / 每节≤7 行 / 表格≤5 列 / 一条信息只说一次;细节进附录)。 -
技能自身归档(本 skill 有改动时必做):🔴 DSH 侧工作副本 =
$DSH_HOME/skills/<name>/SKILL.md(不是.workbuddy/08-skills/)。服务器归档位仍在/opt/dsh/docs/08-skills/<name>/SKILL.md(root 600)。改完本 skill 后单向推:P=/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0 export PATH="$P/usr/bin:$P/mingw64/bin:/c/Windows/System32:/c/Windows:$PATH" S="/c/Users/Administrator/AppData/Local/Temp/wb-scratch"; mkdir -p "$S" cp "<本机 skill>/SKILL.md" "$S/SKILL.md" # 中文路径先转纯 ASCII scp -i ~/.ssh/id_ed25519 -P 22 "$S/SKILL.md" [email protected]:/tmp/SKILL.md ssh -i ~/.ssh/id_ed25519 -p 22 [email protected] \ 'D=/opt/dsh/docs/08-skills/<name>; mkdir -p /opt/dsh/backups/docs-skills; \ cp $D/SKILL.md /opt/dsh/backups/docs-skills/SKILL.md.bak-$(date +%Y%m%d_%H%M%S); \ cp /tmp/SKILL.md $D/SKILL.md && chmod 600 $D/SKILL.md && chown root:root $D/SKILL.md && rm -f /tmp/SKILL.md'md5 必须双端一致再算完成;同时更新引用它的元数据:
README.md模块表行、INDEX.md场景速查行 + 全量清单行。方向固定「本机 → 服务器」,勿反向覆盖(服务器副本是归档,不是工作副本)。 ⚠️ 改「跨载体规则」时必做全载体扫描(2026-09-15 实证):一条交互 / 流程规则往往同时躺在多个载体里, 只改一处 ⇒ 其余载体立刻自相矛盾,比不改更糟。动手前先grep -rn关键短语,把命中处全部列出:载体 典型位置 技能正文 SKILL.md的 §/铁律/硬约束/反模式/自检 +references/**项目根常驻层 CODEBUDDY.md §1(连带同节的指针行,如「排版按 …」)用户级记忆(跨项目) ~/.workbuddy/MEMORY.md钩子文案 07-scripts/stop-dialog-guard.py的REASON/CONTEXT生成物(勿手改) dsh-env-bootstrap/references/常驻规则-快照.md⇒ 改完源后跑python resident-rules.py --snapshot重生成,再--check应 RC=0登记行 README.md模块表 ·INDEX.md判据:改完再 grep 一遍,命中应只在已改处。钩子文案最关键 —— 它每轮都在"教训 AI 该怎么做", 与规则打架时危害最大(本轮实证:钩子写「不要在结尾甩问题」,用户要「待确认内容放到最后」,两句话直接对撞)。 收口 = 上表逐格确认 + 两副本 md5 一致 + 镜像 md5 一致 + 七件套全绿。
📂 档案模板(8 段结构)已下沉 →
dsh-change-workflow/01-档案模板.md
红线 R1-R11 原文速查
来源 = 工作区
CODEBUDDY.md§3 的可移植快照(dsh-env-bootstrap/references/常驻规则-快照.md,权威单向)。 ⛔ 编号即红线号,任一条命中 ⇒ 先停手;效率论证不构成豁免。
- 禁止启动 dsh 时自动获取最新版本;版本升级独立流程(档案 07;/usr/local/etc/npmrc
update-notifier=false已全局生效)。 - 不改官方 dsh 主程序与缓存(
/usr/local/lib/node_modules/@deepseek-ai/dsh及依赖);扩展只走 profile 层官方插件机制。 - client bundle 严禁
exports.default = apply(或任何 default 函数导出)——详见上文 R3 解释(@dsh-local/portal-entry v0.4.0→v0.4.1 实证教训)。 - R4 禁止借真实账号(
admin/guest)的会话跑测试:⛔ 旧理由("登录 last-wins 会删该账号全部旧会话、把用户踢下线")自 2026-09-20 起已失效 —— 序47 已改为多会话并存(04-调整方案/145-…md)。仍然禁止的三条理由:① 借来的会话办不了该用户自己的实例操作 ② 污染真实账号的会话列表与audit_log③ 拿真实账号去验证logout-all/revoke会真把人踢下线。改用mksess.cjsPG 直插临时 session 或专用测试账号(档案 15 实证教训 + 序47 复核)。 - 权限可见面只准收窄,扩大必须先确认(2026-09-11 用户新增,R5)——见下节「R5 权限扩大门禁」。
- R7|禁止未经确认的批量 / 全仓写入(2026-09-12 用户新增红线)—— 见下节「R7 批量写入门禁」。
- R8|生产变更知会(2026-09-12 立 · 2026-09-21 修订)—— 开发环境服务器 ⇒ 直接做,只需动手前一句说明 + 攒批;⛔ 不是"先取得确认",见下节「R8 生产变更知会」。
- R9|⛔ 绝对禁止「人工删锁 / 接管」 —— 不得删锁目录、不得以「持有者疑似已死 / 卡住」为由单方面接管;锁只能由持有者自己释放(抢不到 = 终点 ⇒ 只读、报告、结束)。
- R10|⛔ 绝不以 root(或非该实例 uid)运行 / 触碰用户实例的东西 —— ① 验证 / 冒烟 / 探针必须以该 uid 运行或进沙箱,⛔ 禁 root 直跑 profile;② ⛔ 不用 root 去读 / 写 / 删用户实例的 home 与数据目录。
- R11|⛔ 只做正向迭代 —— 判据(十维:目标 / 方向 / 架构 / 功能 / 性能 / 安全 / 交互 / UI / 便利性 / 扩展性):是否让任一维净变差?命中 ⇒ 立即停下复盘(写清劣化在哪一维 / 代价多大 → 找保住正向收益的做法 → 拿不出就停手只报告);⛔ 三种伪装禁止:必要代价 / 后续再优化 / 藏进交付不写。
📂 R7 / R8 / R5 三条红线的由来与完整判据已下沉 →
dsh-change-workflow/02-红线详解-R5-R7-R8.md(要判「算不算批量写入 / 要不要先知会 / 这次是扩大还是收窄」时必读)
📂 Profile·Skill 装载与管理面三节已下沉 →
dsh-change-workflow/03-沙箱与技能机制.md(Profile 层 cordis patch 机制 · Skill 装载机制(bundledSkillDir)· Skill 管理面(编排器 API + 静态页))
📂 通用运维锚点已下沉 →
dsh-change-workflow/04-运维锚点与取证.md(重启用锚点 · 存量会话档位体检 · 功能插件启用:探活·快照回滚·逐插件隔离 · 会话记录取证)
本机 Git Bash 环境坑(2026-09-11 实证,几乎每次都踩)
- PATH 常坏:
Bash工具里dirname/head/ls报command not found,且ssh不在 PATH 里(私钥真名是~/.ssh/id_ed25519,⛔ 没有id_ed25519_dsh这把)。每次先前置:P=/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0 export PATH="$P/usr/bin:$P/mingw64/bin:/c/Windows/System32:/c/Windows:$PATH" - node 是 Windows 程序,不认
/c/...msys 路径(会拼成d:\c\Users\...)。脚本里写路径要用C:/Users/...;而ssh/scp/cp这些 msys 程序要用/c/...。 - 中文路径下发文件不可靠 → 中转文件先
cp到纯 ASCII 路径(C:/Users/Administrator/AppData/Local/Temp/wb-scratch/)再scp。 - heredoc 通道 UTF-8 安全:
ssh HOST 'cat > /tmp/x' << 'EOF'(引号定界符)可原样传输中文,已多次验证;但别在 heredoc 定界符后再接' 2>/dev/null之类的引号拼接,会 EOF 报错 —— 复杂脚本改成「本地写 → scp → 远端 node 执行」。 - PowerShell 工具在本会话输出被吞(命令成功但无 stdout)→ 本地操作优先用 Bash(修好 PATH 后)。
.bak-*可能已被 git 跟踪(历史提交过):不要批量挪动/删除它们,否则产生意外的D变更;只做定向git add。- 🔴 沙箱
node-safe-delete会拦构建工具的emptyDir(2026-09-25 实证):vite build清空outDir时会对每个被删文件走 node 的rmSync,只要总数 > 阈值 50 就整批拒绝(报[safe-delete][SAFE_DELETE_BULK_CONFIRM_REQUIRED] …)⇒ 构建中止。正解 = 构建前先把旧产物目录mv走(emptyDir对不存在的目录是空操作),⛔ 不要以为构建脚本或依赖坏了。 - 🔴 Windows 侧直连 WSL2 靶机的回环端口(2026-09-25 实证):靶机实例监听
127.0.0.1:<port>时,Windows 上curl http://127.0.0.1:<port>/可直接到达(wslrelay.exe在做 localhost 转发,netstat -ano里看到的就是它)⇒ ⛔ 不需要再ssh -L建隧道(建了也不报错,只是白建;旧记录里的tunnel.log全是Connection refused就是这个原因)。
📂 数据源与插件口径已下沉 →
dsh-change-workflow/05-插件与数据源口径.md(外部数据源缓存必须带结构版本守卫 · 官方白名单插件来源 · 技能 vs 插件怎么区分 · 术语与环境约定)
📂 沙箱与权限预设机制已下沉 →
dsh-change-workflow/03-沙箱与技能机制.md(改沙箱/权限默认值前必读)
📂 实例可见面与边界整章已下沉 →
dsh-change-workflow/06-实例可见面与共享边界.md(bwrap 挂载面铁律 · 🔒 基础运行时版本冻结 · 📦 实例共享工具(jq/ripgrep/ffmpeg)· 资源与网络边界 · 只读诊断配方 · 📋 实例可访问路径清单 · 技能上传安全)
📂 技能/插件区分判据与术语约定已下沉 →
dsh-change-workflow/05-插件与数据源口径.md
📂 功能插件启用与对话取证已下沉 →
dsh-change-workflow/04-运维锚点与取证.md
多任务并行调度协议(2026-09-11 用户提出,长期沿用)
核心结论:可以并行,但并行度由「冲突域」决定,不由任务数量决定。 只读/调研类可自由并行;改码 → 构建 → 重启 → 提交 → 写文档是单通道,必须串行。
📂 并行调度详解已下沉 →
dsh-change-workflow/07-并行调度详解.md(冲突域清单 · 批次模型与依赖处理 · 反例 · 落地机制:三把锁--claim-exec/.doing-<单号>/ 服务器侧/opt/dsh/state/.op-lock)
详情索引(references/)
本技能 = 主干(本文件)+ 详情档。主干只留判据 / 流程主干 / 命令骨架;长表、案例、实测记录、历史细节在下面各档。
跨档引用怎么查:主干与详情档正文里出现的「§N」「见 §8 坑 N」「见下表 / 见上表」等编号,按本表的「覆盖的原章节」列定位到对应档(下沉后原编号不再有独立章节标题)。
| 详情档 | 覆盖的原章节 | 原行段 | 行数 |
|---|---|---|---|
dsh-change-workflow/00-平台速查.md |
平台速查(硬编码事实,勿猜) | L14–L92 | 79 |
dsh-change-workflow/01-档案模板.md |
档案模板(04-调整方案/-<标题>.md) | L391–L403 | 13 |
dsh-change-workflow/02-红线详解-R5-R7-R8.md |
R7 批量写入门禁 · R8 生产变更知会 · R5 权限扩大门禁 | L414–L480 | 67 |
dsh-change-workflow/03-沙箱与技能机制.md |
Profile 层 cordis patch 机制 · Skill 装载机制 · Skill 管理面 · dsh 沙箱与权限预设机制 | L481–L504 + L555–L600 | 70 |
dsh-change-workflow/04-运维锚点与取证.md |
通用运维锚点(重启用) · 功能插件启用:探活 · 快照回滚 · 逐插件隔离 · 会话记录取证 | L505–L525 + L935–L998 | 85 |
dsh-change-workflow/05-插件与数据源口径.md |
外部数据源缓存必须带结构版本守卫 · 官方白名单插件来源 · 技能 vs 插件:怎么区分 · 术语与环境约定 | L539–L554 + L907–L934 | 44 |
dsh-change-workflow/06-实例可见面与共享边界.md |
实例可见面 · 软件共享 · 网络与安全边界 · 铁律:实例能看到什么,完全由 bwrap 挂载面决定 · 🔒 基础运行时版本冻结 · 📦 实例共享工具 · 资源与网络边界 · 只读诊断配方 · 📋 实例可访问路径清单 · 技能上传安全 | L601–L906 | 306 |
dsh-change-workflow/07-并行调度详解.md |
冲突域清单 · 批次模型与依赖处理 · 反例(同域并发的具体破坏形态) · 落地机制 | L1003–L1052 | 50 |
dsh-change-workflow/08-浏览器验证栈详解.md |
正确调用(Windows,2026-09-11 实测可用) · ⛔ dsh composer(Lexical)输入难点 · 三个必踩坑 | L231–L293 | 63 |
变更历史(迁移前 · 自 frontmatter 移出以保证 YAML 合法)
原文照抄,仅为保留历史;其中的宿主路径与脚本位置是当时的旧值。
【2026-09-22 按要求统一版本号】frontmatter `version` → `1.0.0`(原 v2.9.2-playwright-banned);正文与历史中的版本号为当时记录,未改动。此前 【2026-09-12 新增「三把锁」落地机制 + 更正过期指向】把「多任务并行调度协议」从**设计**补成**可强制执行的判据** —— 全局执行锁 `05-交接单/.exec-lock`(同一时刻只允许一个执行会话)+ 单级占用锁 `.doing-<单号>` + **服务器侧操作锁 `/opt/dsh/state/.op-lock/`**(管生产态:重启 / drain / 改实例 env·quota / 批量铺插件 / 改 nginx·nft·证书,因「服务器态变更看不出来」故必须显式加锁);工装 `07-scripts/handoff-guard.sh`(新增【1d】分支)与 `07-scripts/op-lock.sh`。同时**更正产出闭环里的过期指向**:原文写 `docs-status/文档库状态备注.md` + `bash 07-scripts/docs-status-sync.sh --pull`,**该机制已不存在** → 改为 `INDEX.md §二` 登记 + 收尾四件套(audit → manifest → sync-check → `PUSH=1 handoff-guard`)。出处:当日 3 次并行事故 + `04-调整方案/69`。此前】**R7 禁止未经确认的批量 / 全仓写入**(由来:为"让 scp 出去的文件行尾干净",用脚本把 **147 个文件** CRLF→LF —— 当期只被要求改一个 UI 字符串;被用户指为「容易把服务器搞崩」,且随后 `cp -r` 确实把**未改动的** 2 个文件覆盖到了服务器)+ **R8 会中断在线用户的生产变更须先知会**(由来:档案 58/59 连续两次重启,直接引发用户"会话连接异常"报障)。【2026-09-11 晚,用户当面纠正后大修】① **阶段 0 新增第 0 条「开工前置检查」并标明"本技能最重要的一步"**(装工具/写自动化/查机制前先看:可用技能列表 → 本机/项目已有技能与记忆 → "本机已有的能否满足")+ 红线 **R6「先查已有资产,再动手」**;② **浏览器自动化定型**:用 `browser-harness`(CDP 9223,Windows 调用方式 + helper 清单),**禁再装 agent-browser / Playwright**;⛔ **铁律:browser-harness 附着的是「用户正在用的 Chrome」→ 动手前先 `list_tabs()`,不是全新空浏览器就立刻停手**(本轮真实事故:在其浏览器里登录测试号 → 覆盖用户 `.alotbuy.com` 的 `sid`);dsh composer 是 Lexical(`data-lexical-editor`),`type_text`/`fill_input` 均未生效;③ **更正「空闲回收」认知**:回收**从未生效**(TTL 默认 7 天 / cap 4 / 每请求 touch),但 **"回收没生效"≠"实例不会中断"** —— 中断真凶 = **服务重启(09-11 达 48 次)→ 旧实例变孤儿 → 另起新实例 → 旧页面 401** + **实例崩溃自动重启(近 4 天 69 次;admin 根因 `duplicate loader entry id: permission`)**;附三数核对手法;④ 阶段 1 新增「**用户报障第一步:先读 journal 拿真实失败请求**」(三分钟定位,判据速查 401/502/crash);⑤ 阶段 4 新增「**验证用例必须同时覆盖「有请求体」与「无请求体」**」(档案 51 事后修正实证:只测 POST 会整条漏掉 GET/SSE)+ 「模拟浏览器语义要测到底」(只带最初旧 cookie、显式断言 Set-Cookie 回写);⑥ R4 补**专用测试账号模板**(`role='active'`、`approved_by` 需真实 admin id;`users` 表无 `status` 列)。此前新增「**本环境 TLS 走中间人代理 → Python 脚本必须 SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt**」(两份 CA bundle 的区别 + 零平台改动正解 + 为何不走 R5 注入 env)+「职责边界:技能投放=admin 的事,平台只提供机制与把关」;此前 R5 追加「**安装类操作=扩大,必须先出安装确认清单(七项)**;技能/插件依赖体检只报告+拦截、绝不代装」;另把「复杂引号一律本地写文件→scp(禁 ssh 内 inline node -e/sed)」从建议升为**硬性要求**(本轮同类错误连犯 4 次)。此前补环境事实「正确域名 alotbuy.com(dsh.alotbuy.com 是旧域 301,抓页面只会拿到 301 HTML)」+ 档案 45 存量会话提示落点(login.html);此前新增「**基础运行时版本冻结**」节(档案 44:唯一缝隙=Python user-site 会盖平台包;修法 PYTHONNOUSERSITE + PYTHONUSERBASE 双保险;版本漂移巡检 07-scripts/runtime-baseline.cjs + cron 05:10);补「3.6 不开放」的正解(引导优先,遮蔽不可行)+ 属主自愈(ws-cleanup --reclaim-only,平台 root 跑 pnpm 污染用户 ws);**R5 已立**。此前新增红线 R5「权限可见面只准收窄,扩大必须先确认」**(含「R5 权限扩大门禁」整节:扩大/收窄方向判定表、权限影响评估四问、触发文件清单、以「实例可访问路径清单」做 diff 验收);阶段 2 红线自查与影响评估同步加入 R5 门禁;此前新增「**无法遮蔽 /usr 内文件**」(ro-bind 目录无法创建挂载点 → 叠加遮蔽会让实例起不来;判据「只有先 tmpfs 再白名单的目录才可自由增删」+ 改 bwrap 参数后必须真启动实例验证)+「**实例共享 Python 运行时**」(可移植 3.12 装到 /usr/local/dsh-runtime,迁移打包单元,pip 落点与护栏)+「平台 root 污染用户工作区」待治本项;【档案 39 事后修正】`/etc` 白名单补 **`/etc/alternatives`** —— 它是「软链枢纽目录」,漏掉后 `/usr/bin/python3` 两跳软链断裂,**21 个命令静默 command not found**(python3/pip3/ld…);新增判定准则「必须枚举整条链条会穿过 /etc 的条目」+ 探测器命令 + 「白名单类改动必须跑工具清单前后对比」;此前新增「**技能上传安全**」节(zip symlink 是**元数据**攻击面 → scanDir 内容扫描天然看不到;必须同时覆盖「内容」与「元数据/文件类型」两维度;三项加固);API 表扩到 enable/disable/delete 与 `locked` 语义;安全判据改为「**分清谁在执行**:运行=用户沙箱=自伤 / 上传解压=root=平台风险」;**撤回**我上一轮「scanDir 只告警不拦截」的错判(它内部会对 P0 throw 400);此前更正「bundled 层已修复(档案 40,commit 37e016f)」+ 实例可访问路径清单补 `bundled-skills` 行 + 判据「实例内 resolve+读盘的资源必须真的挂进命名空间(只注入 env 无效)」;此前新增「**实例可访问路径清单(权威版)**」表(能读/读不到/残留三类,写文档与答用户按此口径)+「`/etc` 白名单」(14 项最小集 + symlink 须 realpath 绑目标 + 222→13/576→27 实测)+「**H5 红线:OUTPUT 链按目的地址封本机服务=自伤**」(症状指纹 timeout vs refused、必须只匹配主动发起 TCP 纯 SYN / UDP ct state new、验证闭环三条);bwrap 参数照抄清单与只读诊断配方同步改为 `--tmpfs /etc`;出网护栏补 H1/H5 与一键复现脚本;技能投放通道补 API↔层映射表·平铺要求·宿主基线约束
变更历史(原 frontmatter · 逐字保留)
name: dsh-change-workflow
description: DSH 平台项目的功能改造与优化工作流:需求识别、调研、规划、开发、验证、归档清理的完整阶段链,含红线判据、备份先行、append-only 日志、交付门禁与「本机改完 ≠ 交付」的生效链路。触发场景:用户提出平台需求 / 改造 / 优化 / 缺陷修复 / 功能调研,问「按既有流程执行」「这件事怎么落地」「我做完了吗」,或要把一次性处理沉淀成可复用的处理机制时。
version: 1.0.0
updated_at: 2026-09-15
agent_created: true
变更历史(对侧副本 frontmatter · 逐字保留 · 来自 dsh-change-workflow 的 本机 版)
⚠️ 本档正文取自另一侧(超集);此处补上对侧副本的版本史,⛔ 以保证不丢任何事实。
name: dsh-change-workflow
description: dsh 多租户平台(ai1net.com / dshs,服务器 47.77.182.89)功能改造与优化任务的完整工作流。当用户提出平台需求、改造、优化、缺陷修复、功能调研,或要求"按既有流程执行/沉淀/整理处理机制"时触发。核心:需求识别→调研→规划→开发→验证→归档清理 六阶段 + 红线机制(不自动升级 dsh / 不改官方主程序 / client bundle 禁 exports.default / 不用真实账号测登录 / **R7 禁未经确认的批量·全仓写入** / **R8 会中断在线用户的生产变更须先知会**)+ 备份先行 + append-only 日志 + 表格式交付 + 文档「服务器唯一源 + docs-status 单目录同步」。
version: 1.0.0
updated_at: 2026-09-15
last_change: 【2026-09-22 按要求统一版本号】frontmatter `version` → `1.0.0`(原 v2.9.2-playwright-banned);正文与历史中的版本号为当时记录,未改动。此前 【2026-09-12 新增「三把锁」落地机制 + 更正过期指向】把「多任务并行调度协议」从**设计**补成**可强制执行的判据** —— 全局执行锁 `05-交接单/.exec-lock`(同一时刻只允许一个执行会话)+ 单级占用锁 `.doing-<单号>` + **服务器侧操作锁 `/opt/dsh/state/.op-lock/`**(管生产态:重启 / drain / 改实例 env·quota / 批量铺插件 / 改 nginx·nft·证书,因「服务器态变更看不出来」故必须显式加锁);工装 `07-scripts/handoff-guard.sh`(新增【1d】分支)与 `07-scripts/op-lock.sh`。同时**更正产出闭环里的过期指向**:原文写 `docs-status/文档库状态备注.md` + `bash 07-scripts/docs-status-sync.sh --pull`,**该机制已不存在** → 改为 `INDEX.md §二` 登记 + 收尾四件套(audit → manifest → sync-check → `PUSH=1 handoff-guard`)。出处:当日 3 次并行事故 + `04-调整方案/69`。此前】**R7 禁止未经确认的批量 / 全仓写入**(由来:为"让 scp 出去的文件行尾干净",用脚本把 **147 个文件** CRLF→LF —— 当期只被要求改一个 UI 字符串;被用户指为「容易把服务器搞崩」,且随后 `cp -r` 确实把**未改动的** 2 个文件覆盖到了服务器)+ **R8 会中断在线用户的生产变更须先知会**(由来:档案 58/59 连续两次重启,直接引发用户"会话连接异常"报障)。【2026-09-11 晚,用户当面纠正后大修】① **阶段 0 新增第 0 条「开工前置检查」并标明"本技能最重要的一步"**(装工具/写自动化/查机制前先看:可用技能列表 → 本机/项目已有技能与记忆 → "本机已有的能否满足")+ 红线 **R6「先查已有资产,再动手」**;② **浏览器自动化定型**:用 `browser-harness`(CDP 9223,Windows 调用方式 + helper 清单),**禁再装 agent-browser / Playwright**;⛔ **铁律:browser-harness 附着的是「用户正在用的 Chrome」→ 动手前先 `list_tabs()`,不是全新空浏览器就立刻停手**(本轮真实事故:在其浏览器里登录测试号 → 覆盖用户 `.alotbuy.com` 的 `sid`);dsh composer 是 Lexical(`data-lexical-editor`),`type_text`/`fill_input` 均未生效;③ **更正「空闲回收」认知**:回收**从未生效**(TTL 默认 7 天 / cap 4 / 每请求 touch),但 **"回收没生效"≠"实例不会中断"** —— 中断真凶 = **服务重启(09-11 达 48 次)→ 旧实例变孤儿 → 另起新实例 → 旧页面 401** + **实例崩溃自动重启(近 4 天 69 次;admin 根因 `duplicate loader entry id: permission`)**;附三数核对手法;④ 阶段 1 新增「**用户报障第一步:先读 journal 拿真实失败请求**」(三分钟定位,判据速查 401/502/crash);⑤ 阶段 4 新增「**验证用例必须同时覆盖「有请求体」与「无请求体」**」(档案 51 事后修正实证:只测 POST 会整条漏掉 GET/SSE)+ 「模拟浏览器语义要测到底」(只带最初旧 cookie、显式断言 Set-Cookie 回写);⑥ R4 补**专用测试账号模板**(`role='active'`、`approved_by` 需真实 admin id;`users` 表无 `status` 列)。此前新增「**本环境 TLS 走中间人代理 → Python 脚本必须 SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt**」(两份 CA bundle 的区别 + 零平台改动正解 + 为何不走 R5 注入 env)+「职责边界:技能投放=admin 的事,平台只提供机制与把关」;此前 R5 追加「**安装类操作=扩大,必须先出安装确认清单(七项)**;技能/插件依赖体检只报告+拦截、绝不代装」;另把「复杂引号一律本地写文件→scp(禁 ssh 内 inline node -e/sed)」从建议升为**硬性要求**(本轮同类错误连犯 4 次)。此前补环境事实「正确域名 alotbuy.com(dsh.alotbuy.com 是旧域 301,抓页面只会拿到 301 HTML)」+ 档案 45 存量会话提示落点(login.html);此前新增「**基础运行时版本冻结**」节(档案 44:唯一缝隙=Python user-site 会盖平台包;修法 PYTHONNOUSERSITE + PYTHONUSERBASE 双保险;版本漂移巡检 07-scripts/runtime-baseline.cjs + cron 05:10);补「3.6 不开放」的正解(引导优先,遮蔽不可行)+ 属主自愈(ws-cleanup --reclaim-only,平台 root 跑 pnpm 污染用户 ws);**R5 已立**。此前新增红线 R5「权限可见面只准收窄,扩大必须先确认」**(含「R5 权限扩大门禁」整节:扩大/收窄方向判定表、权限影响评估四问、触发文件清单、以「实例可访问路径清单」做 diff 验收);阶段 2 红线自查与影响评估同步加入 R5 门禁;此前新增「**无法遮蔽 /usr 内文件**」(ro-bind 目录无法创建挂载点 → 叠加遮蔽会让实例起不来;判据「只有先 tmpfs 再白名单的目录才可自由增删」+ 改 bwrap 参数后必须真启动实例验证)+「**实例共享 Python 运行时**」(可移植 3.12 装到 /usr/local/dsh-runtime,迁移打包单元,pip 落点与护栏)+「平台 root 污染用户工作区」待治本项;【档案 39 事后修正】`/etc` 白名单补 **`/etc/alternatives`** —— 它是「软链枢纽目录」,漏掉后 `/usr/bin/python3` 两跳软链断裂,**21 个命令静默 command not found**(python3/pip3/ld…);新增判定准则「必须枚举整条链条会穿过 /etc 的条目」+ 探测器命令 + 「白名单类改动必须跑工具清单前后对比」;此前新增「**技能上传安全**」节(zip symlink 是**元数据**攻击面 → scanDir 内容扫描天然看不到;必须同时覆盖「内容」与「元数据/文件类型」两维度;三项加固);API 表扩到 enable/disable/delete 与 `locked` 语义;安全判据改为「**分清谁在执行**:运行=用户沙箱=自伤 / 上传解压=root=平台风险」;**撤回**我上一轮「scanDir 只告警不拦截」的错判(它内部会对 P0 throw 400);此前更正「bundled 层已修复(档案 40,commit 37e016f)」+ 实例可访问路径清单补 `bundled-skills` 行 + 判据「实例内 resolve+读盘的资源必须真的挂进命名空间(只注入 env 无效)」;此前新增「**实例可访问路径清单(权威版)**」表(能读/读不到/残留三类,写文档与答用户按此口径)+「`/etc` 白名单」(14 项最小集 + symlink 须 realpath 绑目标 + 222→13/576→27 实测)+「**H5 红线:OUTPUT 链按目的地址封本机服务=自伤**」(症状指纹 timeout vs refused、必须只匹配主动发起 TCP 纯 SYN / UDP ct state new、验证闭环三条);bwrap 参数照抄清单与只读诊断配方同步改为 `--tmpfs /etc`;出网护栏补 H1/H5 与一键复现脚本;技能投放通道补 API↔层映射表·平铺要求·宿主基线约束
agent_created: true