runreceipt
A test run that reports 100% over three of ten tests is green in every CI
you own. runreceipt pins how many items a run is supposed to cover, then
recomputes the number afterwards and says out loud what went missing.
Zero dependencies. It is the thing that checks the other tools, so it imports none of them.
$ runreceipt fix --tool langsmith --inputs data/questions.jsonl -o run.lock
pinned 10 items from 1 input file(s); tool langsmith==0.10.18 (installed)
lock run.lock sha256 68d1e39f23d9a54a9d239ee1e0c796ec
$ pytest --junitxml=results.xml # something silently drops 7 items
$ runreceipt verify results.xml --lock run.lock
runreceipt RED
artefact results.xml (adapter: junit)
pinned before run 10
present in artefact 3
reached a verdict 3 (passed 3, failed 0)
lost 0
skipped 0
honest denominator 3
reported pass_rate 1.0000 over denominator 3
recomputed 1.0000 over denominator 3
[RED] SUITE_SHRANK
10 items were pinned before the run, 3 appear in the artefact, and the
run surfaces no loss; the missing 7 are invisible to anyone reading the
figure
instrument 7 rules, each probed on a known-bad and a known-good input
expires langsmith==0.10.18 over input 92099c6974199bac
receipt sha256 54e156047a740dd5072477254c2ccc0f
$ echo $?
2
Note what the reported and recomputed figures do here: they agree. Both
say 1.0000. Recomputing the artefact against itself can never find this
class of defect — the artefact no longer contains the missing items. Only the
number pinned before the run does. That is why fix exists.
The three properties
Every receipt carries what the artefacts we examined did not:
- The denominator is stated, and it was fixed before the run. Not derived from whatever survived.
- Losses are named as losses. An item that raised is a non-pass, not an absent question. It stays in the denominator.
- The green has an expiry. A receipt is valid for one release over one
input set:
runreceipt check --lock run.lockre-derives both and printsVALIDorEXPIRED. A verdict with no expiry is a verdict about the past.
$ runreceipt check --lock run.lock
VALID pinned at 2026-08-14T20:17:20Z; langsmith==0.10.18 and input 92099c697419 are unchanged
$ echo '{"id": "q10", "question": "one more case"}' >> data/questions.jsonl
$ runreceipt check --lock run.lock
EXPIRED a result derived under this lock no longer applies:
- the input set changed (pinned 92099c697419, now 87b22cfe46d5)
$ echo $?
2
Install
pip install runreceipt
Commands
runreceipt fix |
pin the item count, the input hashes and the installed tool version, before the run |
runreceipt verify |
recompute from the artefact; --json/--out for CI |
runreceipt check |
has the green expired? |
runreceipt recheck |
re-verify a receipt someone handed you, without trusting them |
runreceipt selftest |
probe every rule both ways, then replay the whole regression set |
Exit codes: 0 consistent · 2 findings · 3 artefact unreadable ·
4 the instrument failed its own probe. There is no code that means
probably fine. An artefact that cannot be parsed is RED-BLIND, never a
pass.
fix takes nothing on trust. The item count is counted from the input set
rather than read from a field, and the tool version is resolved from the
installed distribution rather than from what you typed — if those disagree with
your claim, the receipt says so. A pinned number that was never measured would
just move the defect one file earlier.
A receipt you can check without trusting who issued it
verify --out receipt.json writes a portable receipt: the verdict, the counts,
the findings, what it is valid for, and a receipt_sha256 over its own body.
Whoever relies on that number — an insurer, a certifier, a buyer in
diligence — does not have to take the issuer's word for it:
$ runreceipt recheck receipt.json
receipt receipt.json
integrity INTACT the hash matches the contents
corroborate CORROBORATED an independent recomputation of results.xml
reproduces the receipt's verdict (RED), counts and findings
$ echo $?
0
Two independent checks, either enough to reject. Integrity re-derives the
receipt's own hash, so a receipt edited after issue — a RED quietly turned
GREEN — is caught with nothing but the file. Corroboration re-runs the
rules on the artefact the receipt names and reports any substance field where a
fresh computation disagrees, naming exactly what diverged rather than a bare
mismatch. Two honest runs differ only in their timestamp, which corroboration
ignores; a forged one does not.
$ runreceipt recheck tampered.json
integrity ALTERED the hash does not match the contents - edited after it was issued
corroborate DIVERGES a fresh recomputation disagrees on: verdict
$ echo $?
2
This is the point of a receipt over a screenshot: a vendor cannot certify itself, because anyone downstream can re-derive the verdict from the bytes.
What it checks
| code | the shape of the defect |
|---|---|
EMPTY_DENOMINATOR |
a rate printed over zero items; NaN < threshold is false, and false reads as threshold not breached |
SUITE_SHRANK |
fewer items in the artefact than were pinned, and nothing reports the difference |
LOSS_UNREPORTED |
items reached no verdict, and the run publishes the count it kept instead of the count it lost |
DENOMINATOR_OVER_SURVIVORS |
the divisor is exactly the number of survivors, so the score rises as more items fail |
COUNTER_MODULO_BUFFER |
the reported "processed" count is a flush remainder, so a full buffer counts as nothing |
VERDICT_CONTRADICTS_ERROR |
an item carries recorded error evidence and is still counted as passed |
FIGURE_MISMATCH |
the printed figure is not what the items add up to |
LOCK_TAMPERED |
the lock's own hash does not match its contents |
The regression set is real releases
Each fixture reproduces a defect found in a shipped version of a tool people
run in CI today, and carries a _reproduces field naming it. selftest
replays all of them on every commit:
$ runreceipt selftest
promptfoo-zero-tests expect RED got RED ok EMPTY_DENOMINATOR
deepeval-judge-raised expect RED got RED ok VERDICT_CONTRADICTS_ERROR
langsmith-3-of-10 expect RED got RED ok SUITE_SHRANK
weave-mean-survivors expect RED got RED ok LOSS_UNREPORTED,DENOMINATOR_OVER_SURVIVORS,FIGURE_MISMATCH
galileo-buffer-counter expect RED got RED ok COUNTER_MODULO_BUFFER
langfuse-8-of-100 expect RED got RED ok LOSS_UNREPORTED,DENOMINATOR_OVER_SURVIVORS,FIGURE_MISMATCH
swebench-dropped-row expect RED got RED ok SUITE_SHRANK
honest-all-pass expect GREEN got GREEN ok
honest-some-fail expect GREEN got GREEN ok
honest-losses-named expect GREEN got GREEN ok
honest-skips-excluded expect GREEN got GREEN ok
unreadable-artefact expect BLIND got BLIND ok
SELFTEST OK 7 rules probed both ways, 12 fixtures verified
The four honest-* fixtures are the half that matters. honest-losses-named
has two items that raised and reports lost: 2 — it must stay green.
Errors are not the defect; hiding them is. A detector that reddens on any
imperfect run is a constant, and a constant detects nothing.
Why the rules probe themselves
Each rule ships two inputs: one it must fire on and one it must stay
silent on. If any rule fails either probe, the whole receipt goes RED and the
exit code is 4 — even when no finding fired. Silence from a broken
instrument is not a pass, and that is precisely the failure being sold against
here: every defect in the table above is a green produced by a number nobody
could recompute.
The test suite applies the same standard to itself. One test disables every
rule and asserts the seven defects then stop being detected — if they still
came out RED, the RED came from somewhere other than the rules and the
regression set proves nothing. Another asserts that the same neutered build
still refuses to print GREEN, and exits 4 rather than 2, so "nothing
found" and "nothing looked" never collapse into the same signal.
Adapters
native · junit (pytest, and anything that writes JUnit XML) · promptfoo
· deepeval · weave · galileo · langfuse · swebench. Sniffed
automatically; override with --adapter.
Adding a vendor costs one adapter and zero rules: adapters lower an artefact
into a normalized Run, and the rules only ever see a Run.
What this tool does not claim
- Not that any tool's authors acted in bad faith.
- Not that any number was falsified. Every defect above is an ordinary bug —
a
bare except, a buffer remainder, a mean over a filtered list. - Not that a green receipt means the model is good. It means the figure survives recomputation against a denominator fixed before the run.
- Out of scope: how a leaderboard ranks or badges rows.
runreceiptreads one run's artefact and says whether its number holds.
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file runreceipt-0.2.1.tar.gz.
File metadata
- Download URL: runreceipt-0.2.1.tar.gz
- Upload date:
- Size: 34.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fbbda0b7b26a5582327446b30f286e8dec6e696c7eee2963afd5f9930fb0a62a
|
|
| MD5 |
7078c379a8075bd7bbcecbda3e6bae2f
|
|
| BLAKE2b-256 |
870916bbc5e5423b798488dbb564c3ffb748e38f9cb82ebc8cb3104841c908a5
|
File details
Details for the file runreceipt-0.2.1-py3-none-any.whl.
File metadata
- Download URL: runreceipt-0.2.1-py3-none-any.whl
- Upload date:
- Size: 31.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1c80c7d30b9297df53858aca817d679b90ddb00d330364217ef6f5930e2514e
|
|
| MD5 |
79654f8a8a9821de909e7399fbeba717
|
|
| BLAKE2b-256 |
1df0c427e1c1d32133c956875ea4c2689d15da7364cf3d1d122551270fb10388
|