From c963d8466f2ab2c7de3ad51aae4e5eff5bcc57d0 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 25 Oct 2026 08:06:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E5=A4=8D=20c20e7c6=20?= =?UTF-8?q?=E5=B8=A6=E8=BF=9B=E6=9D=A5=E7=9A=84=20`&&=20false`=20=E6=AD=BB?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=20+=20=E8=87=AA=E6=B5=8B=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 伤在哪 c20e7c6 把 ci.yml 的空表守卫写成了: if [ "$n" -eq 0 ] && false && ! grep -qEi '...# none...' <<< "$TABLE"; then ^^^^^^^^ `&& false` 让整个条件恒假 ⇒ ID:19 的「空表必须显式声明」守卫**永不触发**。 这比没有守卫更坏:门禁*看起来*有了守卫,实际恒绿。599252f 引入时是正确的 (无 `&& false`),是 c20e7c6 把它改坏的。 ## 改了什么(两处,无夹带) 1. .github/workflows/ci.yml —— 只删 `&& false` 这一个 token,其余逐字不动。 守卫恢复为「空表且未写 # none ⇒ 报错退出 1」。 2. .github/scripts/check-ci-gates.py —— 在 599252f 已有的正反自测之上**加一层**: · `_rejected_checked[0] != 4` 调用计数契约:把四条 `_must_reject(...)` 调用 整段删掉/注掉时,前面所有断言代码都还在、但一次都不会跑(自测静默死掉、 门禁照绿,变异 M8d 实测 exit=0)。现在删一行也红。 · `idx >= len(_EMPTY_BAD_CASES)` 越界可读报错:样本集被清空时不再抛 IndexError 崩溃,给出可读断言消息。 与 599252f **不冲突、不抵消**:599252f 加的是「空表标记校验 + 正反自测」, 本提交是在其之上补的「自测自身是否真的跑了」。 ## 实证(提交前实跑) · grep -n '&& false' .github/workflows/ci.yml → 0 命中(exit=1) · python3 .github/scripts/check-ci-gates.py → EXIT=0「CI 门禁自检通过」 · yaml.safe_load → yaml OK(exit=0) · 反向:把 heredoc 数据行清空只留空行 → 门脚本 EXIT=1, 报「排除表步骤用当前 ci.yml 实跑不通过(正向基线坏)」⇒ 缝上后守卫真的会咬; 跑完已还原,md5 对拍 e87461b1e2de95cfc293359cbc0d16de 逐字一致 变异矩阵(10 条,全部 EXIT=1):M1 删整段守卫/红4、M2 `&& false`/红4、 M3 `exit 0`/红4、M4 `grep→false`/红4、M5 删 case 跳过/红1、 M6 `|| true` 短路/红2、M7 `&& :` 恒真/红1、M8 样本集清空/红5、 M8d 删四条自测调用/红1、M8f 只删第 3 条/红1。基线 EXIT=0 红条数 0。 Co-authored-by: pi --- .github/scripts/check-ci-gates.py | 18 +++++++++++++++++- .github/workflows/ci.yml | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/scripts/check-ci-gates.py b/.github/scripts/check-ci-gates.py index a7c2e2ae..8016a296 100755 --- a/.github/scripts/check-ci-gates.py +++ b/.github/scripts/check-ci-gates.py @@ -189,8 +189,16 @@ else: fails.append("空表反向样本数变了(期望 4,实际 %d)—— 除新增样本,否则说明样本被删" % len(_EMPTY_BAD_CASES)) + _rejected_checked = [0] # 用 list 当可变计数(闭包内可写) + def _must_reject(idx): - """断言第 idx 个空表形态**必须**被保。""" + """断言第 idx 个空表形态**必须**被拒。""" + _rejected_checked[0] += 1 + if idx >= len(_EMPTY_BAD_CASES): + # 样本被删:不抛 IndexError,给可读报错(崩溃不如可读断言好)。 + fails.append("空表反向样本集缺第 %d 条(只剩 %d 条)—— 样本被删或容器被改坏" + % (idx, len(_EMPTY_BAD_CASES))) + return _n, _i = _EMPTY_BAD_CASES[idx] if _run_body(_replace_table(_i)).returncode == 0: fails.append( @@ -202,6 +210,14 @@ else: _must_reject(2) # 只有空行+空白 _must_reject(3) # 非显式标记的注释 + # ★ 四次调用本身也必须被核对(ID:19 变异 M8d): + # 把上面四行 `_must_reject(...)` 整段删掉/注掉时,前面所有断言代码都还在, + # 但**一次都不会跑** —— 自测静默死掉、门禁照绿(实测 exit=0)。 + # 所以把调用次数当成一份合同:删一行也红,不会默默少检。 + if _rejected_checked[0] != 4: + fails.append("空表反向自测只调用了 %d/4 次 —— 有检查被删/跳过(自测静默失效)" + % _rejected_checked[0]) + _probe = "test|internal/probe/synthetic|TestSyntheticProbe" # 只打 reason:reason 空、ticket 有效 if _run_body(_inject(_probe + "||PROBE-TICKET")).returncode == 0: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7545d420..f1bbfd27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -272,7 +272,7 @@ jobs: # 为何不能允许"什么都没写":那样"表被删空"与"有意清空"无法区分, # 而门脚本(check-ci-gates.py)的正则捕获到空串后,会静默走下面的 # "无排除项"分支 —— 排除项漏删/误删都不会有任何人发现(假绿,ID:19)。 - if [ "$n" -eq 0 ] && false && ! grep -qEi '^[[:space:]]*#[[:space:]]*none[[:space:]]*$' <<< "$TABLE"; then + if [ "$n" -eq 0 ] && ! grep -qEi '^[[:space:]]*#[[:space:]]*none[[:space:]]*$' <<< "$TABLE"; then echo "::error::-race 排除表为空却没有显式空表标记 —— 请在上面的 heredoc 里保留一行 \`# none\`" exit 1 fi