The checksum that was a timestamp

2026-09-25 · GH #330 · runs 708c70ba → b49bf857, d1fda481

The job was small and well-specified: rebuild the 6-locus Gate A validation callset, see whether its sha256 still comes out f4515791…. If it matched, that would be a better result than having kept the file — it would prove Gate A is byte-reproducible across the c0a2684 change to its row-ordering logic, and retroactively validate every dense_callset_checksum recorded before it.

It didn’t match. And the interesting part is why not, because it isn’t any of the things the question was set up to distinguish between.

The suspect, eliminated before the run

The named hypothesis was _contig_rank. Before c0a2684 it ranked undeclared contigs at 10_000 + hash(c) % 10_000, which is per-process randomized, so VCF row order — and therefore the checksum — wasn’t reproducible. The 6-locus artifact was built 2026-07-14, before that fix. Plausible.

It’s also checkable without running anything, which is what I did first. The randomized branch only fires for a contig the caller never declared. All six sentinels sit on contigs the gVCF header declares, at fixed ranks. So the fallback never fired, and the predicted row order was fully determined.

The rebuild came out in exactly that order. The suspect had an alibi before the crime was even measured.

What actually reproduced

Everything except the bytes. Same union_size = 6. Same callable_homref=5 / callable_variant=1 / explicit_missing=0 / structural_fail=0, reconciling 6/6. Same callability policy across all eight keys, same FASTA checksum, same caller id, same autosomal median depth of 32.0.

And — this is the part that settles it — all six callability_manifest reason strings came back character-identical, down to the depth and quality evidence behind each call. The classifier saw the same reads and made the same decision at every locus. Biology intact.

The protocol I was given said a content difference at any of the six loci means stop. There wasn’t one. There was no content difference at all.

The container

build_dense_callset writes through gzip.open(path, "wt"). That leaves mtime=None, and gzip then stamps the current wall-clock time into its MTIME header field — and the output filename into FNAME. Both live inside the bytes being hashed.

FLG=0x08  FNAME='dense_callset_1173a8db-…_default.vcf'
MTIME 1790362824 -> 2026-09-25 19:00:24 UTC

Compress the same 95,478-byte payload three times, a second apart, and you get three different sha256s. Pin mtime=0 and it’s stable. Then the direct test: two independent worker Gate A runs over the same gVCF, FASTA and targets —

                  b49bf857          d1fda481
  .gz sha256      31f47a7f…         1ce02004…     CONTAINER identical: False
  payload sha256  bfc5451b…         bfc5451b…     PAYLOAD   identical: True

So mods_prs_run.dense_callset_checksum was never a content identity. It’s a per-build nonce. Not for this artifact specifically — for every Gate A artifact ever recorded, 5cad7d30 and d76e1c65 included. The expected outcome of this experiment (“it matches anyway”) was unreachable. Nobody could have reproduced f4515791…, at any time, on any version of the code.

That also means an existing test docstring was half wrong. It said a randomized rank made row order “and therefore mods_prs_run.dense_callset_checksum” differ between runs. The row-order half is true and c0a2684 fixed a real bug. The checksum half described an outcome that never arrived, and the fix looked complete because nobody re-derived the checksum afterwards.

The awkward instruction

The brief said, in bold: do not fix a mismatch by editing _GATE_A_SHA256. “A pin edited whenever it disagrees is decoration.” Exactly right, and written before anyone knew the pinned quantity was a clock reading.

What I did instead was replace it rather than edit it. _GATE_A_PAYLOAD_SHA256 hashes the uncompressed payload, which is invariant to build time and output filename and still false for any other content. The compressed size ceiling — the thing that actually stands between that test and the 4.55 GB load that killed a worker last week — is checked first and untouched. The old value stays in the file as _GATE_A_RUN_708C70BA_GZ_SHA256, labelled as audit trail and not to be used as a guard.

I checked the new guard isn’t vacuous, because “58 passed” looks identical whether the guard discriminates or just returns True. Both production callsets are rejected in 0.1–0.2 ms, without being opened. The repro2 artifact — same payload, different container — returns True, which is the correct answer and precisely the thing the .gz hash could not express.

What I found on the way

The clobber is worse than the issue records. The shared path has been overwritten twice, not once: Gate E1’s 11.4M-locus callset landed on the sentinel build, then classifier-v4’s 19.2M-locus callset landed on that, finishing three hours before I started. Three run rows claimed that one path. Two of them — e4fcbbd8 and 6658f656 — were pointing at bytes belonging to a different run. Both now point at the scoped filename whose bytes hash to their recorded checksum, and every live row reconciles against disk.

Which is also why I ran the rebuild into its own directory rather than the default. With the naming still unscoped, a Gate A run that forgets out_dir overwrites production — and the thing I’d have overwritten was fifteen hours old.

The lesson, such as it is

A checksum is only an identity if you know what it’s over. This one was over a container that embeds a clock, and every consumer — the run row, the frontend Hash label, the test guard, run_gate_e.py — treated it as a content hash. The assumption was never written down, so it was never checked, and the one experiment that would have exposed it is the one nobody had run: build the same thing twice.

The cheap version of that experiment costs about ninety seconds. I only ran it because a number disagreed. It should be what you run before you start pinning things.


Landed: payload-hash pin, fixture moved to gate_a_validation/, a regression test pinning the gzip finding, the corrected docstring, supersession notes on 708c70ba and b49bf857, and two corrected dense_callset_path rows. 94 passed, 0 skipped; test_reads_the_real_gate_a_validation_callset runs for the first time since July.

Left open, deliberately: mtime=0 at the four gzip.open write sites would make artifact checksums genuinely content-addressed, which is what everyone already assumed they were. It changes every future checksum, so it wants a decision, not a drive-by. And the scope-in-the-name fix — the actual root cause of #330 — is still untouched.