Files
dsh_ai1net_server/归档/humanize-原文件备份-20260922/dsh-opensource-release/03-实测坑.md.pre-坑20去重
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

94 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
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.
# 实测坑(全部踩过,别再踩)
> **归属**:技能 `dsh-opensource-release` 的详情档(**按需读**,不是每次都要读)。
> **本档覆盖**:实测坑(全部踩过,别再踩)(原行 L621–L703)。
> **主文件 / 判据与流程主干** = `../SKILL.md`(§0.5 事故清单 · §0 事实 · §1 硬规则 R-O1–R-O17 · §1.5 用户当场纠正的硬口径 · §5 授权结构 · §6 发布 SOP 主干 · §7 验证八件套 · §相关)。
> **来源**:2026-09-22「技能重组线」把 `../SKILL.md` 的 L621–L703 段下沉到本文件;同日按 humanizer 技能做了一轮「人话化」措辞修订,技术标识、判据与条目数未动。
> **跨档引用**:正文里的「§N / 见 §8 坑 N / 见下表」等编号,用 `../SKILL.md` 末节「详情索引」的原章节列定位。
> **维护**:本文件与 `../SKILL.md` 的指针行成对;改内容时同时核对主文件的指针描述是否仍准确。
---
## 8. 实测坑(全部踩过,别再踩)
1. **`text` 被重新赋值导致替换未落盘**:脱敏函数里 `text = text.replace(...)` 之后再用 `if text2 != text` 判「是否需要写盘」⇒ 全局替换**永远不落盘**(只落行级改动)。**必须留 `orig = text` 做比较基准。**
2. **行级重写的 key 必须写「全局替换之后的文本」**:例:原行 `(档案 70 的 anysearch 事故…)`,key 要写 `(档案 70 的 该插件 事故…)`。用替换前的原文当 key ⇒ 静默不命中,留下半吊子句子。
3. **行级重写会丢缩进**:按 `strip()` 匹配就必须把**原行的 indent 补回去**(多行替换值还要给后续行也加 indent),否则 TS 缩进错乱。
4. **全局替换顺序敏感**:`wake.html` 的专用规则必须在笼统的 `ai1net → <baseDomain>` **之前**,否则专用规则永远不命中(会被先替换成 `<baseDomain>.com` 这种半成品)。
5. **重建会抹掉手写文件**:所以有 `OVERLAY`(先快照 `_overlay/` 后恢复)+ `--force` 安全闸。**验证过一次**:重建后 5/5 个 overlay 文件逐字节一致。
6. **`.git` 绝不能带过去**:历史里含全部内部信息;必须是**全新仓库**。
7. **Windows 下 `install.sh` 没有执行位**:README 一律写 `sudo bash install.sh`;要执行位就 `chmod +x` 后再提交。
8. **别信「探针没命中」的直觉**:探针必须**在重建之后、还原 overlay 之后**跑(overlay 里也可能带泄漏)。
9. ⛔ **绝不对 `_build_export.py` 本身做批量替换**:脚本里同时存在「源模式」与「目标值」(例如规则 `("dshs", "dsh-web-platform")`),盲替会把**源模式**一起改掉 ⇒ **改名规则静默失效**(2026-09-13 实际误伤 4 处)。改脚本只用精确 `Edit`,改完**必须重建一次并用 grep 复查**。
10. **`Dockerfile` / `Dockerfile.dsh` 曾被整份跳过脱敏**:`is_text()` 按扩展名判断,二者无扩展名(或 `.dsh` 不在白名单)⇒ 漏掉。已在 `is_text()` 里加特判。
11. **`package.json` 属「源派生」而非 OVERLAY**:手改 version / description / repository / license 会在下次 `--force` **被覆盖**。这类项目自有字段必须写成 **GLOBAL 规则**。
12. **`dshs.db` 是无前缀写法**:`src/config.ts` 里默认库文件名不带 `dsh-` 前缀,只替换 `dshs` 会漏掉它;要单独一条规则。
13. **改名要连导出目录名一起改**,并同步两个辅助脚本里的绝对路径(`_build_export.py` 的 `DST`、`_verify_tsc.mjs` 的 `EX`),改完**重跑一次 tsc** 验证路径仍对。
14. **废弃的旧候选名要进阻断性探针**:改名改了两轮时(`dshs` → 中间候选 → 终选),如果探针只查最早那个名字,**中间名会静默残留**(`_overlay` 里最容易中招)。做法:把**所有曾用名**都加进 `LEAK_PROBES`。
15. **改 overlay 的自指标识可以做批量替换**(overlay 里只有本项目自己的标识,上游出处名是另一个字符串),但**必须先用 grep 确认「旧名出现处全是自指」**再替换;`_build_export.py` 本身**绝不**能这么干(见坑 9)。
16. 🔴 **`--claim-exec` 的输出必须当场看;claim 与 release 绝不能写进同一条命令**(2026-09-13 **实际闯祸**):把 claim 结果重定向到文件、只显示 `OWNER` 首行 ⇒ **没发现抢锁失败**;命令末尾的 `--release-exec` 是 `rm -rf` 语义,于是**把另一个会话(`R1-注入层-1104`)的锁删掉了**。正确姿势:
- 抢锁**单独一条命令**,且**当场看它的输出**(`✓ 已持全局执行锁` vs `✗ 抢锁失败`);
- 看到「占用者」不是自己就**立即停手**,不碰文档库/代码库;
- **放锁前再 `cat 交接单/.exec-lock/OWNER` 确认首行是自己**;
- 万一误放:脚本实现是 `rm -rf "$LOCKEXEC"`、**不留档**,只能按 `handoff-guard.sh` 第 43 行的格式原样重建(`OWNER` / `开始:MM-DD HH:MM` / `在做:…`,值取自抢锁失败时的输出),再用 `bash scripts/handoff-guard.sh` 的信息模式验证「占用者」已回到原会话,并**在回复里明确告知用户**;若该会话其实已结束,请用户自行撤锁(处置权只属于用户)。
17. 🔴 **迁移工作根:先改脚本常量,再搬目录**(2026-09-13 实际踩):顺序颠倒(先 `mv` 后改 `OUT`)时,任何一次重建都会**在旧位置把整个仓库重新建出来**(那次建出 135 个文件),而且因为旧位置**没有 `_overlay`**,**6 个手工层文件(README / LICENSE / NOTICE / PLUGIN-PORTING / install.sh / LICENSE-UPSTREAM-MIT)全部缺席**;若此时旧目录被当成交付物,就是一次「文档凭空消失」的事故。
**正确顺序**:① 改 `_build_export.py` 的 `OUT` **和** `_verify_tsc.mjs` 的 `EX` → ② 复制/搬运目录 → ③ 在新位置重建 → ④ 核对 `文件数` 与**手工层 6/6** + 跑 `tsc` → ⑤ **`ls` 确认旧位置不存在**(没被重建复活)→ ⑥ 同步本技能与项目 MEMORY 里的路径引用。
⚠️ 附带教训:`_build_export.py` 的安全闸与 OVERLAY 机制**都依赖 `OUT` 指向真实工作根**;`OUT` 指错时它**不会报错**,只是**在别处默默新建一套**。
18. 🔴 **`INCLUDE_DIRS` / `INCLUDE_FILES` 是白名单:漏了不报错,只是少文件**(2026-09-13 实际踩:`assets/` 漏收录 ⇒ 导出的仓库缺注入脚本,平台会 fail-fast、自带校验脚本也会失败)。**两条防线**:
- **`REQUIRED_EXPORT` 清单**(脚本内,26 项):构建时逐个 `os.path.exists`,**缺一即 `return 1`**;新增"运行时要读的文件"必须加进去。
- **用被导出仓库自带的校验脚本验收**:`node scripts/verify-inject.cjs`(读 `assets/inject/*.js` + 检查 `proxy.ts` 走 `loadInject`)。这比"我看了一眼目录"可靠得多。
⚠️ 同类风险点:`package.json` 的 `files`(npm/git 安装按它过滤,与 `INCLUDE_*` 是**两套名单**,都要补)。
19. 🔴🔴 **`cp _overlay/<f> <仓>/` 会把「下一版内容」整份带进当前版**(2026-09-18 血证,已误发上公网):`_overlay/` 在**改名窗口期**是「未来版」,导出仓是「当前版」;为修一处残留引用而 `cp _overlay/README.md` 覆盖旧仓,就把 **新名 + 新中文名 + `git clone …/<新仓>.git` + `cd <新仓>` + `systemctl is-active <新仓>`** 一并推了上去。
⛔ **真实危害不是"名字不对"而是功能性缺陷**:README 让用户 clone 的那个仓库当时**是空的**(只有建仓提交)⇒ **照 README 做的人克隆到空仓库,部署直接断**。
✅ **规矩**:`_overlay` 处于下一版状态时**绝不整文件 `cp` 到导出仓**,只准**逐处精确 `Edit`**,只落本次要发的那几段。
⚠️ **最阴的一点**:这种误覆盖会让「导出仓 vs `_overlay` 的 diff **变空**」。**diff 为空本身就是"新版被覆盖进去"的证据**,不是"一致"的好消息。**看到意外的一致要停下来查原因。**
✅ **推前必做两条复核**:① `grep -rn "<新名>\\|<新中文名>" .` ⇒ **须为空**;② **部署命令自洽** —— `git clone` 指向的仓库**真有内容**、`cd` 的目录名、`systemctl` 的服务名三者一致。
20. 🔴 **新增「中英文件对」时必须同步登记**(2026-09-18 实证;**2026-09-19 扩充为四处**):
这份清单是**白名单**:没登记的对象**不会报错,只是静默跳过检查**。所以新增 `manual/X.md` + `manual/X.zh-CN.md` 时,**四处一起改**:
① `_build_export.py` 的 `OVERLAY`:漏了 ⇒ **静默少带**,文件根本不在导出物里;
② `_build_export.py` 的 `REQUIRED_EXPORT`:漏了 ⇒ 缺文件**不报错**(它只查"清单里的在不在");
③ **`_check_parity.mjs` 的 `MANUAL`/`PAIRS`**:漏了 ⇒ 该对**静默跳过**对等检查(标题数 / 表格行数 / 图数 / 英文汉字数全不检);
④ **`_check_links.mjs` 的 `MANUAL`**:漏了 ⇒ 该文档**整份不参与**链接与锚点检查。
🔴🔴 **③ 与 ④ 是两份独立实现**(不 import 同一常量)⇒ **必须各改一次**,改一处不算改。
✅ **验收判据(别只看 exit code)**:**回读检查器自己报告的文档数有没有 +N**,本轮实测 `links` **24 → 26**、`parity` 输出里多出 `manual/contributing.md` 一行(headings 4/4)。
⚠️ **同类静默跳过已出现四次**(坑 18 `INCLUDE_*`/`REQUIRED_EXPORT`、坑 20 的两份 `MANUAL`)⇒ 判据统一为:**凡"清单式"机制,新增对象后必须回读清单计数是否 +N**(AST 实读,别数文件)。
⚠️ **同类静默跳过已出现三次**(坑 18 `INCLUDE_*` / `REQUIRED_EXPORT`、本节 `PAIRS`)⇒ 判据统一为:**凡"清单式"机制,新增对象后必须回读清单计数是否 +N**(AST 实读,别数文件)。
21. **`COMMERCIAL-LICENSE.*` 的落位规矩(2026-09-18 立)**:① 只在**四份 README**(仓 + `_overlay/` 各中英)授权节尾加**一句**链接,⛔ 不在正文铺开;② ⛔ **不得**把「商用须授权」的句子写进 `LICENSE` 或 README 的 AGPL 段落(那才是 §10 追加限制);③ 英文版**汉字数仍须恒为 4**(只在顶部入口行出现「中文文档」)。
22. 🔴🔴 **`sanitize()` 不作用于 `OVERLAY`,所以「干跑预演通过」≠「构建能过」**(2026-09-19 血证)——
`restore_overlay()` 排在 `sanitize()` **之后** ⇒ 手工层(README / manual / LICENSE / install.sh …)**永远不会被脱敏**,上面任何字面量都会原样进公开仓。
实测:`manual/decisions.{md,zh-CN.md}` 里一行目录树写着 `poc/`(该目录从未收录)⇒ **重建时被阻断探针抓出 `blocking hits: 2`**。
⇒ **纪律**:① 改完手工层**必须重建**(或至少跑整树探针);② 若写了「干跑预演」脚本,**它只覆盖源派生文件,不能当手工层的通行证**;③ 命中后按「**先改仓库同名文件 → 再 `Copy-Item` 进 `_overlay`**」的顺序修(反了会被 `stash_overlay()` 覆盖回去)。
23. 🔴 **裸替换「纯字母数字」的内部标识会改坏 `package-lock.json`**(2026-09-19):
内部主机号 `w106`/`w47` 全由 base64 字母表字符组成 ⇒ 裸替会命中 `integrity` 哈希,**`npm ci` 校验失败**,且症状与改动毫无关联、极难排查。
⇒ 只替**带引号**或**作对象键(后跟冒号)**的形态;**纯数字标识(如 `15432`)也要先扫一遍上下文**再决定(本轮实测 11 处全在 `127.0.0.1:15432` 这类 URL 里,安全)。
24. 🔴 **改名之后,新项目名可能与旧脱敏规则「撞名」**(2026-09-19):
新名 `dsh_ai1net` 含 `ai1net`,而 `ai1net` 同时是**真实域名**的词根 ⇒ 若照旧写一条笼统的 `ai1net` → `<baseDomain>`,会把新名打成 `dsh_<baseDomain>`。
⇒ **只打带 `.com` 的域名形态**(`dsh_ai1net` 永不含 `.com`)。**同理**:任何"新名包含旧敏感词根"的情形,规则都必须**精确到可区分的形态**,并**规则 + 探针同时加**。
25. **内部运维 / 演练 / 诊断脚本族的排除判据(2026-09-19 定型)**:新增一整批 `*-probe` / `*-drill` / `*-jitter` / `*-entropy` / `bootstrap-*.sh` / `dshlog.mjs` / `find-ui*` / `check-layering*` 时按四条判:
① **README / manual 从不引用它**;② 含内部主机号 / 测试机 IP / 内部隧道端口;③ 引用内部文档(`交接单` / `04-调整方案` / `poc/`);④ 是「把节点接进本平台自研编排」的一次性手册。
⇒ 四条全中 = 与 **K8s 那一族同处置(整批 drop)**,别试图逐条脱敏(脱完也不可用,且留下一堆 `<>` 占位符)。
⚠️ **`.py` 这类脚本进来时先看 `TEXT_EXT`**:不在白名单的扩展名**整份跳过脱敏且不被任何探针扫**(本轮 `.py` 就踩了,已补)。
26. 🔴🔴 **只改手工层(`OVERLAY`)文字时,不必整树 `--force` 重建**(2026-09-19 定型,**是坑 22 的推论**):
坑 22 已证 `restore_overlay()`(纯 `shutil.copy2`)排在 `sanitize()` **之后** ⇒ 手工层**不过脱敏、逐字节直落**。
⇒ **「改 `_overlay/<f>` + 跑 `_sync_overlay.py`(方向 `_overlay` → 导出仓)」得到的产物,与整树重建逐字节相同。**
✅ **收益**:风险面从**整棵树**缩到**改的那几个文件**,不会顺带把源仓当前状态(别人的未提交改动 / 新提交 / `??` 目录)带进公开仓,
也就不必先过 `dirty_src_files()` 那道闸门(省掉一轮「等源仓干净」的阻塞)。
⚠️ **前提与边界**:① 改动**必须落在 `OVERLAY` 清单内**;非手工层文件**只能**「改规则 + 重建」;
② 仍须 `_sync_overlay.py --check` 报**「逐字节一致」**再提交(方向搞反 = 事故 #27);
③ 交付前仍跑**全套门禁**(`_check_dst` 整树探针 / `_check_public` 两仓 / links / parity / tsc),
其中 `_verify_tsc.mjs` 与 `_check_public.py` **必须串行**(junction 假阳性)。
④ 若这轮**同时**还有规则改动,就必须回到整树重建 —— 捷径只适用于「纯手工层文字」。
27. 🔴🔴 **写文档用词之前,先 grep 整个代码库,确认这个词有没有被别的意思占用**(2026-09-19 用户点出):
实测:`manual/project*.md` 里用「**水位**」表达「上下文已占用量」⇒ ① 对读者是**类比**(要先自己换算);
② **更硬的是术语撞车** —— `水位` 在本项目**已有两个别的含义**:`src/net/relay/client.ts:150` = **WS 出向水位**(字节,默认 256 KiB)、
`src/db/schema.ts:301` = **worker 容量水位**。同一个词在文档 + 代码里指**三样**东西,**这是歧义,不是风格**。
⇒ **改法:贴回同段前一句已经引入的项目词**(该段前半句是「上下文被当作**有上限的预算**」)⇒
ZH `其次是**上下文已经占了多少**` | EN `then **how much of the context is already used**`。⛔ **不另造新比喻**。
🔑 **为什么必须单独立一条**:**探针体系对这类问题完全无感**,该文件所在 `manual/` 没有任何探针,
`blocking hits: 0`、required 全在、两仓 0 命中 ⇒ **门禁全绿,而词依然是坏的**。
判据:**探针只答「有没有不该出现的敏感信息」,不答「这个词会不会产生歧义」。**
⛔ **同一段里另有 5 处类比,用户只点了一处 ⇒ 一律不动,列条目报上去等拍板**(`背着这份重量` / `腐坏` / `悄悄搭车` / `淹没` / EN 独有 `blast radius`)。
**同段同病也不许自行扩大改动面**:报清单的成本是一个回合,擅改的成本是信任。
---