输入清单固定干净源码版本、准确元数据、工具链、配置、环境和资源。同一组输入进入两个独立空目录,再比较按路径排序的 SHA-256 清单,并保留输入与结果。普通构建会随 UTC 时间戳变化。历史上较早测试的 4,695 条路径中有 165 条不同;后续四次修补后 Linux 构建相同。该结果仅适用于所记录输入,不保证未来内容或其他平台。
固定版本缩小漂移范围。比较受控构建并留存输入;历史四次构建一致的结论有明确边界。

Context

本站用 Hugo 生成器与 PaperMod 主题构建。两者任一未跟踪的升级都可能静默改变输出:主题的模板、生成器的渲染规则,或两者都可能改变两次"相同"重建之间的 HTML 字节。发布清单标识一份产物。复现它是更强的论断:源码、环境与步骤必须受到足够控制,才能再次产生相同字节。

Decision

把主题纳入仓库,并把确切的生成器版本与主题版本记录在钉住文件 data/upstream-pins.toml。make build 用 .hugo_version 检查安装的 Hugo 版本;CI 另外调用的 make check-upstream 将它与 TOML 中的版本及 THIRD_PARTY_NOTICES.md 记录的 PaperMod 发布版交叉核对。后者检查记录一致性,不是重新逐文件比对 vendored 内容与上游。

Hugo 0.165.0
PaperMod v8.0
data/upstream-pins.toml   (钉住)

被纳管的主题放在 themes/ 目录下,作为提交的一部分,因此也成为一次发布字节身份的一部分。

后果

  • 输入受控: 固定版本去除了两个漂移来源,但本身不能证明输出字节一致。
  • 升级可见: 更新生成器版本或 vendored 主题字节,是显式可审查的变化。即使发布标签未变,主题修改仍然能在 Git 中看见。
  • 来源可审计: 任何一次发布的读者都能看到是哪个生成器与主题产生了这些字节。
  • 接受的取舍: 仓库带上 vendored 主题与钉住簿记;相比静默的输出漂移,这个代价很小。

可复现性仍需要测试

当前 Makefile 每次构建都把当前 UTC 时间写入 data/buildinfo.toml。时间会被渲染到站点,因此不同时间的两次普通构建有意不保持字节一致。提交标签也不能描述未提交修改。应把干净源码版本和准确构建元数据与产物一起保存。

要测试渲染的确定性,可在隔离检出中固定相同的构建元数据、工具链、配置、环境和资源输入,再让 Hugo 分别输出到两个空目录。为两份输出生成按路径排序的 SHA-256 清单并比较,把选定输入与结果一起留存。不要声称这个 Makefile 会采用 SOURCE_DATE_EPOCH,因为它明确调用了 date。

较早的完整站点两次构建检查在固定构建元数据后,两份产物各含 4,695 个文件,但有 165 条路径的字节不同,其中包括 HTTP 与 Http 等分类显示大小写。这个失败结果仍保留在历史记录中;仅固定版本并未建立输出确定性。

原报告记载了一次后续隔离 Linux 检查:固定相同构建元数据与 Hugo 时钟,在一项仅涉及内容的分类修改前后各比较四次完整构建。修改为 28 个语言/分类大小写冲突组添加显式分类元数据,把中文 nginx 标签改为漏桶,并保留其历史 /zh/tags/令牌桶/ URL。修改后的四份产物各含 4,695 个文件,按路径排序的 SHA-256 清单完全相同;所有发布路径、标签成员顺序、分页目标和 XML 路由均保留,构建没有警告。报告中的结论仅涉及所记录源码快照、Hugo 0.165.0 Extended、vendored 主题和 Linux 环境下的重复一致性,不是跨平台、跨年份、未来版本或未来内容的可复现保证。改变这些输入后应重新比较,也不能用两次普通 make build 代替固定元数据测试。

恢复后的文章与配图保留了这项历史报告,但恢复材料不包含后续四次构建结果的原始运行日志或比较清单。本轮审查没有独立复现该结果。后来进行的分类标题元数据恢复是另一项工作,不能补足原实验缺失的证据。

固定版本不代表永远批准运行旧软件。应审查上游修复、判断其影响,适当同步更新版本记录与 vendored 字节,然后执行完整验证并检查生成差异。

为什么它胜出

输入未钉住的构建,今天可能给正确的答案,明天却给出不同的树,而 diff 与清单都无法告诉你为什么。固定版本缩小了构建变化时的调查范围;时间戳、内容、配置与环境仍可能造成差异。这个边界有用,但不应被夸大为可复现性的证明。

来源