> 0 FAIL 不等于那些条件都成立——一份严格审计里八处用廉价代理冒充实质检查

Research Tools Intermediate 2026-10-13 20:30 2026-10-13
#audit #gates #fail-open #provenance #reproducibility #taxonomy

一个审计脚本负责在投稿前确认实验产物齐全、一致、可追溯。 给它做了一次逐条自评,结论写得很直接:

结论先行

一个审计脚本负责在投稿前确认实验产物齐全、一致、可追溯。 给它做了一次逐条自评,结论写得很直接:

目前代码即使将来得到 0 FAIL,也不等于下列条件全部成立。

八条,每一条都是用一个廉价的代理替代它声称检查的东西。 八条的失效方向全部是"通过":

# 它真正检查的 它声称检查的 代理失效时
1 glob 命中的项逐个合法 必需的模型/seed 文件都在 glob 一项都没命中 → 无可审 → 通过
2 几个字段存在 conformal 配置正确 过渡期的 α=0.15 也能过(没核目标 split、α、重算 k、test_n、逐 seed 对齐)
3 result.json 存在 这一轮跑完了 空日志、缺步、测量网格不全、非有限通道都能过
4 首尾步数没查 轨迹完整 两份一致地被截断的结果一起通过
5 两个 metadata 字符串相等 checkpoint 是同一个 缺 parent_hash 只 WARN;字符串相等 ≠ 真实 hash 相同
6 两个 bit string 等长 逐样本配对成立 等长不证明是同一批样本(要 ID / 顺序 / hash)
7 表格文件的 mtime 比输入新 表里的数字对、且正文引用了它 mtime 只说"晚于",不说"由它生成"
8 JSON 能解析 JSON 写完了 作业还在写时读到半成品,解析得动、内容不全

为什么

这八条不是八个独立 bug,是一种写法的八次出现: 检查"存在"而不检查"内容",检查"长度"而不检查"身份", 检查"时间"而不检查"因果"。

而它们之所以全部 fail-open,是有结构原因的: 代理都是"更容易满足"的条件。 文件存在比内容正确容易; 两个串等长比它们来自同一批样本容易;mtime 晚于输入比"由输入生成"容易。 所以每一次代理替换都会把门槛降低,而降低门槛的方向就是通过。

一个审计报 0 FAIL,字面意思是"我没找到问题"。 而当 glob 可以一项都不命中时,"我没找到问题"和"我没找过"是同一个输出。

第 1 条是整组里最便宜也最关键的修法,而且它的修法形态很固定: 把"必需输入"写成一份显式清单,缺一个就 FAIL,而不是让一个 glob 去决定审计范围。这份脚本后来就是这么修的,注释写得正好:

# required maintrack_state inputs (explicit manifest; a missing
# file must FAIL rather than leave a glob with nothing to audit)

第 7 条值得单说,因为它最常见也最有说服力地假装成了检查。 mtime 证明不了任何因果关系:它只说这个文件比那个文件晚。 要证明"这张表是由这些输入生成的",需要的是输入 hash + schema 版本 + 分支计数 + 重新生成一遍比一比。而要证明"正文真的引用了这张表里的数", mtime 一个字都没说(这件事要去渲染产物里 grep, 见 检查渲染产物,不是源码——Django 的 {# #} 只支持单行、同一个数出现在图和表里,两者不能互相裁判——换算因子只住在生成器里)。

第 8 条是唯一一条与并发有关的:活跃写入时的 JSON 可能处于部分完成状态。 两种修法都对——用临时文件原子替换,或者只对作业结束后的不可变快照做最终审计。 审计一个还在变的目录,结果本身不可复现。

怎么用

一条通用的自检:把每条检查的"声称"和"实际"写成两列, 不一致的那些就是代理。然后逐条问"代理失效时,结果是 FAIL 还是 PASS"。 全是 PASS 的话,这份审计的 0 FAIL 没有信息量。

具体的六条替换:

不要 要
glob 决定审计范围 显式必需清单,缺一个就 FAIL
文件存在 = 完成 终止标记 + 内容断言(步数、网格、有限性、schema)
字符串相等 = 同一个产物 真实 hash,并且缺 hash 要 FAIL 不是 WARN
等长 = 配对 样本 ID / 顺序 / hash 一致
mtime = 由它生成 输入 hash + schema 版本 + 重生成比对
审计活跃目录 原子替换,或只审不可变快照

出处

自己的实验产物审计脚本(audit_maintrack_artifacts.py)的一次逐条自评, 八条写在投稿行动清单的"工程阻塞与审计盲区"一节。 第 1 条后来以显式必需清单修掉,上面那段注释是修补时写的原文。