> 0 FAIL 不等于那些条件都成立——一份严格审计里八处用廉价代理冒充实质检查
一个审计脚本负责在投稿前确认实验产物齐全、一致、可追溯。 给它做了一次逐条自评,结论写得很直接:
// TABLE_OF_CONTENTS
结论先行¶
一个审计脚本负责在投稿前确认实验产物齐全、一致、可追溯。 给它做了一次逐条自评,结论写得很直接:
目前代码即使将来得到 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 版本 + 重生成比对 |
| 审计活跃目录 | 原子替换,或只审不可变快照 |
- 还要断言全集而不是任意一个(见 用 any() 写的回归测试是假保证——它只要求最容易过的那个夹具通过), 并且覆盖面从被检查的东西推导出来(见 手写的自检清单永远是全绿的——它只包含你刚修过的东西)。
- 门禁的 PASS 要带作用域并打印未检查项,见 退出码 0 承诺的是"流程跑对了",不是"结论是通的"——所以门禁要把两件事都打印出来; 测不出失败的门禁在制造信心,见 测不出失败的门禁比没有门禁更糟——它在制造信心。
出处¶
自己的实验产物审计脚本(audit_maintrack_artifacts.py)的一次逐条自评,
八条写在投稿行动清单的"工程阻塞与审计盲区"一节。
第 1 条后来以显式必需清单修掉,上面那段注释是修补时写的原文。