Chio/Docs
LOGIN · JOIN

Backup & Restore

Back up receipt history, authority keys, and durable state, then restore a node that reopens admission only after explicit checks.

Deployment chio@5f63e6e

On this page

A node rebuilt from backup with its receipt history intact, its checkpoint head matched against a copy you hold outside the operator, and admission closed until revocations and pins are current.

In every deployment mode (in process, sidecar, local MCP bridge, process host), a node holds SQLite stores and one kernel signing key. Copy them on three tiers and rehearse the restore before a restored node admits calls or rejoins a partner.

Start
Backup tiersChoose a tier.
operatoroutsidereplicatesonline backupencrypted copysigned checkpointspromotesrestorescompares headchecks before admissionintegritychainrevocationspinsKlive nodeK1followers1snapshotK2restored nodes2archivewanchoroperatoroutsidereplicatesonline backupencrypted copysigned checkpointspromotesrestorescompares headchecksintegritychainrevocationspinsKlive nodeK1followers1snapshotK2restoreds2archivewanchor

Every tier restores into a node that stays closed

A follower is promoted, a snapshot is restored, and an archive is retrieved and decrypted into a snapshot first. The restored node admits calls only after its integrity, checkpoint chain, revocations, and pins check out. Rehearse a restore.

One node's stores copied three ways, with the archive and the checkpoint anchor held outside the operator. Providers, cadences, and the single follower are illustrative; partner revocation feeds and multi-region replication are left out.

1. Inventory the six data sets a restore needs

Each data set is a separate backup target with its own recovery point and retention policy.

Data setStorageA restore needs it for
Receipt storeSQLite in WAL mode, --receipt-dbThe signed audit log, append-only by sequence
Kernel signing keySeed file, KMS, HSM, or TEE-boundFurther receipts under the current identity
Trusted issuer setEnvironment or config, replicated by the trust control planeDeciding which capabilities verify
Capability authority state--authority-db on trust-control nodesIssued capabilities, lineage, and revocations
Partner setPartner cards, pinned by chio partner addWhose checkpoints and revocation deltas count
Policy and HushSpec filesFilesystem, version controlReproducing decisions from the exact policy bytes

A trust-control node also keeps --revocation-db and --budget-db. You should end with each store's path, exactly as your services pass it, and a recovery point for each.

2. Take a consistent snapshot of the live receipt store

The receipt store is one SQLite database per kernel node, or one replicated across trust-control followers, opened with these pragmas:

crates/platform/chio-store-sqlite/src/receipt_store/bootstrap/open.rs43-47sql
PRAGMA journal_mode = WAL;
PRAGMA synchronous = FULL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
PRAGMA auto_vacuum = INCREMENTAL;

Copy it with SQLite's online backup API, which writes a consistent, self-contained snapshot of the live database, committed WAL content included.

Terminal · create and inspect one snapshotbash
set -e
umask 077
mkdir -p /backups
backup_snapshot="/backups/receipts-$(date -u +%Y%m%dT%H%M%SZ).sqlite"
test -f /var/lib/chio/receipts.sqlite
test ! -e "$backup_snapshot"
sqlite3 -readonly /var/lib/chio/receipts.sqlite \
  ".backup '$backup_snapshot'"
sqlite3 -readonly "$backup_snapshot" "PRAGMA integrity_check;"
Output
ok

You should see ok. The integrity check covers SQLite structure; step 3 checks the signed history.

  • Copy through SQLite. A filesystem copy needs every writer stopped and a consistent database and WAL set. A WAL checkpoint followed by live file copies races new writes; the WAL lifecycle explains why.
  • Record the boundary. With synchronous = FULL a commit is durable when it returns; a commit after the backup finished belongs to the next snapshot. Continuous point-in-time recovery needs its own capture and restore mechanism.
  • Size the volume from pragmas. PRAGMA page_count times PRAGMA page_size gives the database size without a filesystem stat, consistent in WAL mode; the retention path computes it this way.
  • Include retention archives. Rotation moves history into archive databases. Back up the live and archived stores together and verify their checkpoint coverage together.

3. Prove the snapshot's checkpoint chain before you keep it

Open the snapshot with the installed chio and read its checkpoint status.

Terminalbash
$ chio --receipt-db "$SNAPSHOT" receipt checkpoint status
Output
status: healthy
committed_entry_seq: 1834720
checkpoint_seq: 4928
checkpointed_entry_seq: 1834496
next_range: 1834497..=1834720
retention_watermark_entry_seq: none

You should see status: healthy: the command walked the stored checkpoint chain and found it intact. Record committed_entry_seq as the snapshot's recovery point; next_range is history after the last checkpoint.

Inspect the snapshot with SQL
Terminalbash
# Last retained receipt sequence; this is not a row count.
$ sqlite3 -readonly "$SNAPSHOT" "SELECT MAX(seq) FROM chio_tool_receipts;"
# Last stored checkpoint; checkpoint status checks its signed chain.
$ sqlite3 -readonly "$SNAPSHOT" \
    "SELECT MAX(checkpoint_seq) FROM kernel_checkpoints;"
Output
1834720
4928

4. Ship an encrypted copy off the host

Send the snapshot to versioned object storage for long-term retention. Configure the bucket, its permissions, the KMS key, and lifecycle retention first; this nightly cron script then uploads and keeps no local copy.

/usr/local/bin/chio-backup.shbash
# /usr/local/bin/chio-backup.sh - run nightly via cron
set -euo pipefail

DB="/var/lib/chio/receipts.sqlite"
BUCKET="s3://larkfield-chio-backups/prod"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
SNAP_DIR="$(mktemp -d)"
SNAP="$SNAP_DIR/receipts-$STAMP.sqlite"
trap 'rm -rf "$SNAP_DIR"' EXIT
umask 077
test -f "$DB"

# 1. Online snapshot with consistency check.
sqlite3 -readonly "$DB" ".backup '$SNAP'"
sqlite3 -readonly "$SNAP" "PRAGMA integrity_check;" | grep -qx "ok"

# 2. Request storage-service encryption under the configured KMS key.
aws s3 cp "$SNAP" "$BUCKET/receipts-$STAMP.sqlite" \
    --sse aws:kms \
    --sse-kms-key-id "$CHIO_BACKUP_KMS_KEY_ID"

# 3. Drop the local copy.
rm -f "$SNAP"

# 4. The 90-day retention rule is an S3 lifecycle policy, configured
#    once per bucket outside this script.
echo "ok: $STAMP"
Output
ok: 20261011T020000Z

You should see ok: and the stamp. With --sse aws:kms the storage service encrypts the object, which keeps that service inside the data's trust boundary. To keep the provider out, encrypt the file under a separate KMS key before upload.

The cold tier accepts a longer recovery point, typically nightly snapshots kept 90 or 365 days. A common starting schedule keeps hourly snapshots 7 days, daily 90 days, and monthly 7 years; the receipt log is often the document of record for agent decisions, so external rules decide.

5. Anchor checkpoints outside the operator

The kernel batches receipts into signed checkpoints. Each checkpoint body commits to a Merkle root over its batch and to the SHA-256 of the previous body, so the chain is its own integrity proof.

Three checkpoints, each committing to its batch root and to the previous checkpoint bodycheckpoint 1retainprev noneroot a67ca293H 94772e85MTHr1r2r3r4checkpoint 2retainprev 94772e85root 7121bcc3H bd6bc28fMTHr5r6r7r8checkpoint 3retainedprev bd6bc28froot 3259a428H 26aef010= c3MTHr9r10r11r12click a receipt to alter it; mark which checkpoint a verifier retainedthe producer re-signs the later checkpoints
rootk=MTH(batchk),prevk+1=H(bodyk)\htmlClass{role-seal}{\mathrm{root}_k} = \mathrm{MTH}(\htmlClass{role-call}{\mathrm{batch}_k}),\quad \htmlClass{role-seal}{\mathrm{prev}_{k+1}} = H(\mathrm{body}_k)
MTH(batchk)=rootk  for every k\mathrm{MTH}(\htmlClass{role-call}{\mathrm{batch}_k}) = \htmlClass{role-seal}{\mathrm{root}_k}\ \ \text{for every } k
every batch root matches its signed body
Three checkpoints, each committing to its batch root and to the previous checkpoint bodycheckpoint 1retainprev noneroot a67ca293H 94772e85r1r2r3r4checkpoint 2retainprev 94772e85root 7121bcc3H bd6bc28fr5r6r7r8checkpoint 3retainedprev bd6bc28froot 3259a428H 26aef010= c3r9r10r11r12click a receipt; mark a retained checkpointthe producer re-signs the later checkpointsevery batch root matches its signed body

every batch root matches its signed body.

rootk=MTH(batchk)prevk+1=H(bodyk)MTH(batchk)=rootk  for every k\begin{array}{l} \htmlClass{role-seal}{\mathrm{root}_k} = \mathrm{MTH}(\htmlClass{role-call}{\mathrm{batch}_k}) \\ \htmlClass{role-seal}{\mathrm{prev}_{k+1}} = H(\mathrm{body}_k) \\[8pt] \mathrm{MTH}(\htmlClass{role-call}{\mathrm{batch}_k}) = \htmlClass{role-seal}{\mathrm{root}_k}\ \ \text{for every } k \end{array}
In Rust · checkpoint.rs:10115 lines
pub struct KernelCheckpointBody {
    /// First receipt seq in this batch.
    pub batch_start_seq: u64,
    /// Last receipt seq in this batch.
    pub batch_end_seq: u64,
    /// Number of leaves in the Merkle tree.
    pub tree_size: usize,
    /// Root from MerkleTree::from_leaves.
    pub merkle_root: Hash,
    // … issued_at, kernel_key
    /// Hash of the immediately preceding checkpoint body when this checkpoint extends a prior batch.
    // … serde attributes
    pub previous_checkpoint_sha256: Option<String>,
    // … chain_root (absent on v1 checkpoints)
}
Receipts and checkpoint bodies reduced to the chained fields, hashed with SHA-256 as in merkle.rs; nothing here is signed. A changed receipt conflicts with its batch root, and a re-signed history conflicts only with a commitment retained or authenticated outside the producer.Verifiable evidence

An anchored checkpoint lets a relying party verify that any later checkpoint extends it and, through a ReceiptInclusionProof, that a receipt in a covered range was in its batch. Each body also names the kernel public key in use, which a restore depends on. Fields are listed under Checkpoint body fields.

/usr/local/bin/chio-checkpoint-anchor.shbash
# /usr/local/bin/chio-checkpoint-anchor.sh - run weekly
set -euo pipefail

DB="/var/lib/chio/receipts.sqlite"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
umask 077
mkdir -p /var/lib/chio/anchors
ANCHOR="/var/lib/chio/anchors/checkpoint-$STAMP.json"
test -f "$DB"
test ! -e "$ANCHOR"

# 1. Advance the checkpoint chain. The checkpoint is signed by the
#    kernel keypair loaded from the seed file.
chio --receipt-db "$DB" receipt checkpoint create \
    --kernel-seed-file /var/lib/chio/authority.seed

# 2. Confirm the chain is intact before anchoring.
chio --receipt-db "$DB" receipt checkpoint verify

# 3. Export the latest checkpoint row for the anchor.
sqlite3 -readonly -json "$DB" \
    "SELECT * FROM kernel_checkpoints ORDER BY checkpoint_seq DESC LIMIT 1;" \
    > "$ANCHOR"

# 4. Retain $ANCHOR outside the operator: an internal transparency log,
#    object storage with object lock, or a witness that co-signs checkpoints.
Output of step 1
created: true
checkpoint_seq: 4928
checkpoint_range: 1834273..=1834496
committed_entry_seq: 1834496
checkpointed_entry_seq: 1834496

You should see created: true and the range the new checkpoint covers. Keep the anchor file where the producer cannot rewrite it.

Read more in Bilateral Receipts, where the checkpoint chain is the integrity proof between two organizations.

6. Rehearse a restore, then open admission

Restore a recent snapshot from the tier you are testing into an isolated environment, and compare its checkpoint head with the anchor you retained. Hashes are shortened.

Terminal · drill hostbash
$ cp "$SNAPSHOT" /srv/drill/receipts.sqlite
$ jq -r '.[0] | "\(.checkpoint_seq) \(.merkle_root)"' "$ANCHOR"
$ sqlite3 -readonly /srv/drill/receipts.sqlite \
    "SELECT checkpoint_seq, merkle_root FROM kernel_checkpoints
     WHERE checkpoint_seq = 4928;"
Output
4928 7d41…e09c
4928|7d41…e09c

You should see the same root from the anchor and from the store. Bring up a kernel against the restored store, run one call through it, and extend the chain with the restored seed:

Terminal · drill hostbash
$ chio --receipt-db /srv/drill/receipts.sqlite receipt checkpoint create \
    --kernel-seed-file /srv/drill/authority.seed
Output
created: true
checkpoint_seq: 4929
checkpoint_range: 1834497..=1834721
committed_entry_seq: 1834721
checkpointed_entry_seq: 1834721

You should see checkpoint 4929 over a range that starts right after the anchored one. A healthy receipt checkpoint status afterwards confirms that the new checkpoint's previous_checkpoint_sha256 extends the retained head. Open admission only when four checks pass:

  1. Integrity. PRAGMA integrity_check prints ok and the checkpoint status is healthy.
  2. Chain. The anchored root matches the store, and the next checkpoint extends the retained head.
  3. Revocations. Revocation deltas from the control-plane log, and from each partner in a federated deployment, close the staleness window within your freshness budget.
  4. Pins. The restored seed matches the kernel_key in the retained checkpoints, and the trusted issuer keys and partner cards come from trusted configuration.

Tear down the drill and record the measured recovery time, the surprises, and any drift between runbook and reality. Drill monthly or quarterly to start, matched to your recovery obligations, and again after any change to storage, key custody, authority, or the restore procedure.

Each tier recovers to a different point

The recovery point objective (RPO) is the history you can lose, and the recovery time objective (RTO) the time to verified service. Measure both in drills.

TierRPORTO
Hot, follower promotionLast acknowledged replication boundaryPromotion plus verified admission
Warm, online snapshotCompleted snapshot cadenceRestore plus verification
Cold, encrypted archiveCompleted archive cadenceRetrieval, decryption, restore, and verification

The hot tier is a trust-control follower holding replicated receipt and authority state. Its sync interval is a polling cadence, so its recovery point is the last completed replication. Before promotion, inspect completed replication, writer ownership, and the authority the application will use.

Read more in Trust Control Plane, which holds the coordinator's contract.

Authority and partner state catch up before admission reopens

The capability authority database holds issued capabilities, their lineage, and the revocation list. A restored copy starts at the snapshot boundary and catches up from the authoritative log.

  • Revocation freshness budget. A 24-hour-old snapshot leaves up to 24 hours of revocation deltas to replay from partners or the control-plane log; size snapshot cadence for that window.
  • Lineage travels with capabilities. Retain capabilities and their delegation lineage together. Verification after restore also needs the intended issuer pins, current revocations, the validity window, and the other dispatch preconditions.
  • Issuance is reassessed. Keep or replace each capability after checking expiry, revocation, signer continuity, and lost authority state; validity at snapshot time approves nothing.
  • Partners rejoin from their cards. Restore partner pins from trusted configuration or the card's authority key history, and verify a newly advertised key against that history before it replaces a pin.
  • Federation re-handshakes. Each side announces its checkpoint head and both agree on a fresh exchange schedule. Partner revocation logs are pulled from the retained acknowledged offset, because a valid signature leaves revocation status open.

A restore that fails a check stays closed to admission

The anchor covers history the snapshot lacks

Recover that history from another tier, or reconcile and record the gap before new writes. A new checkpoint extends the retained head; the missing receipts stay missing.

Revocations lag the freshness budget

Keep admission closed and replay deltas from the control-plane log and from each partner's revocation log at the retained offset until the window closes.

The signing seed did not survive

Rotate to a new keypair and re-anchor relying parties on it. Receipts already signed keep verifying while their public keys and trust history are retained; Secrets & Signing Keys has the procedure and how the checkpoint chain crosses a rotation.

Checkpoint fields, store tables, and encryption layers

Checkpoint body fields

FieldTypeHolds
schema
String
chio.checkpoint_statement.v2 for new checkpoints
checkpoint_seq
u64
Monotonic checkpoint counter
batch_start_seq
u64
First sequence in the batch
batch_end_seq
u64
Last sequence in the batch
tree_size
usize
Leaves in the Merkle tree
merkle_root
Hash
Root over the batch
issued_at
u64
Unix seconds at issue
kernel_key
PublicKey
The kernel public key in use
previous_checkpoint_sha256
Option<String>
SHA-256 of the preceding body, when one exists
chain_root
Option<Hash>
RFC 6962 root over the checkpoint chain so far

The body is KernelCheckpointBody. Statements issued as chio.checkpoint_statement.v1 carry no chain_root; consistency proofs verify against it on v2. The receipt schema and its signing are on Receipts.

Receipt store tables

TableOrdered byHolds
chio_tool_receipts
seq
Signed receipts, append-only
kernel_checkpoints
checkpoint_seq
Checkpoint statements, signatures, and kernel keys

Encryption layers

LayerMechanismCovers
Storage
KMS-backed volumes: EBS, Persistent Disk, Managed Disks
The receipt store at rest, with no Chio configuration
Application
encrypted_blob in chio-store-sqlite
Captured bodies, kept from the database operator
Backup
A separate KMS key, applied before upload
Snapshots, kept from the backup-storage provider

encrypted_blob stores captured request and response bodies per tenant, under a tenant-provided 32-byte key with a per-blob ChaCha20-Poly1305 nonce. For high-sensitivity tenants, combine storage and backup encryption under separate KMS keys, optionally with per-tenant application-layer encryption on top.

Multi-region patterns

PatternRecovery pointFits
Async cross-region replication
Last acknowledged replication, plus latency
One clear primary region
Per-region kernels with federation
Each region's own stores
Independent regions that settle per region

With async replication, a regional trust-control follower replicates from the home-region leader on the cluster sync interval and stays read-only outside its home region until promoted. With per-region kernels, each region runs its own kernel, receipt store, signing key, and capability authority, and federation links the regions so receipts and revocations cross-verify. Independent regional kernels keep operating while their own dependencies and authority stay available. A failed region's unreplicated records and signing authority stay with that region; federation transfers neither.

Source files

The store bootstrap is in crates/platform/chio-store-sqlite/src/receipt_store/bootstrap/open.rs and the retention path in crates/platform/chio-store-sqlite/src/receipt_store/evidence_retention.rs. Checkpoints are in crates/kernel/chio-kernel/src/checkpoint.rs, and the control plane in crates/platform/chio-control-plane/src/lib.rs. Backup health signals (snapshot age, drill results, partner freshness) belong on Node Observability.

Next: converge the restored node with its cluster

Backup & Restore · Chio Docs