What a receipt proves
A receipt is a DSSE envelope wrapping an in-toto Statement: a short JSON payload signed with your agent's Ed25519 key. Anyone with the envelope and your public key can verify the signature in one command, with no Restorable service involved. This page walks through what the verified payload actually lets an auditor conclude, field by field.
The one-line summary
On this date, the agent whose public key is published did decrypt and restore this specific ciphertext into a scratch database, did run these specific checks against the restored database, and did record these specific results.
Everything else in the product exists to produce receipts with that property and to make it easy to forward them.
Field by field
A receipt is a DSSE envelope. The signed payload is an in-toto Statement v1 whose predicate carries the evidence. Abbreviated:
// DSSE envelope (outer)
{
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64 of Statement below>",
"signatures": [{ "keyid": "", "sig": "<Ed25519 signature>" }]
}
// in-toto Statement (decoded payload)
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{
"name": "bkp_01hz...",
"digest": { "sha256": "..." }
}],
"predicateType": "https://restorable.app/restore-test/v1",
"predicate": {
"receipt_id": "rcpt_01hz...",
"org_id": "org_abc",
"agent_id": "agent_xyz",
"source_id": "prod-db",
"backup": {
"backup_id": "bkp_01hz...",
"ciphertext_sha256": "sha256:...",
"ciphertext_size_bytes": 1234567,
"created_at": "2026-04-13T03:00:00Z",
"cipher": { "alg": "age-v1", "recipients_hash": "sha256:..." }
},
"restore": {
"started_at": "2026-04-14T02:00:00Z",
"completed_at": "2026-04-14T02:06:52Z",
"duration_ms": 412000,
"runtime": {
"type": "self-hosted",
"agent_version": "0.5.1",
"agent_build_sha256": "sha256:...",
"os": "linux-amd64",
"target_engine": "postgres",
"target_engine_version": "16.2"
}
},
"result": "pass",
"checks": [{
"test_id": "recent-orders",
"check_sha256": "sha256:...",
"summary_sha256": "sha256:...",
"expect": "n > 100",
"result": "pass",
"expect_matched": true,
"summary_row_json": "{\"n\":1247}"
}],
"attestation": { "type": "none", "data": null },
"issued_at": "2026-04-14T02:07:00Z",
"prev_receipt_id": "rcpt_01hy..."
}
} The signature
The DSSE envelope uses Ed25519 signatures. The signature covers the payloadType and payload fields, byte-for-byte. A single bit flipped anywhere invalidates it. The customer's Ed25519 public key is the only thing that verifies it. Nothing signed by Restorable passes.
agent_id and org_id
Identify which agent produced the receipt and which org it
belongs to. The orchestrator rejects any receipt whose
agent_id does not match the bearer token used to
submit it. The verifier rejects any receipt whose signature does
not match the public key registered for that agent_id.
backup
Binds the receipt to one specific ciphertext blob.
ciphertext_sha256 is the hash of the bytes that
were decrypted and restored. recipients_hash is a
hash of the age recipient list used to encrypt;
auditors can detect a key rotation by watching it change.
created_at is when the backup was taken, not when
the receipt was signed.
restore.runtime
Records what code ran the restore.
agent_build_sha256 is a hash of the agent binary
itself. Auditors who want the strongest property check that
hash against the published reproducible-build record for the
declared agent_version. If the hashes match, the
auditor has established that the code which ran is the code that
was published.
target_engine and target_engine_version
are what the scratch container reported. A receipt that restored
a Postgres 16 source into a Postgres 16 scratch is stronger
evidence than one restored into a downgraded or upgraded
scratch.
result and checks[]
result is the roll-up: pass,
fail, or error. The rule is any
fail wins, otherwise any error wins,
otherwise pass. The verifier recomputes this from
the per-check entries and rejects the receipt if the stored
roll-up disagrees.
Each checks[] entry records:
-
test_id: which check definition produced this entry. -
check_sha256: hash of the check's canonical body (SQL text for Postgres, canonical JSON for Mongo). Lets the auditor verify the receipt references the exact check you say it does. The check text itself never leaves your infrastructure. -
expectandexpect_matched: the assertion and its boolean outcome. -
summary_row_json: the one row the summary query returned, as a JSON string. This is the single field where check output crosses from the scratch container into the signed artifact.
attestation
The pluggable trust root.
type: none means the receipt was produced by a
self-hosted agent; the customer trusts the binary they installed.
type: amd-sev-snp means the receipt was produced
inside an AMD SEV-SNP enclave; the data carries the
attestation report that an auditor can verify against AMD's root
CA.
The customer's Ed25519 signature covers the receipt either way. Attestation is evidence inside the signed payload, not a replacement for the signature.
issued_at and prev_receipt_id
issued_at is when the agent signed the receipt. The
orchestrator rejects receipts older than one hour at submission,
so an attacker cannot stash and later replay old receipts.
prev_receipt_id chains receipts per source. The
verifier can walk the chain backwards to the first receipt for
a source. Breaks in the chain are visible.
What a passing receipt does not prove
Three honest limits. Know them before you rely on the artifact.
It does not prove your production database is healthy
The restore ran in a scratch container on a snapshot of your data from some earlier moment. A receipt pass means the snapshot was restorable and looked like data at the time the snapshot was taken. It does not say anything about what your live production database looks like right now.
It does not prove your checks are comprehensive
The receipt attests that the checks you wrote passed against the
restore. If your checks are too narrow (SELECT 1
against any table), the receipt's evidentiary weight is low. If
your checks exercise representative queries, the evidence is
strong. Writing the right checks is the customer's job; see
Writing a check.
It does not prove the backup will restore a month from now
Libc collations drift. Postgres extensions get dropped. Hardware moves. A backup that restored yesterday might not restore next week. The weekly cadence of restore tests is the defence; the receipt attests only to the moment the restore ran.
How an auditor uses it
The typical flow in an audit meeting:
- Customer forwards the weekly evidence email. It contains the receipts for the week and a transparency-log tree head.
-
Auditor builds
restorable-verifyfrom source (lives at codeberg.org/restorable/restorable undercmd/restorable-verify), downloads the customer's public key from the dashboard, and runs:restorable-verify --pubkey agent.pub receipt.intoto.json - The verifier prints the parsed payload, confirms the signature, checks the schema, and verifies the roll-up. Any failure exits non-zero and says why.
Nothing in that flow requires Restorable to be reachable. The receipt is its own evidence.