An input ledger fixes a clean source revision, exact metadata, toolchain, configuration, environment and assets. The same inputs feed two separate empty destinations. Sorted path-to-SHA-256 manifests are compared and the inputs and result retained. Ordinary builds vary with the UTC timestamp. A historical earlier test differed at 165 of 4,695 paths, while four later patched Linux outputs matched. That result applies only to its recorded inputs, not future content or other platforms.
Pins narrow drift. Compare controlled builds and keep the inputs with the result; the recorded four-build match has a specific historical scope.

Context

The site is built with a Hugo generator and a PaperMod theme. Untracked upgrades of either can change output silently: the theme’s templates, the generator’s rendering rules, or both can alter the HTML bytes between two “identical” rebuilds. A release manifest identifies one artifact. Reproducing it is a stronger claim: source, environment, and instructions must be controlled well enough to generate identical bytes again.

Decision

Vendor the theme into the repository and record the exact generator version and theme version in a pin file, data/upstream-pins.toml. The make build target checks the installed Hugo version against .hugo_version; make check-upstream, also invoked separately by CI, cross-checks that version with the TOML pin and the PaperMod release recorded in THIRD_PARTY_NOTICES.md. The latter checks recorded consistency, not a fresh comparison of every vendored file with upstream.

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

Themes that are vendored stay under themes/ in the tree so they are part of the commit and therefore part of the byte identity of a release.

Consequences

  • Controlled inputs: pinning removes two sources of drift. It does not alone prove identical output.
  • Visible upgrades: updating the generator pin or vendored theme bytes is an explicit reviewable change. Theme modifications remain visible in Git even if the release label is unchanged.
  • Auditable provenance: a reader of any release can see which generator and theme produced the bytes.
  • Trade-off accepted: the repository carries the vendored theme and the pin bookkeeping; the cost is small against silent output drift.

Reproducibility still needs a test

The current Makefile writes the current UTC time into data/buildinfo.toml on each build. That timestamp is rendered in the site, so two normal builds at different times are intentionally not byte-identical. A commit label also does not describe uncommitted edits. Record a clean source revision and the exact build metadata alongside the artifact.

To test deterministic rendering, work in an isolated checkout, fix the same build metadata, toolchain, configuration, environment, and asset inputs, then invoke Hugo twice into separate empty destinations. Generate sorted path-to-SHA-256 manifests for both outputs and compare them. Keep the chosen inputs with the result. Do not claim that SOURCE_DATE_EPOCH is honored by this Makefile: it explicitly calls date.

An earlier complete-site two-build check with fixed build metadata produced 4,695 files in each output and 165 differing paths, including taxonomy display casing such as HTTP versus Http. That failed result remains part of the history; the version pins alone did not establish deterministic output.

The original report describes a later isolated Linux check that fixed the same build metadata and Hugo clock, then compared four complete builds before and four after a content-only taxonomy change. The change adds explicit term metadata for the 28 language/term case collisions, corrects the Chinese nginx tag to 漏桶, and preserves its historical /zh/tags/令牌桶/ URL. All four patched outputs contained 4,695 files and had identical sorted path-to-SHA-256 manifests; every published path, tag member sequence, pagination target, and XML route was retained, with no build warnings. The reported conclusion concerns repeatability only for that source snapshot, Hugo 0.165.0 Extended, vendored theme and Linux environment. It does not establish cross-platform, cross-year, future-version or future-content reproducibility. Re-run the comparison after changing those inputs, and do not use two ordinary make build invocations as a fixed-metadata test.

The recovered article and diagram retain that historical report, but the recovered materials do not include the original run logs or comparison manifests for the later four-build result. It has not been independently reproduced in this review. The later recovery of taxonomy title metadata is a separate exercise and does not supply the missing evidence for the original experiment.

Version pins are not permanent approval to run old software. Review upstream fixes, assess their relevance, update the recorded versions and vendored bytes together where appropriate, then run the full verification and inspect the generated diff.

Why this wins

A build whose inputs are not pinned can produce the right answer today and a different tree tomorrow, and neither the diff nor the manifest can tell you why. Pinning narrows the investigation when the build changes; timestamps, content, configuration, and environment remain possible causes. That is a useful boundary without overstating reproducibility.

Sources