Published artifacts

Some results in this repository are not committed. A converged sweep is a few hundred kilobytes of CSV that takes minutes to hours of MUMPS solves to produce — reproducible from a committed config, but expensive enough that recomputing it to look at a number would be absurd. Those bytes live in public object storage; what is committed is a small pointer beside them.

Three tiers, by who reads the number

Format follows audience, not habit:

tier

what

format

machine

sweeps, calibration runs, packet histories — read by plotting code and comparisons, never by a person

compressed .npz, values float32, energy axis float64

published

short tables meant to be read: resonance positions, BO levels, anion levels

CSV

quoted

numbers a note or a model states: fitted parameters, fit reports

JSON, full precision

float32 in the machine tier is measured, not assumed: on the O₂ deck it costs a relative 5.9e-8 on σ, against the tightest tolerance anything here is held to (1e-3), and takes a sweep from 390 kB of CSV to 74 kB. The energy axis stays float64 — values tolerate rounding independently, an axis does not, because rounding two neighbouring mesh points onto the same float turns a curve into a multivalued one. The O₂ mesh has 134× margin at its finest spacing and loses no points, but level-aware meshes exist to resolve peaks a few meV wide, so that margin is not worth spending for 13 kB.

Everything a claim depends on stays in git: the golden inputs tests read, the fit reports that lock model constants, the figures the physics notes discuss. The classification, the measurements behind it, and why the line is drawn there are recorded in docs/adr/0008-computed-artifacts-live-in-public-object-storage.md — a repository-only document, so it is named by path rather than linked: adr/ is excluded from this site.

Fetching

$ qscat-run fetch validation/factory/results/o2-ve
validation/factory/results/o2-ve: fetched 3 file(s)

No account, no credentials, no extra package — reads are anonymous HTTPS. To see what a directory would pull without pulling it:

$ qscat-run fetch validation/factory/results/o2-ve --list
validation/factory/results/o2-ve  (from 69742d8)
  https://data.qscat.org/o2-ve/cross_section.830cffb8a044.csv
  https://data.qscat.org/o2-ve/cross_section.927f86a1ff2f.npz
  https://data.qscat.org/o2-ve/cross_section.f8808b6e6355.png

Only outputs are on that list. Every published run directory also carries a committed config.resolved.yaml and manifest.json — the input the sweep is a function of, and the record of what produced it — so a clone with no network can still say exactly what a published number came from and how to re-run it. Both are kilobytes; putting them behind a download would make them unavailable precisely when they are wanted.

Every file is checked against the sha256 recorded when it was published. A mismatch is an error, and the bad bytes are not written — a truncated download must never end up on disk looking like data. A file already present and correct is skipped, so re-running costs nothing and an interrupted fetch resumes.

Directories without an artifacts.json keep their results in git and need no fetching.

What a pointer may say

A pointer is a file, and a fetch acts on it: it decides which host is contacted and which paths are written on the reader’s machine. So it is read as untrusted input, and a pointer that breaks any of these is refused whole, before a byte is requested or written — the digest is no help here, since it describes the bytes and not where they land.

  • Names are relative paths to a file below the run directory. Nested names (wavefunction/psi_E0.05.npz) are supported, because a run writes its wavefunction, eigenstate and resonance snapshots into subdirectories. Absolute names, .., and anything that would have to be escaped to appear in a URL are not: the file on disk and the object in the store must be the same name. Each destination is resolved before any download and must lie inside the directory that was asked for, which also refuses a write that a symlink would carry out of it.

  • URLs are parsed, and must be HTTPS at data.qscat.org — the one read-only hostname the bucket is published at. A string that merely starts with https:// is not a check: https://evil.example/@data.qscat.org/x does too.

  • Digests are 64 lower-case hex characters and byte counts are non-negative integers. The first twelve characters of the digest are the object key, so a malformed digest names the wrong object rather than failing later.

Referencing an artifact directly

The URL is stable, so anything that can read a URL can use one — a plotting notebook, a script in another language, a curl in a shell, a colleague who has never cloned this repository:

$ curl -O https://data.qscat.org/o2-ve/cross_section.<digest>.npz
import io, urllib.request, numpy as np
url = "https://data.qscat.org/o2-ve/cross_section.<digest>.npz"
d = np.load(io.BytesIO(urllib.request.urlopen(url).read()))
E, sigma = d["energy"], d["ti:ve:v0->0"]

Take the digest from the run’s artifacts.json, or from https://data.qscat.org/<experiment>/index.json.

The path is the addressing scheme:

https://data.qscat.org/<experiment>/<name>.<sha256[:12]>.<ext>

The folder is a readable label; the digest is the address. That is what makes such a URL safe to paste into a paper, an issue or a message: different content almost certainly hashes to a different key, and where it does not, the mismatch is caught rather than served. The key carries 12 of the 64 digest characters — 48 bits, so a collision runs at ~1.8e-9 across a thousand objects — while the run’s artifacts.json records the full digest and every fetch is verified against it. A link therefore cannot quietly come to mean something other than what you cited: it either resolves to the bytes you cited, or it fails loudly. If a result is corrected, the correction gets its own URL and the old one keeps the old value for as long as it is kept, so the two remain comparable.

It also means a re-run that reproduces its numbers republishes to the same address and changes nothing — which is the usual outcome here.

There is deliberately no “latest” alias; that would be the one URL whose meaning could change. To find the current bytes for an experiment, read artifacts.json in the repository at the commit you care about — or fetch https://data.qscat.org/<experiment>/index.json, the one object addressed by name rather than by content, which is why it may be replaced.

Nothing expires today. Blobs are shared between pointers once two runs produce the same bytes, so deleting by age is unsafe; the eventual mechanism is reachability — dropping blobs no pointer names — rather than a clock.

Publishing

Publishing is maintainer-only and lives in the private qscat-infra repository, which holds the Terraform for the bucket and the upload tool. It needs an R2 token with write access to the artifacts bucket.

This is not obscurity: the bucket is bound to data.qscat.org as a read-only hostname, and every write goes through a separate authenticated S3 endpoint. There is no public write path to close.