Verify

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.
  • expect and expect_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:

  1. Customer forwards the weekly evidence email. It contains the receipts for the week and a transparency-log tree head.
  2. Auditor builds restorable-verify from source (lives at codeberg.org/restorable/restorable under cmd/restorable-verify), downloads the customer's public key from the dashboard, and runs:
    restorable-verify --pubkey agent.pub receipt.intoto.json
  3. 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.