修复: 第五个落点 —— 判据写对了,却**没有任何人执行它**(check-file-modes.sh 空转)

pi `4fc16f66` 报的是 rc=2 那半个缝(M18)—— 我复核后**已经在 `e99a657` 修好了**,
而它的**父提交正是 pi 报信时读的 `6ee9902`**。又是同一形状的竞态。

但照 pi §四 那句「**每条分支的前提本身也要能被构造**」做审计时,
我发现**第五个落点,而且这一格的判据本身写得完全正确**:

`deploy/check-file-modes.sh` = 源文件权限政策的**唯一**判据。
它甚至专门修过自己那份 `[ -x ]` 恒真的 root 陷阱(`b7dc9e9`),改成直接读权限位 —— 修得对。
**但它从来没有被任何东西执行过。** 全仓 `grep -rn "check-file-modes"` 只有:
  · `check-deploy-drift.mjs` 3 处**注释**(谈分工)
  · `run-all.mjs:951` **注释**(讲 root 陷阱)
  · `summary.py:77` **注释**,且**说法与实现不符**
  · `summary.py:431` 一句 `print(...)` 的**文案**
⇒ 唯一一处非注释提及是一句 print 的文案。没有 install.sh / redeploy-* / hook / CI / cron 调它。
**而它当时正红着**:2 个文件是 600 + `jobs/` 缺属主 x 位。

★ 为什么这一类**只能靠判据守**(实测):
  `git status` 看不见、`git diff` 看不见、`git ls-files -s` **只记 100644/100755**
  (组/其他读位**不进版本库**)⇒ 把受跟踪文件 `chmod 600 ↔ 644` 两次 `git status` 都空。
  套件也看不见(`jobs/` 缺 x 位时实测 `diag=none`、`ran=48`,全绿)⇒ **没有第二条通道**。
  0600 在"跑的人恰好是属主"时不炸,换身份就是 EACCES,而那串报错**看起来像代码问题**。

修法:
  · 接进 `deploy/install.sh`(check-shared-libs 之后),**走 `--check` 累积通道**
    (照既有 npm_rc/CHECK_GATE_RC)。⚠️ 我第一版**直接调**,而门禁红时它 `exit 1`、
    install.sh 是 `set -e` ⇒ 当场吞掉后面所有诊断:**实测**接线后 `构建 Gateway`
    在日志里命中 **0 次**(接线前 2 次)—— 正是 install.sh:141-157 刚修过的同一个毛病,
    我自己又造了一遍。改累积后回到 2 次,门禁报 `[FAIL] 权限政策没过(退出码 1)`。
  · 修那两个文件:600 → 644 ⇒ 权限政策现在绿(rc=0)。
  · 新增判据 `criteria-hygiene` 第 7 条:政策门禁必须被入口脚本在**可执行位置**调用。

★★ 变异验证时**我自己先假绿了一次**(单记):第一版用 `code()` 剥注释,而
`lib/read.mjs` 的 `stripComments()` 只认 `//` 与 `/* */` —— 那是 **JS** 的注释,
**`install.sh` 是 shell,注释是 `#`** ⇒ 对 shell 文件**原样返回** ⇒ 判据读到了
**我写在它上面那段解释里的** `` #   `check-file-modes.sh` 红时 `exit 1` ``。
  变异①(接线整段删掉)⇒ 第一版 **rc=0(假绿)**;自剥 shell `#` 后 **rc=1** ✓
  变异②(只在 echo 里提一句)⇒ rc=1 ✓
  变异③(gates 恒空 ∀x∈∅)⇒ rc=1(`只找到 0 个`)✓
(③ 第一次 python 锚点没匹配上、变异没施加,我重测并**先打印"变异已施加"**才算数。)

★ 射程如实标出:只管 `deploy/check-*.sh`(政策门禁族);**不管** `check-deploy-drift.mjs`
这类**按需手动工具**(自带 `--self-check`、文档写明"事后自查")——要求它进入口是**错的**。
它实测同样 0 处调用,但**本判据不覆盖它,也没修它**。判"有没有接线",不判"接得对不对"。

★ 通用纪律(CRITERIA.md §16.1.2):**判据自己也要能"被证明它真的在看代码"。**
若判据读的**语言**与 stripComments 实现的**语言**不一致(shell vs JS、Python vs JS),
那"剥注释"是**假的** ⇒ 判据消费散文,而**变异验证是唯一能戳破它的东西**。

全套 checks=516→**517** pass=512 fail=5 red=5 broken=0 verdict=red(5 条红都是别的会话的);
mutants=48 ran=48 skipped=0 diag=none baseline=7/7✓;5 个自检各 rc=0。
This commit is contained in:
2026-09-20 05:22:42 +08:00
parent efb1c1d2ca
commit 1c3335f0c9
4 changed files with 238 additions and 1 deletions

View File

@ -280,6 +280,45 @@ echo "==> 校验插件共用模块同源"
# 一侧改了另一侧没改,几个平台的行为就会悄悄分叉。
"$REPO/deploy/check-shared-libs.sh"
# ★★ 源文件权限政策(dsh 2026-09-19 接线)。
#
# 为什么单独一条、为什么**必须挂在这里**:这一类**只有它一个判据**,而且
# `git` 根本看不见 —— `git ls-files -s` 只记 **100644 / 100755**(可执行位),
# **组/其他读位不进版本库**。实测:把受跟踪文件 `chmod 600 ↔ 644`,
# `git status` **两次都空**、`git diff` 也不动 ⇒ 没有任何"顺带"的通道会发现它。
#
# 后果不是"本地难看":这些是**源码**,会被构建/测试/部署**以不同身份**读
# (`install.sh` 自己会切身份做可写性判定;`client/electron/test` 里还有
# **以 nobody 降权跑**的判据)。0600 在"跑的人恰好是属主"时不炸,换个身份就是
# EACCES —— 而那串报错**看起来像代码问题**。
#
# ⚠️ 这条判据**写出来很久了却从没被任何东西调用过**(`grep -rn` 全仓:唯一的
# 非注释提及是 `mutants/summary.py` 里一句 `print(...)` 的文案),
# 而它**当时正红着**(3 条:2 个 600 的文件 + `jobs/` 缺 x 位)——
# 正是本仓反复消的那个形状:"**判据在,但走不到**"。
# 它自己那份 `[ -x ]` 恒真的 root 陷阱也修过一版(`b7dc9e9`),修得对,
# 但**没有任何人会跑到那一行**,所以修与不修没有可观察差别。
#
# ⚠️⚠️ 接线时必须走 `--check` 的**累积**通道(`CHECK_GATE_RC`),不能直接调:
# `check-file-modes.sh` 红时 `exit 1`,而本脚本是 `set -e`
# ⇒ 它会在**这里**中止,后面 `build-stamp`/钩子/`--check` 的收尾诊断**一行都不打**。
# 我第一版就是直接调的,**实测**:接线后 `构建 Gateway`/`启用并启动` 等在日志里
# **命中 0 次**(接线前 `构建 Gateway` 命中 2 次)—— 那正是本文件 141-157 行
# 刚修过的同一个毛病("第一道红吃掉后面所有诊断"),我自己又造了一遍。
# ⇒ 照 `npm_rc` 的既有做法:真装路径仍 `set -e` 立刻中止(装机器时不该带着红往下走),
# 干跑路径**记下来、跑完、最后 `exit "${CHECK_GATE_RC:-0}"`**。
if [[ $CHECK_ONLY -eq 1 ]]; then
_fm_rc=0
"$REPO/deploy/check-file-modes.sh" || _fm_rc=$?
if [[ $_fm_rc -ne 0 ]]; then
echo " [FAIL] 权限政策没过(退出码 ${_fm_rc})—— 上面标了 [FAIL] 的那几处就是" >&2
# 只记**第一个**失败码(既有做法):`CHECK_GATE_RC` 是"干跑退出码"的单一来源
[[ -n "${CHECK_GATE_RC:-}" ]] || CHECK_GATE_RC="$_fm_rc"
fi
else
"$REPO/deploy/check-file-modes.sh"
fi
# 插件的纯函数测试(自动转发去重等)。
# 插件不参与构建产物,但它的逆行为会直接变成用户收件箱里的重复邮件,
# 因此也纳入部署前的门禁。zod 已在 node_modules 里就不重装。