Skip to content

Make pinned Rustkyll source builds byte-reproducible #118

Description

@alexeygrigorev

Parent: #115

Outcome

Pinned legacy source builds are byte-for-byte reproducible across separate clean workspaces and processes. This unblocks the generated-source freshness gate for Studio → Courses without weakening provenance or accepting a wall-clock/random-order snapshot.

Incident evidence

Independent #115 QA found scripts/build_pinned_legacy_sources.py --check stale after the current-main integration. Regenerating aligned the two checked artifacts only temporarily: two consecutive full builds changed them again.

The repeat-build delta is exactly 209 generated paths:

  • 203 main-site podcast pages whose season JSON-LD endDate inherits the current build time from an undated collection item;
  • docs and podwiki feed.xml timestamps;
  • main-site sitemap.xml ordering; and
  • the three .rustkyll-manifest.json map orderings.

The affected builds use pinned Rustkyll 0.4.6 and 0.5.0 code paths. Local source inspection found no existing SOURCE_DATE_EPOCH or reproducible-build flag. Rustkyll uses Utc::now/Local::now in collection fallback dates, site.time, feed fallback, and future-post filtering; sitemap input and manifest maps originate from Rust HashMap iteration/serialization. The manifest also records source mtimes.

Required implementation

Rustkyll compatibility lanes

  • Add standard SOURCE_DATE_EPOCH support to compatible 0.4.x and 0.5.x releases/backports; do not force an unrelated renderer upgrade.
  • Parse the value once and fail closed on invalid, negative, overflow, or ambiguous input.
  • Thread one frozen UTC instant through collection default dates, site.time, empty-feed fallback, and future-post cutoff.
  • In reproducible mode, use UTC when the source has no explicit timezone; preserve an explicit configured timezone.
  • Sort final sitemap entries deterministically, root first and then lexicographically by final loc, without changing membership.
  • Canonically serialize manifest map keys. Preserve the manifest rather than excluding it from checked output.

Website wrapper

  • Derive SOURCE_DATE_EPOCH from each already-verified pinned commit's committer timestamp with an exact revision-bound git query.
  • Pass only the minimal deterministic environment to Rustkyll and record source-date-epoch=<value> in checked deterministic-override provenance.
  • Normalize copied tracked source-file mtimes to that same epoch before rendering so manifest values are stable across clean clones.
  • Reject missing, malformed, mismatched, or out-of-range revision/epoch/mtime data with bounded diagnostics.
  • Regenerate generated-path-baseline.jsonl and source-build-provenance.json only after deterministic rendering is proven.

Current pinned epoch reference values, to be independently derived rather than hard-coded as authority:

  • main: 1785872368
  • docs: 1786017922
  • podwiki: 1785736104

Acceptance criteria

  • Two full builds of every pinned source in separate fresh workspaces/processes are byte-identical for the complete generated trees and all checked artifacts.
  • A third full build followed by scripts/build_pinned_legacy_sources.py --check remains green without changing tracked bytes.
  • The exact generated path membership and route contract remain unchanged; main sitemap retains the same 811-URL multiset in documented deterministic order.
  • All 203 affected podcast pages retain valid JSON-LD shape with stable source-derived/frozen dates; docs/podwiki feeds have stable timestamps.
  • All three manifest files have stable key order and stable source-mtime values across clean clones.
  • Tests cover one frozen instant shared by every Rustkyll time consumer, invalid epoch inputs, explicit timezone behavior, deterministic sitemap membership/order, and canonical manifest serialization in both compatibility lanes.
  • Website tests cover strict revision-bound epoch derivation, minimal environment inclusion, invalid data failure, safe tracked-file mtime normalization, and deterministic override provenance.
  • --artifacts-only or an immediate --check is not accepted as reproducibility proof because neither independently proves a clean full rebuild.
  • Make Studio → Courses the canonical course-operations surface #115's 26 canonical Studio Courses targets, exact /cadmin source mappings, access matrix, current-main Import historical registration totals safely and show aggregate event counts #112/Recover fail-closed ECR manifest verification for active release capture #117/Ship real public content on the final editorial route map #105 behavior, CMP adoption, and public route inventory remain unchanged.
  • Normal uv-backed compatibility/Django/quality/Playwright gates pass, with no secret, credential, private source row, or production mutation.

Process

This is a technical blocker for #115. Rustkyll changes require their own review/release evidence, after which website pins must be updated and the #115 candidate refreshed to current main before a new independent QA pass. Do not bypass the generator, post-process volatile output as an unexplained mask, or weaken the freshness check.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Must-have or release-blockingbugSomething isn't workingcontentArea: contentintegrationArea: integrationoperationsArea: operationstestingArea: testing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions