文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。
IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。
插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。
仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
42 KiB
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
dsh-change-workflow — dsh 平台改造工作流
dsh 多租户平台(服务器 47.77.182.89,dshs + dsh 0.1.2-rc.1)功能改造的标准处理方法。把平台运维历史中反复验证过的处理方式固化为可执行流程:先识别边界 → 源码级调研 → 方案对比确认 → 小步开发 → 全链路验证 → 归档清理。
📂 平台速查表(域名 / 生效路径 / 端口 / 存量事实)已下沉 →
references/00-平台速查.md⛔ 两条留在这里的判据:① 正域名 =ai1net.com(alotbuy.com是已降级的 301 旧域 —— 抓它只会拿到 301 HTML,别误判为「改动没生效」);② 平台页由门户直出(127.0.0.1:3080),生效副本只有/opt/dshs/web/一份。
六阶段流程
阶段 0 · 需求识别(先盘点再动手,勿跳步)
-
⛔ 开工前置检查(2026-09-11 用户点名要求,血的教训):这是本技能最重要的一步。 病根 = 不先查「已经有什么」就凭直觉装工具/猜机制;出错后要用户追问,才把早就写在文档里的说明翻出来 (最刺眼的一次:我早在技能里写了「勿走 agent-browser」,自己却忘了,绕最远的路)。
任何「装工具 / 写自动化 / 查机制」之前,按序做完这三条:
- 先看可用技能列表(会话开始就在)→ 有对应的立刻加载技能,禁止另起炉灶;
- 先看本机/项目已有资产:
~/.workbuddy/08-skills/、项目.workbuddy/08-skills/(尤其本技能)、MEMORY.md; - 要装任何东西前自问:本机已有的能否满足? 能→不装;不能→先说清楚再装。
→ 固化结论(别再重新发现):浏览器自动化只允许两件 ——
agent-browser(默认,独立浏览器) 与browser-harness(看用户现场用,先list_tabs());⛔ 禁止 Playwright /playwright-core(2026-09-13 用户明令); 静态页与 API 取数用curl/WebFetch,别动浏览器。 -
环境盘点:本机/服务器/工具链/权限/DB/实例状态先行(历史教训:未盘点就默认本机装 Docker 走错路线,被用户批评)。
-
需求边界:区分"方案请求"(只输出方案,P0 硬规则:方案与执行分离,未经明确授权不改文件)与"任务明确直接执行"。
-
用户确认:关键分叉用选项表收敛(AskUserQuestion:推荐项置首标注 "(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 事后修正实证):
只测
-
浏览器验证栈(2026-09-11 更正为非侵入式,勿走 agent-browser):
⛔ 铁律 0:动手前先
list_tabs()确认「你在驱动哪个浏览器」browser-harness 会附着到本机正在运行的用户日常 Chrome(CDP 9223)——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);curl --noproxy '*' http://127.0.0.1:9223/json/list直接证实 9223 就是用户浏览器的调试端口。所以:脚本第一步永远是
print([t["url"] for t in list_tabs()])。- 只要看到的不是你自己新开的空浏览器 → 立刻停手,只做只读检查。
- 禁止在未确认隔离前做任何有副作用的动作(登录 / 输入 / 提交 / 关闭标签)。
- 已造成的真实事故:在用户浏览器里
fetch('/api/auth/login')→ 覆盖其.ai1net.com的sid,用户在该浏览器里变成测试账号身份。 补救 = 删掉测试账号(cookie 失效 → 用户重新登录即可)。收尾必须close_tab关掉自己开的标签。
📂 浏览器验证栈的调用细节已下沉 →
references/08-浏览器验证栈详解.md(正确调用 · dsh composer(Lexical)输入难点 · agent-browser 旧实测记录 · 独立 headless 定型做法 · 三个必踩坑) ⛔ 留在这里的判据:只用agent-browser/browser-harness(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 两次撞号实证):"取号 + 写档案 + 改
-
三层沉淀(用户习惯,必做):
- 治本层:机制/流程固化进 skill/档案(本次即本 skill)
- 失误层:教训 append 到
.workbuddy/memory/YYYY-MM-DD.md(append-only,不改写历史) - 记忆层:长期约定/红线写
.workbuddy/memory/MEMORY.md(≤3000 字符/会话)
-
汇总表:带状态/结果列交付,列明未完成项与副作用。排版按
dsh-feature-first §5.4(首屏给判定 / 层级≤3 / 每节≤7 行 / 表格≤5 列 / 一条信息只说一次;细节进附录)。 -
技能自身归档(本 skill 有改动时必做):工作副本在本机
.workbuddy/08-skills/<name>/SKILL.md(技能必须本地加载),服务器归档位在/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 段结构)已下沉 →
references/01-档案模板.md
红线 R1-R8 原文速查
- 禁止启动 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 用户新增红线)—— 见下节「R8 生产变更知会」。
📂 R7 / R8 / R5 三条红线的由来与完整判据已下沉 →
references/02-红线详解-R5-R7-R8.md(要判「算不算批量写入 / 要不要先知会 / 这次是扩大还是收窄」时必读)
📂 Profile·Skill 装载与管理面三节已下沉 →
references/03-沙箱与技能机制.md(Profile 层 cordis patch 机制 · Skill 装载机制(bundledSkillDir)· Skill 管理面(编排器 API + 静态页))
📂 通用运维锚点已下沉 →
references/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。
📂 数据源与插件口径已下沉 →
references/05-插件与数据源口径.md(外部数据源缓存必须带结构版本守卫 · 官方白名单插件来源 · 技能 vs 插件怎么区分 · 术语与环境约定)
📂 沙箱与权限预设机制已下沉 →
references/03-沙箱与技能机制.md(改沙箱/权限默认值前必读)
📂 实例可见面与边界整章已下沉 →
references/06-实例可见面与共享边界.md(bwrap 挂载面铁律 · 🔒 基础运行时版本冻结 · 📦 实例共享工具(jq/ripgrep/ffmpeg)· 资源与网络边界 · 只读诊断配方 · 📋 实例可访问路径清单 · 技能上传安全)
📂 技能/插件区分判据与术语约定已下沉 →
references/05-插件与数据源口径.md
📂 功能插件启用与对话取证已下沉 →
references/04-运维锚点与取证.md
多任务并行调度协议(2026-09-11 用户提出,长期沿用)
核心结论:可以并行,但并行度由「冲突域」决定,不由任务数量决定。 只读/调研类可自由并行;改码 → 构建 → 重启 → 提交 → 写文档是单通道,必须串行。
📂 并行调度详解已下沉 →
references/07-并行调度详解.md(冲突域清单 · 批次模型与依赖处理 · 反例 · 落地机制:三把锁--claim-exec/.doing-<单号>/ 服务器侧/opt/dsh/state/.op-lock)
详情索引(references/)
本技能 = 主干(本文件)+ 详情档。主干只留判据 / 流程主干 / 命令骨架;长表、案例、实测记录、历史细节在下面各档。
跨档引用怎么查:主干与详情档正文里出现的「§N」「见 §8 坑 N」「见下表 / 见上表」等编号,按本表的「覆盖的原章节」列定位到对应档(下沉后原编号不再有独立章节标题)。
| 详情档 | 覆盖的原章节 | 原行段 | 行数 |
|---|---|---|---|
references/00-平台速查.md |
平台速查(硬编码事实,勿猜) | L14–L92 | 79 |
references/01-档案模板.md |
档案模板(04-调整方案/-<标题>.md) | L391–L403 | 13 |
references/02-红线详解-R5-R7-R8.md |
R7 批量写入门禁 · R8 生产变更知会 · R5 权限扩大门禁 | L414–L480 | 67 |
references/03-沙箱与技能机制.md |
Profile 层 cordis patch 机制 · Skill 装载机制 · Skill 管理面 · dsh 沙箱与权限预设机制 | L481–L504 + L555–L600 | 70 |
references/04-运维锚点与取证.md |
通用运维锚点(重启用) · 功能插件启用:探活 · 快照回滚 · 逐插件隔离 · 会话记录取证 | L505–L525 + L935–L998 | 85 |
references/05-插件与数据源口径.md |
外部数据源缓存必须带结构版本守卫 · 官方白名单插件来源 · 技能 vs 插件:怎么区分 · 术语与环境约定 | L539–L554 + L907–L934 | 44 |
references/06-实例可见面与共享边界.md |
实例可见面 · 软件共享 · 网络与安全边界 · 铁律:实例能看到什么,完全由 bwrap 挂载面决定 · 🔒 基础运行时版本冻结 · 📦 实例共享工具 · 资源与网络边界 · 只读诊断配方 · 📋 实例可访问路径清单 · 技能上传安全 | L601–L906 | 306 |
references/07-并行调度详解.md |
冲突域清单 · 批次模型与依赖处理 · 反例(同域并发的具体破坏形态) · 落地机制 | L1003–L1052 | 50 |
references/08-浏览器验证栈详解.md |
正确调用(Windows,2026-09-11 实测可用) · ⛔ dsh composer(Lexical)输入难点 · 三个必踩坑 | L231–L293 | 63 |