Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下11.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

396 lines
46 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
# 工作日志 · 2026-09(第 12 片)
> ⚠️ **本目录日志已按【月】分片**(2026-09-15 用户定,单文件 ≤50 KB):同月续片 `2026-09.md` / `2026-09-下.md` / `2026-09-下2.md` / `2026-09-下3.md` / `2026-09-下4.md` / `2026-09-下5.md` / `2026-09-下6.md` / `2026-09-下7.md` / `2026-09-下8.md` / `2026-09-下9.md` / `2026-09-下10.md` / `2026-09-下11.md` / `2026-09-下12.md` / `2026-09-下13.md` / `2026-09-下14.md` / `2026-09-下15.md` / `2026-09-下16.md` / `2026-09-下17.md` / `2026-09-下18.md` / `2026-09-下19.md` / `2026-09-下20.md` / `2026-09-下21.md` / `2026-09-下22.md` / `2026-09-下23.md`
> **写入约定**:一律 append 到**当月最后一片**;该片超 50 KB ⇒ 新建 `2026-09-下N.md`;⛔ **不要再按日新建 `2026-09-DD.md`**。
> **覆盖来源**:2026-09-13.md
> **上一片**:`2026-09-下10.md` **下一片**:`2026-09-下12.md`
---
## 07:15–07:35 会话 `a2665dd3`:钩子全量 E 化 + 副作用讲清(用户指令「钩子改为 E 盘路径,后续都不使用 D 盘」)
### 一、★更正:hook 命令 = **会话启动时快照**(我 07:0x 的结论是错的)
- 并行会话 `3c12a818` 做了决定性实验:它 **06:47 启动**(当时配置仍是 D:)→ **06:55 改成 E:** → **07:05 拆掉临时目录联接后,Write/Edit 报的仍是旧路径 `D:/AI技能/...`**。
- ⇒ **改 `settings.json` 对已在跑的会话无效**;我先前"改完立即生效、无需重启"的判断,成因是**它在 06:53 建了 `D:/AI技能 → E:/ProgramData/AI技能` 目录联接**(07:05 已拆),把"能写"伪装成了现读。**同机两会话独立踩同一坑 ⇒ 该误判已是已验证的坑,勿再犯。**
- **正确处置**:改完 hook 路径 → **完全重启 WorkBuddy(关窗 ≠ 退出)或新开会话**。**应急兜底 = 走 Bash**(本钩子有意不拦 Bash)。
### 二、本轮落地:钩子 0 处 D:
- `~/.workbuddy/settings.json` 两条 hook 命令里**解释器仍是 `D:/miniconda3/python.exe`**(脚本路径早已是 E:)→ 已改为 `E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe`。备份 `settings.json.bak-pypath-<HHMMSS>`。
- 校验:**`settings.json` 现在 `D:` 出现次数 = 0**;顶层键 5 个 / hooks 2 个事件完好;JSON 可解析。
- **三组自测(用新命令逐字跑)**:① `SessionStart` 载荷 → 正确输出锁状态(当时显示【空闲】)② 写非保护路径 → 无输出 = 放行 ③ 写文档库(无锁)→ 正确 `deny` + 抢锁指引。⇒ 新命令可用。
- ⚠️ 自测踩坑:git-bash 里把 `/e/...` 传给 Windows python 会被解释成 `e://e//...` → **给 python/node 必须传 `E://...` 或 `E:/...`**(写脚本时同理)。
### 三、还剩哪些 D 盘锚点(活层,共 6 文件少许行;历史档案按「只增不改」不动)
| 文件 | 行 | 内容 |
|---|---|---|
| `dsh-server-docs/scripts/lock-guard-hook.py` | L15 / **L41** | `DSH_CODE_REPO` 默认值 = `D://github//dsh_shenxian`(**钩子第二保护根**) |
| `dsh-server-docs/BRIEF.md` | L70 | 「**代码仓仍为** `D://github//dsh_shenxian`」 |
| `dsh-server-docs/INDEX.md` | L174 | 本机代码副本 = `D://github//dsh_shenxian` |
| 项目根 `CODEBUDDY.md` | L117 | 传文件先转 LF(提到该仓 `core.autocrlf=true`) |
| `.workbuddy/memory/MEMORY.md` | L20 / L38 | 状态层记载 |
| `skills/dsh-change-workflow/SKILL.md` | L335 | 泛称「本机 `D://github` 下的镜像」 |
- **实质未动 = 代码仓本身**:`D://github//dsh_shenxian`(**142 MB** git 仓)—— 它同时是**钩子的第二保护根**与**两个自动化的工作目录**(`5840ce59…` 代码仓三方同步 / `1e1db4eb… 遗留项自动推进`)。是否迁到 E: **待用户拍板**(迁移会牵动自动化 cwd,且本会话 `automation_update list` 返回空 ⇒ 自动化改配置这条路我这边走不通)。
- 现状:**全局执行锁已空闲**(并行会话已释放,`.exec-lock` 目录已无);服务器 op-lock 亦空闲。
### 四、本轮写入方式
- 因快照生效,**本会话 Write/Edit 仍被 fail-closed 拒绝**(实测 07:08/07:09 连续三次)⇒ 本轮所有文件写入改走 shell(钩子有意留的兜底通道),内容与格式与 Write 等价。
## 07:17–07:35 会话 `a2665dd3`(锁名 `stale-ledger-fix-0717`):账实不符修正 + 钩子误伤白名单
**用户裁定**:代码仓 `D://github//dsh_shenxian` **位置不改** ⇒ 钩子 `CODE_REPO` 保持 D: 是**正确值**(非残留);"钩子 0 处 D:" 已达成(仅剩的解释器也已换 E:)。
**落地 5 项(均在文档库内,持锁 `stale-ledger-fix-0717`,已反序释放)**:
| # | 文件 | 改动 |
|---|---|---|
| 1 | `交接单/README.md` §一 T03 行 | 池内版本 `0.3.2` → **`0.3.9`**(附 0.3.2→0.3.9 的连修链;admin 已装并验通 / **guest 仍未启用**) |
| 2 | `BRIEF.md` §3 T03 行 | `@0.3.1` → **`@0.3.9`** + 版本演进链 + guest 未启用 |
| 3 | `BRIEF.md` §4 | 「池内现仅 `dsh-univer-office`」→ 补「09-13 起另有 `dsh-plugin-mcn-suite @0.3.9`」 |
| 4 | `03-路线图与待办.md` §二 | P1「摘除 guest 的 `@liustack/modlens`」→ **✅已消解**(实测 bundles 与 node_modules 均已无) |
| 5 | `04-调整方案/76` | 文末追加「**修正(2026-09-13)**」:部署已发生(0.2.15 09-12 23:19 进候选池 + 已进 guest bundles);**端到端仍 400 未闭环** ⇒ §七 待办 ① 由"待拍板"改为"复测 + 定位 400" |
| 6 | `scripts/lock-guard-hook.py` | 新增例外:**路径含 `.workbuddy` 目录段一律放行**(修「自动化写自己 memory 被误伤」)+ docstring 说明 |
**验证**:
- 钩子脚本 `py_compile` OK;**A/B 实测**:临时保护根下 `.workbuddy/**` → 放行 ✓ |同根普通文件 → deny ✓ |真实代码库 `.workbuddy\memory\*.md` → 放行 ✓ |真实代码库 `src/*.ts` 无锁 → deny ✓(保护强度未削弱)。
- **四件套**:`docs-audit` rc=0 | `docs-manifest` rc=0 | `docs-consistency` rc=0 | 对账:一致 **123** / 内容不一致 **8** / 仅本地 **4** / 仅服务器 **0**(**无幽灵文件**)。其中不一致的 `交接单/T03`、`04-42`、`04-67` 与仅本地的 `04-74/75` 属**其它会话积压**,我只动了自己那 6 个。
- 我自己产生的副产物 `scripts/__pycache__/`(py_compile 生成)**已清理**。
- **未 scp、未 commit**(§4:用户未发话)。
**踩坑记录**:以 heredoc 传 python 做精确替换时,**我自己把路径锚点过度转义**(`\\` → 实为两个反斜杠)导致 0 命中;改用**无反斜杠锚点**(行内关键词 / 按行号插入)后一次成功。★规则:**锚点里尽量不写反斜杠**。
---
## 07:25 取证:「检测用户回到dsh页面并恢复进程状态」那个会话的最后状态(已查明)
- **会话身份**:`0323bcd8-1a76-4727-bf81-08a480bf02fa`|cwd = `D:/AI技能/aliyun-dsh-server`(**迁移前旧路径**)|创建 **09-12 23:47:19(本地)**。
- **标题来历**:首条消息原文「能否检测到用户是否回到dsh页面,现在自动恢复dsh进程状态的功能吗,体验还是不自然」→ 23:47:19 自动改名「能否检测到用户是否回到dsh页」→ 23:47:24 再次自动改名 **「检测用户回到dsh页面并恢复进」(被截断,非用户手动命名,`isUserDefined=false`)**。
- **最后活动:09-13 00:22:48(本地)**,此后无任何 ACTIVITY 记录 ⇒ **干净收尾,不是"还在跑"**。
- **收尾状态 = 档案 77 全链完成**:代码落地(`proxy.ts` 注入脚本 5 处)→ 编译 → 部署 → `dshs` 重启(00:15)→ 端到端 curl 全绿 → 归档(`04-77` + INDEX + BRIEF + docs-manifest)→ **00:19 推送 4 个文件**到 `/opt/dsh/docs`。**未 commit / 未 push**(用户未要求)。
- **它留下的唯一未闭环项** = **浏览器端观感实测**(档案 77 §四 L5)→ **09-13 07:0x 已由本会话 `3c12a818` 用真 Chrome + 临时会话补做并关闭(10/10 全绿,见上文)**;另有 §八 三项遗留(崩溃熔断可无限重来且无告警等)**仍未处置**。
- **锁**:会话内曾因并发抢锁失败而停手;00:10 经用户点头后 `--release-exec` + `FORCE=1 release univer-fork-deploy`,随后以 `recover-inplace-77` 重占,完工已释放 ⇒ 06:47 复核时**三把锁均空闲**。
- **取证手段(本机可复现)**:`~/.workbuddy/logs/*/edge-sync.log`(`EB_SYNC_ADDED` / `§3.4 RENAME` / `§3.4.1 ACTIVITY`)。⚠️ `~/.workbuddy/workbuddy.db` 的 `sessions` 表**已静态化**(最新 created_at = 09-11 22:32,查不到该 sid);`~/.workbuddy/projects/` 亦**无该 sid 的 jsonl** ⇒ **转录只在云端,本机不可读**,任务态只能靠「共享日志 + 文档库 + 服务端实测」三条腿复原。
## 07:19–07:3x 会话 `48a9c14c`(标题「同步方案规划到会话」):把最新「方案规划」会话同步进本会话(**只读 + 落一份同步包**)
### 一、用户指令与定位
- 用户:「同步“方案规划”到这个会话」→ 查出**两个同名会话** → 用户补「找最新的那个就行」。
- 定位:**最新 = `ba6c2de4-0332-4a5a-a539-9f122481016b`**(09-12 17:32 建,原题「看看dsh项目最新状态」,17:32 与 18:32 两次被用户改名为「方案规划」,最后活动 09-12 19:17,寿命 ≈1h45m,cwd = 旧路径 `D:/AI技能/aliyun-dsh-server`)。**两指标(createdAtMs 与 lastActivityAtMs)一致**指向它。
- 另一个同名 = `ca58e09f`(09-11 23:52 → 09-12 16:54)= **「交接单」机制诞生地**;本机留有它**开头**的转录(`projects/d-AI技能-aliyun-dsh-server/ca58e09f-*.jsonl`,1.19 MB / 208 行 / 10 条消息,仅到 09-12 00:04)。
### 二、⛔ 本机拿不到最新会话原文(三条硬证据,本次实测)
1. `projects/d-AI技能-aliyun-dsh-server/` 最后一份 `.jsonl` mtime = **09-12 07:00**,此后无。
2. 按 sid 组织的四个索引目录**集体停更**:`file-history` 09-11 23:57 / `changes-index` 09-12 06:59 / `artifact-index`·`file-tree-manifests` 09-12 07:00。
3. `edge-sync.log` **只有 push 无 pull**(全文仅一个 `workbuddy.cn/cf-publish/api/publish`)⇒ 无回拉接口。
- 另证伪两条弯路:`cache/conversation-product-spill/` 是 UI 配置产物(`acc-product-config-*.json`),**不含对话**;`workbuddy.db` 的 `sessions` 表已静态化(`ba6c2de4` 不在表内,`ca58e09f` 在)。
### 三、✅ 复原方式与产出
- 用该会话的 **9 个活动点**(17:32 / 17:38 / 17:48 / 18:32 / 18:57 / 18:59 / 19:14 / 19:15 / 19:17)与 `2026-09-12.md` 里 17:32–19:20 的段落**逐一对齐时间戳**,复原出完整推进线:① 17:32 只读现状盘点 → ② 18:35 文档库刷新(四件套 + 有意跳过 3 处)→ ③ 18:46 AnySearch 放弃 + R9 收尾(audit 175/176)→ ④ 19:00 服务器同步 + guard 提速 5 倍 + op-lock 归属加固 → ⑤ 19:16 清理残留(残留 2→0)。
- 产出:**`.workbuddy/session-sync/20260913-同步包-方案规划-ba6c2de4.md`**(11.7 KB:身份卡 / 不可读证据 / 内容复原 / 同窗并行会话单列 / 当前状态对接 / 若要原文的三条路径)。
### 四、纪律遵守
- **全程只读**:未抢锁、未改文档库、未动代码仓、未碰服务器(当时 `.exec-lock` 被 `stale-ledger-fix-0717` 自 07:17 持有 → 按 R9/§6 停手,只读)。
- 本会话 Write/Edit **仍被 fail-closed 拒绝**(07:24 实测:报的仍是旧路径 `D:/AI技能/.../lock-guard-hook.py`)⇒ **本会话 07:19 启动,拿到的 hook 快照仍是旧的**;所有写入**走 Bash**(钩子有意留的兜底通道)。
- 技能更新:`skills/workbuddy-session-forensics/SKILL.md` 新增 **§1b 辅助索引**(四个按 sid 命名的索引目录 + 集体停更判据 + `conversation-product-spill` 不是对话)与 **§5 同名多会话怎么办**(判据 + 同步包交付姿势 + 归属置信度)。
## 07:33–07:50 会话 `a2665dd3`:**univer `/univer-api/state` 400 根因定位(已闭合)**
**前置**:用户已完全重启 → **钩子恢复**(Write 探针成功,印证「快照」语义);但**文档库锁被新会话 `未闭环-0732` 持有**(07:32 起)→ 本轮**只做服务器侧 + 本地源码只读**,未动文档库。
### 现场(实时复现)
- 07:30:57–07:31:03,guest 用户浏览器(`remoteAddress=125.85.124.119`)**每 ≈1 秒**请求
`GET /univer-api/state?file=/var/lib/dshs/users/4092b965-…/ws/MCN短视频创作/抖音爆款视频榜_2026-09-12.univer&sessionId=session-eda867a0-e560-4a02-9d77-dd15ecc9deec`
- 实例内**无 gateway 子进程**;`tmp/` 下无 `.sock`;实例启动于 **00:16:08**(= fork 部署之后)。
### ★真因(代码 + 现场双证,与"改造失败"无关)
1. `src/host/webServer/router.ts:37` → `stateRoute` → `resolveAuthorizedFile`(`session-scope.ts:17-20`)→
2. `resolveExistingUniverPath`(`service/workspace.ts:18-25`)→ `resolveAuthorizedPath(..., **mustExist=true**)` →
3. `realpath(candidate)` 抛 `ENOENT` → `UniverError('path does not exist', 'INVALID_FILE_PATH')`(`workspace.ts:93-95`)→
4. `router.ts:60-70` 把 `INVALID_FILE_PATH` 映射为 **HTTP 400**。
- **实测**:`find $G/ws -name "*.univer"` = **空** ⇒ **那个文件从未落盘**(09-12 20:0x–23:2x 建表时 gateway 起不来 → 内容没写成功)。
- 与会话/环境**均无关**:会话目录 `session-eda867a0-…` 存在 ✓;实例 env `UNIVER_DSH_GATEWAY_SOCKET=auto` ✓、`NODE_OPTIONS=--max-old-space-size=160` ✓。
### 客户端行为(解释了"每秒 400")
- `src/client/hooks/use-univer-state.ts`:`setInterval` **900 ms** 轮询;命中 `isMissingUniverFile`(= `INVALID_FILE_PATH`)时置 `missing` 标志并**继续轮询**(设计如此,语义 = "等文件出现")。
- ⇒ 用户看到的"打不开 + 一直转"= **旧页面在等一个永远不会出现的文件**,不是崩溃、也不是代理没生效。
### 结论与下一步
- **不是改造失败**:fork 的 socket 传输在 Linux 上已验证可用,实例也确已按 socket 模式配置。差的只是**目标文件不存在**。
- **闭环只差一步**:在一个**新会话**里让 agent **重新创建一次表格**(`univer_new`)→ 文件真的落盘 → state 返回 200。旧 viewer 页可直接关掉(否则它会一直 0.9s 轮询)。
- **待用户点头(R8)**:是否要我在 guest 实例里跑一次 `POST /univer-api/gateway/start` 做无侵入验证(会多一个 node 进程;实例现 208/384 MiB,**万一超限会触发实例重启、断当前会话约 1 分钟**)。**未擅自执行**。
- 待办:本条诊断需在**锁释放后**追加进 `04-调整方案/76`(我会做)。
## 07:18–07:45 会话 `15ee0d4f`:同步包(决策方法 `6cd67b31`)+ ★钩子真相修正
**任务**:用户「把之前叫做 **决策方法** 的那个会话信息同步过来」。定位到 `6cd67b31`(**用户亲手改名**「决策方法」,`isUserDefined=true`;09-12 09:32:13 → 22:52:40;cwd = `D://AI技能//aliyun-dsh-server`)。
**原文不可读**(三条硬证据:`projects/` 无该 sid 的 jsonl;`workbuddy.db` 的 sessions 表停在 09-12 07:00;全 `~/.workbuddy` 除日志外 0 命中)⇒ 交付**会话同步包**:`.workbuddy/session-sync/20260913-同步包-决策方法-6cd67b31.md`。
### ★修正两处会误导后续会话的结论
1. **活动配置 = `E://ProgramData//.workbuddy//settings.json`**(`CODEBUDDY_CONFIG_DIR=E://ProgramData//.workbuddy`)。
今天 06:55 / 07:11 两次「修 hooks 路径」改的是 **`C://Users//Administrator//.workbuddy//settings.json`(搬迁遗留副本)⇒ 白改**;
「06:47 会话改完仍报旧路径」的真因是**改错文件**,**不是**"快照粒度"问题。
07:25 已修**活动配置**的两条命令(解释器 + 脚本全 E 绝对路径,`D:/AI技能` 残留 0,**提问闸门条目未动**);备份 `settings.json.bak-20260913-lockpathfix-0725`。
2. **钩子「能调用,但拦不住」(fail-open)**:宿主日志 `[HookExecutor] spawn … cmd=<逐字命令>` 可直接看到宿主实际执行的命令。
改错文件期间 = `spawn → abnormal exit code=2` ⇒ **fail-closed 拒写**(这才是今天「Write/Edit 全被拒」的真根因)。
重启后(`last-launch.json` = 07:30:01)宿主**已用新命令**(spawn 无 abnormal exit),但**受保护路径 + 无锁的写入仍成功**、`lock-hook.log` 无 `PreToolUse-deny` 行;
而**同一命令 + 合成载荷手工跑能正确 deny 并写日志**(07:31:26)⇒ 钩子进程走了"放行"分支(脚本对读不懂的载荷 fail-open)。
**待办**:给 `scripts/lock-guard-hook.py` 加一行"原样落盘 stdin"诊断,采一次真实载荷(**脚本改动即时生效、无需重启**;但改它属动本库 ⇒ 须持锁)。
### 取证工具(可复跑)
- 宿主日志:`E:/ProgramData/.workbuddy/logs/2026-09-12/aliyun-dsh-server__*.log` → `grep "HookExecutor"`(spawn / abnormal exit)
- 应用启动时刻:`E:/ProgramData/.workbuddy/last-launch.json`;钩子自证:`<工作区>/.workbuddy/lock-hook.log`
- 会话标题/改名/活动:`~/.workbuddy/logs/*/edge-sync.log`(⚠️ 日志目录名 = 启用那天,新建会话仍要去旧日期目录 grep)
### 本库内动作
- **未改任何既有文件**;只自建过 2 个探针(`dsh-server-docs/_gate_probe_A.md`、`_gate_probe_sync.md`)—— **已删**。
- **07:32 起全局执行锁被 `未闭环-0732`(会话 `a2665dd3`)持有** ⇒ 按 §6/R9 未抢锁、未动文档库;服务器 op-lock 未动。
- 顺手(非本库):修技能 `workbuddy-session-forensics §4` —— 旧版教人去改 `~/.workbuddy/settings.json`(**错的**)→ 改为「先 `env | grep CODEBUDDY_CONFIG_DIR` 定位活动配置」+ 新增「宿主日志校验法」。
- **未 commit / 未 push / 未 scp**。
---
## 07:30–07:55 会话 `3c12a818`(锁名 `未闭环-0732`):按「继续执行未闭环任务」逐项清账
### 一、档案 78(新):崩溃熔断冷却期 + 告警 —— 修档案 77 §八 遗留 1
- **根因**:熔断分支 `resetCrashState()` 清空预算,而 `launch()` 每次又 reset ⇒ **任何**重试路径(用户 F5 / 档案 77 的注入脚本自愈 / `ensure-*.cjs` 直铺 / 并行调试会话)都能让崩溃循环**无限重来**;且熔断只写一行 stderr,无告警。
- **改法(6 文件)**:熔断态 `breaker` Map **跨轮存活**(刻意不被 `resetCrashState()` 清)+**指数冷却**(10 min × 2^(n-1),封顶 6 h);冷却期内 `launch()` 抛 `CrashBreakerOpenError` → `/api/dsh/enter`、`/api/dsh/launch` 返回 **503 `instance_circuit_open`**(带 `retryAfterMs` / `opens`);冷却过后只给一次干净预算;**双通道告警**(stderr `[crash-breaker]` + `/var/log/dsh-crash-breaker.log`)+ `/api/dsh/status` 暴露 `breaker`;平台自身操作(插件启用/隔离)走 `force` 绕开冷却。
- **验证**:`npm run build` rc=0;`node --test test/crash-policy.test.mjs` **10/10**(新增 3 条);`npm test` **45 pass / 0 fail / 1 skipped**。**端到端(真崩实例)待部署窗口**(如实标注,未假装已验)。
- **状态**:✅ 代码完成+本地全绿;⏳ **部署待 R8 窗口** —— 与 **档案 65** 同批做(都需重启)。
### 二、INDEX「状态摘要」改成**机器生成**(修档案 77 §八 遗留 2,防复发)
- 新增 `scripts/docs-index-stats.py`:读 INDEX §二 清单表 → 算分布 → `--write` 就地刷新摘要行,并做**图例自检**(表格用到的图标必须在「图例」里有定义,缺则补)。
- 实况更正:旧摘要写「档案 **72** 份 / ✅54」→ 实际 **档案 77 行 / ✅63 |🔄7 |🧪1 |📝2 |🔍4 |📋2 |🟡1 |🔧1**;**🔧 原先根本不在图例里**(已补)。加入 04-78 后再刷一次。
### 三、账实不符 4 条 → 订正 3 条(第 4 条并行会话已做)
- `交接单/README §一` T03 主述「现行池内 = `@0.3.2`」→ 改 **`@0.3.9`**(历史保留);
- `交接单/T03`:状态行 + ③候选池 + ④剩余 → 「上传已完成、admin 已验通、**剩 guest 启用**」;「同窗口摘 modlens」标注**已消解**;
- `03-路线图` 档案 70 行尾「该能力去留待用户定」→ 标注**已于 09-12 18:56 关闭**(用户选 B · 彻底放弃);
- 档案 76(fork 0.2.15 状态差异)**并行会话已在 07:1x 补「修正」节**,本次未动。
### 四、⚠️ 自己踩的坑(已修,务必记住)
- **python `io.open(...).read()` 是「通用换行」→ 会把 CRLF 静默转成 LF**。本次误伤 4 个文件:`src/config.ts`(312 CRLF)、`INDEX.md`(189)、`03-路线图与待办.md`(109)、`交接单/README.md`(146) —— git diff 一度出现 331/312 行噪声。
- **处置**:**逐文件**还原 CRLF(只在我碰过的文件上,**不是**全库批量转换),复核 `git diff --numstat` 恢复为「只剩真实改动」(如 `config.ts` 19 增 0 删)。
- **规则(新增)**:任何脚本化改写**必须** `io.open(p, encoding="utf-8", newline="")` 读写;插入文本用显式 `\r\n`。
### 五、文档库
- 四件套全绿(audit / manifest / consistency rc=0 + stats --write);只推本次碰过的 **8 个文件** → 对账 **一致 121 → 129**。
- 仍余 **4 不一致 + 3 仅本地 = 其它会话积压**(`BRIEF.md`、`scripts/lock-guard-hook.py`、`04-42`、`04-67`;`04-74/75/76`)—— 本次**未动**。
⚠️ **BRIEF 的本地版含并行会话 07:18 的未推编辑 ⇒ 本次未推 BRIEF**(不替别人推半成品)。
### 六、唯一留给用户拍板的项
- **自动化「遗留项自动推进(DSH 平台)」疑似随工作区迁移失效**:其 cwd 是旧路径;本工作区 `automation_update list` 为空,`logs/automation.log` 里自 **09-12 14:54 那次 failed(interrupted)** 后再无 dispatch(对照:另一条「代码仓三方同步(DSH)」一直在正常跑)。
⛔ **未擅自重建** —— 重建 = 新增一个 8 小时周期的无人看管跑批,且**我不知道它原本的 prompt**;自拟一版可能在无人看管时做出越界动作(commit/push/碰生产)。**要重建请给授权 + 期望行为。**
## 07:36–07:55 会话 `a2665dd3`:**T03 guest 启用失败的完整根因链(3 个缺陷)+ 已修复实例**
**用户报**:T03 投放,guest「启用」遇到插件安装问题、执行失败。
### 现场(journalctl 原文,逐条可核)
- **07:41–07:43**:guest 实例装上 mcn-suite 后反复 `[dsh-plugin-mcn] 榜单查询失败: unable to open database file`。
- **07:43:28 / :46 / :56**:`[dsh-child main]` 侧 **`FATAL ERROR: Reached heap limit`**(`Mark-Compact 155.3(169.1) -> 153.9 MB` 等)⇒ `exitCode 134`(SIGABRT)。
- **07:43:58**:`Error: Command failed: … setpriv … pnpm remove dsh-plugin-mcn-suite --ignore-workspace-root-check …`
→ stderr 解出:**`ERROR Unknown option: 'ignore-workspace-root-check'`**
→ 栈:`runPnpmAs(…:406)` ← `uninstall(…:575)` ← **`business-plugins.js:614`**
→ **`dshs.service: Main process exited, code=exited, status=1/FAILURE`**(整个平台进程退出)→ systemd 4 秒后自动重启(07:44:02)。
- **07:45:19–07:46:16**:服务重启后 guest 继续被拉起 → 每次都在加载插件时 OOM → attempt 1..5 → **`crash-loop-circuit-open`**(熔断)。**两个实例均 inactive。**
### 三个独立缺陷
| # | 级别 | 位置 | 内容 |
|---|---|---|---|
| **D1** | **P0** | `src/web/routes/business-plugins.ts:682`(部署版 `business-plugins.js:614`) | 隔离定位循环里写的是 `try { uninstall(sel.id) } catch {}` —— **缺 `await`**。`uninstall` 是 async,同步 try 抓不到它的 rejection ⇒ **unhandled rejection ⇒ orchestrator 进程退出**(这就是"插件失败把整个平台带崩"的机制) |
| **D2** | P1 | 同文件 `:644`(部署版 `:575`) | `pnpm remove … --ignore-workspace-root-check` —— 该 flag 对 **remove 子命令不存在**(pnpm 9.15.9 报 `Unknown option`;`add` 才有)⇒ **禁用路径必然失败**,进而必然触发 D1 |
| **D3** | P1 | 容量 | mcn-suite 进 bundles 后实例**启动即 V8 堆溢出**:与 `dsh-univer-office` 同堆共存时 **160 MiB 堆装不下**(univer 单独实测 +65 MiB)。⇒ 探活失败 → 平台进"隔离定位"分支 → 触发 D1/D2 |
> 附:平台的 catch(`restoreProfile`)因进程直接退出而**没跑** ⇒ profile 停在**半应用态**(mcn-suite 已在 deps+bundles)⇒ 实例永久起不来(熔断循环)。**这一条本身也是缺陷后果,值得写进档案。**
### 已做的修复(持 op-lock `guest-profile-repair`,已释放)
- 回滚 guest profile:从 `dependencies` 与 `bundles` 同时移除 `dsh-plugin-mcn-suite`(脚本 `_repair_guest_profile.py`,跑完即删);备份 `…/profiles/web/package.json.bak-rollback-20260913T0748`。
- **验证**:临时会话(R4,`deleted_sessions=1`)触发一次 `enter` → 实例**恢复** `dsh web: http://127.0.0.1:39631`、scope `active running`、**无 OOM / 无 crash-restart**、cgroup **186.5 MiB / 384**。⇒ **D3 触发者 = mcn-suite 确认**(移除即好)。
- 清理:服务器与本机临时脚本、临时会话、op-lock 全部清干净。
### 状态复核(用户要求"先检查状态")
- T03 guest 启用:❌ 失败,**profile 已回到启用前**(现在再点一次会原样重演)。
- 平台服务:07:43 崩过一次,已自愈,现 `active`。
- 实例:guest 已恢复 running;admin 未跑(无人访问,正常)。
- 文档库:全局锁仍在 **`未闭环-0732`** 手上(07:32 起)⇒ 本轮**未动文档库**;上述根因待锁后落档 `04-76`/新建档案。
- 档案 76 的 univer 400:诊断已闭合(前一轮)。
## 07:52–08:10 会话 `a2665dd3`:**插件内存成本实测(回答「装得越多越占吗」)**
**方法**(与 09-12 同口径,隔离、不碰生产):`systemd-run -p MemoryMax=700M` + `node --expose-gc` + 与生产一致的 `NODE_OPTIONS=--max-old-space-size=160`;逐项 `await import()`,前后各两次 gc 取 **rss / heapUsed 增量**。lab 目录用软链指向 guest 的 `node_modules`(**不写用户目录**),跑完即删。
| 被测 | rss Δ | heapUsed Δ |
|---|---|---|
| (空 node,base) | 54.8 MiB | 3.7 MiB |
| `@dsh-local/business-plugins` | **+1.7** | +0.2 |
| `dsh-univer-office` | **+63.7** | +33.5 |
| `dsh-plugin-mcn-suite` | **+38.5** | +15.8 |
| 两者同进程先后加载 | +63.1 → 再 +23.2;**合计 141.9 MiB,未 OOM** | — |
- **结论 A(数量 ≠ 成本)**:3 个自研轻量插件合计 +1.7 MiB;**一个**重型就 +38~64 MiB ⇒ 成本由「**顶层静态 import 拉起的依赖图**」决定,不由插件个数决定。
- **结论 B("全是脚本和网页"为什么占内存)**:`lib/client.js`(418 KB) 是**发浏览器**的、不进实例常驻;`skills/`(4.5 MB) 是随包技能文本、按需读。真正吃内存的是 host 侧模块树:V8 要常驻 源码字符串 + 编译产物 + Module 对象/命名空间 + 闭包常量池 ⇒ **780 KB lib 代码 → +38.5 MiB rss(≈20× 放大)**;且 `--max-old-space-size` **管不到 V8 code range/JIT**。
- **结论 C(容量账)**:空载实例实测 ≈285 MiB(09-12 smaps)⇒ 384 MiB 档位余量仅 ~100 MiB,**装 1 个重型即贴顶、装 2 个必崩**(今天 D3 已实证)。建议公式:`cgroup ≥ 堆上限 + dsh 堆外(~50) + Σ插件加载成本 + 页缓存(~18) + 用户任务余量(≥80)`;**建议档位 = 512 MiB / 堆 224**;维持 384/160 则须「每实例 ≤1 个重型插件」;两者都要 ⇒ 只能改懒加载或扩宿主内存(花钱,用户定)。宿主 1.87 GB ⇒ 512/实例只能稳跑 **2 个**常驻实例。
- ⚠️ **实测口径说明**:本表是 **import/入口激活** 的增量,**不含 `apply()` 与运行期**(lab 里 `@deepseek-ai/dsh-llm` 用最小 stub 顶替);生产实例还叠 dsh 本体 + 148 官方插件。故属于**下界**,不是全量。
- 备注:guest 的 `xlsx`/`react`/`react-dom` **顶层并未安装**(suite 自带 node_modules 为空),但 `import dsh-plugin-mcn-suite` 仍成功 ⇒ 其 host 入口对 xlsx 不是顶层静态依赖(属懒加载),这也是**它比 univer 轻 25 MiB** 的原因之一。
---
## 08:00–08:10 会话 `3c12a818`:**先核状态,再执行** —— 档案 78 部署 + 65 生效确认 + anysearch 脚本退役 + 档案 76 生产 400 诊断
### 一、核实到的三条「其实已经完成/不成立」
| 项 | 核实结论 | 证据 |
|---|---|---|
| **档案 65 部署** | ✅ **本就是生效状态** | 服务器 `lib/` **00:13** 构建(含 `searchProvider` 托管段)→ **00:15:23 重启即已载入**;`03-路线图` 里那句「待定窗口」判断有误 |
| **服务 07:44:02 被重启** | ⚠️ **不是我做的** | PID `396808`(00:15) → `410287`(07:44);当时 guest 有活跃用户(07:42:33 登录、07:52 仍有请求)⇒ 疑为用户经门户「服务」页手动重启 |
| **`ensure-anysearch-admin.cjs`「须退役」** | ✅ 风险已很低,且**本次已彻底关闭** | 无 cron/timer 调用;仅被自身与姊妹脚本引用 ⇒ 加**硬拦**即够(见三) |
### 二、档案 78 部署(R8 已执行,08:02)
- **基线安全核验(关键一步)**:逐文件比对「服务器 vs 本机」→ 服务器**仅有 9 行独有内容、且全是被本次重写的 import/export/旧实现行**(orchestrator 9 / spawner 1 / dsh 2)⇒ 服务器版本 == 改动前基线,**不会覆盖别人改动**(此前 automation 说「服务器工作树 3 文件与本地同哈希」,本次再证)。
- 备份 `/opt/dsh/backups/pre78-20260913-075910/`(`lib-pre78` + `src/` + `web/`)→ 上传 6 文件(**全部转 LF**;`config.ts` 本机是 CRLF)→ 服务器 `npm run build` **rc=0** → `restart` → **active / 门户 200 / 孤儿 scope 0**(PID 410287→**413817**)。
- **新代码确在跑的硬证据**:临时会话打 `/api/dsh/status` → 返回 `keys: running,instance,watchdog,breaker,url`(`breaker: null`)⇒ 新字段生效;临时会话已删(`deleted_sessions=1`)。
- `/var/log/dsh-crash-breaker.log` 已建(root 600,服务 root 可写)。
- **R8 影响**:断在线用户 **2–5 秒**(当时 guest 在线);实例引用由档案 72/77 的「回到页面自检 + 就地恢复」自动重建。
### 三、档案 65 §7.3 第 3 条:退役 anysearch 覆写脚本(免重启)
- `scripts/ensure-anysearch-admin.cjs` 头部加**硬拦**:默认 `exit 2`,仅 `DSH_ALLOW_RETIRED_ANYSEARCH=1` 放行(保留考古/回滚用途)。备份 `ensure-anysearch-admin.cjs.bak-retire-20260913`。
- 实测:默认 `rc=2` + 明确文案;显式放行可正常干跑;`node --check` 语法 OK。
### 四、档案 76 的 `/univer-api/state` 400(**诊断到候选根因,未改任何东西**)
- 现象:07:35–07:36 guest 用户 1 秒内被连续 ~10 次 **400**;08:02 后无该调用(用户离开)⇒ 暂未复现。
- **硬线索**:**guest `ws` 下 0 个 `.univer` 文件**(`ws/MCN短视频创作/` 目录在、文件不在)。
- 400 映射(fork `src/host/webServer/router.ts`):`INVALID_REQUEST`/`INVALID_FILE_PATH`/`SESSION_SCOPE_UNAVAILABLE` → 400;权限/scope 拒绝 → 403。
- 寻址校验点(`univerfile-manager.ts`):`:142` key 缺失 / `:146` 不可解码 / `:154` 非 `.univer` / `:217-222` **超出 `allowedRoot`**。
- **两个候选根因未定论**:① 寻址类;② **`SESSION_SCOPE_UNAVAILABLE`**(07:36 距 00:15 重启不久,**旧会话可能已失效** —— 与"反复重试"现象吻合)。
- **下一步(最小代价)**:让该页面带网络面板再开一次,直接读响应体 `{ok:false,code,message}`(一览即定)。
### 五、文档与锁
- 四件套 rc=0;推 6 个文件 → 对账 **一致 129 → 130**;余 4 不一致 + 2 仅本地 = 其它会话积压(BRIEF、lock-guard-hook.py、42、67;74/75)。
- 回填:档案 78(状态→已部署 + 新增 §九 部署记录)、档案 65(状态→已生效 + §7.3 第 3 条标完成)、档案 76(追加生产实测节)、`03-路线图`(关掉 65/78 待窗口两条 + 新增「档案 76 400」P1 待查)、`INDEX`(65→✅、78→已部署)。
- **双锁已释放**:全局执行锁 + `/opt/dsh/state/.op-lock/crash-breaker-78`(⚠️ 释放 op-lock 必须带 `ME=<原占用者>`,否则按 R9 被拒 —— 本次第一次就踩了)。
## 08:06–08:15 会话 `a2665dd3`:**`@dsh-local/business-plugins` v0.2.7 —— 卡片加内存预估 + 列表上方内存状态条(用户要求)**
**用户要求**:不动配额;优化「功能管理」卡片样式、卡片上加「预估所占内存」、卡片可加高;**列表上方加内存占用状态条**(当前占用 + 点击启用时增加预估 + 是否超出上限)。
**改动(只动 client 面,`poc/business-plugins/lib/client.js` 556 → 758 行;host 面与 `inject` 数组一字未动)**
1. **内存模型常量**(新增,放在 `T` 之后):`MEM_LIMIT_MIB=384`(档案 58)/ `MEM_BASE_MIB=285`(空载基线实测)/ `MEM_TABLE={"dsh-univer-office":64,"dsh-plugin-mcn-suite":39}` / `MEM_FALLBACK_MIB=2` / `MEM_TIGHT_RATIO=0.85`;辅助 `memMiB()` / `sumMiB()`。
2. **卡片**:`padding 14/16 + minHeight 112`(原 12/14、无高);信息列新增第三行「预估内存 ≈ N MiB」chip,**按成本分色**(≥60 红 / ≥30 黄 / 其余次要色)。
3. **列表上方「内存预估」状态条**(新组件 `MemBar`):标题 + `当前 X MiB → 勾选后 Y MiB / 上限 384 MiB`(`tabular-nums`)+ 右侧状态词(余量充足 / 接近上限 / ⚠ 将超出上限)+ **双色进度条**(实心 = 服务端当前已启用那批 `savedIds`,浅色 = 本次勾选增量)+ 口径小字「预估(实测基准,非实时读数)」;超限时整条红边 + 追加警告行。
4. **区分「当前」与「勾选后」**:新增 `savedIds` 状态(`load()` 时快照服务端已启用集合)—— 否则 `p.enabled` 被 toggle 就地改掉,就没有"增量"可显示。
5. **确认弹窗**:`applyChanges()` 把内存行与超限警告拼进 `window.confirm` 文案。
6. 双语字典各加 11 个 `mem.*` key(zh/en 各 2 次出现,key 集对齐)。
**验证(全过)**
- `node --check` 语法 OK;R3:`exports.default` 仅出现在注释(0 处代码);只导出 `apply`+`inject`。
- **渲染仿真**(自写临时 harness:极简 React `useState`+`jsx-runtime` 构树 + `__ModuleLoader__`,用完即删):初始 `当前 326 / 勾选后 326`(=285+2+39 ✓)→ 勾选 univer 后 `当前 326 / 勾选后 390`(>384,应转红 ✓)→ 取消 mcn-suite 后 `勾选后 351` ✓。
⚠️ harness 第一版假阳性(每帧重跑 effect → `load()` 覆盖勾选);真实 React 只在挂载跑一次 ⇒ 是 harness 缺陷,不是产品缺陷。
- 打包 `dsh-local-business-plugins-0.2.7.tgz`(13,121 B / 4 文件)→ 按部署口径另存 `business-plugins-0.2.7.tgz`,**md5 `ce8e07f9d2906d70d12a9d000bdf5ac1`**;包内 client.js 四个标记齐全。
**口径与升级路径**:状态条是**预估值**(客户端拿不到 cgroup 实测值)。要做**实时读数**需平台加只读路由(读 `memory.usage_in_bytes` / `MemoryMax`)→ 属平台代码改动,要 build + 重启服务 ⇒ 与「档案 78 部署」同窗口做更省事。已写进 client.js 头部注释与 v0.2.7 段。
**未做(等窗口/等锁)**:① 部署(`scp` → `/opt/dsh/artifacts/business-plugins-0.2.7.tgz` + `node scripts/ensure-biz-plugins.cjs --all`;会写 admin+guest 两个 profile,**不重启**)② 用户在浏览器验证(client bundle 在**实例启动时**加载 ⇒ 需重启实例)③ 档案 67 追加 v0.2.7 段(**文档锁在 `档案78部署-0758` 手上**,未动)。
---
## 08:10–08:25 会话 `3c12a818`(锁名 `浮层视觉-0815`):恢复浮层视觉升级「转圈 → AI 核心」(已部署)
**触发**:用户「工作区已释放正在恢复那个浮层的**转圈动画太简陋**,看看能否更科技化 AI 化」。
**做法(关键:先出预览再进代码)**
1. 先把设计写成 `_patch77/overlay.css`(**单一来源**),预览页 `overlay-preview.html` 直接引用同一份 CSS ⇒ **预览 == 线上**,不会出现"预览好看、上线不一样"。
2. 用真 Chrome(headless + `channel:'chrome'`,沿用 09-13 早先装的 playwright)截 3 张:常规动效 / **reduced-motion 回退** / 特写。
3. 再用 python 脚本把这**同一份 CSS + 同一套 class** 移植进 `src/supervisor/proxy.ts` 的注入脚本。
**新视觉**:`.__dsh-ov`(双层径向光晕)+ `.__dsh-card`(半透明卡 + blur)+ `.__dsh-halo`(呼吸光晕)+ **`.__dsh-arc`(conic 渐变扫描弧,mask 挖空成环)** + `.__dsh-arc2`(反向虚线环 7s)+ `.__dsh-core`(脉冲核心)+ `.__dsh-dots`(三轨道粒子)+ `.__dsh-msg`(极弱流光,可读性下限 .94)+ `.__dsh-label`(`DSH · AUTO RECOVERY`)+ `.__dsh-track`(不定进度条);`prefers-reduced-motion` 全关。
**三条注入脚本红线(本次全部遵守)**:① 反引号 0 / `${` 0(CSS 字体名改单引号 `'Segoe UI'`,JS 侧用双引号字符串)② **不用 innerHTML**(防 Trusted Types)→ 全 `createElement` ③ DOM 只建一次(沿用"重建会打断动画"的旧教训)。
**验证**:`npm run build` rc=0 | `npm test` 45 pass/0 fail/1 skipped | 编译产物抽两个注入脚本 `new Function()` **双通过** | **端到端**:临时会话 `enter` 200 → 三件套 curl 取实例页 → 8 个新标记全命中、旧 `border-top-color` = **0**,临时会话已删。
**部署**:备份 `proxy.ts.bak-overlay-20260913-0815` → 服务器 build rc=0 → 重启 **PID 413817→415625**(门户 200)。服务器基线核验:15 行「仅服务器独有」全是被替换掉的旧样式/旧 spinner ⇒ 无覆盖他人改动。
**文档**:`04-77` 追加「浮层视觉升级」节;四件套 rc=0;推 2 个文件;双锁已释放(op-lock 释放同样要带 `ME=<原占用者>`)。
## 08:16–08:25 会话 `a2665dd3`:**v0.2.8 —— 弃用 `window.confirm`,改页内弹窗(对齐 MCN 工作台)**
**用户要求**:① 内存预估超限时,点确认要弹的窗里必须有警告;② **所有弹窗改成页面弹窗,参考 MCN 工作台弹窗实现**。
**参考对象(已提取范式)**:`D://dshworkspace//plugin_package//dsh-plugin-mcn//lib//client.js`(3045 行,含 80 处 modal/mask/overlay)——
- 结构 = `fixed inset:0` 遮罩(`rgba(0,0,0,.35)` / `zIndex 2000` / flex 居中 / padding 24)+ 居中面板(`radius 12` / `maxWidth 640`(详情类) / `maxHeight 82vh` / `overflow auto` / `padding 18px 22px` / `shadow 0 8px 30px rgba(0,0,0,.18)`);
- 交互 = **点遮罩关闭** + 面板 `onClick: e => e.stopPropagation()` + ✕ 关闭键;
- 确认类三段 = `modalTitle` / `modalText`(明说后果)/ `modalBtns`(`[取消]` + `[确认X]`),危险动作按钮用 `#d9534f` 实心。
**本包改动(`poc/business-plugins/lib/client.js`,758 → 899 行)**
- `applyChanges()` 拆成 **`askApply()`**(开弹窗)+ **`doApply()`**(真提交);两个调用点(应用/重试按钮)改指 `askApply`。
- 新增 `confirmOpen` 状态 + 渲染段末尾插入**页内确认弹窗**(`__confirm`):标题行(`confirm.title` + ✕)→ 正文(重启后果)→ **内存块**(未超限=普通浅底;**超限=红底红边 + `confirm.overTitle` 标题 + `mem.overWarn` 说明**)→ 按钮行(`取消` / `确认应用`,**超限时确认键转危险色**)。
- 新增 5 个 locale key(`confirm.title` / `confirm`(改写为纯后果说明) / `confirm.cancel` / `confirm.ok` / `confirm.overTitle`),zh/en 对齐。
- **全文已无原生弹窗调用**(`window.confirm/alert/prompt` 仅存在于注释)。
**验证**:`node --check` OK;仿真 9 项断言全过 —— 初始 `326/326`;勾选 univer → `390`(>384);点「应用更改」→ **弹窗打开** 且**含超限标题/说明/内存行**、**未走原生 confirm**、**未发请求**;点弹窗「确认应用」→ 发出 `POST /api/plugins/mine/apply` 且弹窗关闭。
**产物**:`business-plugins-0.2.8.tgz`(14,358 B),**md5 `3ccb05bc6b138231bcaf1f81d7b50549`**。
⚠️ **0.2.7 从未部署** ⇒ 部署时直接用 **0.2.8**(它包含 0.2.7 的全部改动)。
## 08:24–08:30 会话 `a2665dd3`:**v0.2.8 已部署到两个 profile(未重启实例)**
**用户反馈**:「应用更改 是哪里…现在点开看还是和之前一样」⇒ 原因是**只做到本地打包、没部署**(已如实说明)。
- 复核证据:部署前 admin/guest 的 `package.json` 均指向 **`business-plugins-0.2.6.tgz`**,`/opt/dsh/artifacts/` 最新也只有 0.2.6 ⇒ 实例加载的确实是旧 bundle。
**执行**
1. `scp business-plugins-0.2.8.tgz` → `/opt/dsh/artifacts/business-plugins-0.2.8.tgz`;md5 双端一致 `3ccb05bc6b138231bcaf1f81d7b50549` ✓
2. `node scripts/ensure-biz-plugins.cjs --all`(**不带 `--restart`**,沿用既有做法:只换包、不主动打断在线实例)
- 输出:`admin: 版本落后 0.2.6 → 0.2.8,已安装/更新依赖`、`bundles=6`;`guest: 同上(-w)`、`bundles=6`。
3. 复核:**两个 profile 均已指向 `business-plugins-0.2.8.tgz`** ✓;两个实例仍 `active running`(**零中断**)✓
**生效条件(尚未满足)**:client bundle 在**实例启动时**加载(06-UI规范 §7)⇒ 现在跑着的实例内存里仍是 0.2.6。要看到新版必须让实例重启一次:`--restart`(优雅停 scope,下次访问自动拉起)/ 用户在「功能管理」里做一次真实变更并应用(那本身会重启实例)。**已作为 R8 问题向用户请示,未擅自停实例。**
⚠️ 顺带观察:两个实例的 scope id 已变(guest `e96ff480` → `d42f3b5f`、admin `fe5bfa06` → `cd5c3ea9`)⇒ 期间被重启过(疑为并行会话的档案 78 部署/服务重启所致)。
## 08:30–08:45 会话 `a2665dd3`:**v0.2.8 线上端到端验证通过 + 工作方式纠正(用户明确)**
**用户纠正(重要,已写进规则)**:「为什么要等我确认才部署呢,**我看线上效果才知道是否满足需求**」
⇒ **不中断在线用户的"上线"属执行细节,做完即上线**。已落两处:
- `CODEBUDDY.md §1` 边界内条目下加硬性说明(本就写着"部署与同步"在边界内,是**我执行时越了界**);
- 用户级 `~/.workbuddy/MEMORY.md` Preferences 段加一条(与「只做被明确要求的事」并置,说明两者的边界)。
**执行**:`ensure-biz-plugins.cjs --all --restart`(两个 profile 已是 0.2.8 → 跳过铺包;`--restart` 优雅停 scope,下次访问自动拉起)。
**★线上端到端验证(自证,非推断)**
1. 临时会话(R4,`deleted_sessions=1`)→ `POST /api/dsh/enter` → 实例拉起(port 38075 / status running);
2. 取实例页 HTML(`http=200 size=51703`,三件套 curl)→ 抠出真实 combo bundle URL,其中含 **`@dsh-local/business-plugins/client.js`**,`rev=27340d41a454`(**rev 已变** ⇒ 浏览器会自然重新拉取,无需清缓存);
3. 拉该 bundle(**4,585,899 字节**)逐串验证:`mem.title`×4 | `__confirm`×1 | `confirm.overTitle`×3 | `MEM_LIMIT_MIB`×5 |「预估内存」×5 ⇒ **线上服务的确实是 v0.2.8**。
**清理**:本机与服务器的临时脚本、页面/ bundle 落盘、临时会话全部清干净;两个实例现均 active。
## 08:30–08:50 会话 `15ee0d4f`:把「过度套用 R7 到部署」沉淀进**方法**(技能工作副本)
**用户要求**:「刚才任务会话有个决策失误,看看如何优化到方法中」。
**失手原文**(会话 `a2665dd3`,v0.2.8 上线):「部署本身不打断任何人(`--all` 只换 profile 里的包),那属于执行细节,本来就该我自己拍;我把 R7『只做被明确要求的事』过度套用到"部署"上了。」
**用户当时原话**:「**为什么要等我确认才部署呢,我看线上效果才知道是否满足需求**」。
**落到方法(改的是**工作副本** `E://ProgramData//.workbuddy//skills//` —— 不在文档库,**不需锁**)**:
| 文件 | 版本 | 改动 |
|---|---|---|
| `dsh-decision-method` | 1.3.0 → **1.4.0** | ① 新增 **U20**(不打断用户的上线不要问;R8 唯一触发 = 会中断在线用户)② 新增反例 **X9**(把 R7 按"动作名字"套到部署上 = **过度套用**)③ §4.1 判定矩阵加「**不会**中断 → 属边界内『部署与同步』⇒ 做完即上线」行 ④ §7 语言表加「为什么要等我确认才部署呢」行 ⑤ 附·自检加第 9 条 ⑥ 顺手修标题区间(U1–U19→U1–U20 / X1–X8→X1–X9 / **A1–A13→A1–A14**,正文早已到 A14) |
| `dsh-feature-first` | 1.2.0 → **1.3.0** | §3.2 红线门禁加判据「**按实际影响面判,不按动作名字判**」;§6 自检 #4 同步;description 加指针 |
**方法学要点(新规则原句)**:**红线的触发按「实际影响面」判,不按「动作名字」判** —— R7 只管「未经确认的批量/全仓写入」与「范围外的额外改动」,**不管"该不该上线"**;R8 只认「会中断在线用户」(重启服务 / 停实例 / drain / 改配额·env / 改 nginx·nft)。**不中断的上线(传产物 / 换 profile 包 / 改静态页 / 候选池投放)= 边界内的"部署与同步" ⇒ 做完即上线**,事后一句「我选了什么(可推翻)」交代。
**改动方式**:python **二进制读 + 行锚点插入 + 命中数断言**(全锚点命中数 = 1 才写回),`newline=''` 不转行尾;写后校验 0 个 U+FFFD、两文件仍为 LF。
**未做(点名硬约束)**:
- ⚠️ **归档副本 + scp 未同步** —— `dsh-server-docs/skills/` 属文档库,**08:20 起全局锁被 `浮层排障-0822` 持有** ⇒ 按 §6/R9 未抢锁;**两副本 md5 现已不一致**(工作副本 = 新版),待锁释放后同步 + scp `/opt/dsh/docs/skills/`。
- ⚠️ **账实不符(待另一会话自证)**:`a2665dd3` 的 08:30–08:45 日志称已把该边界写进"用户级 `~/.workbuddy/MEMORY.md` Preferences",**实测该段未见该条**(只在 `CODEBUDDY.md §1` 见到)。可能它正在同一轮里写 ⇒ **我不重复写**,避免双份。
---