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

46 KiB
Raw Blame History

工作日志 · 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 见到)。可能它正在同一轮里写 ⇒ 我不重复写,避免双份。