92 lines
16 KiB
Markdown
92 lines
16 KiB
Markdown
# 实测坑(全部踩过,别再踩)
|
||||
|
|
|
|||
|
|
> **归属**:技能 `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 05-交接单/.exec-lock/OWNER` 确认首行是自己**;
|
|||
|
|
- 万一误放:脚本实现是 `rm -rf "$LOCKEXEC"`、**不留档**,只能按 `handoff-guard.sh` 第 43 行的格式原样重建(`OWNER` / `开始:MM-DD HH:MM` / `在做:…`,值取自抢锁失败时的输出),再用 `bash 07-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 实读,别数文件)。
|
|||
|
|
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 / 内部隧道端口;③ 引用内部文档(`交接单` / `调整方案` / `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`)。
|
|||
|
|
**同段同病也不许自行扩大改动面**:报清单的成本是一个回合,擅改的成本是信任。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|