A verdict travels with the bytes it was measured from.
A .hotato bundle is a directory anyone can re-score without your machine, your credentials or your trust: the clipped audio, the per-frame timing evidence, the input-health report, the policy that decides pass, the provenance of who created it, and a CI scaffold that runs it.
contract.json # the contract itself (schema hotato.contract.v1) audio/event.wav # the (clipped) two-channel recording, or the mono file evidence/ frames.jsonl # per-frame timing evidence behind every measurement timeline.html # the to-scale caller/agent timeline, self-contained trust.json # the input-health (trust doctor) report card.svg # a self-contained 1200x630 SVG card (redacted by default) traces/ # populated by `hotato trace attach` source/ call_metadata.json # redacted-by-default: stack, category, expect stack_config_snapshot.json policy/verify.yaml # the SAME subset `hotato verify --policy` reads reports/ initial.html # the full scored report at creation time after.html provenance.json # who/when/how this contract was created ci/ github-action.yml # a weekly + on-push CI scaffold junit.xml # this ONE contract's JUnit result at creation time
docs/CONTRACTS.md documents it. Paths are recorded in contract.json["bundle"]["paths"], so a reader resolves them from the contract rather than from a convention.Shareable by default, identifying only on request.
A contract exists to be sent to someone: a vendor, a colleague, a reviewer on a pull request. So the parts most likely to be pasted somewhere public are redacted unless you ask otherwise.
source/call_metadata.json carries the stack, the category and the expect decision, redacted by default. evidence/card.svg is a self-contained 1200×630 card, also redacted by default. --include-identifiers is the deliberate opt-out.
The audio in the bundle is a clip around the moment, not the whole call, and --pre and --post set how much of it travels. --no-clip keeps the full recording when the moment needs its context.
# print one contract's contract.json $ hotato contract inspect contracts/refund-cutoff-001.hotato # pack it into one portable file $ hotato contract pack contracts/refund-cutoff-001.hotato # and verify every member on the way back out $ hotato contract unpack contracts/refund-cutoff-001.hotato.pack \ --out contracts/refund-cutoff-001.hotato
MANIFEST.sha256.json of every member travels inside it.Unpack
The archive’s own manifest is treated as untrusted input.
Digests are checked against the bytes measured live during extraction, not against what the archive claims about itself.
- An undeclared member.
Any file in the archive that its own
MANIFEST.sha256.jsondoes not declare is refused. - A digest that does not match.
A corrupt or tampered archive exits 2, and nothing partial is left behind on disk.
- Nothing is trusted before it is checked.
The verification happens before a single byte is trusted, so a hostile archive has no window in which it is treated as evidence.
A packed bundle is one file. That is what makes a failure reviewable in a pull request, forwardable to a vendor, and re-runnable a year later by someone who was not there.
statement hotato contract canonical identity digest v1 digest over the canonical JSON: sorted keys, no insignificant whitespace binds the scorer config marker, so a rescore under a different config no longer matches this digest signature optional HMAC; with no key, the attestation is unsigned
contract create as a detached file, so the digest can be checked without re-deriving it.- detached
- canonical JSON
- signing optional
An identity digest that a changed scorer cannot silently satisfy.
The attestation covers a canonical digest of the contract’s identity, and the scorer’s config marker is bound into it.
That binding is the part that matters: a bundle re-scored under a different scorer configuration no longer matches its embedded digest, so a threshold quietly changed underneath a contract shows up as a mismatch rather than as a green run.
Signing is optional throughout. The key is read from HOTATO_ATTEST_KEY or from ~/.hotato/attest.key; with neither present the attestation is written unsigned, and the digest still does its job. The tool never invents a signer.
Two artifacts with the same inputs get the same address.
A proof carries a content_id computed over its own canonical JSON with the content_id field excluded, so the identifier is a function of the evidence rather than of when you ran it.
Under a pinned SOURCE_DATE_EPOCH, the same inputs regenerate the same address. Two people can produce the proof independently and compare one string to see whether they measured the same thing.
The same idea runs through the rest of the surface: an autopsy id is derived from the recording’s content, which is why pinning refuses when the source file’s bytes no longer hash to it.
verdicts counts relative input names sha256 digests content_id # and what it does not carry no transcript text · no audio bytes · no absolute paths
hotato prove’s own description of the hotato.proof.v1 schema.Read what each kind of evidence lets you claim.
The distinction the bundle exists to protect: re-scoring stored evidence says the evidence, policy and scorer still agree with the human label. Whether the current agent still behaves that way is a fresh capture, and the CLI keeps the two claims on separate headlines.