--- name: run-provenance description: >- Use when a kept run must be attributable to exactly what produced it — the environment it ran under (interpreter, locked packages, domain engines, system layer, native architecture), the input bytes it consumed (datasets, external files, config/constants), and the random draws it made (seeds recorded and re-derivable). Everything `results/_/meta.json` must record about a run, and how to make each field true. Also use before any campaign whose results will be kept, when two machines disagree on a result, and when asking "did the data change between these two runs". Trigger phrases: "pin the environment", "lockfile", "which versions produced this", "works on my machine", "environment drift", "hash the inputs", "did the data change", "which dataset version", "fingerprint the config", "data provenance", "record the seeds", "which seed produced this", "unseeded randomness", "make this run reproducible", "what produced this number". tier: core --- # run-provenance "What produced this number?" must have one answer, computed and recorded, not remembered. Every kept run carries a meta file — `{{evidence_dir}}/_/meta.json` — and that file must pin three provenance chains, each of which dies silently if not captured at run time: the **environment** it ran under, the **inputs** it consumed, and the **seeds** it drew from. "It worked last month" is not provenance — it is a memory. ## When to use - Before any campaign that lands in `{{evidence_dir}}` (preflight) — the meta file needs all three chains. - Creating or onboarding a project without a lockfile; adding a dependency or upgrading an interpreter/domain engine. - Launching any run that consumes input data, or registering a new external dataset. - Any stochastic computation whose output will be kept. - Debugging cross-machine or cross-month differences: same code, different number. ## When NOT to use - Choosing *values* of run parameters — that is `canonical-params` (protected defaults). - Chasing bit-level nondeterminism *within* one pinned environment (threads, BLAS, GPU kernels) — that is `numerical-determinism`; seeds and locks cannot fix what scheduling scrambles. - Deciding *how many* seeds/replications a claim needs — that is `statistical-reporting`. - The schema the meta file is written into and which runs are "kept" — that is `evidence-convention` (the hub this writes into). - Verifying a computation's correctness — provenance proves *which inputs/env/draws*, not *right answer*. ## Environment — declare → lock → commit → fingerprint 1. **Declare**: dependencies live in a manifest — `{{env_manifest}}` (e.g. `pyproject.toml`, `environment.yml`). Direct dependencies only; no pip-install-by-hand history. 2. **Resolve and commit the lock**: a lockfile pinning the full transitive set (`uv.lock`, `poetry.lock`, `conda-lock.yml`) is committed next to the manifest, plus the interpreter pin (e.g. `.python-version`). Group heavyweight/phase-specific stacks (dev tooling, GPU frameworks) in optional dependency groups so the base install stays resolvable. 3. **Record the system layer**: domain engines and system libraries the lockfile cannot see (commercial engine builds, CUDA/driver versions, compiler toolchains, BLAS implementation) are recorded in the README or environment doc, and their versions land in each run's meta file. 4. **Run native, not emulated.** On mixed-architecture machines, verify the interpreter matches the hardware — an emulated interpreter (e.g. x86-64 under translation on ARM) runs native extensions silently and sometimes drastically slower, and can change numerical library code paths. Preflight: `python -c "import platform; print(platform.machine())"` must print the machine's native architecture. 5. **Fingerprint into evidence**: each run's meta file records interpreter version, architecture (+ native-arch verified flag), lockfile hash, and domain-engine versions. ## Inputs — hash every byte the run reads 1. **Hash inputs at read time**: `shasum -a 256 ` (or the in-code equivalent) for every dataset file the run reads, recorded as `{"_sha256": "", "_path": ""}` pairs in the meta file. **Hash what was read, not what should have been read** — compute from the actual path the code opened; a hash of the "official" copy proves nothing about the run. 2. **Hash the defaults too.** The config/constants file defining the canonical operating point (`{{canonical_values}}`) is an input; record its hash (e.g. `config_sha256`) so every result is pinned to the exact defaults it assumed (see `canonical-params`). 3. **Pin the code identity.** Record the git commit and whether the tree was clean at launch; for scripts that ran from a dirty tree, record per-file git blob hashes (`git hash-object