Bundle anatomy¶
A bundle is a folder. 204 KB for the tutorial run. It should still make sense after Touchstone is gone.
run-004/
├── MANIFEST.json every file below, with its SHA-256 and size
├── PLAN.sha256 the plan hash, checkable with shasum alone
├── plan.lock.json image digests, seeds, declared egress, resource ceilings
├── environment.json what it ran on, what it cost, whether egress was enforced
├── items.jsonl one row per test item, stamped with the pack that produced it
├── estimates.json every rate with its interval, method, parameters, denominator
├── scorecard.json the grade each indicator got, and what decided it
├── ledger/RUNLOG.jsonl append-only, written as each event happened
└── runs/ the per-unit item files, before merging
Also present when they apply: audit.yaml (audit responses),
anchors/ (anchoring), runs/<run_id>.egress.log
(containment).
Every file is JSON, JSON Lines or a hash¶
No database, no index, no proprietary format. This is the whole argument of the format.
A bundle is handed to an auditor, a regulator, a procurement team, or somebody's lawyer three years after the run. Any of them can open it with a text editor. None of them should have to install a Python package to find out what it says. If the only reader is this tool, the evidence is only as durable as this tool.
Sealing¶
$ touchstone bundle ./run-004
./run-004: sealed 9 file(s)
sha256 dd02c96f00ed44c64c2bd4867d86d03ae7155ddf720cb8e45c628409b4692bba
MANIFEST.json¶
{
"bundle_format": 1,
"touchstone_version": "0.8.0",
"sealed_utc": "2026-08-27T09:21:44Z",
"run_ledger": "complete",
"sha256": "dd02c96f00ed44c64c2bd4867d86d03ae7155ddf720cb8e45c628409b4692bba",
"files": [
{"path": "PLAN.sha256", "size": 77, "sha256": "…"},
{"path": "environment.json", "size": 412, "sha256": "…"},
{"path": "estimates.json", "size": 18244, "sha256": "…"},
{"path": "items.jsonl", "size": 168290, "sha256": "fc127dc53abc…"},
{"path": "ledger/RUNLOG.jsonl", "size": 1104, "sha256": "…"}
]
}
Paths are relative to the bundle root, forward slashes on every platform. A path that
starts with / or contains .. is refused. A manifest lists things inside the bundle, and
one that reaches outside is either a mistake or an attack.
touchstone_version in a bundle sealed by 0.1.0
0.1.0 bumped the package version and left the string the code stamps reading 0.0.1,
so every artefact it sealed names a version nobody published. A bundle reading 0.0.1
was sealed by 0.0.1 or by 0.1.0 and nothing inside it separates the two. Fixed in
0.2.0, and no hash is affected. See Status.
The bundle hash¶
sha256 is over the canonicalised file list alone, not over MANIFEST.json as a file:
hashlib.sha256(
json.dumps(
[entry.model_dump() for entry in files],
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
).encode()
).hexdigest()
So it does not move when sealed_utc does, and it is independent of how MANIFEST.json
itself is formatted. This is the one value a report quotes and an anchor timestamps.
You can recompute it with jq and shasum:
$ jq -cS '.files' run-004/MANIFEST.json | tr -d '\n' | shasum -a 256
dd02c96f00ed44c64c2bd4867d86d03ae7155ddf720cb8e45c628409b4692bba -
run_ledger¶
| Value | Meaning |
|---|---|
complete |
the ledger reached run_finished |
absent |
a directory assembled by hand |
There is deliberately no incomplete. A run that stopped part way produced files that
hash perfectly well and mean nothing, and bundle refuses to seal it rather than labelling
it.
absent is legitimate. A hand-assembled bundle for re-analysis is a real thing to want,
and the field says so. See The ledger.
bundle_format¶
Bumped when the layout changes in a way an older verify cannot read. Currently 1.
What each file is for¶
| File | Read it for |
|---|---|
plan.lock.json |
what was run: image digests, seeds, declared egress, resource ceilings, access tier |
PLAN.sha256 |
the plan hash on its own, checkable with shasum -c |
environment.json |
what it ran on, what it cost, whether egress was enforced |
items.jsonl |
the observations. Everything else is derived from this |
estimates.json |
the rates, with method, parameters and citation beside each |
scorecard.json |
the grades, the rules that decided them, and the ceilings that bit |
ledger/RUNLOG.jsonl |
what happened, in order, as it happened |
runs/ |
the per-unit files before merging, so the merge is checkable |
Where to go next¶
- Verifying a bundle, with and without this tool.
- Re-analysis, recomputing from
items.jsonl. - Anchoring, proving the plan existed before the run.