Files
MailUI4Agents/deploy/check-require-declaration.sh
JianFeeeee 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

198 lines
12 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)。
strip_comments() { sed 's/#.*$//' "$1"; }
# ── 判定的**唯一**实现:输入文本 ⇒ 输出违规行号(每行一个,`行号:内容`)
# 正向对照与正式扫描**都走这一个函数** —— 否则"两套实现"会各自漂移,
# 而漂移恰恰是这条判据要防的东西(见下方 ★★ 共模洞)。
AM_SCAN_RE='^[[:space:]]*AGENTMAIL_REQUIRE='
_scan_text() {
local _t
_t="$(sed 's/#.*$//' <<< "$1")"
grep -nE "$AM_SCAN_RE" <<< "$_t"
}
# ① 找"调用者":非注释行里以 `. ` 或 `source ` 起头、且提到 env-defaults.sh 的文件。
callers=""
for f in $(find deploy -name '*.sh' -type f | sort); do
[ "$f" = "$LIB_REL" ] && continue # 定义点不是调用点(见射程)
body="$(strip_comments "$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
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
body="$(strip_comments "$f")"
_had=0
grep -qE "$AM_SCAN_RE" <<< "$body" && _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 < <(grep -nE "$AM_SCAN_RE" <<< "$body")
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 body _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