Skip to content

Reading an evidence record

A drill produces exactly one record, appended to the evidence log. It is the product — the test is the means — so it is worth being able to read one without consulting the schema.

{
"adapter": {
"digest": "sha256:4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c",
"name": "postgres",
"protocol": "probavi-adapter/0",
"version": "0.1.0"
},
"backup": {
"checksum": "sha256:9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f",
"created_at": "2026-07-30T01:58:02.000Z",
"kind": "pgdump",
"size_bytes": 565248
},
"checks": [
{
"detail": "accepting connections",
"name": "service_healthy",
"ok": true
},
{
"detail": "100000 rows (min 100000)",
"name": "row_count:orders",
"ok": true
}
],
"drill": {
"config_hash": "sha256:7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d7d",
"name": "prod-orders-db",
"pitr_target": "2026-07-30T14:32:00.000Z"
},
"env": {
"arch": "amd64",
"host_id": "3f7a9c2e5b1d8e04",
"os": "linux",
"probavi_digest": "sha256:1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d",
"probavi_version": "0.1.0"
},
"error": null,
"outcome": "pass",
"prev_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"sandbox": {
"params": {
"image": "postgres:16",
"memory": "2GiB"
},
"provider": "docker"
},
"schema": "probavi-evidence/2",
"seq": 1,
"sig": {
"alg": "ed25519",
"key_id": "56475aa75463474c",
"sig_b64": "/f2ahJgj08qDVSvbTIZ/+XI7IfkCavp0WfS7ETIezC+E2PMzFzEmX/G8d4d+S42Q9ZegQDU8aMKJTBb/gS5KDQ=="
},
"timings_ms": {
"engine_ready": 1166,
"provision": 1170,
"restore": 190,
"total": 2840,
"transfer": 110,
"validate": 61
},
"ts": "2026-07-31T02:00:11.482Z"
}

A real record from the format’s published example log, re-indented for reading — on disk it is one canonical line, and that is the form the signature covers.

Signed with:03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8

Where it sits in the chain. seq is the position, prev_hash the SHA-256 of the record before it, and ts when the drill finished. The first record in a log carries an all-zero prev_hash because it has no predecessor. Alter any record and every link after it stops verifying, which is what makes the log append-only in practice and not merely by intention.

What was drilled. drill names the target and carries a hash of the configuration used, so two records can be compared for whether they were produced by the same drill or by a drill someone edited in between. Where a point-in-time restore was requested, the target time is recorded with it.

What it was drilled from. backup records the source kind, the archive’s size, its checksum, and when it was created. The creation time is the field to read first when a pass looks too good: a record that proves a three-month-old archive restores is a true statement about a backup nobody should be relying on.

Where it was restored. sandbox names the provider and the parameters it was given — the image, the memory limit — recorded verbatim. adapter names the adapter, its version, the protocol version it spoke, and digest, the SHA-256 of the adapter executable the core launched. Version and digest are not the same fact: the version is a number the adapter reports about itself, so two different builds can share one, and the digest is what tells them apart. Together these are what make a restore duration comparable with another one: same image, same limits, same adapter build, or the two numbers are not measuring the same thing.

What happened. outcome is the drill’s verdict, and error carries the reason when there is one. checks lists every check by name with its result and a short detail — the row count that was found and the bound it was measured against, not merely that it passed.

How long it took. timings_ms breaks the drill into phases: provisioning the sandbox, transferring the archive, the restore itself, waiting for the engine, and validation. The restore phase is measured separately from the checks on purpose, so the number you compare against a recovery time objective is not inflated by however much SQL you chose to run afterwards.

Where it ran. env records the operating system, architecture, a stable host identifier, the Probavi version and probavi_digest, the SHA-256 of the probavi executable that wrote the record. Enough to tell two drill hosts apart, to explain a change in timings, and to say which build produced the proof; not enough to identify anything else, because nothing about the drill leaves the machine. Either digest is null where the file could not be read — a digest is worth recording, never worth failing a drill for.

The signature. sig carries the algorithm, the key identifier and the signature itself, over the canonical single-line form of the record.

Checking a log takes the file and a public key — no network, no licence, and not even a Probavi installation, since the format ships with an independently released verifier.

What it establishes is precise: that these records were produced by the holder of that key and have not been altered since. Every signature is valid, and every prev_hash matches the record it points at, so nothing has been edited, reordered or removed from the middle — which is exact, and leaves one removal for the section below.

The exception is at the end. Delete the newest records from a log and what remains is a shorter log whose sequence still starts at 1 and whose chain is still unbroken, so a verifier reading only the file answers VALID — correctly, because nothing given that file alone can tell a truncated log from a log that is simply shorter. The next drill appends onto the shortened chain, so the removal leaves no trace afterwards either.

Closing it takes one value kept outside the file. Every verification prints its head: the highest seq it reached, together with the SHA-256 of that record’s stored line, written <seq>:sha256:<hex>. Keep the head one run reports, and hand it to the next as --anchor.

A log that still carries that line unchanged has grown since, which is the ordinary case, and the answer is the one the file would have given anyway. A log that ends before that seq is missing records from its end. A log whose line at that seq hashes differently was truncated and then grown again — long enough, and wrong where it matters. The last two are INVALID and exit 2, because a log shorter than its anchor is not the log that anchor was taken of, which is the same finding as a record removed from within it.

The strength of an anchor is entirely in where it is kept, so keep it where the log’s writer cannot reach — a ticket, a mail, a commit in a repository the drill host cannot write to, or a probavi push receiver that retains what it was sent. Probavi keeps none of them: it prints the value and stops. Signing the head would add nothing, because the attacker this format is built against is the one holding the signing key.

None of this moves the format. No field is added, nothing further is signed, and records are still probavi-evidence/2 — an anchor is an input to verification, and without one the file and the public key remain sufficient for everything the chain alone can prove.

Verification is a statement about the record, not about your recovery position. It cannot tell you that the backup was of the right database, that the checks were the right checks, or that the restore duration is inside your objective. A perfectly valid signature over a record of a drill with one healthcheck and no other assertion proves that the drill happened and reports what little it asserted.

The two digests are narrower than they look, and the specification says so rather than leaving it to be assumed. Each is the hash of a file read just before it was run, so it names the bytes Probavi selected — not proof that those bytes are the instructions that executed. A file replaced between the hash and the launch would go unrecorded. Closing that window means reading the running process’s own image, which does not exist on every platform Probavi supports, so the record makes the narrower claim it can keep.

That is not a limitation to work around. It is the division of labour: the signature makes the history trustworthy, and the checks you choose decide what the history is worth.