★★ (A) pi §三 两个 claim 实测确认: 两条守卫均为**下限**(`:229 -lt 1`、`:248 -lt AM_CALLER_MIN`);
域偏宽有真触发(`. /tmp/not-our-lib/env-defaults.sh.other` ⇒ 被当调用者并报其裸赋值)✓(上封已修)
★★★★ (B) **我自己的漏** —— 谓词 5 个条件逐条审:
^[[:space:]]*(\.|source)[[:space:]].*[^[:alnum:]_.-]lib/env-defaults\.sh["']?[[:space:]]*$
① 行首锚 ⇒ 无见证 ★ / ② **`(\.|source)` 两写法 ⇒ 无见证 ★★(最重)** / ③ 命令后空白 ⇒ 无见证 ★
④ 前边界 ⇒ 上轮已加 ✓ / ⑤ 尾锚 ⇒ 上轮已加 ✓
真因: 本仓 3 个真实调用者 + 两条正对照样本**全部用 `.`** ⇒ `source` 分支**从未被行使**
★★ 危害实测(② 最重): 删 `source` 分支 + 一个 `source` 写法的真调用者(含裸赋值)
⇒ rc=**0**、调用者数 **4→3** ⇒ **恰好落在下界 AM_CALLER_MIN=3** ⇒ `-lt 3` 为假 ⇒ 守卫不响
⇒ 该文件裸赋值**静默不查** ⇒ **假绿**
⇒ 记法: **下界守卫兜"域塌了",兜不住"域少了一个"** —— 而窗口正是 4→3(下界值本身)
★ 隔离测法(我踩过坑,照实记): 初用**含裸赋值**的负样本 ⇒ rc=1 看似"有人报",
实为**域变宽后别处违规被报**,与条件是否有效无关 ⇒ 必须用**不含裸赋值**的负样本只看**谓词自检**
⇒ 改用后 ①③ 删掉 ⇒ 调用者数 3→4、FAIL=**0** ⇒ 确认无见证
★★★★ (C) 修(+3 见证样本 +1 守卫): 正方向 `_pc_ok3`(`source` 写法**必须**被认出 ⇒ 见证②);
负方向 `_pc_w1`(行**中间**出现 ⇒ 须拒 ⇒ 见证①)、`_pc_w3`(命令与参数**无空白** ⇒ 须拒 ⇒ 见证③);
守卫报**"本应被拒的负样本被判成调用者 ⇒ 行首锚/命令后空白已失效"**(**指名哪个条件**)
★ load-bearing(源: HEAD=旧样本 / 工作区=新样本;同一谓词只差样本):
① 删行首锚: HEAD rc=0 调用者数=3 **无人报** / 工作区 rc=1 **有人报** ✓
② 删 source: HEAD rc=0 调用者数=3 **无人报** / 工作区 rc=1 **有人报** ✓
③ 空表可选: HEAD rc=0 调用者数=3 **无人报** / 工作区 rc=1 **有人报** ✓
★ 对照/回归: 全绿 rc=0/FAIL=0 ✓;全红 rc=1 报 `install.sh:135 用了裸赋值` ✓;
真树 rc=0、`(3 个调用者,裸赋值 0 处)` ✓;`bash -n` rc=0 ✓;criteria-hygiene **10/10** ✓
★★ 元教训: **"写下一条一般规则" ≠ "把它施加到该规则覆盖的每一个实例上"** ——
我上轮写的是"**每个**条件都要有一条只踩它的样本",然后只给 2 条加了
⇒ 可判做法: 写完这类规则**立刻对当前对象枚举实例**(把谓词逐条拆开数 = 5 条,一条一格),
否则规则只是**那句话**,不是**这件事**
★ 围栏 1172(偶/配对无缺;新块 6 个围栏独立闭合)—— 期间 pre-commit 抓到一次**奇数围栏**(我漏写 (C) 段闭合),已补
641 lines
51 KiB
Bash
Executable File
641 lines
51 KiB
Bash
Executable File
#!/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"; }
|
||
# ★★ 谓词里带一个**可选的 `export` 前缀**(2026-09-25 我发现的行首锚定 gap,见文末"申报的边界"⑨):
|
||
# `export AGENTMAIL_REQUIRE="…"` **是字面裸赋值**(⑥ 声明的射程之内),
|
||
# 但 `^[[:space:]]*AGENTMAIL_REQUIRE=` **不认前缀** ⇒ 它落在**谓词之外** ⇒ 假绿。
|
||
# ⚠️ 这里**只**放宽"同一行内 token **前面**的词法"(安全);
|
||
# **没有**放宽到"能看见 `;` / `&&`" —— 那要求跨 token 的**语句结构**,实测会引入假红(见 ⑨)。
|
||
AM_SCAN_RE='^[[:space:]]*(export[[:space:]]+)?AGENTMAIL_REQUIRE='
|
||
# 对**已 strip** 的文本做匹配(唯一的匹配实现)
|
||
_scan_stripped() { grep -nE "$AM_SCAN_RE" <<< "$1"; }
|
||
# 文本 ⇒ 违规行号(= 去注释 + 匹配),供**正向对照**用
|
||
_scan_text() { _scan_stripped "$(strip_text "$1")"; }
|
||
|
||
# ★★★ 调用者判定专用的去注释:**引号感知**的逐字符扫描(不是 sed 近似)。
|
||
# pi `9bb3cc32` 实测出上一版"词首规则"`s/\(^\|[[:space:]]\)#.*$//` 有**两处方向相反**的残留:
|
||
# ① **假阴**(未全闭): `. "$A #B/deploy/lib/env-defaults.sh"` —— 引号内「空格+`#`」
|
||
# 被误当注释 ⇒ 截成 `. "$A` ⇒ 又丢了调用者身份 ⇒ rc=0、调用者数仍 3
|
||
# ② **假红**: `. /dev/null;# 提到 env-defaults.sh` —— `;` 后的 `#` **该**截而没截
|
||
# ⇒ 一个**不是**调用者的文件进了集合 ⇒ rc=1 并报它的裸赋值
|
||
# ★ 两处**方向相反**,且**单一正则/单个 sed 表达式修不了**:
|
||
# 只加元字符边界 ⇒ 修好 ②、对 ① 无改善;只追引号 ⇒ 修好 ①、对 ② 无改善。
|
||
# ⇒ 真规则需要**跨字符状态**(在引号里吗)⇒ 必须写成**自动机**,不能写成一次替换。
|
||
# ⇒ 本节实现 bash 手册的真规则: **以 `#` 开头的词**(词边界 = 行首 / 空白 / 元字符),
|
||
# 且 `#` 在**引号内**时不是词首(引号内容不被分词)。
|
||
# · `'…'` 内除 `'` 外全部字面;`"…"` 内 `\` 可转义、`"` 结束
|
||
# · `\#` 不是注释
|
||
# 实测(原型矩阵,本仓真实两行 + pi 两例 + 反例):
|
||
# `. "$REPO/deploy/lib/env-defaults.sh"` ⇒ 提到 ✓(算调用者)
|
||
# `. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/…"` ⇒ 提到 ✓
|
||
# `. "${REPO#/home}/deploy/lib/env-defaults.sh"` ⇒ 提到 ✓(上一版假绿)
|
||
# `. "$A #B/deploy/lib/env-defaults.sh"` ⇒ 提到 ✓(pi ①,已闭)
|
||
# `. /dev/null;# 提到 env-defaults.sh` ⇒ 截断 ⇒ 不算 ✓(pi ②,已闭)
|
||
# `. /dev/null # 提到 env-defaults.sh` ⇒ 截断 ⇒ 不算 ✓
|
||
# `# . env-defaults.sh`(整行注释) ⇒ 空 ⇒ 不算 ✓
|
||
# `X="a#b"` ⇒ 不截断 ✓
|
||
# ⚠️ 射程: 这是**行级近似**的真 shell 规则(不处理 `$( … )` 里跨行的引号、heredoc、
|
||
# 续行拼接)—— 足以判"这一行是不是 source 行",**不**声称是通用 shell 词法器。
|
||
# ★ 与 ⑧b 的关系: 本节闭的是**调用者判定**(⑧c);正式违规扫描仍走 `strip_text`,
|
||
# ⑧b(注释语义)那一半**未**闭,本处不声称。
|
||
_strip_comments_lex() {
|
||
awk '
|
||
{
|
||
line = $0; n = length(line); out = ""; prev = 1; sq = 0; dq = 0; esc = 0
|
||
for (i = 1; i <= n; i++) {
|
||
c = substr(line, i, 1)
|
||
if (esc) { out = out c; esc = 0; prev = 0; continue }
|
||
if (sq) { out = out c; if (c == "\x27") sq = 0; continue }
|
||
if (dq) {
|
||
if (c == "\\") { out = out c; esc = 1; continue }
|
||
out = out c; if (c == "\"") dq = 0; continue
|
||
}
|
||
if (c == "\\") { out = out c; esc = 1; prev = 0; continue }
|
||
if (c == "\x27") { out = out c; sq = 1; prev = 0; continue }
|
||
if (c == "\"") { out = out c; dq = 1; prev = 0; continue }
|
||
if (c == "#") {
|
||
# 词首 = 行首,或前一个字符是空白/元字符
|
||
if (prev) break # 注释开始 ⇒ 本行到此为止
|
||
out = out c; prev = 0; continue
|
||
}
|
||
out = out c
|
||
if (c == " " || c == "\t" || c == ";" || c == "|" || c == "&" || \
|
||
c == "(" || c == ")" || c == "<" || c == ">") prev = 1
|
||
else prev = 0
|
||
}
|
||
print out
|
||
}' "$1"
|
||
}
|
||
|
||
# ★★★ "这一行/这段文本算不算 source 了本库" 的**唯一**判定实现。
|
||
# ★ 为什么必须是**唯一**的: 正式扫描与自检**都**经由它 ——
|
||
# 否则把调用点换掉(例如退回 `cat`)时自检**照样绿**,而域已经错了。
|
||
# 我第一版就是"自检直接调 `_strip_comments_lex`",实测**共模**
|
||
# (调用点被换成 `cat` 时自检不响)⇒ 抽成这一个函数,调用点也是它。
|
||
# 判法: 去注释(词法器)后,**行首**是 `.` 或 `source` 且**提到** env-defaults.sh。
|
||
_is_caller_text() {
|
||
local t
|
||
t="$(printf '%s\n' "$1" | _strip_comments_lex /dev/stdin)"
|
||
# ★★★ 谓词从"**行内出现子串**"收紧为"**指向本库**"(pi `ae164e2e` 实测打出的**假红**,我复现):
|
||
# 旧谓词 `.*env-defaults\.sh` 只要求**子串出现** ⇒
|
||
# `. /tmp/not-our-lib/env-defaults.sh.other` ⇒ rc=**1** 并报该文件裸赋值
|
||
# 而该文件**没有 source 本库** ⇒ **假红**(域偏宽)。
|
||
# ★ pi 的诊断对: **域的宽度该用谓词精度管,不该用阈值管** ——
|
||
# 空集守卫 / 下界守卫**都只兜"域偏小/空"**这个方向,加"上界阈值"只会把
|
||
# 一个**精确性**问题换成一个**调参**问题(阈值调多少都是猜)。
|
||
# ⇒ 收紧后的谓词: source 的**参数末尾**必须是 `…/lib/env-defaults.sh`(可带引号/空白),
|
||
# 即"**这一行指向的就是本库那个文件**",而不是"这一行里出现过这个字符串"。
|
||
# ① `[^[:alnum:]_.-]lib/env-defaults\.sh` ⇒ 前面必须是**路径分隔符或词边界**,
|
||
# 排除 `not-our-lib/env-defaults.sh`(`-lib` 前面是 `-`)这类同后缀异文件
|
||
# ② `["']?[[:space:]]*$` ⇒ **尾锚** ⇒ 排除 `env-defaults.sh.other` 这类**更长**的名字
|
||
# ★ 实测(本仓 3 个真实调用者 + 6 个反例):
|
||
# `. "$REPO/deploy/lib/env-defaults.sh"` ⇒ 调用者 ✓
|
||
# `. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/…"` ⇒ 调用者 ✓(两处真实形态)
|
||
# `. "${REPO#/home}/deploy/lib/env-defaults.sh"` ⇒ 调用者 ✓
|
||
# `. "$A #B/deploy/lib/env-defaults.sh"` ⇒ 调用者 ✓
|
||
# `. /tmp/not-our-lib/env-defaults.sh.other` ⇒ 不算 ✓(pi 的假红例)
|
||
# `. /tmp/not-our-lib/env-defaults.sh` ⇒ 不算 ✓
|
||
# `. /tmp/x/lib/env-defaults.sh.other` ⇒ 不算 ✓
|
||
# ⚠️ 射程: 这是**行内文本**的判法,不解析变量实际取值 ——
|
||
# 若有人写 `. "$SOMEDIR/lib/env-defaults.sh"` 而 `$SOMEDIR` 指向别处,
|
||
# 本判据仍算它"调用者"(**偏宽**、方向安全,会多查一个文件而不是漏查)。
|
||
grep -qE '^[[:space:]]*(\.|source)[[:space:]].*[^[:alnum:]_.-]lib/env-defaults\.sh["'"'"']?[[:space:]]*$' <<< "$t"
|
||
}
|
||
|
||
# ① 找"调用者":非注释行里以 `. ` 或 `source ` 起头、且提到 env-defaults.sh 的文件。
|
||
callers=""
|
||
for f in $(find deploy -name '*.sh' -type f | sort); do
|
||
[ "$f" = "$LIB_REL" ] && continue # 定义点不是调用点(见射程)
|
||
# ★★★ 调用者判定**不看 strip 后的文本**(pi `30ee8ff2` 实测的**假绿**,我复现并采纳):
|
||
# 若 source 行里含 `${VAR#…}` 这类**参数展开**(`#` 不是注释),`strip_text` 会
|
||
# 从那个 `#` 起把整行截断 ⇒ 行内**不再提及** `env-defaults.sh` ⇒ 该文件**静默退出调用者集合**
|
||
# ⇒ 它的裸赋值**根本不被查**(连进都没进集合)⇒ **假绿**。实测(`d3c74ec` 的树上):
|
||
# `deploy/redeploy-nc.sh`:
|
||
# . "${REPO#/home}/deploy/lib/env-defaults.sh"
|
||
# AGENTMAIL_REQUIRE="z" ← 真违规
|
||
# ⇒ rc=**0**,仍报"3 个调用者,裸赋值 0 处"(z 被漏)
|
||
# 对照: 把 source 行换成 `. "$REPO/deploy/lib/env-defaults.sh"` ⇒ rc=**1** ✓
|
||
# ★ 而**三条不变量全过**(首尾探针 / 行号算术 / 逐行局部不变量)—— 因为删的首字符
|
||
# **确实**是 `#`、stripped 也**确实**是 raw 的前缀 ⇒ ⑧a 那套"形状"检查**看不见它**。
|
||
# ⇒ 修法: 调用者判定改用**词首注释规则**(shell 的真实规则: `#` 只在**词首**才是注释)。
|
||
# ★ 不能用"raw 原文"了事: 我实测过 raw 版会把
|
||
# `. other.sh # 注释里提到 env-defaults.sh`
|
||
# 当成调用者 ⇒ **假红**(安全方向,但仍是错的)。
|
||
# 也不能用 `strip_text`(它是 `s/#.*$//`,正是被 pi 抓到的那个错)。
|
||
# ★★ 第二版(pi `9bb3cc32` 报回**两处方向相反**的残留后重做):
|
||
# 上一版是"词首规则"的**单条 sed 近似**,它同时缺**元字符边界**与**引号状态**
|
||
# ⇒ 一处假阴、一处假红(两例见 `_strip_comments_lex` 上方的实测记录)。
|
||
# ⇒ 现改为调用 `_strip_comments_lex`(**引号感知的逐字符扫描**)——
|
||
# 理由: 真规则要"当前是否在引号内"这个**跨字符状态**,一次 `s///` 表达不了。
|
||
# ⚠️ 射程(本处**只**修调用者判定这一半): 正式违规扫描仍走 `strip_text`
|
||
# (`s/#.*$//`)⇒ 若某**违规行**本身含 `${VAR#…}` 且截断后不再匹配 `AM_SCAN_RE`,
|
||
# 那仍会漏(属 ⑧b 的**语义**半,真边界)—— 本处**不**声称那半已闭。
|
||
# ⇒ 调用点只用 `_is_caller_text`(它内部走词法器)。
|
||
# ⚠️⚠️ **射程(我实测后收窄 —— 第一版我在这里写宽了)**:
|
||
# 本对照调的是**函数** `_is_caller_text`,**不是调用点**。
|
||
# 所以它盖住的是: 词法器(引号状态/元字符边界)+ 那条正则。
|
||
# 它**盖不住**: 有人把**调用点**改掉(`_is_caller_text "$(cat "$f")"` → `true`/`false`)——
|
||
# 那种改法**不改函数**,自检照样绿(我实测确认)。
|
||
# ⇒ 该方向由别的层兜: 域**空**由 ② 空集守卫、域**偏窄**由 ②′ 下界守卫;
|
||
# 而域**偏宽**(把非调用者算进来 ⇒ 假红)**当前没有守卫** —— 我**不**声称它已被盖住。
|
||
if _is_caller_text "$(cat "$f")"; then
|
||
callers="$callers $f"
|
||
fi
|
||
done
|
||
# 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 `9bb3cc32` 之后加):
|
||
# 为什么必须加: 调用者判定在**每条命令**开头就跑,是**域的定义**——
|
||
# 若它静音(认不出任何人)或过宽(把非调用者认成调用者),
|
||
# **正式扫描不会报错**,只会"少查/多查文件" ⇒ 与"共模失效"同型。
|
||
# ★★ 第一版我写成 "直接调 `_strip_comments_lex`",**实测在维度上更窄**(我自己抓到):
|
||
# 它只对照"词法器"这一环,**不**对照 `_is_caller_text` 里那条正则;
|
||
# ⇒ 抽成 `_is_caller_text` 后,对照覆盖"词法器 + 正则"**两环**(仍未覆盖调用点,见 ① 处射程)。
|
||
# ★ 走同一函数才能对照到"引号状态 + 元字符边界"这两处(而不是只对照一个正则)。
|
||
_pc_ok=0; _is_caller_text '. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/env-defaults.sh"' && _pc_ok=1
|
||
_pc_ok2=0; _is_caller_text '. "$A #B/deploy/lib/env-defaults.sh"' && _pc_ok2=1
|
||
# ★★★★ 2026-09-25 我自 catch(承下面对照的"逐条件见证"规则,但**这条规则我上次只用在 2 条条件上**):
|
||
# 谓词 `^[[:space:]]*(\.|source)[[:space:]].*[^[:alnum:]_.-]lib/env-defaults\.sh["']?[[:space:]]*$`
|
||
# 一共 **5** 个条件,而上一次我只给 ④前边界 / ⑤尾锚 配了见证样本 ⇒ 实测:
|
||
# ① 行首锚 `^[[:space:]]*` / ② **`source` 分支** / ③ 命令后 `[[:space:]]`
|
||
# **三条各自被删掉都无人报**(真树 3 个调用者**全用 `.`** ⇒ ② 从未被行使)。
|
||
# ★ 危害实测(② 最重): 删 `source` 分支 + 一个用 `source` 写法的真调用者(含裸赋值)
|
||
# ⇒ rc=**0**、调用者数 **4→3** ⇒ **恰好落在下界 `AM_CALLER_MIN=3` 上 ⇒ 下界守卫不响**
|
||
# ⇒ 那个文件的裸赋值**静默不查**(假绿)。★ 即"4→3 恰好吃掉一次丢调用者"——
|
||
# 下界守卫兜的是"域塌了",兜不住"域少了一个"。
|
||
# ⇒ 修: 三个条件各配一条信号量 —— ② 用**正**样本(source 写法的真调用者必须被认出),
|
||
# ①③ 用**负**样本(现行必须**不**认它;一旦删掉对应条件它就会被误纳 ⇒ 被 ③ 那条反向对照抓住)。
|
||
# ① 的负样本: 行**中间**出现 ` . …lib/env-defaults.sh`(不在行首 ⇒ 不是 source 命令)
|
||
# ③ 的负样本: `source"…"`(命令与参数之间**没有空白** ⇒ shell 会当成别的词 ⇒ 不是 source)
|
||
_pc_ok3=0; _is_caller_text 'source "$REPO/deploy/lib/env-defaults.sh"' && _pc_ok3=1
|
||
# ①②③ 的**见证前提**: 下面两条负样本在当前谓词下必须**被拒**(若被接受 ⇒ 谓词已被放宽)
|
||
_pc_w1=0; _is_caller_text 'echo " . $REPO/deploy/lib/env-defaults.sh"' && _pc_w1=1
|
||
_pc_w3=0; _is_caller_text 'source"$REPO/deploy/lib/env-defaults.sh"' && _pc_w3=1
|
||
if [ "$_pc_ok" -ne 1 ] || [ "$_pc_ok2" -ne 1 ] || [ "$_pc_ok3" -ne 1 ]; then
|
||
printf ' [FAIL] 判据自检失败(调用者判定):**已知调用者样本**没被认出 ——\n' >&2
|
||
printf ' ⇒ 域会**静默偏小**,域外的违规整批不查(防空转守卫只在"空集"时响)。按红计。\n' >&2
|
||
exit 1
|
||
fi
|
||
if [ "$_pc_w1" -ne 0 ] || [ "$_pc_w3" -ne 0 ]; then
|
||
printf ' [FAIL] 判据自检失败(调用者判定,见证样本):本应被**拒**的负样本被判成了调用者 ——\n' >&2
|
||
printf ' ⇒ 谓词的行首锚 / 命令后空白这两个条件**已失效**(域会被放宽)。按红计。\n' >&2
|
||
exit 1
|
||
fi
|
||
_pc_bad=""
|
||
_is_caller_text '. /dev/null;# 提到 env-defaults.sh' && _pc_bad="分号后的注释(该截没截)"
|
||
_is_caller_text '# . env-defaults.sh' && _pc_bad="${_pc_bad}整行注释"
|
||
# ★ pi `ae164e2e` 打出的**假红**(谓词过宽: 子串出现 ≠ 指向本库)—— 这条必须在反向对照里:
|
||
# ★★★★ 2026-09-25 我自 catch 的一个**见证力**缺陷(原来只放下面第一条样本):
|
||
# 那条样本**同时**踩两条独立条件 ——
|
||
# ① **前边界** `[^[:alnum:]_.-]lib/env-defaults`: `not-our-lib` 里的 `-` 在排除集里 ⇒ 拒
|
||
# ② **尾锚** `["']?[[:space:]]*$` : 结尾 `.other` ⇒ 拒
|
||
# ⇒ 实测: **删掉任一条条件,它仍被另一条拒** ⇒ 反向对照**照样绿** ⇒ **两条条件都没有见证** ★
|
||
# (实测: 去前边界或去尾锚,各自都能让一个异库文件进集合并报它的裸赋值,而无人拦。)
|
||
# ⇒ 修法: 每个条件各配一条**"只踩它"**的样本(其余条件全满足),否则那个条件是**没人看着**的。
|
||
# ★ 这是一般规则,不只对本谓词: **被多条条件同时拒的样本,对其中任何一条都没有见证力。**
|
||
_is_caller_text '. /tmp/not-our-lib/env-defaults.sh.other' && _pc_bad="${_pc_bad}同名后缀的**别的**文件"
|
||
# ① 只踩【前边界】(结尾是合法 `.sh`,但 `-lib` 前的 `-` 不是词边界):
|
||
# ⇒ **删掉前边界**时这条样本会被认成调用者 ⇒ `_pc_bad` 非空 ⇒ **本对照报红**(见证成立)
|
||
_is_caller_text '. /tmp/not-our-lib/env-defaults.sh' && _pc_bad="${_pc_bad}只在前边界违规的异库文件"
|
||
# ② 只踩【尾锚】(前面是合法的 `/`,但结尾 `.other` 让文件名更长):
|
||
# ⇒ **删掉尾锚**时这条样本会被认成调用者 ⇒ `_pc_bad` 非空 ⇒ **本对照报红**(见证成立)
|
||
_is_caller_text '. /tmp/zzz/lib/env-defaults.sh.other' && _pc_bad="${_pc_bad}只在尾锚违规的异库文件"
|
||
if [ -n "$_pc_bad" ]; then
|
||
printf ' [FAIL] 判据自检失败(调用者判定,反向):**非调用者样本**被判成了调用者(%s)——\n' "$_pc_bad" >&2
|
||
printf ' ⇒ 域会**偏大**,与该库无关的文件被拖进来报红(假红)。按红计。\n' >&2
|
||
exit 1
|
||
fi
|
||
unset _pc_ok _pc_ok2 _pc_ok3 _pc_w1 _pc_w3 _pc_bad
|
||
|
||
# ★ 反向对照(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` 偏离 ⇒ 抓到。
|
||
# ★ 三个条件合起来 = "**这个过滤器是逐行原位的**"(保行数、保顺序、保两端)。
|
||
# ★★★ 但**固定标记**本身有个洞(pi `551d7d85` 实测打出的**假红**,我复现):
|
||
# 标记 `__AM_P_TAIL__` 是**字面量**,而它**出现在本判据自己的源码里**(:374/:379 两处)。
|
||
# 于是当**本判据文件自己被当成数据读**(域偏宽 / 自指)时,`grep -F … | head -1`
|
||
# 匹配到的是**源码里那行**,不是文件末尾之后的真尾探针 ⇒ 行号算术失败 ⇒ **假红**。
|
||
# ★ 这不是"域偏宽"的问题被误报 —— 是**探针的记号与数据不可分辨**。
|
||
# ⇒ 修法: 用**运行时 nonce**(`$RANDOM` + pid)代替固定字面量 ⇒
|
||
# 该记号**不可能**预先出现在任何静态文件里 ⇒ 碰撞**不可表示**(比"再多加几个下划线"根本)。
|
||
# ⚠️ 射程: 这消掉"记号与数据碰撞"这一类;**不**改变"域偏宽"本身是否该红(那是谓词的事,见 `_is_caller_text`)。
|
||
_am_nonce="AM${RANDOM}${RANDOM}${$}Z"
|
||
_probe_in="AGENTMAIL_REQUIRE=\"${_am_nonce}HEAD\"
|
||
$(cat "$f")
|
||
AGENTMAIL_REQUIRE=\"${_am_nonce}TAIL\""
|
||
# 内容行数与探针**同一条读取路径**(都经 `printf '%s\n'` 补回被命令替换吃掉的尾换行)
|
||
_probe_n="$(printf '%s\n' "$(cat "$f")" | awk 'END{print NR}')"
|
||
_probe_out="$(_scan_stripped "$(strip_text "$_probe_in")" || true)"
|
||
# ★★ 这里也**不能**写成 `grep -F … <<< "$_probe_out" | head -1 | cut -d: -f1`(我第三版就是):
|
||
# 与上面那条**同一形状** —— `head -1` 一拿到就退出 ⇒ 生产者收 SIGPIPE ⇒ pipefail 下整体 141。
|
||
# ★ 它的安全性**与 :102 不同族**,我实测分清了(别把"形状相同"当成"缺陷相同"):
|
||
# · :102 静音 ⇒ `hits` 空 ⇒ 报"裸赋值 0 处" ⇒ 落**绿**侧(fail-open)✗
|
||
# · 本处静音 ⇒ `_ph` 空 ⇒ 下面 `[ -z "$_ph" ]` ⇒ 落**红**侧(fail-closed)✓
|
||
# (我逐场景验过: `_ph` 空 / 取到错行 / 顺序反 ⇒ **三种都判红**)
|
||
# · 且生产者的**输出上界=1 行是构造性的**(`_am_nonce` 是运行期随机 ⇒ 静态 `$f` 不可能预先含它),
|
||
# 不是"尺寸恰好装得下"⇒ 我把巨量输出强喂给它(40 次): 141 出现 **40/40**,
|
||
# 而**取值仍恒正确**(`head` 已拿到它要的那一行)—— 与 :102 的"值被丢掉"也不同
|
||
# ⇒ 结论: **同形状、不同缺陷**。我仍改成无管道的写法(成本为零,且让规则在全文件一致,
|
||
# 免得下一次有人动掉 nonce 那个界时这处才第一次暴露)。
|
||
# ⇒ 记法: 修一条规则时,**"这处违不违规"与"这处会不会因此出错"是两个问题** ——
|
||
# 前者看形状,后者看**失效方向**;只有后者决定它是不是同一个 bug。
|
||
_phead="$(grep -m1 -F "${_am_nonce}HEAD" <<< "$_probe_out" || true)"; _ph="${_phead%%:*}"
|
||
_ptail="$(grep -m1 -F "${_am_nonce}TAIL" <<< "$_probe_out" || true)"; _pt="${_ptail%%:*}"
|
||
_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 _am_nonce
|
||
# ★★★ 逐行局部不变量(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)?sn:rn
|
||
for (i=1;i<=m;i++) {
|
||
if (substr(r[i],1,length(s[i])) != s[i]) { printf "第%d行 不是前缀(改了行中间的字符)\n", i; continue }
|
||
d = substr(r[i], length(s[i])+1)
|
||
if (d != "" && substr(d,1,1) != "#") printf "第%d行 首删字符=%s(不是注释后缀)\n", i, substr(d,1,1)
|
||
}
|
||
for (i=m+1;i<=rn;i++) {
|
||
t = r[i]; sub(/^[ \t]*/, "", t)
|
||
if (t != "" && substr(t,1,1) != "#") printf "第%d行 被整行丢弃且非注释\n", i
|
||
}
|
||
if (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 才与 ⑥⑦ 同级。
|
||
# ⑧c ★★★ **⑧b 还要再拆一半**(pi `30ee8ff2` 实测的**假绿**,我复现后已修 —— 见 ① 调用者判定):
|
||
# pi 指出 ⑧b 不只是"规格本身错"(无害),它有一条**真实可达的假绿**:
|
||
# 当"不该当注释的 `#`"出现在**唯一那处 `env-defaults.sh` 提及之前**时,
|
||
# 整行 strip 后**不再提及** env-defaults.sh ⇒ **调用者身份丢失** ⇒ 该文件被跳过
|
||
# ⇒ 它的裸赋值**连查都不查**(不是漏判,是**根本没进集合**)⇒ **假绿**。
|
||
# 实测(`d3c74ec` 的树上,我复现): `deploy/redeploy-nc.sh`
|
||
# `. "${REPO#/home}/deploy/lib/env-defaults.sh"` + `AGENTMAIL_REQUIRE="z"` ⇒ rc=**0**
|
||
# 现实形态同样命中: `. "$(dirname "${BASH_SOURCE[0]#/x}")/lib/env-defaults.sh"` ⇒ rc=**0**
|
||
# ★ 而**三条不变量全过**(首尾探针/行号算术/逐行局部不变量)—— 因为删的首字符
|
||
# **确实**是 `#`、stripped 也**确实**是前缀 ⇒ ⑧a 那套"形状"检查**看不见它**。
|
||
# ⇒ ★ 已修(① 调用者判定改用**词首注释规则** `s/\(^\|[[:space:]]\)#.*$//`):
|
||
# 参数展开 `#` / 引号内 `#` **不再**被当注释 ⇒ 调用者不再丢失 ⇒ 上面两例都 rc=**1** ✓
|
||
# 且**不引入假红**: `foo.sh # 注释里提到 env-defaults.sh` **仍不算调用者** ✓
|
||
# ⇒ ★★ 于是 ⑧b 拆两半:
|
||
# ⑧c **调用者判定**(判"哪一行是 source 行"只用到**行首形态**,与注释后缀无关)
|
||
# ⇒ **可闭**(已闭)—— 我原先把"strip 的实现"与"调用者判定"**耦合**在一起
|
||
# ⑧b **注释语义本身**(strip 对**行内**`${V#x}` / 引号内 `#` 的截断会不会
|
||
# 让**违规行自己**不再匹配 `AM_SCAN_RE`)⇒ **真边界**
|
||
# 实测残余: `AGENTMAIL_REQUIRE#="z"` 被 strip 成 `AGENTMAIL_REQUIRE` ⇒ 不再匹配 ⇒ rc=0
|
||
# (但这行**本来就不是合法赋值** ⇒ 落在射程边缘;合法形态 `AGENTMAIL_REQUIRE="${V#x}"`
|
||
# 截断后仍是 `AGENTMAIL_REQUIRE="${V` ⇒ **仍匹配** ⇒ rc=1 ✓ 不漏)
|
||
# ⇒ ★★★ 记法(本轮第三格): **"耦合"本身会被误报成"边界"** ——
|
||
# 我原来说"⑧b 是语义边界",其实里面**掺着一处没必要的耦合**(调用者判定依赖 strip)。
|
||
# 判法: 申报边界前问一句 **"这个信息真的只能由被检对象给出吗,还是我顺手复用了它?"**
|
||
# ⇒ ⑧a 是"换错观察对象"、⑧c 是"**多余的依赖**" —— 两次都把可闭的说成了边界。
|
||
# ⑨ ★★★★ **违规扫描的"行首锚定"**(2026-09-25 我核 pi `02e3fd46` 时撞出;pi 没报、我也没记):
|
||
# 我原写 `AM_SCAN_RE='^[[:space:]]*AGENTMAIL_REQUIRE='` ⇒ 只认"行首形态"。
|
||
# ★ 而**本判据 `:18` 自己举的缺陷形态就不是行首**:
|
||
# `. env-defaults.sh; AGENTMAIL_REQUIRE=__no_such_cmd ⇒ rc=0(**漏过**)`
|
||
# ⇒ 同一件事**分行**写 ⇒ 抓到;按 `:18` 那样**同行**写 ⇒ **假绿**。实测:
|
||
# `. …/env-defaults.sh; AGENTMAIL_REQUIRE=__no_such_cmd` ⇒ rc=0 ★假绿(分行则 rc=1 ✓)
|
||
# `export AGENTMAIL_REQUIRE="h"`(source 之后) ⇒ rc=0 ★假绿
|
||
# `true; AGENTMAIL_REQUIRE="h"` / `true && AGENTMAIL_REQUIRE="h"` ⇒ rc=0 ★假绿
|
||
# ★★ 两种失效**不同**,不要混成一条:
|
||
# ⑨a `export X=` ⇒ 行内**是**字面裸赋值,但谓词不认前缀 ⇒ 落在**谓词之外**
|
||
# ⇒ **已闭**(上面的 `(export[[:space:]]+)?`)—— 且实测**不引入假红**
|
||
# ⑨b `; X=` / `&& X=` ⇒ 该行**算调用者**(调用者数 4)却**不判违规**
|
||
# ⇒ 同一行两种规则不一致 ⇒ **申报为边界**(今天不判)
|
||
# ⚠️ ⑨b 为什么**不顺手放宽**: 我实测过一个"能看见分隔符"的候选
|
||
# `(^|[;&|][[:space:]]*)[[:space:]]*(export[[:space:]]+)?AGENTMAIL_REQUIRE=`
|
||
# ⇒ 它把 **`echo "a; AGENTMAIL_REQUIRE=x"`**(**合法**行,token 在字符串字面量里)判成违规
|
||
# = **假红** ⇒ 不可用。要让 ⑨b 可用,必须先有**引号感知** ⇒ 那就撞回 ⑧b 那条真边界。
|
||
# ⇒ ★★★ 记法: **"放宽谓词"与"放宽到能看见分隔符"是两件事** ——
|
||
# 前者只动**同一行内 token 前面**的词法(安全、可闭);
|
||
# 后者要求**跨 token 的语句结构**(会撞上 `⑧b`,且先用假红付账)。
|
||
# ⚠️ 射程: 真仓里**无**此类活缺陷(`grep -E '(^|[^[:space:]]);[[:space:]]*AGENTMAIL_REQUIRE=|export AGENTMAIL_REQUIRE=|&&[[:space:]]*AGENTMAIL_REQUIRE=' deploy/*.sh` = 0)
|
||
# ⇒ 这是**判据域**的 gap,不是生产缺陷。
|
||
# ══════════════════════════════════════════════════════════════════════════════
|