Files
MailUI4Agents/deploy/check-require-declaration.sh
JianFeeeee 0a83eafd91 申报 ⑧ 更正: 逐文件探针的**真盲区**是"抹掉某一行的内容"(保行数/顺序/两端);★ 并区分"换面可闭"(⑤,我误判过) 与"要拿到它得先有它"(真边界)
★ 我第一版 ⑧ 写的是"不能证明注释剥离的语义正确",并举了"只删整行注释"当例子 ——
   **实测那个例子会被抓到**(rc=1),所以那段**陈述不准确**,已换掉。
★ 实测出的**真盲区**(可复现): 只清空**违规行本身**的内容、其余行原样 ⇒
   行数/顺序/两端全不变 ⇒ 探针通过 ⇒ **假绿**:
     `strip_text` 尾接 `sed '135s/.*//'`(或 `'135s/.*/ /'`)⇒ rc=**0**
   对照: `sed '135s/AGENTMAIL_REQUIRE/X'` ⇒ rc=**1**(它同时动了别的可匹配串)
   ⇒ 所以"改内容就一定被抓"是**错的**;只有"**抹掉那一行**"这个形状才进盲区。
★ 要判它需**逐行内容对照**,而那要求判据先知道"正确内容是什么" ——
   对"读入环"而言这是**循环的**(它就是要读出内容的那一环)⇒ 这**确实是**边界。
★ 关键区分(我这轮才想清楚,写进注释):
   ⑤ 是"**同一次观察**的无限后退" ⇒ 我当时据此申报为边界,那是**误判**(已闭);
   ⑧ 是"**观察对象之外**的东西"(探针看行号与两端,看不到行内被抹)。
   ⇒ 判"能不能闭"要先问: 缺的信息是"**换一次观察就能拿到**"(可闭)
     还是"**要拿到它就得先有它**"(真边界)。
2026-09-25 07:52:59 +08:00

339 lines
24 KiB
Bash
Executable File
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.

#!/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
_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 个但换掉其中一个"不判(需会漂的名单)。
# ⑧ ★ 逐文件探针的**射程**(实测申报,不是"没想到"): 它证明的是
# "**读入这一环保行数、保顺序、保两端**",**不**证明"**每一行的内容被原样保留**"。
# ⇒ 具体的盲区(我实测出来的、可复现的形状): 只**清空违规行本身的内容**、
# 其余行原样 ⇒ 行数/顺序/两端全不变 ⇒ 探针通过 ⇒ **假绿**:
# `strip_text` 尾接 `sed '135s/.*//'`(或 `'135s/.*/ /'`)⇒ rc=**0**
# (而 `sed '135s/AGENTMAIL_REQUIRE/X'` 会**被抓到** —— 因为它同时改了别的可匹配串,
# 所以"改内容就一定被抓"是**错的**,只有"抹掉那一行"这个形状才进盲区。)
# ⇒ 要判它需要**逐行内容对照**(例如把每个非注释行哈希后比长度/前缀),
# 而那要求判据先知道"正确内容是什么" —— 对"读入环"来说这是循环的(它就是要读出内容的那一环)。
# ⇒ 因此这**确实是**边界(与 ⑥⑦ 同级)。★ 注意与 ⑤ 的区别:
# ⑤ 是"**同一次观察**的无限后退"(我当时据此申报,那是**误判**,已闭);
# ⑧ 是"**观察对象之外**的东西"(探针能看行号与两端,看不到行内内容被抹)。
# ⇒ 判"能不能闭"要先问: 缺的信息是"**换一次观察就能拿到**"(可闭)还是
# "**要拿到它就得先有它**"(真边界)。
# ══════════════════════════════════════════════════════════════════════════════