#!/usr/bin/env bash # 政策:**调用者**声明"我依赖哪些外部命令"**必须**走动作 `agentmail_require`, # 不许写裸赋值 `AGENTMAIL_REQUIRE="…"`。 # # # 为什么有这条(一个实测出来的活缺陷) # # `deploy/lib/env-defaults.sh` 的 ③b 会检查调用者声明的命令在不在, # 缺一个就 `exit 2` + 人话("命令不在"与"命令在但输出为空"必须分开 —— # 后者会让下游把"工具缺失"读成"检查通过",见该文件里 journalctl 那两处假绿)。 # # ★ 而那段检查**原先只在 `source` 期跑**,三个调用者却都是**先 source 后赋值**: # install.sh source 131 / 赋值 135 # redeploy-gateway.sh source 42 / 赋值 46 # redeploy-plugin.sh source 52 / 赋值 56 # ⇒ source 那一刻表是**空的** ⇒ `[ -n "${AGENTMAIL_REQUIRE:-}" ]` 直接跳过 # ⇒ **那张表从未被检查过**。实测 A/B: # AGENTMAIL_REQUIRE=__no_such_cmd . env-defaults.sh ⇒ rc=2(检查本身是好的) # . env-defaults.sh; AGENTMAIL_REQUIRE=__no_such_cmd ⇒ rc=0(**漏过**) # # 修法是两处都挂检查(source 期 + report 内)。但那只堵住"**恰好忘了调 report**"—— # 病根是"**声明**(赋值)与**检查**(调用)是两个可分离的动作"。 # ⇒ 把声明本身做成**动作**:`agentmail_require <命令…>` —— 想声明就**必然**检查, # "赋了值但那次检查没跑"这个状态**从表示上不存在**。 # # # 为什么需要**这条判据**(而不是只靠"大家都记得用动作") # # 下一个新调用者仍可能写裸赋值 —— 而裸赋值**不报错、不警告**: # 它只是让那张表在 source 后才存在,于是检查又一次静默失效(与上面那个缺陷同形)。 # ⇒ 判据的作用是**让"写裸赋值"立刻变红**,而不是靠人记住。 # # # 射程(pi 2026-09-25 §四 要求写清它管什么、不管什么) # # **管的集合 = "调用者"**,定义为:`deploy/` 下 `*.sh` 里**真的 source 了** # `env-defaults.sh` 的那些文件,**减去定义动作的那个库自己**。 # ⇒ 理由是"调用者"这个身份**由 source 这个动作定义**,不由文件名或目录猜。 # ⚠️ 我第一版按"`deploy/**/*.sh` 全扫"划范围,**实测立刻假红**: # 它把 `env-defaults.sh:449`(`agentmail_require()` 函数体里的 # `AGENTMAIL_REQUIRE="$*"` —— **动作自己的实现**)判成违规。 # ⇒ 那是"动作的定义点",不是"调用者的使用点",两者必须分开。 # # 管:调用者里**行首**的裸赋值 `AGENTMAIL_REQUIRE=…`(去掉注释后匹配) # 不管: # · 定义动作的那个库(`deploy/lib/env-defaults.sh`)—— **它不是自己的调用者**, # 且它的 `agentmail_require()` 体内**必须**有那行赋值(那是实现,不是违规) # · `AGENTMAIL_REQUIRE_SELF=…`(**不同名**,与 `=` 匹配天然不撞) # · 注释里提到 `AGENTMAIL_REQUIRE` 的文字(strip 注释后再匹配 ⇒ 不误伤) # · **没 source 本库的文件**:它们拿不到 ③b 检查,故"用哪种写法"与本政策无关 # (本政策只保证"**声明了就被检查**",不保证"人人都会声明") # · **间接**赋值(`v=AGENTMAIL_REQUIRE; eval "$v=…"`、经 `read`/`env` 注入等) # ⇒ 这类绕过本判据**仍可漏**。本判据盖"字面裸赋值",不声称盖住全部写法。 # # 退出码:0 = 全走动作式;1 = 有裸赋值 **或**集合为空(防"判据静默空转");2 = 环境不足 set -uo pipefail REPO="${AGENTMAIL_REPO:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" cd "$REPO" || { echo " [FAIL] 进不去仓库根 $REPO" >&2; exit 2; } for c in grep awk sed find; do command -v "$c" >/dev/null 2>&1 || { echo " [FAIL] 环境不足:缺少 $c" >&2; exit 2; } done LIB_REL='deploy/lib/env-defaults.sh' # ★★ 不要写 `strip_comments "$f" | grep -q …`:`set -o pipefail` 下 `grep -q` # 一命中就退出,`sed` 随即收到 SIGPIPE(rc=141),**管道整体返回 141** ⇒ # `if` 判**假** ⇒ 该文件被漏掉。而它退出的时机取决于缓冲区 ⇒ **结果不稳定**。 # 实测(我第一版):同一条命令两次分别找到 **2** 个与 **1** 个调用者 # (三个调用者只认出一两个),而 `rc=141` 被 `if` 静默吞掉 —— # 一个**不稳定且偏少**的集合,正好会让"裸赋值 0 处"变成假绿。 # ⇒ 正确做法:先把去注释结果**收集到变量**,再对它做匹配(无管道、无 SIGPIPE)。 # # ── 去注释的**唯一**实现(全文件只有这一处 `sed`)。 # ★★ pi `597086ad` 实测抓到我"说了但没做到"(这是我的第三次同形): # 我注释写"正向对照与正式扫描**都走这一个函数**",而旧版 `_scan_text` # **另写了一份 `sed`**、正式扫描又走 `strip_comments` ⇒ **其实有三份去注释实现**。 # 实测(只把 `strip_comments` 改成"删掉整行"): # ⇒ 正式扫描静音,而**自检不响**(自检走 `_scan_text` 那份 sed) # ⇒ 我上一封"自检盖住整条管线 / 已盖住 pi 标的残余"的结论**作废**。 # ⇒ 修法: 去注释收敛到**这一个函数**,匹配收敛到 `_scan_stripped`, # 正向对照与正式扫描**都经由它们** ⇒ 任一层漂移都同时影响两者 ⇒ 自检必响。 strip_text() { sed 's/#.*$//' <<< "$1"; } AM_SCAN_RE='^[[:space:]]*AGENTMAIL_REQUIRE=' # 对**已 strip** 的文本做匹配(唯一的匹配实现) _scan_stripped() { grep -nE "$AM_SCAN_RE" <<< "$1"; } # 文本 ⇒ 违规行号(= 去注释 + 匹配),供**正向对照**用 _scan_text() { _scan_stripped "$(strip_text "$1")"; } # ① 找"调用者":非注释行里以 `. ` 或 `source ` 起头、且提到 env-defaults.sh 的文件。 callers="" for f in $(find deploy -name '*.sh' -type f | sort); do [ "$f" = "$LIB_REL" ] && continue # 定义点不是调用点(见射程) body="$(strip_text "$(cat "$f")")" case "$body" in *env-defaults.sh*) # 无管道匹配:`grep` 读文件而非读管道 ⇒ 不会因 SIGPIPE 造出不稳定的 rc。 if grep -qE '^[[:space:]]*(\.|source)[[:space:]].*env-defaults\.sh' <<< "$body"; then callers="$callers $f" fi ;; esac done unset body # shellcheck disable=SC2086 set -- $callers n_callers=$# # ② 防空转:集合为空 ⇒ 判**红**,不是"通过" if [ "$n_callers" -lt 1 ]; then echo " [FAIL] 一个 source env-defaults.sh 的调用者都没找到 —— 大概率是路径/目录变了。" >&2 echo " 本判据的集合是「调用者」,集合空了它就**静默空转**(恒绿)⇒ 必须判红。" >&2 exit 1 fi # ②′ ★★ **下界守卫**(2026-09-25 我自己实测出来的洞,不是 pi 报的): # ② 只判"集合**空**",而**把发现域收窄**(例如把 `find deploy -name '*.sh'` # 改成 `find deploy -name 'redeploy*.sh'`)时集合**非空**(2 个)⇒ ② 不响。 # 而收窄会让**域外的违规整批消失** ⇒ 判据报"裸赋值 0 处" ⇒ **假绿**。 # 实测: 树上 install.sh:135 是裸赋值,把 find 收窄到 `redeploy*.sh` ⇒ # rc=**0**、打"(2 个调用者,裸赋值 0 处)",且**自检 0 次**(自检看不见域的问题)。 # ⇒ 与 ② 的关系: ② 管"**全丢**"(集合空),本守卫管"**部分丢**"(域被收窄)。 # ⇒ 判法用**下界**: 调用者 ≥ 3(现状: install.sh / redeploy-gateway.sh / redeploy-plugin.sh)。 # 与 `client/electron/test/criteria-hygiene.test.mjs` 里 `gates.length >= 3` 同一做法。 # ⚠️ 残留(如实记): 下界只挡住"收到 3 个以下";若有人收到**恰好 3 个但换掉其中一个** # (比如只扫 install.sh + 两个不存在的东西),本守卫仍看不出 —— 补它需要 # "已知调用者名单"这种**会漂**的硬编码,我选择不写,改在这里申报。 AM_CALLER_MIN=3 if [ "$n_callers" -lt "$AM_CALLER_MIN" ]; then echo " [FAIL] 只找到 ${n_callers} 个调用者(下界 ${AM_CALLER_MIN})—— 发现域被**收窄**了。" >&2 echo " 本判据的域是「deploy/ 下所有 source env-defaults.sh 的 *.sh」;" >&2 echo " 域一收窄,域外的违规就整批消失,而集合仍非空 ⇒ **防空转守卫看不见** ⇒ 假绿。" >&2 echo " (实测: 收窄 find 后 rc=0 且打'裸赋值 0 处',而树上确有违规。)" >&2 exit 1 fi fails=0 for f in "$@"; do # ★★ 这里**也不能**写成 `strip_comments "$f" | grep -nE …`(我第一版就是): # 它**目前**不出事只是因为选了 `-n`(`grep -n` 要读完全部输入才退出 ⇒ 没有早退 # ⇒ 生产者不会收到 SIGPIPE)。**换一个字母就翻面** —— pi 2026-09-25 做了可判实验: # `-n` → `-q` 后,三个调用者的裸赋值**全部被静默漏掉**(rc 由 1 变 **0**)。 # 我复现同一实验:现行 `-n` 报 **3** 个违规 / rc=1;改成 `-q` 报 **0** 个 / rc=**0** ✓ # ⇒ "恰好选了不早退的那个选项"**不是正确性,是运气** —— 而它考的是同一件事: # 判据读输入的**那一步**能不能把对象静默丢掉(`-n` 与 `-q` 只差"要不要早退")。 # ⇒ 与 :78-82 一致:**先收集到变量,再对变量匹配(无管道)**。 # # ★★ 第二版(pi 指出我"只修了一半"之后重做)—— 三样一起做,才真的闭掉: # ① **去管道**(`<<<` here-string)⇒ 生产者不会收 SIGPIPE,`-n`/`-q` 都不再有 141; # ② **两条独立通道**:"有没有违规"用 `grep -q` 的**退出码**; # "哪几行"用 `grep -n` 的**输出**。两者各自可靠 ⇒ 输出为空不再能冒充"没有违规"。 # ③ ★ **不一致时 fail-closed**:若"退出码说有"而"行号通道没给出行号" # (例如有人把取行号那处的 `-n` 写成 `-q` —— `-q` 什么都不打印), # 则**判据内部自相矛盾** ⇒ **按"有违规"计**,并明确报出这个矛盾。 # 我实测过少了 ③ 会怎样:只做 ①② 时,把取行号那处改成 `-q` # ⇒ 报 **0** 个违规、rc=**0**(3 个违规**全部静默漏掉**)—— 判定又一次落在输出通道上。 # ⇒ 记法: **"计数"与"证据"若走同一条通道,那条通道静音就等于"没有"**; # 要要么分通道、要么**规定静音=失败侧**(本处取后者)。 # # ★★★ 第四版(pi `3be8a091` 指出 ③ **有个我没写出来的前提**,我复现并采纳): # ③ 的 fail-closed 只在"**两通道不一致**"时触发 ⇒ 它默认两条通道**失效不相关**。 # 而我的两条通道**共享 2 个东西**:(a)**同一个正则字面量**(两处逐字相同) # (b)**同一个 `$body`**(都来自 `strip_comments "$f"`)。 # ⇒ ★ **单点改动同时哑掉两条时,它们"一致"(都沉默)⇒ `_had=1 ∧ _cnt=0` 不成立 # ⇒ fail-closed 恰好不触发**。pi 的实测我照做复现: # 把两处正则字面量一起改成 `'^NOMATCH='`(树上**注入着 3 处裸赋值**) # ⇒ rc=**0**,并打出"裸赋值 **0** 处"—— **与真实情况相反**。 # ⇒ 记法: **"两条通道"要成为独立证据,须"不存在单点改动能同时哑掉两条"** —— # 条数 ≥ 2 **不是**独立性的判据;要检查它们共享的**字面量/变量/函数/上游命令**。 # # ★ 补法是**正向对照**(判据自检),且**让它走与正式扫描同一个函数**: # 用一个**已知违规样本**跑 `_scan_text`,若它匹配不到 ⇒ 说明"沉默"是**共模**的 # ⇒ 按**红**计(不能因为两条通道都不说话就当成"没问题")。 # ★ 为什么走同一函数而不是抄一份正则:抄一份 ⇒ 又出现"两套实现",而 # pi 标的残余正是"正则对、别的环节(body 被过度 strip)静音"—— # 走同一函数能把**整条管线**(strip + 匹配 + 行号输出)一起对照到, # 而不只对照那个正则字面量。 _pc="$(_scan_text 'x=1 AGENTMAIL_REQUIRE="git go"' || true)" case "$_pc" in *AGENTMAIL_REQUIRE*) : ;; # 自检通过:能匹配到已知样本 *) printf ' [FAIL] 判据自检失败:违规扫描连一个**已知样本**都匹配不到 —— 这是**共模失效**\n' >&2 printf ' (两条通道因同一原因一起沉默 ⇒ fail-closed 的不一致条件不成立)。按红计。\n' >&2 exit 1 ;; esac unset _pc # ★ 反向对照(2026-09-25 我补,pi 的 §五 让我去看这一维) # ⚠️ **我实测后降级了它的说法 —— 它不改门禁判定,只改"报的是哪一句"**: # 把 `AM_SCAN_RE` 放宽到能匹配**合规**行(例如 `'='`)时: # 旧版(无反向对照): rc=**1**,但报的是"deploy/install.sh:135 裸赋值" —— # **指错了地方**(树上那条是**合规**的 `agentmail_require …`), # 运维会去改一个**没有错**的地方。 # 新版: rc=**1**,报"判据自检失败(反向):**合规**样本被判成违规 —— 匹配太宽(假红)"。 # ⇒ ★ 退出码**两者相同** ⇒ 按我自己收的 ⑰′ 标准("被别的守卫顺手抓住 ≠ 这条路径有守卫"), # 这条**不算新防线**,只算**诊断改进**(把"指错地方"变成"指对地方")。 # 我**不**声称它补了一个洞。 # ⚠️ 它**盖不住** pi 标的 §五 残余(`strip_text` 吞违规 ∧ 自检失败分支被打平): # 那一对的成因是"**匹配太窄**",而反向对照管的是"**匹配太宽**" ⇒ 方向相反、互不覆盖 # (我实测: 那一对仍 rc=0 静默)。⇒ 我把 §五 记为**边界**,见文件末尾。 _nc="$(_scan_text 'x=1 agentmail_require git go' || true)" case "$_nc" in "") : ;; # 反向对照通过:合规样本**不**被匹配 ✓ *) printf ' [FAIL] 判据自检失败(反向):**合规**样本被判成了违规 —— 匹配太宽(假红)。\n' >&2 printf ' ⇒ 上面若也报了"裸赋值",那几处**不是真违规**,先查 `AM_SCAN_RE`。\n' >&2 exit 1 ;; esac unset _nc # 正式扫描:**先 strip 一次**(唯一实现 `strip_text`),两条通道都用这同一份结果。 # # ★★ 逐文件探针(2026-09-25 pi 的 `head -132` 单点变异逼出来的,**我实测后加到两个探针**): # pi 的变异: `strip_text` 尾接 `| head -132` —— **一行改动**同时打穿自检与下界守卫: # ① 自检样本只有 **2 行** ⇒ `head -132` 对它**恒等** ⇒ 自检照样匹配 ⇒ 通过 # ② 下界守卫看的是"有几个文件 source 了"⇒ install.sh 的 source 在 **131** 行、 # 违规动作在 **135** 行 ⇒ `head -132` 保住 source、砍掉违规 ⇒ 仍是 3 个调用者 ⇒ 通过 # ③ 而正式扫描看不到违规行 ⇒ `_had=0 ∧ _cnt=0` ⇒ "一致" ⇒ fail-closed 不触发 # ⇒ 实测 rc=**0**,打"裸赋值 0 处"(树上注入着 1 处)⇒ **静默漏** # ⇒ 打穿窗口 = **[source 行, 动作行) = [131,134],宽 4**(我逐个 head -N 量过) # ★ 根因(pi 的记法,我收): **"共用同一实现"只在"样本能触发该变异"时才看得见** —— # 一个**输入相关**的变异(head/truncate/长度阈值/locale)对**短样本**是恒等的, # 于是"共用实现"反而保证了**两者一起静默**。 # ★ 补法: **对每个真文件也跑一次探针**(拿"真文件 + 已知注入"当样本), # 这样"样本的形状"就是**被检对象本身的形状**,长度相关变异无法对它恒等。 # ⚠️ **我为什么用两个探针而不是一个**(我实测出来的,比 pi 的建议多一步): # 只放**一个**探针时,它只能抓**从反方向**来的截断 —— # 探针在**尾部**: `head -132` 砍掉它 ⇒ 能抓;`tail -132` **保住**它 ⇒ 抓不住 # 探针在**首部**: `tail -132` 砍掉它 ⇒ 能抓;`head -132` **保住**它 ⇒ 抓不住 # (我逐条实测:单探针在 head/tail 上各漏一半) # ⇒ 所以**首尾各一个**,并**要求头探针的行号 < 尾探针的行号**: # 截断必砍掉其中一端;`tac` 之类保内容但**反转顺序**的变换则由"顺序"这条抓。 # ⚠️ 申报射程: `sort -u` / `grep -v '^$'` 抓不到(它们保留违规行、不改变"有/无"); # 但实测它们对本文件**不构成假绿**(只可能改变**计数**,而计数类失效由 fail-closed 兜)。 _stripped="$(strip_text "$(cat "$f")")" # ⚠️⚠️ **只靠首尾两个探针还有一个洞,是我实测出来的(pi 的建议里没有这一步)**: # 把**中间某一行**(例如那条违规行)删掉、首尾都保住 ⇒ 两个探针**都在**、顺序也对 # ⇒ 探针通过,而违规行没了 ⇒ **假绿**。实测: `awk 'NR!=135'` / `sed '135d'` ⇒ rc=**0**。 # ⇒ 补一条**行号算术不变量**: 读入是逐行原位 ⇒ 尾探针行号必须**恰好等于** # 头探针行号 + 内容行数 + 1(+1 是尾探针自己那一行)。 # 中间少一行 ⇒ 尾探针**上移** ⇒ 等式不成立 ⇒ 抓住。 # 实测(`probe` 原型): 干净 `h=1 t=802 n=800`(802 = 1+800+1 ✓); # `sed 135d` ⇒ `t=801` ≠ 802 ⇒ 抓到;`head/tail -N` ⇒ 缺探针 ⇒ 抓到; # `tac` ⇒ `h=802 t=1`(顺序反)⇒ 抓到;`grep -v '^$'` / `sort -u` ⇒ `t` 偏离 ⇒ 抓到。 # ★ 三个条件合起来 = "**这个过滤器是逐行原位的**"(保行数、保顺序、保两端)。 _probe_in="AGENTMAIL_REQUIRE=\"__AM_P_HEAD__\" $(cat "$f") AGENTMAIL_REQUIRE=\"__AM_P_TAIL__\"" # 内容行数与探针**同一条读取路径**(都经 `printf '%s\n'` 补回被命令替换吃掉的尾换行) _probe_n="$(printf '%s\n' "$(cat "$f")" | awk 'END{print NR}')" _probe_out="$(_scan_stripped "$(strip_text "$_probe_in")" || true)" _ph="$(grep -F '__AM_P_HEAD__' <<< "$_probe_out" | head -1 | cut -d: -f1)" _pt="$(grep -F '__AM_P_TAIL__' <<< "$_probe_out" | head -1 | cut -d: -f1)" _pexp=$(( ${_ph:-0} + _probe_n + 1 )) if [ -z "$_ph" ] || [ -z "$_pt" ] || [ "$_ph" -ge "$_pt" ] || [ "$_pt" -ne "$_pexp" ]; then printf ' [FAIL] %s 逐文件探针失败:读入这一环**不是逐行原位**的 —— 它在丢弃或重排内容。\n' "$f" >&2 printf ' head 探针=%s,tail 探针=%s,内容行数=%s(要求 tail == head + 行数 + 1 = %s)。\n' \ "${_ph:-空}" "${_pt:-空}" "$_probe_n" "$_pexp" >&2 printf ' ⇒ 三个条件缺一不可: 两端都在(抓 head/tail 截断)、顺序对(抓 tac)、\n' >&2 printf ' 行号算术成立(抓"只删中间某一行"—— 这一条只靠首尾探针**抓不到**)。\n' >&2 printf ' ⇒ 这一环静音时,**自检看不出来**(自检样本只有 2 行,长度相关变异对它恒等),\n' >&2 printf ' 下界守卫也看不出来(它只数"有几个文件 source 了",不看行号)。\n' >&2 printf ' ⇒ 按红计(绝不能因为两条通道都不说话就当成"没问题")。\n' >&2 exit 1 fi unset _probe_in _probe_out _probe_n _ph _pt _pexp # ★★★ 逐行局部不变量(2026-09-25 **pi 提的非循环补法**,我实测后采纳): # 上一节的首尾探针只锁"**行数 / 顺序 / 两端**"(**形状**),锁不住"**某一行内容被抹**"。 # pi 的补法: 对**每一行**要求 # ① stripped_i 是 raw_i 的**前缀**(只许删尾部) # ② 被删的**第一个**字符必须是 `#`(只许删注释后缀) # ⇒ 为什么**不循环**(这是关键,我上轮把它的可闭性判错了): # raw 由 `cat` **独立**读,stripped 由 `strip_text`(**被检对象**)读 —— 两次**独立读取**; # 而"只删注释后缀"是**规格声明**,**不是**被检对象的产物。 # ⇒ 所以**不需要**"先知道正确内容"(我上轮的循环论证不成立)。 # ⇒ 实测(注入 1 处违规的树上): # `sed '135s/.*//'`(**我上轮申报的 ⑧ 盲区**)⇒ **抓到**(第135行首删字符='A' ≠ '#') # `sed '56s/.*/ /'` ⇒ 抓到(不是前缀);`sed 's/AGENTMAIL_REQUIRE//'` ⇒ 抓到(不是前缀) # 真 strip ⇒ **合规**(**非恒红** ✓) # ⚠️ 射程(pi 也这么申报,我照抄): 它盖"**删的不是注释后缀**"这一类 # (= **形状**盲区,**现已闭**);对"**删对了注释、但那个 `#` 本不该被当注释**" # (如字符串字面量里的 `#`)**仍不管** —— 那才是真需要"正确内容"的**语义**盲区。 # ★★ 我上轮把这两类**合并成一条**、整条申报成"真边界",是**错的**: # 形状那半**可闭**(本节即闭),语义那半才是边界。⇒ 记法: # **申报"闭不了"之前,先问缺的信息是"形状"(换次读取就有)还是"语义"(要先有正确内容)** —— # 我上轮就是拿语义的循环性去论证形状那半也不可闭,于是**多申报了一个边界**。 _loc_bad="$( awk ' NR==FNR { r[FNR]=$0; rn=FNR; next } { s[FNR]=$0; sn=FNR } END { m = (sn rn) printf "stripped 比 raw 多 %d 行\n", sn-rn }' <(printf '%s\n' "$(cat "$f")") <(printf '%s\n' "$_stripped") )" if [ -n "$_loc_bad" ]; then printf ' [FAIL] %s 逐行局部不变量失败:读入环**改了行的内容**(不只是丢行/重排)。\n' "$f" >&2 printf '%s\n' "$_loc_bad" | sed 's/^/ /' >&2 printf ' ⇒ 首尾探针锁"行数/顺序/两端",锁不住"某一行被抹/被改"。按红计。\n' >&2 exit 1 fi unset _loc_bad _had=0 grep -qE "$AM_SCAN_RE" <<< "$_stripped" && _had=1 _cnt=0 while IFS=: read -r _ln _rest; do [ -z "${_ln:-}" ] && continue printf ' [FAIL] %s:%s 用了裸赋值(应改为 `agentmail_require <命令…>`)\n' "$f" "$_ln" >&2 _cnt=$((_cnt + 1)) done < <(_scan_stripped "$_stripped") if [ "$_had" -eq 1 ] && [ "$_cnt" -eq 0 ]; then printf ' [FAIL] %s 有裸赋值,但**取行号的那条通道没给出任何行号** —— 判据内部不一致;\n' "$f" >&2 printf ' 按 fail-closed 记作 1 处违规(两条通道必须都给得出,否则判定就落在单条通道上)。\n' >&2 _cnt=1 fi fails=$((fails + _cnt)) unset _stripped _had _cnt _ln _rest done if [ "$fails" -gt 0 ]; then cat >&2 <<'MSG' 为什么必须走动作:`AGENTMAIL_REQUIRE="…"` 只是**赋值**,而检查在 `env-defaults.sh` 的 ③b(source 期)—— 赋值在它**之后** ⇒ 那次检查读到空表 ⇒ **整张表从未被检查过**(2026-09-25 实测的活缺陷)。 后续那次检查(report 内)只接住"**记得调 report**"的情形; 写成动作则"赋值与检查"**不可分离** ⇒ 忘不忘都不影响。 改成: agentmail_require git go npm node curl systemctl flock MSG exit 1 fi awk -v n="$n_callers" 'BEGIN{printf " 通过 调用者声明全走 `agentmail_require` 动作(%d 个调用者,裸赋值 0 处)\n", n}' exit 0 # ══════════════════════════════════════════════════════════════════════════════ # 申报的边界(不是"待修项",是**今天不判**的地方) # ══════════════════════════════════════════════════════════════════════════════ # ①②③ 是"判据读输入之后"的失效;④ 是"哪些对象进入判据";⑤ 是"谁来验验证器"。 # # ⑤ ~~正向对照**保护不了它自己**~~ ⇒ ★ **已闭(2026-09-25 晚,逐文件探针)**, # 但保留这段历史,因为**我当时的"闭不了"判断本身是错的**,而错法有教学价值: # 当时(a)`strip_text` 尾接吞掉违规行 ∧(b)自检失败分支不可达 ⇒ rc=0 静默。 # 我判"要闭需一条独立于本文件的检查 ⇒ 无限后退 ⇒ 记为边界"。 # ★ 错在哪: 我把"独立"理解成"**另一个文件**",而真正需要的是 # "**另一次观察**" —— 逐文件探针(见上文)用**被检对象自己的形状**当样本, # 它仍在本文件里,却**不经过自检那条判断**(直接看 `_scan_stripped` 的行号算术)。 # ⇒ 实测: 同一 ①∧② 合取,现在 rc=**1**(报"逐文件探针失败")。 # ⇒ 记法: **"无限后退"论证要先问"上一层与这一层是不是同一次观察"** —— # 若新一层换了**观测对象**(从"已知样本"换成"被检文件本身"),它不是后退,是**换面**。 # 我当时把"换面"误判成了"后退",于是把一个可闭的点申报成了边界。 # ⑥ 间接赋值不判(eval/read/env 注入),只判**字面裸赋值**。 # ⑦ 下界守卫只挡"收到 <3 个调用者";"恰好 3 个但换掉其中一个"不判(需会漂的名单)。 # ⑧ ★★ **已闭一半 —— 我上轮整条申报成"真边界"是错的**(pi `02e3fd46` 纠正,我复现后采纳)。 # 我原写: "只抹掉违规行内容(`sed '135s/.*//'`)⇒ 首尾探针看不见 ⇒ **真边界**"。 # ★ 错在哪(我自己的论证有洞): 我说"要判它需逐行内容对照,而那要求判据先知道正确内容 ⇒ 循环"。 # pi 指出**不需要"正确内容",只需要"strip 的规格"** —— 两者不同。补法(我已落盘,见上文): # 对每行要求 ① stripped_i 是 raw_i 的**前缀** ② 被删的**第一个**字符必须是 `#` # ★ 为什么**不循环**: raw 由 `cat` **独立**读、stripped 由 `strip_text`(被检对象)读 # ⇒ **两次独立读取**;而"只删注释后缀"是**规格声明**,不是被检对象的产物。 # ⇒ 实测: `sed '135s/.*//'`、`'135s/.*/ /'`、`'56s/.*/ /'`、`'s/AGENTMAIL_REQUIRE//'` # **全部 rc=1**;真 strip ⇒ rc=**0**(非恒红)✓ ⇒ **形状盲区已闭**。 # ★★ 于是原 ⑧ 要**拆成两半**(这是本轮真正的记法): # ⑧a **形状**盲区("某行的字符被抹/被改")⇒ **可闭**(已闭,见上文逐行局部不变量) # ⑧b **语义**盲区("删对了 `#`,但那个 `#` 本不该当注释" —— 如 `X="a#b"` 里引号内的 `#`) # ⇒ **真边界**。实测: `X="a#b"` ⇒ 真 strip 截成 `X="a`(**规格本身错**), # 而局部不变量**通过**(删的确实是 `#`)⇒ 它看不见。要判它需要**正确内容/真正的解析器**。 # ⇒ 记法: **申报"闭不了"之前,先问缺的信息是"形状"还是"语义"** —— # 形状: "换一次读取就有"(可闭);语义: "要先有正确内容"(真边界)。 # ★ 我上轮就是**拿语义的循环性去论证形状那半也不可闭**,于是**多申报了一个边界**。 # ⚠️ 与 ⑤ 的关系: ⑤ 也是"我把可闭的误判成边界"(拿"同一次观察的后退"当理由)—— # ⑧a 与 ⑤ 是**同一类错误**(论证里的"循环/后退"其实不成立),⑧b 才与 ⑥⑦ 同级。 # ══════════════════════════════════════════════════════════════════════════════