receipt
Run a command. Get a receipt for what it actually touched — not just what it was asked to do.
An AI agent (or a script, or a CI job) says it's going to fix a bug in one
file. Nothing checks whether that's what it actually did until someone
reviews the diff by hand, if they do at all. receipt snapshots the working
directory before and after, and reports pass, fail, or unverified —
same three-status shape as
invariant,
firedrill, and
carabiner.
$ receipt run --task "fix the auth bug" --declare app/auth.py \
-- python fix_auth.py
[FAIL] touched 1 undeclared file(s): app/payments.py
receipt written to receipts/20260908T121251Z-4f2c9a1b.json
Three statuses, one of them meaning something different here
pass — touched only what was declared. fail — touched something outside
the declared scope, named exactly. unverified — no scope was declared for
this run at all.
That third one is a deliberate difference from invariant/firedrill/
carabiner, where unverified means "a check that should have run, didn't."
Here it means "no promise was made this time" — plain audit logging is a
normal, legitimate use of this tool, not a degraded one. So unverified
does not fail the build; only a broken declared promise (fail) does.
What it actually does
- Hashes and permission-bits every file under the watched directory (sha256
- mode, skipping
.git,__pycache__, etc.). Only regular files are hashed — a FIFO, socket, or device node is skipped rather than opened, sinceopen()on a FIFO with no writer on the other end blocks forever (a real bug, found and fixed: a--dir /tmpscope check hung two CI runs for a full 6 hours each before this guard existed).
- mode, skipping
- Runs the given command, captures stdout/stderr/exit code/timing, and
redacts secret-shaped text (env-var-style
API_KEY=...assignments, credentialed URLs, well-known token prefixes, PEM key blocks) before any of it is stored — see "What redaction doesn't mean" below. A command that never launches at all (bad--dir, missing binary) still produces a receipt —fail, with the launch error as the detail — instead of a Python traceback and no evidence. - Snapshots the directory again, diffs the two. A removed path and an added
path with identical content are reported as one
renamedpair, not an unrelated delete-plus-create; a path whose content is byte-identical but whose permission bits changed is reported asmode_changed— see "Whattouchedmeans" below. - If a scope was declared (exact paths, or glob patterns like
app/*.py), checks the diff against it. - Writes the whole thing — command, task, diff, declared scope, verdict —
to
receipts/<timestamp>-<random>.jsonalongside a sha256 of the receipt itself, same evidence-bundle idiom as invariant's--evidence.
Zero dependencies — stdlib only (hashlib, subprocess, argparse,
fnmatch).
Install
pip install receipt-evidence # the command it installs is `receipt`
Or from a checkout, for development:
pip install -e .
Use
receipt run --task "what this is supposed to do" \
--declare path/one.py,app/*.py \
--dir . --out receipts/ \
-- your-command --with --args
Omit --declare to just log what happened without a scope to check it
against (unverified, still a written receipt, still exit 0).
Test
python tests/test_receipt.py
What touched means
touched is the union of every file that was added, removed, had its
content modified, was renamed (a removed path and an added path sharing a
content hash), or had its permission bits changed with content otherwise
identical. A rename or a chmod on a path outside the declared scope is a
real fail, named clearly — sneaky.txt (renamed from output.txt), or
secret.env (permissions changed, content unchanged) — not silently
folded into "nothing happened" the way a plain content-hash diff would.
What pass doesn't mean
pass only means "touched nothing outside the declared scope within
--dir." A write anywhere outside that tree — /tmp, ~, a sibling
directory, an absolute path elsewhere in a monorepo — is invisible to
receipt and won't affect the verdict. Point --dir at the smallest tree
that actually bounds what the task could legitimately touch; don't read
pass as "touched nothing on the filesystem."
What redaction doesn't mean
Captured stdout/stderr and the command's own argv are swept for
secret-shaped text (receipt/redact.py) before a receipt is written — this
closes a real gap found during review: a wrapped command that echoed
API_KEY=sk-... landed that value verbatim in the receipt JSON. The sweep
is a regex net for common shapes, not a guarantee. It will not catch a
secret with no recognizable shape (e.g. a bare 40-character hex string with
no key name attached, split across two log lines, or base64-wrapped). If a
command's output might contain something sensitive in an unusual shape,
don't assume the receipt is safe to share as-is — read it first.
What's deliberately not here yet
No network/API-call capture — only filesystem diffing. "What did this agent touch" is answerable this way; "what did this agent call" isn't, without hooking into a specific agent framework's own trace or intercepting traffic, which is a real, separate, much bigger project.
No policy evaluation or rule composition beyond a flat declared-scope
check — that's deliberately a different tool's job. receipt stays the
evidence producer;
invariant is where richer policy
(is this evidence actually OK, across multiple runs, with other checks
composed in) belongs.
MIT licensed.
Release files for receipt-evidence 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| receipt_evidence-0.1.2.tar.gz | 20.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| receipt_evidence-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.3 kB
Release files / receipt_evidence-0.1.2.tar.gz
| Download URL | receipt_evidence-0.1.2.tar.gz |
|---|---|
| Size | 20.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0688b9a1bcc5f449ac3cb79a1482c7eb2d2406a988a3129776fdbbe89ad1b9ef
|
|
BLAKE2b-256 checksum How to use checksums |
d7f2e0c88e4e86c947d97636633f7a1cbc8636059fcf6a2ed1c3ec63f4dc8360
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / receipt_evidence-0.1.2-py3-none-any.whl
| Download URL | receipt_evidence-0.1.2-py3-none-any.whl |
|---|---|
| Size | 14.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6c5990a97b741a962e8090f57601848a7efa23616b58e82aa4e1fab25267df42
|
|
BLAKE2b-256 checksum How to use checksums |
3aa7a03fe00e239abc3dbaf1ad4a15cffd46238fde7f3bdcca093fa73cc76741
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log