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

48 KiB
Raw Blame History

工作日志 · 2026-09(第 14 片)

⚠️ 本目录日志已按【月】分片(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-下12.md 下一片:2026-09-下14.md


10:12–10:25 会话 a2665dd3:独立复核「另一会话的内存结论」(用户要求确认)

被评为"确认"的三条 → 复核结果

对方结论 我的实测 判定
exitCode 8×134 + 1×137(137 = cgroup SIGKILL) --since today 统计 = 8×134 + 1×137;137 时刻 10:03:11 ✅ 逐条一致
guest cgroup 384/384/384(高位 100%) usage=368.2 / peak=384.0 / limit=384 ✅ 一致
gateway ≈390.4 MB |实例本体 ≈44.0 MB 331.5 MB(gateway pid 429466)|91.6 MB(dsh pid 427842) ⚠️ 同量级,数值有偏(gateway 热身前/后波动 330→390;本体随加载阶段 44→92)
「是内存,不是恢复逻辑」 与其一致:OOM 是真因;恢复循环(GET /)是结果不是原因 ✅ 成立
「502 是 CF 的,nginx 里没有 502」 ❌ 不成立:/www/wwwlogs/alotbuy.com.log 里状态位 502 共 579 条(/api/remote.mux 362 · /univer-api/state 39 · GET / 29)⇒ nginx/源站自己也返 502(上游 127.0.0.1:3080 失败)。CF 页面是同一失败在 edge 的另一层呈现 ❌ 需修正

★ 对方漏掉的关键一环(我已于 10:10 修掉):陈旧 socket。内存杀掉 gateway 后,残留的 socket 文件让之后每次启动都失败(bind() EADDRINUSE)→ 对外只报 did not become ready within 10000ms。 ⇒ 「无法启动」是两个原因叠加:内存把它杀掉 + 陈旧 socket 让它再也起不来。0.2.17 已修(绑前 unlink)并在 guest 上验证通过 ⇒ 方案 (a) 必须带上这个修复,否则升配额后仍会偶发"永久无法启动"。

★ 方案 (a) 的宿主容量复核(关键修正)

  • 实测宿主:total 1870 MB|used 1062|available 808 MB|swap 已用 679 / 1024 MB(说明已在换页)。
  • 对方给的 640 × maxIdleInstances 2 = 1280 MiB ⇒ 放不下(808 可用 + 345 空闲 swap ≈ 1153 < 1280)✗。
  • ⇒ 若走 (a):必须把常驻/激活实例数压到 1(maxIdleInstances 4 → 1),或做分级配额(univer 用户 640 / 其他 256–384;但配额在 orchestrator.ts:625 硬编码,分级=改码)。长期仍应扩宿主内存(花钱)。

一句话:对方的定因(内存)与"恢复逻辑没坏"成立;但(i)"nginx 无 502"错,(ii)漏了陈旧 socket 这一环(已修),(iii)(a) 的容量账要按 available 808 + swap 679 重算 ⇒ 640 只能配 1 个常驻实例。


10:14–10:2x · 开源项目命名定稿:DSH 多租户平台 / DSH Multi-Tenant Platform / dsh-multitenant

用户指令:「名字就叫 dsh 多租户平台 对应的英文,项目中文名称「太妙」(认可)」⇒ 本会话中间候选 dsh-hosting 被取代。

命名口径(已定稿,写进 README「名称与渊源」+ NOTICE §4 + 台账 §七)

项 值
中文名 DSH 多租户平台
English name DSH Multi-Tenant Platform
技术标识 dsh-multitenant | env 前缀 DSH_MULTITENANT_*(36 种)| 数据根 /var/lib/dsh-multitenant | dsh-multitenant.{service,env} | 库文件 dsh-multitenant.db

占用实测(R-O8 要求):npm registry.npmjs.org/dsh-multitenant → 404;dshbase.com/plugins/dsh-multitenant → 404 ✓ 可用。

执行:改脚本 GLOBAL 的目标值(dsh-hosting→dsh-multitenant、DSH_HOSTING_→DSH_MULTITENANT_、路径与库名同改)+ DST 目录名 → 改 _verify_tsc.mjs 的 EX → --force 重建 → 手工层批量替换(README 42 处 / LICENSE 1 / NOTICE 4 / install.sh 22)→ README 标题改「DSH 多租户平台 · DSH Multi-Tenant Platform」+「名称与渊源」加「中文名 / English name / 技术标识 / 命名口径」→ 再重建验证。

新坑(已写进技能 §8 坑 14):废弃的旧候选名必须进阻断性探针 —— 否则 _overlay 里的中间名会静默残留(本次正是靠把 dsh-hosting / DSH_HOSTING_ / /var/lib/dsh-hosting 加进 LEAK_PROBES,才发现 72 处残留)。

验证(全绿):阻断探针 0 命中(含全部曾用名)| 旧名残留 grep 为空 | tsc --noEmit exit 0 | bash -n install.sh OK 且 --help 显示新名 | 140 文件 | env 前缀 36 种一致。 技能 v1.0.3(R-O8 补命名口径与已排除候选;§8 补坑 14/15;全文路径改 dsh-multitenant)→ 归档副本已同步(md5 631ad317…),四件套 exit 0,锁已释放。

⚠️ 小插曲:本轮有一次 --claim-exec 因命令被重试而报「抢不到锁」—— 只读确认 OWNER 是我自己的会话名(10:17 那次声明),未做任何接管,随后正常释放,现无锁。R9 未被触碰。

10:14–10:40 会话 a2665dd3:gateway 内存构成实测 + 「加堆限」方案被实测证伪(已回滚)

用户问:为什么 gateway 要这么多内存?有办法优化改造吗?

一、构成实测(生产,pid 429466)

项 值
VmRSS 335 MB(多次采样区间 335–400,噪声 ±30)
其中 [anon](V8 堆 + 匿名映射) 293 MB = 87%
node 代码段 | libsql native 41 MB | 1 MB
VmSwap 46–93 MB(已在换页)
gateway 环境里的 NODE_OPTIONS 不存在 ⇒ 没有堆上限(不继承实例的 160)
产物 artifacts/gateway.cjs 单文件 59 MB(esbuild bundle:true, packages:'bundle';只 external node builtins + libsql + 3 个 native binding)

根因:它不是插件而是整套 Univer 协同服务端(@univerjs-pro/collaboration-service 等闭源 SDK + 多 unit 类型 + 协同运行时 + 视图资产服务 + sqlite 文件存储),全部内联、startServer() 时一次性加载 ⇒ 常驻内存是真实需求。

二、★方案 A(给 gateway 加 V8 堆限)实测无效,已回滚

  • 试了 NODE_OPTIONS=--max-old-space-size=160 --max-semi-space-size=8(改 gateway/launcher.ts,0.2.18): RSS 335 → 400 MB、swap 93 → 46 MB,总量持平(cgroup 368→369)⇒ 不是 GC 懒惰,是真需求;且加限还引入 134 abort 风险。
  • ⇒ 已回滚到 0.2.17(launcher 含堆限: 0 处)并复测(RSS 363–386 / swap 58)✓。教训:这类"调参省钱"的假设必须先 A/B 实测再留档(本次幸好测了)。

三、其余方案的可行性摸底

  • B(裁 unit 类型 / 只留 sheet):❌ 基本不可行 —— 加载面由闭源第三方 SDK @univerjs-pro/collaboration-service 决定(我们自己的 collab-service.ts 只引用 snapshot?.board?.name 之类),没有现成 preset 开关;要裁只能篡改 bundle(高风险)。
  • C(代码分割):收益不确定 —— 若 SDK 在启动时就实例化所有 unit 类型,切分无益;需先测(可做,但要 A/B)。
  • D(把 gateway 移出实例 cgroup,独立 systemd 预算):⭐ 现在看最优 —— 不动闭源 SDK、不动插件行为、实例的 384 不再被挤,且"探针还能继续测实例本体";代价是隔离/生命周期模型变化(要评估 R5:仍以该 uid 运行,但生命周期不再随实例)。
  • A 已证伪;换 libsql(1 MB)与多用户共享 gateway(权限面扩大)不值得。

10:24–10:3x · 开源 README:出处改为「文末提一句」(用户纠正措辞与位置)

用户指令:「在最后提一下引用了谁就行 不要上来就重点讲用了谁,感觉是在宣传别人的项目,已经很多地方深度改造过了」

改动(只动 README,不动法律文件)

  1. 删掉开头的 ## 名称与渊源 章节(原在「目录」之后、「特性速览」之前)+ 目录项 —— 那段把上游摆在了正文最前。
  2. 正文改以本项目为主语:
    • ### v1.0.0 相对上游 \dshs` 做了什么 → **### v1.0.0 的六组改造`**;
    • 删掉「上游提供了骨架:…… v1.0.0 把「能跑」补齐为……」,改为「本项目把「能跑」补齐为「能对外长期运维」,改动分六组:」;
    • 版本表 v0.x 行去掉「基于上游 dshs 的」。
  3. 出处只在文末一句(「第三方组件与致谢」):「起步阶段参考了 dshs(上游作者(已按要求不再具名),MIT)……此后在本仓库做了大量深度改造……感谢作者把它开源出来。」——去掉了「离开它就没有这个项目」「只是接着往前走了一步」这类过度抬举。
  4. 授权章节:第一层作用域措辞改「起步时参考的骨架代码」;删掉「也是诚实的做法/可仅使用上游部分自行扩展」(把读者往上游引)。
  5. 法律部分一分不减:LICENSE-UPSTREAM-MIT.txt 逐字节未动;LICENSE 第一层说明、THIRD-PARTY-NOTICES.md §4 保持完整 —— 「轻描淡写」只作用于 README 正文。

验证:README 里 上游/骨架/dshs/上游作者(已按要求不再具名) 只剩文末 3 处(授权表 1 + 致谢 1 + 授权摘要 1);nginx 上游 是无关技术词;阻断探针 0 命中。

技能升到 v1.0.4:新增 R-O8 附则「出处的措辞与位置」(位置 / 主语 / 措辞 / 边界 / 代码注释 五条)+ §8 坑 16(--claim-exec 与其它步骤写在同一命令里,命令被重试会出现「抢不到锁」假告警 —— 先只读看 OWNER 是不是自己,放锁前必须再确认 OWNER,否则可能放掉别人的锁 = R9 反向违反)。归档副本已同步(md5 8b95d10d…),四件套 exit 0,锁已释放。

10:27–10:45 会话 a2665dd3:D 方案与「共享 gateway」的多租户评估

两个新查到的事实(决定结论)

  1. gateway 没有「空闲自停」:gateway-process.ts 只有 stop()(实例停机时调用),没有 idle 超时 ⇒ 一旦启动就常驻,直到实例停。
  2. 应用层路径白名单没启用:代码里有 allowedRoot(UNIVER_ALLOWED_ROOT → server.ts → univerfile-manager 校验「非 .univer / 越出 allowedRoot → 400」),但 launcher 不注入该 env(实测 gateway environ 只有 UNIVER_COLLAB_GATEWAY_PORT / _SOCKET / UNIVER_VIEW_ASSETS_ROOT)⇒ 生产上没有 allow-list,隔离完全依赖 uid + bwrap。

D(把 gateway 移出实例 cgroup)的多租户评估:D 不是共享 —— 仍是"每用户一个 gateway、以该用户 uid 跑",只改 cgroup/生命周期归属。

维度 现在(在实例内) D
uid 100002(该用户) 不变 ✓
文件系统视野 bwrap 合成根:私有 /tmp、/etc 是 tmpfs、/usr·/lib64 只读、仅 bind 该用户目录 宿主完整 fs(/etc、/opt、其它用户的 users/<uid>)✗
应用层校验 未启用(见上) 同样未启用 ⇒ 只剩 uid 权限兜底 ✗
资源 与实例共享 384(互相挤) 各自独立预算 ✓(= D 的收益)
生命周期/记账 随 scope 起停、天然记账 要自理(停实例后它还活着?陈旧残留?)+新记账
⇒ 判定:D 技术上能保住 uid 隔离,但牺牲命名空间/文件系统隔离 = 隔离降级(R5"扩大可见面")⇒ 要走必须同时补:① 注入 UNIVER_ALLOWED_ROOT=<该用户 ws> 启用 allow-list ② 独立 unit 带同样 cgroup 预算 + 硬化 ③ 出权限影响评估并由用户拍板。

「不同用户共用一个 gateway」= ❌ 不可行(四条硬理由):① 它是有状态数据平面(unit/快照/协同会话在内存)⇒ 共享即数据混在一起;② 路径校验未启用 ⇒ 只能靠闭源 SDK 的应用层鉴权,无法审计(供应链 + 越权,R5 扩大);③ 单个用户的大表格能把共享 gateway 压爆 ⇒ 全租户受影响;④ 无法按租户归因资源。唯一成立的同尺度 = 同一用户自己的多会话共用一个(现状)。

★ 由此产生更优的小方案 E(我现在的推荐):给 gateway 加「空闲自停」(如 10 min 无请求 → 自行退出;下次用再起,0.2.17 已保证能重起)。

  • 收益:不测 univer 时零占用(探针变"按需施压",日常不再挤实例);不动隔离、不动闭源 SDK、改动小(host 侧定时器)。
  • 代价:首次打开多 ~5–10 s 启动(当前就绪超时 10 s 需同步调大)。
  • ⇒ 把"结构性冲突"降级为"按需成本",且不碰多租户模型。

10:30 追加:方案 E 的实现落点(已摸清代码结构)与适用范围

代码结构(已读)

  • 生命周期所有者 = src/host/processes/gateway/supervisor.ts 的 GatewaySupervisor:status() / ensure() / start() / dispose(),已有 gatewayStartupTimeoutMs(=10 s,即报错里那个 10000ms)、gatewayRequestTimeoutMs、gatewayPort;GatewayProcess.stop() 已存在。
  • 关键:ensure()/status() 每次都先 gatewayIsHealthy() 探活,探不通就重起 ⇒ gateway 自己退出后,host 会自动重新拉起(无需 host 侧记账)✓

E 的正确实现位置 = gateway 侧自停(不是 host 侧定时器)

  • 原因:worker 与浏览器是直接走 unix socket 的,host 看不到这部分流量 ⇒ 用 host 侧"最近调用时间"判空闲会把忙误判成闲(可能把正在编辑的 gateway 停掉)✗
  • 做法:src/gateway-app/server.ts 记录「最后一次 HTTP 请求 + 活跃 WS 连接数」(transport/http.ts 的 listener 包一层 touch;transport/ws.ts 计数连接);定时检查,无请求 ∧ 无活跃 WS ∧ 超过 UNIVER_DSH_GATEWAY_IDLE_MS(默认 10 min,0=关闭)→ process.exit(0)。
  • 无需在退出前 unlink socket(0.2.17 已保证下次绑得上)✓
  • 配套:gatewayStartupTimeoutMs 10 s → 20 s(冷启动 5–10 s,10 s 太紧)。

★ E 的边界(必须说清):E 只消除空闲期占用;用起来时仍需要 ~480 MB(实例 90 + gateway 390)⇒ 384 配额下"用的时候照样装不下"。所以:

  • 只做 E ⇒ 探针从"常驻挤压"变"按需施压"(更可控,但用的时候仍会 OOM —— 这恰是探针要观察的);
  • E + (a) 提配额(640/256,常驻数 ≤1) ⇒ 平时不碍事 + 用起来也不死 = 完整体验。

10:28–10:4x · 开源 README:开头改为「亮点」(用户指出「基线 ≠ 亮点」)

用户指令:「部署到一台服务器,多个用户注册并经管理员审核,各自获得一套相互隔离 —— 这个只是项目的基础不是重点,重点是多租户各自进程的安全隔离、状态恢复、插件和技能的管理。多看看项目找出这个项目的亮点」「对了多想想 不要弄了半天亮点都体现不出来」。

做法:先读码再写亮点(不凭印象)

  • 按体量排序找最重的模块(proxy.ts 1339 · orchestrator.ts 1062 · business-plugins.ts 742 · skills.ts 686 · k8s-spawner.ts 687 · pg.ts 508 · plugin-compat.ts 337 · leader.ts 254 …),逐个读头注与关键函数;
  • 又读了 isolation.ts / firewall.ts(端口守卫)/ fs-guard.ts(路径围栏)/ crash-policy.ts / reconcile.ts / session-preset.ts / security-scan.ts / spawn.ts / patch.ts / nginx/generate.ts / file-service.ts;
  • 关键数值逐一回代码核对(心跳 25000ms、CrashBreakerOpenError+retryAfterMs、目录 3408 条/覆盖 1854、技能 rank 600/400、扫描 P0/P1 与 8MB)。

README 改动

  1. 开头删掉「部署到一台服务器,多个用户注册…」(= 基线),换成 一句定位 + 三条主线速览(隔离 / 恢复 / 治理)+ 一句「注册审核、独立实例、域名访问是基线不是亮点」。
  2. ## 特性速览(6 条形容词式 bullet)→ ## 亮点(四大节表格,每条带机制 + 反直觉细节 + 为什么这么设计):
    • ① 进程级安全隔离:确定性 uid(baseUid + row_id)+ setpriv ⇒ 0700 是内核边界;端口守卫必须在 OUTPUT 链按客户端 uid 匹配且 -I OUTPUT 1 插链首(否则 ufw/conntrack 的 ACCEPT 先吃包;不支持就 fail loud);nftables 只匹配主动发起(TCP 纯 SYN / UDP ct state new,否则回包一起被拒 ⇒ 实例整体不可用);浏览器信任围栏(剥离 Origin/Referer/Sec-Fetch-*、Host 覆写回环);env 白名单重建;路径围栏可钉 POSIX 语义;上传 P0/P1 分级扫描。
    • ② 故障自愈与状态恢复:崩溃策略是可单测纯函数(退避 + 熔断跨轮存活 + 503 instance_circuit_open 带 retryAfterMs);按需守护不常驻(稳态 1 进程/活跃用户);回收后导航过渡页 / XHR 等就绪 / 401 透明重放 / 回页面自动唤醒;注入脚本 25s 心跳 + 断流(连续两次失败才恢复);注入脚本先模板求值再 node --check(grep/new Function 都是假绿);K8s informer + reconcile 对账 + 仅 leader 写;会话档位自诊断(只读解压多帧 zstd 读实际 preset)。
    • ③ 插件与技能受控管理:双判据兼容性预检(semver 默认语义 + 运行时导出符号);**「兼容 ≠ 可用」**的托管友好性预检(绝对 URL = 阻断);启用三步(探活/快照回滚/逐插件隔离);内存预估+超限拦截;provider 联动;同名上传=替换;技能两层(rank 600/400)+ 两阶段原子替换;watch-based ⇒ 装卸免重启;官方目录 ~3400 条(覆盖 54%)。
    • ④ 工程纵深:双后端同接口(local/k8s)、DB 双后端、关键决策皆纯函数、npm test + 9 个 smoke:*。
  3. 功能详解 加一句「本节是能力清单(有什么);机制见亮点」,避免与亮点重复;安全模型 补 3 行(同机旁路 / 到实例的请求 / 实例环境)。

验证:阻断探针 0 命中;README 436 行、## 亮点 锚点存在;导出物 140 文件。 技能升到 v1.0.5:新增 R-O8 附则 2「README 内容口径」(开头=亮点非门槛 / 亮点先于功能清单 / 每条必须带机制与数值 / 先读码再写亮点+具体读法 / 讲出「难在哪」/ 补工程纵深)。归档副本已同步(md5 39288e87…),四件套 exit 0,锁已释放。


10:34–10:4x · README:去掉「议论式」表述(第三轮纠正)

用户指令:「这是开源项目描述 写这个是干什么呢,不是让你讨好我」—— 直指开头那句 > 重点不是「能起进程」——多租户演示十分钟就能写出来。重点是这三件事:

问题定性:那句话是在跟作者对话(回应我上一轮的"基线 vs 亮点"),不是写给读者的。读者不知道也不需要知道"作者曾经写错过什么";「十分钟就能写出来」这种表达放在开源描述里既没信息量、又像自我表演。

改动

  1. 开头块引用整段删除,换成:一句 tagline(「把 DSH 变成可对公网运营的多租户服务」)+ 用户视角描述(注册审核 → 独立实例与文件根 → 网页桌面 + 自有域名)+ 三条重心的名词式列表(隔离 / 恢复 / 治理)+ 一句指向「亮点」。
  2. 去掉重复定位句 —— 原先 tagline 与描述段把「把 DSH 变成…」说了两遍,删掉一处。
  3. 四个小节标题去括号议论:### 一、进程级安全隔离(原带「(不是「同机不同目录」)」)· ### 二、故障自愈与状态恢复(原带「(面向「长期开着」)」)· ### 三、插件与技能的受控管理(原带「(「能装」和「装了不出事」是两件事)」)· ### 四、工程基础(原「工程纵深(为什么这些机制可信)」)。对比与理由留在表格单元格里当技术说明,标题只写名词。

复查:重点 / 十分钟 / 演示 / 难的是 / 凭什么 / 拖死 等议论词在 README 中 0 命中;阻断探针 0;README 437 行。

技能升到 v1.0.6:R-O8 附则 2 再补三条 —— 面向读者不是面向作者(禁「重点不是 X 而是 Y」)· 小节标题别加议论 · 别重复定位句。归档副本已同步(md5 ee943798…),四件套 exit 0,锁已释放。

📌 三轮纠正的合力(这才是真正的口径):① 出处只放文末(别替别人宣传)→ ② 开头写亮点/重心而非基线门槛 → ③ 措辞面向读者、不写议论与自我表演。三条已全部固化进技能 R-O8 附则 1/2。


10:37–10:5x · 开源文档审计 + 删掉「补齐了 DSH 的哪些缺口」

用户指令:「详细检查准备开源的相关文档 是否有相关问题以及 之前提到要重点检查的问题」+「(架构里的)它补齐了 DSH 的哪些缺口 这个内容也 不是重点」

  • 第二条已执行:删掉 ## 架构 下的 ### 它补齐了 DSH 的哪些缺口(那 4 行讲的还是账号/访问面 = 基线),换成一行指向「亮点」。

审计方法:机检为主(脚本比对)+ 抽样人工核。完整记录写进台账 §八。

✅ 通过 9 项:目录锚点 15/15 有效 · 相对文件引用全在 · README 提到的 npm script 全在且 smoke:* 计数 = 9 · install.sh 的 env 名全部真实存在 · 配置表没有一个是代码里不存在的(反向检查)· 依赖 178 包许可证分布与文档完全一致 · 文档内 0 内部档案号 · ci.sh/workflow/package.json 引用脚本全在 · 配置默认值逐项核对一致。

🔧 已修 4 类(全部在 _build_export.py 里有规则,重建不丢)

  1. ⚠️ 真 bug(代码不是注释):orchestrator.ts 的「目录选择器初始化脚本」默认值指向 poc/workspace-scoped-picker/ensure-workspace-picker.cjs —— 该插件已随 R-O3 移除 ⇒ 默认值改空串(有 existsSync 守卫,空即 no-op),DSH_PICKER_ENSURE_SCRIPT 保留为可选钩子。已进 LINE_REWRITE。
  2. 4 处注释点名已移除脚本(ws-cleanup.cjs / business-plugins.ts / admin.ts / orchestrator.ts 注释)→ 中性化,已进 GLOBAL。
  3. README 配置表漏记 11 项,其中最要紧的是崩溃熔断 6 项与空闲回收 3 项(正是「亮点」里的功能!)→ 已补全(含 RESTART_BACKOFF_MAX / CRASH_MAX_RESTARTS / CRASH_WINDOW / CRASH_STABLE / CRASH_BREAKER_COOLDOWN / CRASH_BREAKER_MAX_COOLDOWN / MAX_IDLE_INSTANCES / INSTANCE_IDLE_TTL / IDLE_REAP_INTERVAL / BUNDLED_SKILL_DIR)。
  4. install.sh 写入值与 README「默认值」列不符(脚本 ISOLATION_MODE=account / ENABLE_PATCH=true vs 代码默认 soft / false)→ 表后加「补充说明」:脚本取值差异 + 平台注入变量 3 个(ROLE/HANDOFF_PATH/USER_ROOT)+ CLI 专用 + 可选钩子。

验证:重建后 tsc --noEmit exit 0 · 阻断探针 0 命中 · 悬空脚本名 grep 为空 · install.sh 语法 OK · 140 文件。

⏳ 待用户拍板 6 项(未擅自改,R7:>10 文件需先点头)

  • ① 29 个文件 / 42 处 docs/*.md 悬空引用(docs/k8s.md×37、k8s-deploy.md×3、blueprint.md×1、domain-config.md×1;全在注释里;其中 docs/k8s.md 上游从未公开过)→ 建议做一次纯注释替换(映射到 README 章节)。
  • ② 183 处「档案 NN」注释编号(可与 ① 一次做完)。
  • ③ 7 处占位符(<YOUR_GITHUB_ACCOUNT>×3 / <CONTACT_EMAIL>×3 / <COPYRIGHT_HOLDER>×1)发布前必须替换。
  • ④ install.sh 未在真 Linux 跑过 ⑤ LICENSE 自拟条款 + 分层授权前提待确认 ⑥ poc/ 两个插件是否加回。

10:38–11:0x 用户转向「保留功能前提下的架构/功能优化」→ 出评估档 80(P0 已上线)

用户两句话:① 「看看能否彻底抛弃 dshs 的架构 以及跳转 portal.html,不要在本项目中体现 dshs 任何内容,把 admin 的门户管理都放到设置选项中 弹窗展示」→ ② 「看看如何优化架构和功能,在保留现有功能的基础上 优化重构后有更好的效果」。

我的判断(写进档案 80):不推倒重来 —— 平台本体(账号/审批/实例生命周期/反代/配额/DB)正是"要保留的功能";而今天几次事故根因全是「层层补丁叠加」(模板转义把注入脚本写崩 ×2、配额与插件集合脱钩致 OOM 137、缺有界恢复、自愈 5 代补丁)⇒ 方向 = 收口 + 显式化 + 构建期校验。

9 项优化(按事故排序):①注入脚本外置+构建期硬校验 ②npm run verify 统一校验 ③实例配额跟随插件集合(自适应内存;今天 384<390 的实证)④门户管理就地化 = 弹窗 iframe(实测平台页无 X-Frame-Options ⇒ 同站子域可嵌;⚠️ 与档案 05 PoC-2 ① 结论冲突 + 触发 R5,推荐 iframe 而非能力下沉)⑤自愈收口成一份状态机 ⑥有界恢复 ⑦门户页面结构 ⑧可观测性统一 ⑨文档/代码同仓。 不做:重写平台 / 管理 API 下沉进实例 / 改内部命名(systemd·路径·DB)/ 给 univer 设内存上限或禁用(用户明示它是压力探针)。

P0 已上线(静态页免重启,7 文件):index→工作台、login→登录、register→注册、portal→管理门户(副标题 →「平台管理」)、admin→管理台、wake→正在启动工作区、design.css 注释去名;线上逐个 <title> 取回已验证。

顺手修掉我自己的工具 bug:scripts/docs-index-stats.py 读取时没加 newline="" ⇒ 把 INDEX CRLF 转成 LF(正是今天记录过两次的陷阱)。已改为「读也 newline="" + 按文件自身 eol join」,并修掉历史遗留的 1 处孤立 LF 与摘要行;复跑 3 次确认幂等(CRLF 198/孤立 LF 0),git diff 只剩 13/4 行真实改动。教训升级:自建工具同样必须守 newline=""。

文档:档案 80 + INDEX 行 + docs-manifest,四件套 rc=0,对账 一致 133(余 4 不一致 + 2 仅本地 = 其它会话积压);锁与占号(80)均已释放。未 commit / 未 push。 ⚠️ 另记:04-调整方案/.lock-78 是并行会话遗留(它把档案写成 79 却留了 .lock-78),我未触碰(R9)。


10:43–10:5x · 「模式 B = 未验证」全量标注(用户纠正)

用户指令:「模式 B · Kubernetes 去掉这个,根本没验证,应该是待开发验证」。

判定:不能删代码,但必须停止把它当可选项宣传。 模式 B 有完整代码(k8s-spawner 687 行 / leader 254 / reconcile 170)+ deploy/ 11 个清单 + 单测 + poc/ 实验记录,但从未端到端部署验收(真实生产只有模式 A)。⇒ 一律标注「实验性 · 未验证」,并与模式 A 在同一张表里用状态行对照。

改动(README 12 处 + install.sh 1 处 + LICENSE/NOTICE 3 处)

  1. ## 部署形态 重写:开头一句「当前可用的是模式 A」;表格新增 「状态」行(A ✅ 生产使用中 / B ⚠️ 未验证,请勿用于生产);把原来的「已完整落地(Phase 0–4)」式段落改为「设计意图 …… 这条路径还没有走过一次完整的部署验收」。
  2. 配置表 7 处 k8s 变量加前缀「(模式 B · 未验证)」/「(模式 B · 未验证;必填)」,DEPLOY_MODE 行同步。
  3. 亮点/功能详解/安全模型 里 5 处 K8s Pod 边界、K8s 侧同一套闭环、双部署后端共用同一接口、数据层双后端 全部加注「模式 B · 未验证」。
  4. 目录结构:deploy/ → 「K8s 清单(模式 B · 实验性,未验证)」;poc/ → 「K8s 可行性 PoC 实验记录 …… 仅实验,未形成可部署形态」;fs/ 行注明 K8s 实现未验证。
  5. FAQ 新增一条:「Kubernetes(模式 B)现在能用吗?」→ 直答「还不能算可用」并说清缺哪一步。
  6. install.sh 报错文案:模式 A 仅支持 Linux(K8s 用 deploy/ 下的清单) → 本脚本只做模式 A(单机);K8s 清单见 deploy/(模式 B,实验性、未验证)。
  7. 归属措辞降级(LICENSE / THIRD-PARTY-NOTICES / README 授权表与致谢):上游贡献里的「双部署形态」→「双部署框架」,避免暗示未验证路径是可选项。

验证:grep 未命中 全通过;残余 已完整落地|Phase|3 副本 0 命中;阻断探针 0;bash -n install.sh OK;140 文件。

技能升到 v1.0.7:新增 R-O9 · 只写「已验证的」能力(宣称必须可复现:有代码+单测+PoC ≠ 可用;未验证的标「实验性 · 未验证」并与可用路径同表对照;删「已完整落地/Phase」式表述;配置表与脚本文案同步;FAQ 直答;归属措辞降级;附落点清单)。归档副本已同步(md5 22f0f649…),四件套 exit 0,锁已释放。

⚠️ 修技能时误删了附则 2 表格的一行(别重复定位句)—— Edit 的 old_string 只写了那一行却替换成整段,导致表格断裂;已即时发现并补回 + 补分隔线。教训:用 Edit 追加内容时,new_string 必须包含 old_string 的全部内容。

10:33–10:55 会话 a2665dd3:方案 E 已实现 + lab 验证通过(0.2.19)

用户:「可以试试」⇒ 实施 E(gateway 空闲自停)。

改动(3 处 + 版本 0.2.19)

文件 改动
src/gateway-app/server.ts 新增 startIdleWatchdog({httpServer, wss}):每个 HTTP 请求 touch lastActivity;wss.clients.size > 0 视为"有人在看"(刷新计时);无请求 ∧ 无客户端 ∧ 静默超过阈值 → 打日志 + process.exit(0);阈值 UNIVER_DSH_GATEWAY_IDLE_MS(默认 600000 = 10 min,0 = 关闭);定时器 unref() + 间隔自适应(阈值/4,夹在 5–60 s)
src/host/processes/gateway/launcher.ts 透传 UNIVER_DSH_GATEWAY_IDLE_MS(可按实例调,不用重建)
src/host/config.ts gatewayStartupTimeoutMs 默认 10_000 → 20_000(冷启动本来要 5–10 s,10 s 太紧 —— 也是 "did not become ready" 的成因之一)
  • 产物核验:gateway.cjs 含 startIdleWatchdog×2 / UNIVER_DSH_GATEWAY_IDLE_MS×1;lib/index.js 含透传 ✓;tsc 三个改动文件零错误。

★ lab 实测通过(独立 socket /tmp/uvlab.sock,阈值 20 s,不碰生产实例)

  • 启动日志:[dsh-univer-gateway] listening on unix:/tmp/uvlab.sock
  • 25 s 后:[dsh-univer-gateway] idle 20s with no client → exiting to release memory ⇒ 进程自行退出 ✓✓
  • 踩坑:lab 的 NODE_PATH 必须 = dirname <pkg>(即 .pnpm/<pkg>@…/node_modules),写成 $GP/../node_modules 会多一层 node_modules → Cannot find module。

产物:dsh-univer-office-0.2.19.tgz(42,046,985 B / md5 7ec224c95fa471076227f1b0ed2d3342);投放中(http=200 待确认)。

回答用户"这插件不就是显示文件吗,为什么这么难"(要点,已口头给出):它不是"预览器"而是服务端活文档系统(协同模型:unit/worktree/快照/变更集);核心是闭源 @univerjs-pro/collaboration-service(加载面不可裁);叠加我们的约束(多租户 + nft 封 loopback ⇒ 只能 unix socket;实例 384 MiB)⇒ 难的不是"渲染",是"在沙箱里常驻一个完整办公套件服务端"。


10:48–11:0x · README 结构按社区标准重排 + 22 项自审

用户指令:「说明文档的顺序还需要优化,参考优秀项目的说明文档,如何结构化展示内容,以及是否应该把授权等信息放在最前面,可以去找找有没有写 github 说明的相关 skill」。

A. 先找技能(按规矩先搜市场)

  • SkillHub 检索到三个对口技能:readme-craft(README 写作/审计/重写;蒸馏 awesome-readme · Standard README · Art of README · Make a README · GitHub Docs · thoughtbot)、standard-readme、readme-grader。
  • 下载到工作区内临时目录($HOME 写入被沙箱拒;SkillHub 的 download API 只在工作区内可落盘)读完即清理;未安装到本机。readme-craft 详情页带两份厂商安全报告(keen / sanbu 均"安全,无风险")。
  • WorkBuddy 内置市场搜 readme 只有 git-copilot(偏提交信息),无专用结构技能。

B. 回答「授权是否放最前」= 不放,但要加一行摘要

  • 依据:6 个权威来源共识排序均把 License 放在最后;readme-craft 的 Trust 评分项明文「License 在最后?」。
  • 但我们是双授权(非 MIT)⇒ 授权是决策信息 ⇒ 在徽章下方加一行授权摘要(+锚点链接),完整条款仍在末节,并以「授权摘要 + 非 SPDX 说明」收尾。

C. 重排为 16 节(认知漏斗 Hook → Onboarding → Content → Trust → Meta)

  • 关键移动:安全模型 从「版本与迭代之后」上移到 架构 之后(原位置属漏斗倒序);插件移植指南 提到 开发 前;第三方组件与致谢 与 授权 对调 ⇒ 授权压轴。
  • 用脚本按 ## 标题切分再拼装(不手工搬),重排后重算 TOC 并机检:16/16 锚点可解析、顺序与章节一致。

D. 补齐审计扣分项

  • H3 Hero Visual:Hero 区(前 50 行内)新增多租户拓扑图;架构 节里那份重复拓扑改写成两条链路(请求 + 自愈,含熔断 10min→6h)⇒ 同时消除信息重复。
  • C3 API 概览(原来 0 分):新增 ## 控制面 API,10 组端点全部用 grep -oE "app\.(get|post|put|delete)\(" 从 src/web/routes/*.ts 抓真实路径(含 skills/mine 两阶段暂存→apply、plugins/mine/apply + task/:id 轮询)。
  • H2:让 package.json 的 description 与 README 一行描述同一起句(写成 _build_export.py 规则)。
  • 顺带修:表格单元格里不能写裸 |(GET|POST 会截断表格)。

E. 自审结论:93/100 → S 级(Hook 25 · Onboarding 25 · Content 20 · Trust 9 · Structure 9 · Polish 5)。扣分只有"发布后才能补的"两项:CI 徽章 0/3、维护者信息 0/3(<CONTACT_EMAIL> 未填);另信息重复扣 1。

技能升到 v1.0.8:新增 R-O8 附则 3 · README 结构顺序(10 条硬规则 + 22 项审计口径 + 定稿顺序图)。归档副本已同步(md5 31d98264…),四件套 exit 0,锁已释放。临时技能目录已清理。

11:0x 用户授权「按方案改进,要新架构 + 命名细节」→ 出档案 81(总纲)+ R0 完成 + R2 提前做掉一项

用户原话:「按照方案改进,要求改造后是一个新的更好的架构包括命名方式等细节」+「按06-工作台ui规范调整优化ui,包括登录窗口样式等,如点击登录后停留几秒不能操作按钮也没有加载动画」。

交付 1:档案 81《目标架构与命名规范(重构总纲)》

  • 命名:对外统一「工作台」(英文 DSH Workspace);内部统一前缀 dshs(单元 / 源码目录 / 数据目录 / DB / env 前缀 / 包名 / 注入脚本 / 注入全局标记 / CSS 类全表,每项都写了迁移方式)。
  • 兼容策略(硬要求):内部改名一律 双名期(symlink + 新旧 env 双读)→ 切换期(删旧名),绝不硬切 —— 今天刚因"hooks 绝对路径失配导致全机写操作被拒"上过一课。
  • 六层架构:静态 / 注入 / 控制面 / 数据面 / 管理面 / 可观测;4 处关键改动:注入脚本出模板字面量 · 自愈收口成状态机 · 配额跟随插件集合 · 有界恢复。
  • 分期 R0–R4(每期带验收与回滚):R0 ✅ 本轮完成|R1 四项(需一次重启)|R2 管理面就地化(先 R5 评估)|R3 内部标识迁移(两次窗口,最后做)|R4 文档同仓。

交付 2:R0 全部完成

  • 静态页去痕迹(7 文件,已上线);npm run verify 统一入口(build + 单测 + 注入脚本运行时校验 + 静态页不变量)。
  • 新增 scripts/verify-static.mjs:① 去痕迹(web/**.{html,css,js} 不得含内部平台名)② title 非空 ③ wake.html 五个 id + 熔断分支 ④ 内联 <script> 必须能 node --check(今天两次事故都是"脚本语法错 = 静默失效",此前无任何校验能发现)—— 现 10 段内联脚本全通过。

交付 3:R2 提前做掉「登录/注册页提交忙碌态」(用户报障,已上线)

  • 根因:login.html 提交处理没有任何忙碌态;/api/auth/login 走 bcrypt 数秒、/api/dsh/enter 还要拉起实例(可达 20s)⇒ 期间按钮可点、无反馈。
  • 改法(对齐 06-UI规范 Token,只加不删):.btn[disabled] / .btn.is-busy / .btn .sp(14px 转圈)+ prefers-reduced-motion 关闭 + 通用 setBusy(busy,label)(记住原 innerHTML、退出原样恢复)+ 两段式文案「登录中… → 正在进入工作台…」;register 同构(成功后保持禁用防重复注册)。
  • 真 Chrome 实测:点击后 220 ms 即 disabled=true + is-busy + spinner +「登录中…」;失败后恢复「登 录」并显示「用户名或密码错误」。截图 _patch77/shots/login-busy.png。
  • 部署:静态页 + 校验脚本 scp(免重启)。回滚 git checkout -- web/login.html web/register.html。

文档:档案 81(含 §七 进度追加)+ INDEX 行 + manifest 已推送;四件套 rc=0;锁与占号(81)已释放。未 commit / 未 push。


11:04–11:1x · README 撤掉前排授权摘要 + 🔴 误放别的会话的锁(事故)

一、README(按用户意见)

用户指出「授权……」虽然章节在最后,但前排正文里还留着我加的那句授权 blockquote ⇒ 撤掉。

  • 现状(已核实):## 授权与商业使用 本来就在最后(第 464 行 / 共 484 行);前排只剩 ① License 徽章(第 7 行,链接到末节)+ ② 我加的第 12 行摘要。
  • 处置:删掉第 12 行那句 blockquote ⇒ 全文授权信息只剩:顶部徽章 + 目录项 + 末节(含文末一行摘要)。徽章已写明 personal free | commercial paid,不丢信息。
  • 验证:TOC 16/16 锚点正确、顺序一致;阻断探针 0;484 行。技能 v1.0.9 同步改口径(附则 3 第 2 条:授权不进前排正文,靠徽章 + 末节摘要)。

二、🔴 事故:把另一个会话的全局执行锁 rm -rf 掉了(已恢复)

经过:为同步技能归档副本,我把 --claim-exec 与 --release-exec 写在同一条命令里,并把抢锁输出重定向到文件只显示了 OWNER 首行 ⇒ 没看见「✗ 抢锁失败」。那时锁属于 R1-注入层-1104(11:03 起)。命令末尾的 --release-exec 实现是 rm -rf "$LOCKEXEC" ⇒ 别人的锁被我删掉。

我的两个失误:① 抢锁结果没当场看;② claim 与 release 同命令(这正是我自己在技能坑 16 里写过的禁忌,写了却没守住)。

恢复:该脚本的 release 不留档,于是按 handoff-guard.sh:43 的格式原样重建 —— R1-注入层-1104 / 开始:09-13 11:03 / 在做:(未声明单号)(值取自抢锁失败时的输出)。已验证:信息模式显示占用者仍是 R1-注入层-1104,我自己再抢被正确拒绝 ✓。该会话的 --release-exec 仍可正常释放,流程未断。 ⚠️ 若 R1-注入层-1104 其实已结束,这个锁会变成残留锁 ⇒ 需用户本人撤(处置权只属于用户)。

同时披露:在那段「以为自己是持有者」的窗口里,我还做了两件事 —— ① cp 覆盖了自己的技能归档副本(低风险);② 跑了四件套,其中 docs-manifest.py 写了一次 docs-manifest.json(派生文件、可重生成)。两者都与对方的工作无内容冲突,但纪律上不该发生。

已固化的改进:技能 §8 坑 16 升级为 🔴 事故级(claim 输出必须当场看 / 放锁前再确认 OWNER / 误放后按 handoff-guard.sh:43 重建并告知用户);技能版本 v1.0.9 → v1.1.0。 ⏳ 待补:技能归档副本仍停在 v1.0.9(锁被别人持有 ⇒ 按纪律停手),锁释放后按 §10 清单同步。

11:0x–11:5x 「继续处理直到改造完毕」:R1 全部落地并验收 + R3 双名期第一步 + R2 的 R5 评估

R1(需重启,已完成)

  • ① 注入脚本外置:assets/inject/{recovery,assist}.js + proxy.ts 用 loadInject() 运行时读(fail-fast)⇒ 27KB 内联模板字面量删除,「转义写崩」从根消失
  • ② 自愈收口:recovery.js 头部写明唯一状态机表(5 类触发面 × 判定 × 动作)
  • ③ 有界恢复:10 min 内恢复 >3 次 → 停手 + 手动「重试」按钮(治无限循环)
  • ④ 配额随插件集合:instanceMemMb()(160+Σ预估,下限384/上限1024)+ V8 堆由配额独占(withHeap() 摘掉 env 里的旧值再按配额补回,DSHS_HEAP_MB_OVERRIDE 可强指)
  • 验收:服务 active(PID 435201)、门户 200;guest 新实例 MemoryMax=544MB + NODE_OPTIONS=--max-old-space-size=256;guest 实例页含 overRecoverBudget/__dshRetryBtn/状态机表/新视觉 ✓
  • ⚠️ 事故(约 1 分钟不可用):__dirname 在 ESM 里不存在 → 平台启动即失败、systemd 重启 3 次;改用 dirname(fileURLToPath(import.meta.url)) 后恢复。教训:npm run build 通过 ≠ 能跑,ESM/CJS 运行时差异编译期查不出 ⇒ 部署后必须立刻 curl 自证。
  • ⚠️ 另一发现:DSH_INSTANCE_NODE_OPTIONS 不在 unit / manager env 里却仍在进程 env ⇒ 改配置够不到,只能代码侧独占堆。
  • 备份 /opt/dsh/backups/pre-r1-20260913-110923。

R3 双名期第一步(零中断,已完成):/opt/dshs、/var/lib/dshs、dshs.service 三个 symlink + daemon-reload ⇒ 两名字同 PID 435201;回滚一条口令(rm -f 三个 + daemon-reload)。剩余:env 双读 → 逐条改绝对路径 → 切换期(改单元真名 + 迁 DB)。

R2(待用户拍板):已出 R5 权限影响评估(结论:建议批准「iframe 内嵌 + 仅 admin + 只读优先」;实测 portal.html 无 X-Frame-Options)。 R4:两条路(文档并入代码仓 / 保持双库但单向导出)待用户选。

文档:档案 81 §八/§九 已写入并推送;四件套 rc=0;双锁已释放。未 commit / 未 push。

11:18 新需求立项:多语言(i18n)· 排最后(R5)

用户原话:「加个需求 相关页面能否 支持多语言,可以最后处理」。

  • 已写进 档案 81 §十(范围 / 方案 / 为什么排最后 / 验收口径)+ §四 分期表加 R5 行 + 03-路线图 待办表加「R2–R5 待排期」行 + INDEX 04-81 行补记。
  • 范围:✅ 平台 6 个静态页(//login/register/portal/admin/wake)+ 注入脚本文案 + 平台 API 错误码→文案映射;❌ 不动实例内 dsh 官方 UI(R2 红线)与业务插件文案。
  • 方案:assets/i18n/{zh-CN,en}.json 词条表 + assets/i18n.js 极小运行时(?lang= > cookie dshs_lang > navigator.language > zh-CN 兜底;data-i18n 标注就地替换;缺 key 回退中文并 warn)+ 页脚语言下拉;verify-static.mjs 加「裸中文」防回归判据。
  • 排最后的三条硬理由:① R2 门户改造会横穿页面,先抽白抽 ② R3 命名未收尾,assets/i18n/ 目录要对齐 dshs 体系 ③ 纯体验增强、不修事故(今天 4 类事故已闭环)。
  • 同步记录 R2(待 R5 权限确认)/ R3(双名期第一步已做,剩 env 双读+路径改写+切换期)/ R4(待选 a/b)的状态。

11:18–11:3x · 致谢「只写一行」+ 🔴 发现导出基线在漂

一、按用户意见精简(第七次文案纠正)

用户指出致谢行「有必要讲这么多吗,好好想想」。原文三段里有两段是在讲我们自己:

  • 「此后在本仓库做了大量深度改造」= 自我表扬(致谢不是讲功绩的地方)
  • 「项目名也从它的 X 换成了我们的 Y(避免指代混淆…)」= 内部事务,读者不关心
  • 「骨架范围枚举(认证与审核 · 网页桌面 · …)」= 与 LICENSE 第一层 / THIRD-PARTY-NOTICES.md §4 重复

改为一行:起步时参考了 [dshs](作者 上游作者(已按要求不再具名),MIT)—— 感谢作者开源。(法律范围保留在 LICENSE / NOTICE 两处 ✓ 未丢)

同类一并清掉(这才是"好好想想"):

  • 版本与迭代里重复的议论句「本项目把『能跑』补齐为『能对外长期运维』」→ 删(标题已是「六组改造」)
  • 授权节尾「—— 这是 MIT 的硬性要求,必须保留」→ 删解释,只留规则本体
  • 部署形态末「因此 README 与 install.sh 一律以模式 A 为准」→ 删(关于文档自身的话术)
  • DeepSeek Harness 那条收紧为一行
  • 三处「机制见亮点」指针砍到 2 处(Hero + 功能详解),NOTICE 致谢行去掉与致谢无关的自家验证状态括注

规则固化:技能 R-O8 附则 1 新增 3 条 —— 致谢只写一行(禁骨架枚举 / 禁自我表扬 / 禁改名理由)· 别解释我们为什么这么写 · 指针行不重复。

验证:TOC 16/16 锚点正确;探针 0;tsc --noEmit exit 0;README 478 行。

二、🔴 导出基线在漂(本轮最重要的发现)

重建时探针突然报 mcn-suite ⇒ 溯源发现:源码仓 D:\github\dsh_shenxian 的 HEAD 仍是 da3e0f9,但工作树已有 20 个未提交文件(orchestrator.ts / crash-policy.ts / proxy.ts / spawner.ts / config.ts / web/*.html / test/* / package.json …)—— 多个会话正在并行改源码(R1-注入层 / R5多语言立项 等)。而导出脚本复制的是工作树 ⇒ 导出里混入了别人未完成的改动,且新增标识('dsh-plugin-mcn-suite'、内存预估表)随他们提交冒出来。

  • 已处理:新增 GLOBAL 规则把 'dsh-plugin-mcn-suite' → '某插件套件'、注释去 MCN 化(dsh-univer-office: 384 按特许保留 ✓);重建后探针 0 命中。
  • 发布前必须二选一(已写进技能 §10):① 改成按 commit 取(git archive <commit>,才是真正的冻结版本);② 或接受"含未提交工作",则每次重建都要重跑探针并复核 README 描述与实际一致。

三、待补(锁在别的会话手里)

技能已到 v1.1.x,归档副本仍停在 v1.0.9 —— 锁先后被 R1-注入层-1104(11:03) / R5多语言立项-1120(11:18) 持有 ⇒ 按纪律停手,同步清单已写进技能 §10。