Files
dsh_ai1net_server/.workbuddy/memory/2026-09-下14.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

437 lines
47 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(第 15 片)
> ⚠️ **本目录日志已按【月】分片**(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-下13.md` **下一片**:`2026-09-下15.md`
---
## 11:21–11:3x · 把本轮教训升格为技能「重点规则」(用户:避免后续犯傻)
**用户指令**:「把这些纳入重点规则 放在 skill 中 避免后续犯傻」。
### A. 技能 v1.2.0 三项改造(`dsh-opensource-release`)
1. **新增 `## 0.5 🔴 事故清单`** —— 放在「何时用」之后,开工前 30 秒读完。**8 条真实事故**:误放别人的锁 · 导出基线会漂 · 盲替脚本自身 · Dockerfile 漏脱敏 · package.json 被重建覆盖 · 废弃候选名静默残留 · 文档文案连改 7 轮 · 脚本用键名当标题。每条给「正确做法 + 详址」。
2. **规则升格与重排** —— 原先埋在 `R-O8 命名规则` 下的三个「附则」**升为顶层规则**:`R-O10 出处的位置与措辞` / `R-O11 README 内容口径` / `R-O12 README 结构顺序`,并把 `R-O9` 排到 R-O8 之后;§1 标题改为「**硬规则 R-O1–R-O12**」并补全索引(原只有 R-O1–R-O5 + R-O6/7/8)。
3. **修掉两条自相矛盾的旧规则**(隐患最大):① 「措辞」行还在教写「此后在本仓库做了**大量深度改造**」(**正是用户否掉的写法**)→ 改为「起步时参考了 X(作者,许可证)—— 感谢作者开源」;② 「难在哪」行的示例引用了**被禁的**「多租户演示十分钟就能写出来」→ 改为「心里有数、但别写进 README」。
### B. 又踩一个(已进事故清单第 8 条)
结构改写脚本里写成 `marks[k][0]` —— 拿到的是**键名** `"A"` 而不是标题字符串 ⇒ `replace` 打歪:把 `README` 的 `A`/`D` 替换掉、把 `PoC` 的 `C` 删掉,两处标题没改名。**已全部精确修回**(改完读回原文复核)。教训:**结构改写要用完整标题字符串做锚点,改完必须读回复核**。
### C. 锁纪律首次按新规矩执行(成功)
锁在 11:25 释放后:**① 单独一条命令抢锁并当场看输出**(`✓ 已持全局执行锁(oss-sync-1125)`)→ **② 另一条命令里先断言 `OWNER == 自己` 才动手**,末尾**再验一次 OWNER** 后才 `--release-exec`。同步技能归档(md5 一致)+ 四件套 rc=0;因 §10 记录更新需再同步一次,第二遍同法执行(`oss-sync-1127`,md5 `dab0ce55…`)。**全程未触碰别人的锁**。
**现状**:技能 **v1.2.0**,两副本 md5 一致(`dab0ce55…`),文档库无锁,四件套 rc=0。**未 scp**(等用户发话)。
## ⚠️ 11:28 我犯的**流程事故**(如实记录,已报告用户)
**事实**:本轮改档案 81 时,我把「抢全局执行锁」与「写文档」写在同一条命令里用 `;` 分隔 ——
抢锁**失败了**(当时锁被并行会话 **`oss-sync-1127`** 持有,11:24 起),但**后续写操作照样执行**:
① 写了 `04-调整方案/81`(2 行替换 + 追加 §10.6)② 重新生成并推送了 `docs-manifest.json`;
③ 结尾执行 `handoff-guard.sh --release-exec`,**把别人的锁放掉了**(现已确认 `.exec-lock` 不存在)。
**违反了什么**:R9 的精神(锁的处置权只属于用户本人)+ 本库「抢不到锁 = 停手」的硬规矩。
**根因**:`claim; write` 是**两条独立命令**,抢锁失败**不会**让后面的 write 停下 —— 我的脚本没有做退出码检查。
(另外:`--release-exec` 未校验「我是不是占用者」,这也放大了后果。)
**纠正(写进 MEMORY.md,必须遵守)**:
1. **抢锁必须与写操作用 `&&` 串联**(`claim-exec ... && <写操作>`),**抢锁失败即整条命令中止**;
2. **写库前先肉眼核一次 `交接单/.exec-lock/OWNER`**,不是自己就停手;
3. **`--release-exec` 只在确认 OWNER 是自己时执行**;
4. 抢不到锁时的合规动作只有:**停手 + 报告用户**(不触碰锁状态,不代用户判断)。
---
## 11:25–11:3x · 核心卖点上移:「全部代码与文档由 AI 生成」
**用户指令**:「还有个重点要加载前面 —— **所有项目和文档全 AI 生成,使用模型 DeepSeek V4 / V4.1 和 WorkBuddy**」。
**README 三处落地**
1. **Hero 区**:新增 2 个徽章(`code & docs-AI-generated`、`DeepSeek V4 / V4.1 × WorkBuddy`,共 6 个,未超审计上限 8)+ **一行声明**:「**全部代码与文档由 AI 生成**(DeepSeek **V4 / V4.1** 模型 × **WorkBuddy**)—— 一个可运行的、完整的多租户服务端。详见 AI 生成」。
2. **新增 `## AI 生成` 专节**,置于**第一个正文章节**(目录之后、亮点之前):覆盖范围表(`src/`·`web/`·`scripts/`·`deploy/`·`Dockerfile*` ✅ / README·PLUGIN-PORTING·LICENSE·NOTICE·install.sh ✅ / `test/` + 9 个 `smoke:*` ✅)+ 一句「人负责定方向、提需求与验收」+ 一句「可当作『AI 能不能独立写出可控的多租户服务端』的样本(含如实标注未验证能力)」。
3. 目录与章节顺序同步 → **17 节**,TOC **17/17 锚点正确**;探针 0;495 行。
**技能 v1.3.0**:§0 事实新增「**内容来源**」行(明示三处一个都不能少:Hero 一行 + 2 徽章 + 首个正文章节);R-O12 顺序图更新并把 6 个徽章写进去。台账 §九 C 同步。
**⚠️ 遗留待用户确认(已当面提出)**:声明写的是「**全部**代码与文档由 AI 生成」。若起步阶段那份骨架(`上游骨架仓库(已按要求不再具名)`,123 个提交)不是 AI 写的,「全部」就偏大 —— 备选措辞是「**本仓库全部内容**由 AI 生成」(把范围限定在本仓库)。用户一句话即可改。
**归档同步**:技能归档副本已同步(md5 `f4bf7eb9…`),四件套 rc=0,全程**锁:先单独抢 + 断言 OWNER + 放前再验**,未触碰别人的锁。
---
## 11:29–11:3x · 出处收敛为「一行致敬」(用户裁定)
**用户指令**:「如果起步阶段那份骨架(上游骨架仓库(已按要求不再具名),123 个提交)**这个都全部改造了,致敬就可以了**」—— 同时等于**回答了我上一条的疑问**:「全部代码与文档由 AI 生成」这个**宣称成立**(骨架已被整体重写),不必再加"范围限定"的补语。
**改动(README 2 处 + NOTICE 2 处 + LICENSE 1 处)**
| 位置 | 改前 | 改后 |
|---|---|---|
| README 授权表 行1 | 「**上游基础部分**|起步时参考的骨架代码:认证 / 桌面 / 单 DSH 启停与反代 / 每文件夹插件 / 双部署框架」 | 「**起步骨架部分**|起步时参考的骨架(**此后已在本仓库整体改造**)」——**去掉逐项枚举**(枚举=把自己继承的份量说大) |
| README 授权表 行2 | 「本项目新增与修改部分|由本项目维护者新增/修改的代码」 | 「**其余全部代码**|新增与重写的源码、配置、脚本与文档」 |
| README 授权摘要 | 「上游基础部分保持 MIT」 | 「**起步骨架部分**保持 MIT」 |
| NOTICE §4「本项目」行 | 「以上游骨架为基线继续演进」 | 「起步骨架来自该项目,此后已在本仓库**整体改造**」 |
| NOTICE §4「致谢」行 | 「认证与审核…双部署框架等骨架能力均源自上游。感谢 上游作者(已按要求不再具名) 的开源工作。」 | 「**感谢 上游作者(已按要求不再具名) 的开源工作。**」 |
| LICENSE 第一层 | 「上游骨架:认证与审核、网页桌面、单 DSH 启停与反向代理、每文件夹插件、模式 A/B 部署框架」 | 「起步骨架;**此后已在本仓库整体改造**。⚠️ 尽管做了整体改造,其版权与许可声明按 MIT 要求**依旧完整保留**」 |
**⚠️ 明确保留没动的部分(保守合规)**:`LICENSE-UPSTREAM-MIT.txt` 逐字节未动;授权**分层结构**保留;`LICENSE` 第一层的 MIT 声明保留 —— **改造过也不删**(万一还有残存代码,这是保命条款)。
**技能 v1.3.1**:R-O10 新增该口径(骨架已整体改造 ⇒ 只留一行致敬、去掉枚举;但 MIT 声明与授权分层照旧保留)。归档副本已同步(md5 `b73cd62e…`),四件套 rc=0,锁按新规矩走。验证:探针 0、TOC 17/17 锚点正确、README 495 行。
---
## 11:30 · 更正我自己的错误结论:官方**有** i18n(档案 81 §10.7)
**起因**:用户追问「有哪里是需要 替换官方包 / 改官方代码的」。核查时发现 §10.6 的结论建立在不完整抽查上(当时只看到 locale 包里有个 `README.i18n.yaml`,就当成"只是文档翻译")——**错了**。
**实测(服务器只读)**:
- `@deepseek-ai/dsh-client-locale`(v0.1.2-rc.1,MIT)真实存在:`lib/index.js` 1.3 KB(host)+ `lib/client.js` 55.5 KB(含内置词典)。
- host 侧只注册 settings 命名空间 `locale` / 字段 `preference`;`LOCALE_IDS = ["zh","en"]`(**随包只发这两种**)。
- client 侧导出 `LocaleRuntime` / `COMMON_NS` / `FALLBACK_LOCALE` / `SETTINGS_NS` / `apply` / `inject`;内置 `common` 词典 = 通用词(确定/取消/关闭/复制成功…)。
- **官方文档给出的扩展 API**:`ctx.locale.addLanguage({id,label,fallback})` + `ctx.locale.register(ns, lang, {...})`,消费走 `ctx.locale.bind(ns)` 或框架 `t` 座位;设置入口 **Settings → General**。官方原话:shipped `zh`/`en`,"external client plugins **can add languages** and their namespace dictionaries"。
- **但官方没铺开**:只有 2 个官方 UI 包引用了 locale(`conversation`、`directory-picker-browse`);其余大量硬编码中文 —— `trajectory` 167 行、`conversation` 138、`chat` 99、`settings-models` 98、`workspace` 64、`settings-plugins` 50、`agent-preset` 50 …
**结论(对 R5 选型的影响)**:新增第 4 个选项 **`D · 走官方 locale 体系`**(自研插件 + 浮层文案用 `addLanguage`/`register` 跟实例内语言开关联动,**不触 R2**)= §10.4 方案 C 的加强版。原先 A/B/C 三项保留。
**"要不要替换官方包 / 改官方代码"的净答复**:平台页 / 注入层 / 自研插件 —— **零官方改动**(都走官方扩展点);官方已迁 locale 的包 —— 切语言自动跟随(白拿);官方**仍硬编码**的 20+ 个包 —— 只能注入层改 DOM(脆弱、覆盖不全),**替换官方同名包 = 改官方产物 ⇒ 触 R2 ⇒ 不做**。
**教训(已写进 §10.7 末尾)**:上一轮的错在**用一个文件名代替「读一读那个包」**就下了"官方没这个能力"的结论。凡结论会影响方案取舍(尤其"做不了"这类),**必须落到实测证据**,不能靠抽样直觉。
**收尾**:档案 81 就地改掉那两处错句 + 追加 §10.7 + §10.5 加 D 选项交叉指针;四件套 rc=0。**未 scp、未 commit**(按 §4 等用户发话)。同步对账报 5 处内容不一致 + 2 处仅本地(含既有的档案 42,**非本次改动,未动**)。锁:claim → 验 OWNER → 改 → 四件套 → 再验 OWNER → release,全程合规。
---
## 11:35–11:56 · 回答「改造完成了吗 / 部署了吗 / 能用吗」→ 真机验收
**结论:R0+R1 已部署上线且真机可用;R2–R5 未做。**
**部署实证**(只读 ssh):
- 服务 `active`,**11:14:45 重启**(`lib/supervisor/proxy.js` 产物 11:14:44 → 产物早于启动 = 新代码在跑)。
- `assets/inject/{recovery,assist}.js` 于 11:09 上传;编译产物含 `loadInject`(6 处)、**不再有内联模板字面量**(0 处)⇒ R1-① 已落地。
- 服务器 `/opt/dshs` HEAD 仍 `b23e386`,但**工作树有 ~19 M + `?? assets/` + `?? scripts/verify-inject.cjs`**,且已 build+重启 ⇒ **服务器工作树不再是干净基线**(MEMORY.md 已记,下次同步前必须比对)。
- R3 双名期三处 symlink 确认(11:17):`/opt/dshs`、`/var/lib/dshs`、`/etc/systemd/system/dshs.service`。
- `assets/` 下只有 `inject/` ⇒ **R5 i18n 词条目录尚不存在**(未做)。
**真机验收**(无头 Chrome `channel:'chrome'` + R4 允许的临时会话 `mksess-guest.cjs`,用后即删):
| 项 | 结果 |
|---|---|
| 公网首页 | `https://alotbuy.com/` 200,title=**工作台** |
| 去痕迹 | 6 个平台页 **0 处 `dshs`** |
| 登录页新版 | `is-busy` 命中 3;两段式文案「登录中」→「正在进入工作台」 |
| 过渡页 | title=正在启动工作区;`wk-orb` 命中 |
| 注入是否真执行 | 2 个 `<script>`;**哨兵 `window.__dshRecover===1`**;`window.fetch` 已非 native(被包装);`__dshAssistBar` 已建;**零页面错误** |
| 助手面板(档案 56) | ✅ **真机验收通过** —— 点「📁 我的文件」弹出面板,列出工作区目录(.cache/.config/MCN短视频创作/syslibs…)+ 说明文案 |
| 恢复浮层 | ✅ **真机验收通过** —— 文案「工作区已休眠,正在唤醒…(已等待 N 秒)」倒计时逐秒递增、`DSH · AUTO RECOVERY`、AI 核心视觉 + 进度条 |
**踩到的验证坑(值得记)**:
1. `page.route` **拦不住 WebSocket** ⇒ 用 `setOffline` / 拦 `/api/*` 都逼不出浮层。
2. `probe()` 有 **15 s 节流**,心跳(25 s)会把它吃掉 ⇒ 手动 `dispatchEvent(new Event('focus'))` 常被静默丢弃。
3. **`recover()` 成功后 0.7 s 内 `location.reload()`** ⇒ 5 秒轮询全踩空,误判"浮层没出现"。
4. ✅ **正解**:把 `/api/dsh/enter` **挂住不回**(`page.route(url, () => {})`),浮层就停住可观测;且 `status` 仿真 `{"running":false}` 是**纯客户端**、不碰生产。
5. 助手条 `__dshAssistBar` **本身是容器**,里面是两个 button(📁 我的文件 / 🧭 能力)—— 必须点按钮,点容器无效。
**清理**:临时浏览脚本已删;临时会话全部删除(残留 6 个是别人的真实登录);截图保留 3 张在 `_中间产物_待清理/`。
---
## 13:32–13:5x · 用户 5 项反馈的处置(A1–A4 + B1/B2)
**⚠️ 最重要的两条更正** —— 我上一轮的「待办清单」里有**过期判断**:
1. **B1/B2 都不需要做了**(我原报"待窗口"):
- **B1(档案 79)已修并部署**:产物 `lib/web/routes/business-plugins.js` 实测 —— `:575` = `['remove', id, '-w', '--reporter', 'silent']`(**D2 已修**)、`:587`/`:617` = `await uninstall(sel.id)`(**D1 已修**)、`:613`+`:718` 路由外层 `void (async …)().catch(…)`(**加固 a 已做**)。**加固 b**(半应用态主动自愈)未做,可选。
- **B2(内存预算)已被 R1 消解**:R1-④ 把堆限与 `MemoryMax` 改成**按插件集合动态计算**(比"写死 256/512"更优)。实测运行中:guest `NODE_OPTIONS=--max-old-space-size=256` / scope `MemoryMax=544 MiB`(=160+univer 384);admin 也是 256。**平台 env 里那个写死的 `160` 已被代码侧 `withHeap()` 覆盖、不是生效值**。装上 mcn-suite 后会自动变 672 MiB。
- ⇒ **"需 R8 窗口"的前提已不成立**。
2. **D3("服务器工作树未回灌")说法要更正**:用 `git hash-object` 比对报"不一致"是**行尾差异造成的假阳性**(本机 CRLF / 服务器 LF)。**去掉 CRLF 后逐字节比对:`src/**`、`web/**`、`design.css` 两侧完全一致**。**真实双向漂移只有 2 处**:① `scripts/ensure-anysearch-admin.cjs` **服务器领先**(多了已退役硬拦 + `exit 2`)⇒ 不可用本机覆盖;② `test/crash-policy.test.mjs` **本机领先**(多了档案 78 的 3 条熔断单测)。
**用户 4 项决定的落档**(持锁 → 改 → 四件套 rc=0 → 验 OWNER → release):
- **A1 · R2 形态改定 = 原生弹窗**(用户:「为什么要 iframe,原生弹窗体验更好」)⇒ 档案 81 新增 **§9.2b**:如实记录 iframe 当初是为**省工程量**(把 `portal.html` 整页嵌进来 ⇒ 零重写)而牺牲体验;改定为**插件内原生渲染只读子集**(调平台 `/api/*`),并写明「不得两头都要」——全量就得把门户重写进插件(双源 + 与档案 39 相冲)。
- **A2 · R4 待用户选**(用户「没看懂 R4 要做什么」)⇒ 档案 81 **§9.3 重写**:先补动机(同一个改动要分两次走 / 两库版本对不上 / scp 镜像靠仪式维持),再列 (a)(b)。
- **A3 · R5 定为 `D`(走官方方案)**(用户:「R5 肯定是匹配 dsh 官方方案,什么会有疑问呢」)⇒ `ctx.locale.addLanguage()` + `register(ns)`;**零官方改动**。⚠️ 唯一边界(是事实不是疑问):官方自己只迁了 2 个 UI 包,**仍有 20+ 个包硬编码中文** ⇒ 切英文后那些包不变。A/B/C 三项降级/被 D 覆盖。
- **A4 · 悬空引用查清**(用户「R7 在哪里 什么东西悬空了」):R7 = 项目根 `CODEBUDDY.md` §3 红线表第 7 条(禁未经确认的批量/全仓写入,>10 文件先出清单)。悬空的 = 开源导出仓库里 **29 个文件的 42 处注释**指向 4 个**根本不存在的文档**(`docs/k8s.md`×37、`docs/k8s-deploy.md`×3、`docs/domain-config.md`×1、`docs/blueprint.md`×1,因 `docs/` 按 R-O4 整体移除)。台账 §八 C 已有该数据与建议改法(纯注释替换)。
- **顺带修正**:MEMORY.md 里档案 79 的描述原是「探针不再带崩平台」**写错了**,已改为正确标题。
- **顺带发现**:开源导出目录已从项目内**移到** `E:\ProgramData\AI技能\dsh-laijing-github\`(另一会话搬的);项目根另有 **26 个 `_*` 临时文件**(多为 09-12 遗留)待清。
**新增规则(用户明令,已写进 `CODEBUDDY.md` §1 + R8 与 MEMORY.md §六)**:🔴 **服务器 `47.77.182.89` 是「开发环境服务器」,不用担心中断用户** ⇒ 重启 / 停实例 / drain / 改配额或 env / 改 nginx·nft **直接做**,只需动手前一句话说明;**未放开**不可逆破坏性操作(删数据 / 迁 DB / 清目录)。
### 技能维护(同轮,锁第二轮)
1. **`dsh-instance-diagnose` 新增「注入层/浮层真机验证配方」节** —— 沉淀本轮 4 个坑:① `page.route` **拦不住 WebSocket**(`setOffline` 也不掐已建 WS)⇒ 逼不出浮层;② `probe()` **15 s 节流**被心跳(25 s)吃掉 ⇒ 手动 `focus` 常被静默丢弃;③ **`recover()` 成功后 0.7 s 就 `location.replace()`** ⇒ ≥2 s 轮询必然踩空、会误判"浮层没出现";④ `#__dshAssistBar` 是容器,里面才是两个 button。**正解**:仿真 `status={running:false}` + **把 `/api/dsh/enter` 挂住不回** ⇒ 浮层停住可观测。附**哨兵表**(`window.__dshRecover===1` 是最强判据,比找 DOM 可靠)。
2. **同技能修正两处事实漂移**:① 「实例配额」原写死 `MemoryMax 384 MiB` + 堆 `160` → 改为**按插件集合动态算**(R1-④);② 隔离复现配方里的 `-p MemoryMax=402653184` → `-p MemoryMax=384M`(顺带消掉 `docs-consistency.py` 把它读成 `4026` 的取值冲突);③ 「绝不在生产实例压测」按新 R8 口径改为「压测走隔离环境」。
3. **补齐缺口**:`dsh-instance-diagnose` **原先没有归档副本**(两处位置规则漏了它)→ 已建;顺带发现 `dsh-decision-method`(工作 v1.4.0 vs 归档 v1.3.0)与 `dsh-feature-first`(v1.3.0 vs v1.2.0)**归档落后** → 按「单向:本机 → 归档」补齐。**现 6 个技能两副本 md5 全一致**。
4. ⚠️ **`docs-consistency.py` 的一个局限**(未改脚本):它把 `MemoryMax=402653184`(字节)读成 `4026` 与 `384`(MiB)冲突 —— **只看数字不看单位**。以后再遇到,优先把文档写成同一单位(`384M`)而不是改脚本。
---
## 14:22–14:3x · 「文档的处理直接执行就好」→ A4 执行 + 文档库全量收口
用户批准后**直接执行**,两件事都做完。
### ① A4 · 开源导出:去「档案 NN」+ 修悬空 docs 引用(**写成脚本规则,重建不丢**)
`E:\ProgramData\AI技能\dsh-laijing-github\_build_export.py` 新增 `REGEX_RULES`(含 `ARCHIVE_REF` / `DOC_SECTION` / `DOC_REF`)+ `import re`,在 `sanitize()` 里于 GLOBAL 之后应用。
| 项 | 重建前 | 重建后 |
|---|---|---|
| 「档案 NN」(含 `档案 19 §C8` / `档案 81 · R1` / `档案 56:…`) | **197 处 / 41 文件** | **0** |
| 悬空 `docs/*.md`(`k8s.md`×37 / `k8s-deploy.md`×3 / `blueprint.md`×1 / `domain-config.md`×1) | **42 处 / 29 文件** | **0**(映射到 README 真实小节:部署形态 / 架构 / 配置) |
| `交接单` | 2 | 0 |
| blocking leak probe | — | **0** |
| info hits | 2 | **0** |
| `tsc` | — | **exit 0 · no diagnostics** |
| 文件数 | 141 | 141(不变) |
另加 GLOBAL 两条字面量补语义缺口(`(档案 74 的下调)`→`(此前下调过)`;`档案 16 已宣布`→`平台已宣布`)+ 命中新阻断探针的 `mcn-workstation`→`my-skill`。
### 🔴 这一轮最大的教训(已同时写进 `dsh-opensource-release` 技能 §0.5 事故 #10 + 台账 §八 D)
**我的"顺手清理"正则把 ASCII 标点也吃进去了**:清理规则写成 `[((]\s*[))]` / `[;;,,]\s*[))]`,**字符类里混了 ASCII `(` `)` `,` `;`** ⇒ 源码里所有 `foo()` 被删成 `foo` ⇒ **`whitelist.ts` / `security-scan.ts` 当场语法错**(tsc 报 *Invalid character* / *Unterminated string literal*)。
❗ **当时 leak 探针全过** —— 因为探针查的是"有没有敏感标识",完全不看代码是否还能编译。
✅ **两条铁律**(已写进脚本注释 + 技能):
1. **清理类正则只准碰全角标点(()·、,:;)**,绝不可把 ASCII 语法符号写进字符类;
2. **改完必跑 `_verify_tsc.mjs`,`exit 0 + no diagnostics` 才是"没改坏代码"的唯一证据** —— **探针全过 ≠ 代码还活着**。
(另:`(\s*/\s*` 这类会吃掉路径前导斜杠的规则一律不要写。)
### ② 文档库收口:推送镜像 + 提交 + push(**均已闭环**)
- **推送镜像**:39 项待处理文件。⚠️ 逐个 `scp` 会超时(39 条 SSH 连接)⇒ 改用**单个 `tar` 管道**(`tar -cf - -T list | ssh 'cd /opt/dsh/docs && tar -xf -'`)**一次连接传完**,随后 `find` 修正远端权限(目录 700 / 文件 600)。**这是本次新摸索出的高效传法,值得复用。**
- **对账**:**141/141 双端一致 ✅**(此前是 129 一致 / 9 不一致 / 3 仅本地)
- **提交**:`6e09edb`(39 项,显式 `git add` 逐个,未用 `git add -A`);工作树清零
- **push**:`43e4ae9..6e09edb HEAD -> main` ✅
- **技能追加**:`dsh-opensource-release` v1.4.0→**v1.4.1**(§0.5 新增事故 #10)→ 归档副本 md5 一致 → 提交 `87505d5` → push ✅(本地=远端=`87505d5`,0 领先)
- 四件套全程 **rc=0**;锁两轮均按纪律走(claim 单独一条 → 验 OWNER → 改 → 四件套 → 再验 → release)
---
## 14:38–14:5x · 「都可以处理」→ 代码仓收口 + 开源导出剩余项
### ① 代码仓(`D:\github\dsh_shenxian`)**已提交并推送**
- **修双向漂移**:① `scripts/ensure-anysearch-admin.cjs` **从服务器取回**(310 行,含 AnySearch 退役硬拦 + `exit 2` —— 本机原先缺);② `test/crash-policy.test.mjs` **送到服务器**(档案 78 的 3 条熔断单测,`breakerCooldownMs` 命中 6 处);③ `mksess.cjs` 两侧本就一致 ✓
- **`.gitignore` 补两条**:`*.tgz`(构建产物,「历史上从未提交过 tgz」已核实)、`.workbuddy/`(工作数据非仓库内容)⇒ 30 项待处理收敛为 24 项
- **提交前跑 `npm run verify`**:注入脚本 2/2 语法通过 + `proxy.ts` 走 `loadInject` 2 处 + **9 页静态不变量全合格**
- **提交 `c0abec8`**(25 项),工作树清零;**push `da3e0f9..c0abec8 → master`** ✅
- ⚠️ 只推 `origin`(Gitea),**`upstream`(github.com/上游作者(已按要求不再具名))绝不推**
- 残留说明:`mksess-guest.cjs` 只在服务器(**有意保留**,服务器专用临时工具,不入库);`poc/business-plugins/*` 本机领先、服务器未用(PoC)
### ② 开源导出剩余 3 项
| 项 | 结果 |
|---|---|
| **#3 占位符 7 处** | ✅ 已填:`<YOUR_GITHUB_ACCOUNT>`→`maogeigei`、`<CONTACT_EMAIL>`→`[email protected]`、`<COPYRIGHT_HOLDER>`→`maogeigei`(**按本机 `git config` 推断**)。取值集中在 `_build_export.py` 的 `PUBLISH_*`;README/LICENSE 属 OVERLAY ⇒ 那 6 处写死,改身份要两处都改 |
| **#4 `install.sh` 真机预演** | ✅ 在服务器隔离目录 `/tmp/inst-dry/` 跑通 `--dry-run`(已清理该目录)。**顺带修 4 个真实缺陷** |
| **#5 法律意见** | ⏳ **AI 做不了**,留给用户/律师 |
### 🔴 本轮两个新的结构性坑(都已进技能 `dsh-opensource-release` §0.5 事故 #11/#12)
1. **同一个名字不能既做「脱敏目标」又做「公开值」**:`maogeigei` 同时在 `GLOBAL`(→占位符)与 `LEAK_PROBES`(判泄露)里 ⇒ ① 刚填好的公开值被规则**改回占位符**("占位符残留 0"是假象),② 探针报 `blocking hits: 4`。修法 = **两处同时移除**(只删一处 = 另一种错);私有仓库地址仍由 `work.alotbuy` 探针兜底。**通用判据:任何进 `PUBLISH_*` 的值,先 `grep` 确认它不在 GLOBAL/探针里。**
2. **`--dry-run` 报"假成功"**:`run()` 只给命令加 `[dry-run]` 前缀跳过执行,**但结果提示语是硬编码的** ⇒ 预演满屏 `✓ 构建完成` / `✓ 服务已启动` / `✓ 部署完成`,还**打印了一个无效的初始管理员密码**(`bootstrap-admin` 没跑);未设域名时文案还出现空缺「把 与 *. 的 DNS A 记录」。**判据:dry-run 输出里不允许出现任何 `✓`,只允许 `[dry-run]`。** 4 处已修并复跑验证。
### ③ 顺带观察:**有另一个会话在并发改开源导出**(非我)
证据:`INCLUDE_DIRS` 被补了 `assets/`(⇒ 导出物 141→**143**)、台账"终检"行的文件数被改成 143、技能版本被从 1.4.1 直接跳到 **1.5.0**。我的 §0.5 #10/#11/#12 三条**已自然合并、无冲突**。⚠️ **我持锁期间它仍在改同一批文件** —— 这是本库「多会话并行」的既有风险(细锁管不住跨单撞车)。**本轮未撞车,但值得留意。**
### 交付闭环
- 文档库:**双端 141/141 一致**;提交 `fc010f5`;**push 已确认落到远端**(用 `git ls-remote origin refs/heads/main` 直连核对 = `fc010f5`)
- 开源导出:**blocking 0 / info 0 / 档案 NN 0 / docs 0 / 占位符 0 / tsc exit 0 / 手工层 6-6 / 必需文件 26-26**
- ⚠️ 导出仓**仍未 `git init`**(未发布)—— 发布应是刻意动作,用户没说要发
### ④ 收尾时发现:**代码仓有对端会话正在改(未提交)**
`src/supervisor/orchestrator.ts` 在我提交(`c0abec8`,14:47)**之后**被人改过 —— `PLUGIN_MEM_MB['dsh-univer-office']` **384 → 512**,注释写着「2026-09-13 14:44 …544 仍欠 ~50-120 MiB,实测 gateway 起不来;512 ⇒ 配额 672」。
⇒ 两点结论:
1. **确实有对端会话在并发改码仓,且未持全局锁**(我持锁期间它仍在写)。本轮**未撞车**(改的是不同文件/不同位置),但这是本库既有风险的又一处实证。
2. **我要更正此前记录的容量口径**:我曾按 R1 的公式算出 guest = 544 MiB 并记为"已消解 B2"。对端实测发现 **544 仍不够**(univer gateway 起不来),故把 univer 的预估提到 512 ⇒ 配额 672。**该项工作属对端在途,我未碰**(R7:只做被明确要求的事;R9:不接管他人工作)。若他提交,我记忆里"544 已足够"的说法需同步改。
**未动**:`mksess-guest.cjs`(服务器专用,有意保留)、上述对端在途改动、导出仓的 `git init`。
---
## 15:10–15:2x · 「授权条款」不属我的范围 → 全项目扫描并移除授权类文件
**用户定性**:*「「授权条款」具体指什么 做这个事情干什么呢,需要你考虑吗,**你的任务是改造和优化,这些内容全部删除**」* ⇒ 授权/法务**不属本项目范围**,不列为待办、不再上抛。
### 全项目扫描结果(文件名 + 内容双维度,覆盖本机三处 + 服务器)
| 位置 | 授权类文件 | 性质 |
|---|---|---|
| `D:\github\dsh_shenxian\LICENSE` | 1 份 | ⚠️ **上游自带**(08-18 上游提交 `a2b095b`,`Copyright (c) 2026 dshs contributors`)⇒ **不是我们造的,未动** |
| `/opt/dshs/LICENSE` | 1 份 | 同上(服务器副本) |
| `dsh-laijing-github\dsh-multitenant\` | **3 份** | `LICENSE`(自拟分层条款)/ `LICENSE-UPSTREAM-MIT.txt`(上游 MIT 副本)/ `THIRD-PARTY-NOTICES.md` —— **我们造的** ⇒ 本次移除对象 |
| `dsh-laijing-github\_overlay\` | **3 份同名快照** | 重建用;**只删导出不删它 = 下次重建复活** ⇒ 一并删 |
| 代码内容里的「授权」表述 | `README.md`(授权章节+徽章+目录项) · `PLUGIN-PORTING.md`(链接) · `package.json`(license 字段) | 连带清理 |
### 执行(**先备份、再删、并改脚本,否则重建会复活**)
- **备份** → `dsh-laijing-github\_授权归档_发布时再放回\`:那 3 份原文 + `README.md.orig-带授权章节` + `package.json.orig-带SEE-LICENSE`
- **删**:导出仓 3 份 + `_overlay` 3 份;**同步改 `_build_export.py`** —— 从 `OVERLAY` 摘除 3 项、从 `REQUIRED_EXPORT` 摘除 3 项(原 26 项 → **23 项**)、删掉把 `license` 改成 `SEE LICENSE IN LICENSE` 的 GLOBAL 规则(`package.json` 回到**上游原值 `MIT`**)
- **连带清理死链**:README 删 License 徽章 / 目录条目 / 文件清单两处 / **整个「授权与商业使用」章节**;`PLUGIN-PORTING.md` 删 `THIRD-PARTY-NOTICES` 链接与「授权」字样
- **技能同步更正**(`dsh-opensource-release` v1.5.1→**v1.5.2**):§5 改为「授权类文件已全部移除」(**但保留「上游 MIT 不得被附加限制」这条事实** —— 删了会误导后人写违规声明);§0 手工撰写层改「现为 3 份」;R-O5 改为「已移除、不再处理」;§3 映射表 `<CONTACT_EMAIL>` 行标注消失
**终检**:导出仓 **0 个授权类文件 / 0 处授权类引用**;`REQUIRED_EXPORT` **23/23**;探针 **0**;`tsc exit 0`;README 目录锚点 **0 悬空**;文件数 143→**140**。
### ⚠️ 已记入台账 §F 的硬提醒(不是待办,是"若要发布必须先做")
**要对外发布,必须先把那三份放回**(并恢复 `OVERLAY`/`REQUIRED_EXPORT`)—— 否则:① 无 `LICENSE` ⇒ 默认「保留所有权利」⇒ **任何人都不能合法使用**;② 缺上游 MIT 声明 ⇒ **侵犯 上游作者(已按要求不再具名) 的著作权**;③ 缺第三方 NOTICES ⇒ 多款依赖许可证同样要求保留。**当前仓库未发布(无 `.git`),所以现状无风险。**
**收尾**:文档库提交 `2e12b07` 并 push(本地=远端,0 未提交);技能两副本 md5 一致;双端 141/141;锁按纪律走完并已释放。
---
## 15:24–15:3x · 「同步到仓库,把仓库中的这类授权信息也都删除」
**用户追加要求**:把**代码仓**里的授权类信息也删掉,并同步到仓库。
### 扫描(两仓,文件名 + 内容双维度)
| 位置 | 授权类信息 |
|---|---|
| 代码仓 `D:\github\dsh_shenxian` | `LICENSE`(上游 MIT 原文,1107B)· `package.json` 的 `"license": "MIT"` · `README.md` 的 **License 徽章**(第 4 行)+ **`## 许可证` 章节**(末 3 行) |
| 服务器 `/opt/dshs` | 同一份工作树的三个文件(另有细微差异:LICENSE 1086B / README 11547B / package.json 2668B) |
| 文档库 | 仅 `skills/dsh-opensource-release/SKILL.md`(**知识记录**,非授权文件,不删) |
### 执行
1. **备份 11 份** → `dsh-laijing-github\_授权归档_发布时再放回\`(导出仓 3 份 + 2 个改动前版本;`代码仓\` 3 份;`服务器工作树\` 3 份)
2. **本机代码仓**:删 `LICENSE`;`README.md` 去徽章与许可证章节;`package.json` 删 `license` 字段(**JSON 合法性已验证**,`version=0.1.0`,无 license 字段)
3. **重建开源导出**(源 `package.json` 变了)→ 导出 `package.json` 也已无 license 字段;`required 23/23`、探针 **0**、文件 **140**、授权类文件/引用 **0**
4. **提交 + 推送**:`ca5b62c`(3 文件,`LICENSE` -21 行 / `README.md` -5 / `package.json` -3+1)→ **`c0abec8..ca5b62c → master`**,本地 = 远端 = `ca5b62c`
⚠️ **只提交我改的 3 个文件**;对端会话在途的 `M src/supervisor/orchestrator.ts`(univer 内存预估 384→512)**原样留着没碰**
5. **同步服务器工作树**(否则又多一处漂移):删 `/opt/dshs/LICENSE`、送 README/package.json(**转 LF**)→ 复核 `LICENSE=无`、`license 字段=0`、`许可证章节=0`,两文件 md5 与本机一致
6. **健康检查**:平台 `active`、首页 **HTTP 200**(改的是 README/package.json,不影响运行时,**未重启**)
### ⚠️ 两点如实说明(已写进 commit message 与台账)
1. **git 历史里仍在**:`LICENSE` 是上游 08-18 提交 `a2b095b` 引入的,删掉的是**工作树**;历史提交仍含它。要彻底抹掉得改写历史(未做,通常也不该做)。
2. **合规前提**:本仓是上游 MIT 项目的衍生仓。**私有内部使用不受影响**(MIT 的"保留声明"义务针对**分发**);但**若日后要对外分发/发布,必须恢复 MIT 版权与许可声明,否则构成侵权** —— 备份就在 `_授权归档_发布时再放回\代码仓\`。
---
## 11:32–11:4x · 双语文档口径(用户:「文档别忘了中英文双语」)
**用户指令**:「文档别忘了**中英文双语**,后续**优先写中文**,需要同步到 GitHub 时再更新英语。」
**结论:定口径 + 写进技能,但英文版此刻刻意不生成**(按用户"同步时再更新")。
**口径(技能 **R-O13 · 双语文档**,v1.3.2)**
| 项 | 值 |
|---|---|
| 母本 | **中文**(`README.md` / `PLUGIN-PORTING.md`),**日常只维护中文** |
| 英文文件 | `README.en.md` / `PLUGIN-PORTING.en.md` |
| 生成时机 | **发布 / 推送 GitHub 之前一步**(SOP 新增 **5.5 步**)—— 即 §7 验证后、§6 交付前 |
| 语言切换行 | 两份顶部都要有;⚠️ **英文文件存在之前不加**(否则死链,扣 P1) |
| 逐字保留 | 代码块 / 命令 / 路径 / env 名 / 包名 / URL / ASCII 图 / 表格结构 / 「版本与迭代」表;占位符同步替换 |
| 不翻译 | `LICENSE-UPSTREAM-MIT.txt`(MIT 英文原文逐字节)· `LICENSE`(**已是中文正文 + 英文摘要**,现状即符合)· `THIRD-PARTY-NOTICES.md`(中文即可) |
| 校验 | 英文版同样跑 TOC 锚点(按英文标题)+ 相对链接存在性;两版版本表**行数与版本号一致** |
**技能改动**:新增 **R-O13**(含 SOP 5.5 步 + §0 事实「文档语言」行 + §10 ⏳ 待办「英文版待生成·发布前必做」);§1 标题改 **R-O1–R-O13**。台账新增 **§九 F 双语文档**。
**顺手修**:frontmatter 的 `last_change` 因替换锚点只取前缀导致**新旧两段拼接**(1.3.2 文 + 1.3.1 残留),已重写为一行干净历史。⚠️ 教训:改 frontmatter 这类单行长文本时,**锚点要覆盖整行**(或整行替换),别用前缀。
**归档同步**:md5 `a4b04f99…` 两副本一致,四件套 rc=0,锁按新规矩走。**当前英文版未生成(符合用户口径)**。
---
## 13:34–13:5x · 开源资料独立成文件夹:`E:\ProgramData\AI技能\dsh-laijing-github\`
**用户指令**:「`E:\ProgramData\AI技能\dsh-laijing-github` 把相关资料整理到 这个文件夹下,这个文件夹作为**项目独立的 github 开源项目文件夹**」。
**结果(新工作根 = `E:\ProgramData\AI技能\dsh-laijing-github\`)**
```
dsh-laijing-github\
├── dsh-multitenant\ ← 仓库根(141 文件,可直接 git init / push)
├── _build_export.py ← OUT 已改指新工作根
├── _verify_tsc.mjs ← EX 已改指新工作根
├── _overlay\ ← 手工撰写层快照(6 个文件)
├── _rename_selfref.py ← 一次性改名脚本(保留备查)
└── _导出说明与脱敏台账.md
```
原位置 `aliyun-dsh-server\_开源导出_20260913\` 已**整体迁走并删除**(无残留)。
**⚠️ 过程中闯了一次祸(已修,已进技能事故清单 #9 / 坑 17)**:我先 `mv` 目录、**后**才改脚本常量 —— 中间为了「验证」跑了一次重建,脚本仍指向旧路径 ⇒ **在旧位置把整个仓库重新建出来(135 文件)**,且因旧位置**没有 `_overlay`**,**6 个手工层文件(README / LICENSE / LICENSE-UPSTREAM-MIT / NOTICE / PLUGIN-PORTING / install.sh)全部缺席**。所幸:① 真身(新位置的仓库 + `_overlay`)完好无损(README 37375 字节未变);② 已删掉复活目录;③ 改正常量顺序后重建 = **141 文件、手工层 6/6 恢复、探针 0、`tsc` exit 0**。
**教训(已固化)**:迁工作根的顺序必须是 **先改 `OUT`/`EX` → 再搬目录 → 再重建 → 核对文件数与手工层 6/6 → `ls` 确认旧位置没复活**;`OUT` 指错时脚本**不报错**,只在别处默默新建一套。
**同步更新**:技能 §0 事实(导出根 → **工作根**)、§9 命令、§10 说明、相关链接全部改新路径;项目 MEMORY.md 的「开源导出」条已改为新工作根。技能升 **v1.4.0**,归档副本已同步(md5 `d00d8fb7…`),四件套 rc=0,锁按新规矩走(claim 单独 → 验 OWNER → 操作 → 再验 → release)。
**未做(等指示)**:`git init` / commit / push(R-O6)。
## 14:25–14:50 会话 `a2665dd3`:**★我的失误(0.2.19 误带堆限)+ 深挖出「384 MiB 结构上装不下 gateway」的定论**
### 一、失误与修复(责任在我)
- **0.2.18 的 V8 堆限补丁我只"回滚了部署、没回滚源码"** ⇒ 随后从同一工作树打出的 **0.2.19 把 `--max-old-space-size=160` 一起发出去了** ✗✗。
- 实例内 agent(turn 9)实测到后果:`Mark-Compact (reduce) 158.4 (160.8) -> 158.0 (160.8) MB` + **`FATAL ERROR: Reached heap limit`**,且 OOM 发生在**编译 51 MB 包的懒编译函数**时(`Runtime_CompileLazy → ObjectLiteralBoilerplateBuilder`)⇒ **gateway 根本起不来**(比我原先判断的"无效"更糟:**有害**)。
- **已发 0.2.20 修复**:删掉堆限块(`max-old-space-size` 归零 ✓),保留 watchdog / idle 透传 / 20s 就绪超时 / 陈旧 socket 清理。池内与 guest 均已 0.2.20 ✓(池内 42,046,113 B @ 14:30)。
- **教训**:回滚必须**回滚源码**(部署回滚只在制品层,下次重建会把补丁带回来)。⇒ 已把"改完必须核对**源码**里没有残留"作为动作。
### 二、★更深的定论:**不设堆限,gateway 仍然 OOM**
- 0.2.20 下去后再触发:`gateway/start` → **`ok:false,"… v8::internal::V8::FatalProcessOutOfMemory …"`** ✗(无任何显式堆限)。
- 机理:**V8 会感知 cgroup 上限**(实例 384 MiB)并据此设堆顶;而 gateway 需要的堆(无上限口径实测 **~290 MB**)+ 代码/外部(~100 MB)**结构性超过 384** ⇒
**在 384 MiB 的实例里,univer 的 gateway 无论怎么调参都跑不起来**(三种口径互证:无上限实测 RSS 335–400 / 堆需求 290 MB / cgroup 感知下 abort)。
- 现场旁证:带 gateway 时 cgroup 读数 **412–499 MiB / 384**(**超顶**),实例已被迫重启过一次(scope 从 `0ee7ec3c` → `0db05698` → `cba60bd0`)。
- ⇒ **结论**:要 univer 真能用,只有 **(a) 提配额(≥640)** 或 **(D) 把 gateway 移出实例 cgroup**;**E(空闲自停)只解决"平时占用"**(已实测回收后 cgroup 落到 98 MiB ✓),**救不了"用的时候"**。
### 三、"能不能按需加载/只加载文档+表格"(实测判定)
- gateway 产物里**没有 slides**(`@univerjs/slides`=0 / `slides-ui`=0 / `docs-ui`=0),只有 `@univerjs/sheets`(42) / `@univerjs/docs`(14) / `boards`(32) 引用 ⇒ **它本来就≈"表格+文档(+board)"**,没有幻灯片可省。
- `@univerjs-pro/collaboration-service` 是 **1.5 KB 转发壳**(真实现别处);产物是**全静态单文件 bundling**,**不存在运行时按需加载**;要按需只能改打包(code splitting),而静态 import 的引擎切分省不了"运行时真要用"的部分。
- 真正的"按需"机会在**用法**:现在连"列 unit / 读内容 / 导出"都要先起 gateway(`resolveTarget()` 用 `GatewayClient.listUnits`)⇒ 若改 fork 的数据访问路径(直接从文件/sqlite 取 unit 列表),**看文件/导出**可只走 worker(短命进程、不常驻)。工作量中等,属"轻量路线"。
---
## 14:26–14:5x · 上传完整性核对 ⇒ **查出 `assets/` 漏收录(严重)**
**用户指令**:「确认所有需要上传的内容 都同步到新文件了吗」。
**方法**:不是"看一眼目录",而是 12 项机检(含**源仓库顶层条目逐项对照**、隐藏文件、违规目录、垃圾文件、README 目录结构自洽、`package.json` 入口、**用仓库自带校验脚本验收**)。
### 🔴 问题 1:`assets/` 没进 `INCLUDE_DIRS`(严重)
- 源仓库有 `assets/inject/{recovery.js,assist.js}`(R1-① 外置的注入脚本,共 ~30 KB),**导出物里没有**。
- 危害:`src/supervisor/proxy.ts` 的 `loadInject()` 按**包根**解析(`<pkgRoot>/assets/inject/<file>`)且**fail-fast 抛错**(「assets/inject 必须随包部署」)⇒ 导出的仓库**根本起不来**;仓库自带 `scripts/verify-inject.cjs`(`npm test` 的一环)也会判失败 —— 实测修前该脚本会报「assets/inject 下没有 .js」。
- 另发现:`package.json` 的 `files`(`lib, web, cordis.patch.yml, README.md`)**也缺 `assets`** ⇒ npm 打包 / `dsh plugin add github:` 装法同样会缺。
- **处置**:① `INCLUDE_DIRS` 补 `assets`;② `package.json` 的 `files` 补 `assets`(写成 LINE_REWRITE 规则);③ **新增 `REQUIRED_EXPORT` 清单(26 项)**:构建时逐个 `os.path.exists`,**缺一即 `return 1`**(白名单收录漏项是**静默**的,必须有断言兜底)。
### 🔴 问题 2:档案号剥除留下空括号残渣(轻微但显眼)
- `REGEX_RULES` 删掉「档案 NN」后,**ASCII 括号侧没有清理规则** ⇒ 导出物里出现 `backoff ().` / `simulate... auto-restarts ().` / `section (). Same-name...`(3 处)。全角侧早有清理规则,ASCII 侧因为**曾误伤 `foo()` 导致 tsc 报错**而被"铁律"挡住。
- **处置**:加两条**受限** ASCII 规则 —— ① `(档案 NN)`(模式里必须出现档案号 ⇒ 不可能命中 `foo()`);② ` ()` 紧跟句末 `.`+空白/行尾(`(): void` 后是 `:` ⇒ 不命中)。
- **验证**:重建后**残渣 0**、正常 `(): type` 保留 **8** 处、`tsc --noEmit` **exit 0** ✓(tsc 就是这类正则的安全网)。
### ✅ 其余 10 项通过
143 文件 · 目录分布(assets 2 / src 51 / web 13 / scripts 28 / deploy 11 / poc 16 / test 5 / .github 1)· 隐藏文件齐(`.gitignore`/`.dockerignore`/`.github`)· 关键文件 17 项齐 · 手工层 6/6 md5 与 `_overlay` 一致 · 源→导出只剩 3 项有意排除(`docs/` `STANDARD.md` `.workbuddy/`)· 违规目录 0 · 垃圾文件 0 · **旧位置没复活** · README 目录结构 6/6 存在 · `bin→lib/cli.js` 有 `prepare` 构建 ✓ · **`node scripts/verify-inject.cjs` → 全部合格 ✅**。
**技能 v1.5.0**:§2 补 `assets/`(含危害说明)与目录计数 · §3 补「档案号 / 悬空引用清理」两行(原先**完全没记录** `REGEX_RULES`)· §0.5 加事故 #10 · §8 加坑 18(白名单静默失败 → 断言 + 自带校验脚本双防线)。台账加 **§八 G 上传完整性核对**(12 项 + 2 个问题 + 处置)。归档副本已同步(md5 见下),四件套 rc=0。
## 14:36 会话 `a2665dd3`:**配额/堆的推导机制(拍板 a 还是 D 的事实依据)**
读 `src/supervisor/orchestrator.ts`(档案 81 R1-④ 的产物):
```ts
const MIN_MEM_MB = 384, MAX_MEM_MB = 1024, HEAP_HEADROOM_MB = 96
instanceMemMb(profileDir) = clamp(BASE_MEM_MB + Σ PLUGIN_MEM_MB[bundle], 384, 1024) // 读 profile 的 bundles 推导
heapMbFor(memMb) = clamp(memMb - 96, 128, 256) // 堆 = 配额 − 96,夹 128–256
withHeap() → NODE_OPTIONS=--max-old-space-size=<heapMbFor>(env 只承载其它选项;强指用 DSHS_HEAP_MB_OVERRIDE)
systemd-run … -p MemoryMax=<memMb>M
```
**⇒ 两个关键结论**
1. **(a) 提配额 = 改 `PLUGIN_MEM_MB['dsh-univer-office']` 一个数字**(+ build + 重启服务):配额自动升到 `BASE+Σ`(上限 1024),**隔离完全不变**。
2. **gateway 的 V8 堆不受 NODE_OPTIONS 影响**(host spawn 时把 env 白名单化,只传 HOME/LANG/LC_ALL/PATH/TMPDIR + 显式变量)⇒ gateway 的堆顶由 **V8 感知 cgroup 上限**决定 ⇒ **只要 MemoryMax 够大,gateway 的堆就够大**。这解释了为什么"给 gateway 塞 flag"无效(0.2.18/0.2.19 反而 abort ✗),而"抬配额"是有效路径 ✓。
3. `heapMbFor` 夹到 **256 上限** ⇒ 若将来实例自己也吃紧,需一并调整该 clamp(属同一次改码)。
## 14:36–14:55 会话 `a2665dd3`:**★更正:实例真实配额是 544 MiB(不是 384)—— 预算表机制已上线**
**更正**:此前我反复引用的"384"是**旧写死值 / MIN_MEM_MB 下限**。实测:
- **guest 实例 `MemoryMax` = 544 MiB**(=基座 `BASE_MEM_MB=160` + `PLUGIN_MEM_MB['dsh-univer-office']=384`)✓
- **admin 实例 = 384 MiB**(其 bundles 不含 univer ⇒ 160+0 → 被 `MIN_MEM_MB` 夹到 384)✓ **两条实测互证了推导机制**
- 预算表**已在服务器上线**(`src`/`lib` mtime 均 **11:14**;实例 11:40 起)✓ 隔离零改动。
- 实例 heap = `heapMbFor(544)` = clamp(544−96,128,**256**) = **256** ✓
**⇒ 这直接改变了 a/D 的判断**
1. **(a) 不是"加内存"这么笼统,机制已在跑**;剩余动作 = 把 `PLUGIN_MEM_MB['dsh-univer-office']` 从 **384 → 512**(配额 → **672**)+ build + 重启服务。**1 行改动、隔离不变。**
2. **(D) 的独有好处只有一条**:实例配额保持不变(探针继续只测本体);而它**不省宿主内存**(gateway 仍占 ~390,只是换 cgroup 记账),却要付**隔离降级 + 生命周期自理 + 审计**的代价。
3. **宿主容量(重要)**:`total 1870 / used 676 / available 1194`,但 **swap 已用 965 / 1024(94%)** ⚠️ ⇒ 抬配额(+128 MB)够让 univer 勉强跑起来,但**要真正宽裕必须扩宿主内存(花钱)**。
4. 当前实测:guest usage 363 MiB(无 gateway 时 rss 198–305);gateway 需求 ~390 ⇒ **544 仍欠 ~50–120 MB**,故需 672 档。