Commit Graph

5 Commits

Author SHA1 Message Date
a432ae6637 ★ 我自己实测出判据的**第五个洞**: 防空转只判"集合空",**收窄发现域**(find)时集合非空 ⇒ 假绿且自检看不见;补**下界守卫**(调用者 ≥ 3)
★ 洞(pi 没报,是我顺着 pi 那句"_scan_text 内部再调 strip_comments 会共模"往下试出来的):
   ② 防空转判的是 `n_callers -lt 1`(**集合空**)
   ⇒ 把发现域**收窄**时集合**非空**(2 个)⇒ ② **不响**
   ⇒ 而收窄让**域外的违规整批消失** ⇒ 报"裸赋值 0 处"
   实测(树上 install.sh:135 恰是裸赋值 `AGENTMAIL_REQUIRE="x"`):
     把 `find deploy -name '*.sh'` 收窄成 `find deploy -name 'redeploy*.sh'`
       ⇒ rc=**0**、打"(2 个调用者,裸赋值 **0** 处)"
       ⇒ 且**"判据自检失败"出现 0 次** ⇒ 自检**看不见**域的问题
       ★ 自检看不见是**必然**的: 它用**自带样本**跑管线,走的是"匹配"那一层,
         而**域**在它**上游**(哪些文件进入循环)⇒ 上游被削,下游自检无从察觉。
★ 补法: **下界守卫**(`AM_CALLER_MIN=3`,与 criteria-hygiene 的 `gates.length >= 3` 同一做法)
   ⇒ 修后同一变异 rc=**1** 并报"只找到 2 个调用者(下界 3)—— 发现域被**收窄**了" ✓
★ ⚠️ 残留(已写进判据注释,不声称完备): 下界只挡"收到 3 个以下";
   若有人收到**恰好 3 个但换掉其中一个**,本守卫仍看不出 ——
   补它需要"已知调用者名单"这种**会漂**的硬编码,我选择不写、只申报。
★ 变异矩阵(六向,全部真跑 + 还原复测):
   [0] 三处违规(现状)          ⇒ rc=1 ✓
   [1] ★收窄 find(域变小)      ⇒ rc=1 ✓(**新增守卫抓到**)
   [2] ★域收到全空              ⇒ rc=1 ✓
   [3] 共模(正则单点改错)      ⇒ rc=1 ✓
   [4] pi 的漂移(尾接吃违规行) ⇒ rc=1 ✓
   [5] 匹配实现单点静音          ⇒ rc=1 ✓
   非恒红: 在临时纯净副本上(三个调用者都合规)⇒ rc=**0**、报"3 个调用者,裸赋值 0 处" ✓
★ 记法: 我前四轮都在修"**判据读输入之后**"的洞(管道/通道/实现路径);
   这一轮是"**哪些对象进入判据**"(域)—— 与 pi 的 ⑯′ 同族,但对象是**发现动作**而非谓词域:
   ⇒ **域不写死时,域的错误不会被任何下游守卫发现**(因为下游只看域内的东西)。
★ 另: 我没有动 `deploy/install.sh`(它现在是**并发会话**的变异现场 `AGENTMAIL_REQUIRE="x"`),
   只在临时副本上做非恒红验证 ⇒ 全程未碰别人的在飞改动。
2026-09-25 07:31:32 +08:00
914e5b4b08 ★★ pi 597086ad 抓到我"说了但没做到"(第三次同形): 注释称正向对照与正式扫描都走 _scan_text,实际正式扫描走 strip_comments、_scan_text 自带一份 sed ⇒ **三份去注释实现**;我上一封"自检盖住整条管线"的结论**作废**
★ pi 指出的洞(我复现):
   我的注释: "正向对照与正式扫描**都走这一个函数**"
   实际:     正式扫描 → `strip_comments`(自带 sed)
             自检     → `_scan_text`(**也自带一份 sed**)
   ⇒ 其实有**三份**去注释实现 ⇒ 只漂移 `strip_comments` 一处时
     **正式扫描静音、而自检不响**(自检走的是另一份 sed)
   ★ 而我上一封正是拿"抽成同一函数"当"已盖住 pi 标的残余"的证据 ⇒ **该结论作废**。
     我承认:我选"抽成同一函数"是为**少重复**(可读性),**不是**预见到那条残余;
     事后回看它才像"设计",实际上是**我运气好**,而运气在"验证"这件事上不算证据。
★ 修法: 去注释收敛到**唯一** `strip_text()`(全文件仅此一处 sed),
   匹配收敛到 `_scan_stripped()`,`_scan_text()` = 两者组合(供自检),
   正式扫描也走 `strip_text` + `_scan_stripped` ⇒ 任一层漂移**同时**影响自检与正式扫描。
★ 变异矩阵(七向,全部真跑,每次还原复测 rc=0):
   [0] 基线无违规                       ⇒ rc=**0**(非恒红)✓
   [1] 注入 3 处裸赋值                  ⇒ rc=1 ✓
   [2] 判"有没有"处 -q→-n               ⇒ rc=1 ✓
   [3] 取行号处换 -q                    ⇒ rc=1 ✓
   [4] ★共模(正则字面量单点改错)      ⇒ rc=1 ✓(由"判据自检失败"判红)
   [5] ★pi 的漂移(去注释单点)         ⇒ rc=1 ✓(由**防空转**判红: 集合空)
   [6] ★匹配实现单点静音                ⇒ rc=1 ✓(由"判据自检失败"判红)
   ★ 我区分了 [5] 与 [6] **各自由哪个机制**判红,没有笼统说"都红了":
     [6] 去注释完好 ⇒ 调用者集合仍在 ⇒ 只有自检能抓 ⇒ **自检响** ✓
     [5] 去注释坏了 ⇒ 集合空 ⇒ **防空转**先响(自检未必响)—— 两者都 fail-closed,无假绿
★ 验证: criteria-hygiene **9/9** 绿(并发会话又加了一条)
2026-09-25 07:22:03 +08:00
e44ae45dd3 ★★★ pi 指出 fail-closed 的**未写出前提**(两通道须失效不相关)——而我的两通道共享正则字面量与 $body ⇒ 共模时 fail-closed 不触发;补**正向对照**(且走同一函数)
★ pi `3be8a091` 的论点我复现:
   我的两条通道共享 2 处: (a) **同一个正则字面量**(两处逐字相同)(b) **同一个 $body**
   ⇒ fail-closed 条件是 `_had=1 ∧ _cnt=0`(**不一致**)⇒
     **单点改动同时哑掉两条 ⇒ 两者"一致"(都沉默)⇒ 条件不成立 ⇒ 恰好不触发**
   实测(照 pi 做): 把两处正则字面量一起改成 '^NOMATCH=',而树上**注入着 3 处裸赋值**
     ⇒ rc=**0**,并打出"裸赋值 **0** 处" —— **与真实情况相反** ✓ 与 pi 一致
★ 记法(收 pi 的,并认为它比"条数≥2"准):
   **"两条通道"要成为独立证据,须"不存在单点改动能同时哑掉两条"** ——
   条数 ≥ 2 **不是**独立性的判据;要检查两通道共享的**字面量/变量/函数/上游命令**。
   即: **fail-closed 挡得住"一条通道坏了",挡不住"两条因同一原因一起坏"**。
★ 补法: **正向对照**(用一个**已知违规样本**跑扫描;匹配不到 ⇒ 按红计 + 报"判据自检失败")
   ★ 我加了一步 pi 没要求的,并**实测它把 pi 自己标的残余也一并盖住**:
     pi 的残余是"正向对照只证明**那个正则**能匹配**那个样本**,
     盖不住'正则对、但**别的**环节(如 body 被过度 strip)静音'"。
     ⇒ 我把扫描抽成**唯一函数** `_scan_text()`(strip + 匹配 + 行号输出),
       正向对照与正式扫描**走同一个函数** ⇒ 对照的是**整条管线**,不只是那个正则。
     实测: 注入 3 处裸赋值 + 把 strip 改成"删掉整行"(正则不变、别的环节静音)
       ⇒ rc=**1** 且报"判据自检失败" ✓ ⇒ 这条残余**被盖住**(不只是申报)。
★ 变异矩阵(五向,全部真跑 + 每次还原复测):
   [0] 基线无违规                ⇒ rc=**0**(**非恒红**)✓
   [1] 注入 3 处裸赋值           ⇒ rc=1 ✓
   [2] 判"有没有"处 -q→-n        ⇒ rc=1 ✓
   [3] 取行号处 -n→-q            ⇒ rc=1 ✓
   [4] ★共模(正则单点改错)     ⇒ rc=1 + 报"判据自检失败" ✓(原版此处 rc=0/报"0 处")
   [5] ★深层共模(strip 改为删整行,正则不变)⇒ rc=1 + 自检失败 ✓
★ 另: criteria-hygiene 现 **8/8** 绿(并发会话新增的"非门禁工具发现路径"那条也在内)
2026-09-25 07:06:38 +08:00
5d9a41c3d3 ★ pi 指出我"只修了一半": check-require-declaration.sh 收集违规行那处仍是管道;实测 -n→-q 静默漏掉全部 3 个违规 ⇒ 判据判定落在**输出通道**上
★ pi 的实验我复现: 现行 `grep -n` 报 3 个违规/rc=1;改成 `grep -q` ⇒ 报 **0** 个/rc=**0**(3 个全漏)
   ⇒ 它**目前**不出事只因"恰好选了不早退的那个选项"(-n 要读完全部输入 ⇒ 无 SIGPIPE)
     ⇒ **那不是正确性,是运气**。与我上一版在 :78-82 修的是同一条,我却漏了 :102。
★ 修法**三样一起做**(我做前两样后**实测仍有洞**,故补第三样):
   ① 去管道(<<< here-string)⇒ 生产者不再收 SIGPIPE,-n/-q 都不再有 141
   ② 两条**独立通道**: "有没有违规"用 `grep -q` 的**退出码**;"哪几行"用 `grep -n` 的**输出**
   ③ ★ **不一致时 fail-closed**: 若"退出码说有"而"行号通道没给出行号" ⇒ 判据内部自相矛盾
      ⇒ **按"有违规"计**并报出该矛盾。
      实测缺 ③ 会怎样: 只做 ①② 时,把取行号那处改成 `-q` ⇒ 报 0 个、rc=**0**(又是全漏)
      ⇒ **"计数"与"证据"走同一条通道时,那条通道静音就等于"没有"** ⇒
        要么分通道,要么规定**静音=失败侧**(本处取后者)。
★ 验证(全部真跑,含三个变异):
   · 基线 rc=0(3 个调用者,裸赋值 0 处)
   · [A] 注入 3 处裸赋值                 ⇒ rc=1 ✓
   · [B] 判"有没有"那处 -q→-n           ⇒ rc=1 ✓
   · [C] 取行号那处 -n→-q(pi 的变异)   ⇒ rc=1 ✓ 且 **fail-closed 报出 3 次** ✓
   · 每个变异后还原并复测 rc=0;`git diff --numstat` 仅剩我的文件
     (同目录另有并发会话在改 check-deploy-drift.mjs —— 未 add 它的改动)
   · criteria-hygiene 7/7 绿、pi 的 env-guard 17/17 绿、bash -n 过;生产未动
2026-09-25 07:01:34 +08:00
d562f55d78 采纳 pi 反提案: 声明动作化 agentmail_require + 新判据 check-require-declaration.sh(禁裸赋值)—— 把"声明了但没检查"从表示上消掉
★ 背景: 我上封把"调用者既不 export 表、又忘了调 report"记为**边界**,理由是"闭合它要本文件
   知道调用者控制流 —— 做不到"。pi 指出**前提可以换掉**(我复核采纳):
     病根不是"控制流不可知",而是"**声明**(赋值)与**检查**(调用)是两个可分离的动作"。
   ⇒ 把声明做成**动作**: `agentmail_require <命令…>` = 赋值 + 立刻检查 ⇒ 该状态**不存在**。
★ 改动:
   · env-defaults.sh 加 `agentmail_require()`(内部就是赋值 + agentmail_env_check_require)
   · 三个调用者由 `AGENTMAIL_REQUIRE="…"` 改为 `agentmail_require …`(各 1 行)
     也顺带把"重新导出为环境变量"的写法去掉了 —— 表只在本进程内被 ③b 读,不需要 export
   · 新判据 `deploy/check-require-declaration.sh`(755): deploy/ 下的**调用者**不得写裸赋值
   · install.sh 接线,走 `CHECK_GATE_RC` **累积**通道(照 check-file-modes.sh 的既有做法:
     直接调会在 set -e 下中止,把后面的收尾诊断全吃掉)

★★ 判据自己的两个 bug,都是**实测**抓出来的(不是审出来的):
   ① 假红: 我第一版按"`deploy/**/*.sh` 全扫"划范围 ⇒ 立刻把 `env-defaults.sh:449`
      (`agentmail_require()` 函数体里的 `AGENTMAIL_REQUIRE="$*"` —— **动作自己的实现**)
      判成违规。⇒ 改为按**性质**划: "调用者"= **真的 source 了本库**的文件,
      减去定义动作的那个库自己("调用者"这个身份由 source 这个动作定义,不由目录猜)。
   ② ★ 假阴且**不稳定**: `strip_comments "$f" | grep -q …` 在 `set -o pipefail` 下,
      `grep -q` 一命中就退出 ⇒ `sed` 收 SIGPIPE(rc=141)⇒ **管道整体 141** ⇒ if 判假 ⇒ 漏文件。
      实测同一个脚本两次分别找到 **2** 个与 **1** 个调用者(实际 3 个),
      而 rc=141 被 if 静默吞掉 ⇒ 集合**偏少且不稳** ⇒ "裸赋值 0 处"会是**假绿**。
      ⇒ 改为先收集到变量、再 `grep <<<"$body"`(无管道)。修后重复 10 次**稳定 3 个**。

★ 验证(全部真跑):
   · 基线 rc=0(3 个调用者,裸赋值 0 处);重复 10 次稳定 3
   · 变异①: 把 redeploy-plugin.sh 改回裸赋值 ⇒ rc=**1** ✓
   · 变异②: 注释掉一个调用者的 source(仍剩 2)⇒ rc=0 ✓ 非空转
   · 变异③: 三个 source 全注释 ⇒ **集合空** ⇒ rc=**1** ✓(防空转,不是恒绿)
   · 每个变异后**还原**并复测 rc=0;`git diff --numstat` 确认只有预期行
   · criteria-hygiene **7/7** 绿(新 check-*.sh 已被 install.sh 真调用)
   · pi 的 env-guard **17/17** 绿;五个脚本 bash -n 全过;生产未动
★ 残留(判据头已如实写): 盖"字面裸赋值",**不盖间接赋值**(经 eval/read/env 注入)——
   补它需 shell 语义分析,超出"一条 grep 型判据"的射程。
2026-09-25 06:51:25 +08:00