use super::super::*;
use super::support::*;
#[path = "background_checkpoint_races.rs"]
mod background_checkpoint_races;
fn signer(keypair: &Keypair, max_batch: u64) -> BackgroundCheckpointSigner {
BackgroundCheckpointSigner {
keypair: Arc::new(keypair.clone()),
max_batch,
}
}
#[test]
fn checkpoint_signer_survives_supervisor_restart() -> Result<(), Box<dyn std::error::Error>> {
let (temp_dir, path) = temp_db("chio-bg-signer-restart")?;
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
store.enable_background_checkpoints(signer(&keypair, 2))?;
store
.receipt_commit_actor
.sender
.try_send(ReceiptCommitCommand::RestartSupervisor)?;
for i in 0..2 {
store.append_chio_receipt_returning_seq(&sample_receipt_with_keypair(
&format!("rcpt-bg-signer-restart-{i}"),
i + 1,
&keypair,
))?;
}
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_some(),
"the restarted writer must retain the installed checkpoint signer"
);
assert_eq!(
store
.receipt_commit_actor
.worker
.health()
.ok_or("missing supervisor health")?
.snapshot()
.restart_total,
1
);
drop(store);
temp_dir.close()?;
Ok(())
}
#[test]
fn background_build_panic_is_isolated() -> Result<(), Box<dyn std::error::Error>> {
let (temp_dir, path) = temp_db("chio-bg-panic-isolated")?;
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
let max_batch = test_hooks::PANIC_DURING_CHECKPOINT_BUILD_MARKER_MAX_BATCH;
store.enable_background_checkpoints(signer(&keypair, max_batch))?;
test_hooks::PANIC_DURING_CHECKPOINT_BUILD.store(true, std::sync::atomic::Ordering::SeqCst);
for i in 0..max_batch {
let receipt = sample_receipt_with_keypair(&format!("rcpt-bg-panic-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
test_hooks::PANIC_DURING_CHECKPOINT_BUILD.store(false, std::sync::atomic::Ordering::SeqCst);
let health = store.receipt_store_health()?;
let last_error = health.writer.last_error.as_deref().unwrap_or_default();
assert!(
last_error.contains("receipt writer job panicked"),
"expected last_error to record the injected panic, got {last_error:?}"
);
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"a panic mid-build must not leave a partially built checkpoint"
);
assert!(
!health.healthy,
"a recorded writer error must still surface through receipt_store_health"
);
let receipt = sample_receipt_with_keypair("rcpt-bg-panic-recovery", max_batch + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_some(),
"writer thread must still be alive and able to build checkpoints after the panic"
);
let recovered_health = store.receipt_store_health()?;
assert!(
recovered_health.healthy,
"a successful batch after the panic must clear last_error: {recovered_health:?}"
);
drop(store);
temp_dir.close()?;
Ok(())
}
#[test]
fn maybe_build_checkpoint_builds_one_checkpoint_per_crossed_threshold(
) -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-bg-threshold");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
store.enable_background_checkpoints(signer(&keypair, 3))?;
for i in 0..7 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-bg-{i}"), (i + 1) as u64, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
let first = store
.load_checkpoint_by_seq(1)?
.ok_or("checkpoint 1 missing")?;
let second = store
.load_checkpoint_by_seq(2)?
.ok_or("checkpoint 2 missing")?;
assert!(
store.load_checkpoint_by_seq(3)?.is_none(),
"no third checkpoint yet"
);
assert_eq!(
(first.body.batch_start_seq, first.body.batch_end_seq),
(1, 3)
);
assert_eq!(
(second.body.batch_start_seq, second.body.batch_end_seq),
(4, 6)
);
let expected_digest = chio_kernel::checkpoint::checkpoint_body_sha256(&first.body)?;
assert_eq!(
second.body.previous_checkpoint_sha256.as_deref(),
Some(expected_digest.as_str())
);
assert!(first.body.previous_checkpoint_sha256.is_none());
let status = store.receipt_checkpoint_status(Some(3))?;
assert!(
status.healthy,
"audit after background checkpoints: {status:?}"
);
assert_eq!(status.latest_checkpoint_seq, Some(2));
assert_eq!(status.latest_checkpointed_entry_seq, 6);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn zero_max_batch_disables_background_checkpointing() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-bg-disabled");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
store.enable_background_checkpoints(signer(&keypair, 0))?;
for i in 0..5 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-bg-off-{i}"), (i + 1) as u64, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"batch_size 0 disables checkpoints"
);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn one_big_batch_crossing_two_thresholds_builds_both_checkpoints(
) -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-bg-multicross");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
store.enable_background_checkpoints(signer(&keypair, 2))?;
for i in 0..4 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-bg-multi-{i}"), (i + 1) as u64, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
assert!(store.load_checkpoint_by_seq(1)?.is_some());
assert!(store.load_checkpoint_by_seq(2)?.is_some());
assert!(store.load_checkpoint_by_seq(3)?.is_none());
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn background_and_writer_routed_child_appends_share_the_threshold(
) -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-bg-child");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
store.enable_background_checkpoints(signer(&keypair, 2))?;
let receipt = sample_receipt_with_keypair("rcpt-bg-child-0", 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
let child = sample_child_receipt_with_keypair_and_timestamp("child-bg-1", 2, &keypair);
store.append_child_receipt_record(&child)?; store.flush_receipt_writes()?;
let checkpoint = store
.load_checkpoint_by_seq(1)?
.ok_or("child append must count toward the threshold")?;
assert_eq!(
(
checkpoint.body.batch_start_seq,
checkpoint.body.batch_end_seq
),
(1, 2)
);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn identical_background_checkpoint_is_idempotent() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-bg-idempotent");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
for i in 0..3 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-idem-{i}"), (i + 1) as u64, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
store.create_next_receipt_checkpoint(3, &keypair)?;
let persisted = store
.load_checkpoint_by_seq(1)?
.ok_or("winner checkpoint 1 missing")?;
{
let mut connection = store.connection()?;
let mut tx =
connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
let savepoint = tx.savepoint()?;
insert_checkpoint_incremental_tx(&savepoint, None, &persisted)?;
savepoint.commit()?;
tx.commit()?;
}
let status = store.receipt_checkpoint_status(Some(3))?;
assert!(
status.healthy,
"idempotent re-insert must keep the store healthy: {status:?}"
);
assert_eq!(status.latest_checkpoint_seq, Some(1));
assert_eq!(status.latest_checkpointed_entry_seq, 3);
assert!(
store.load_checkpoint_by_seq(2)?.is_none(),
"the idempotent re-insert must not add a second checkpoint row"
);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn flush_report_reflects_externally_extended_checkpoint() -> Result<(), Box<dyn std::error::Error>>
{
let path = unique_db_path("chio-flush-external-ckpt");
let keypair = receipt_test_keypair();
let store_a = SqliteReceiptStore::open(&path)?;
for i in 0..3 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-flush-{i}"), (i + 1) as u64, &keypair);
store_a.append_chio_receipt_returning_seq(&receipt)?;
}
store_a.flush_receipt_writes()?;
let baseline = store_a.flush_receipt_writes()?;
assert_eq!(baseline.latest_checkpoint_seq, None);
assert_eq!(baseline.uncheckpointed_end_seq, Some(3));
let store_b = SqliteReceiptStore::open_existing(&path)?;
store_b.create_next_receipt_checkpoint(3, &keypair)?;
let report = store_a.flush_receipt_writes()?;
assert_eq!(
report.latest_checkpoint_seq,
Some(1),
"flush must reflect the externally extended checkpoint"
);
assert_eq!(report.latest_checkpointed_entry_seq, 3);
assert_eq!(report.uncheckpointed_start_seq, None);
assert_eq!(report.uncheckpointed_end_seq, None);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn shared_db_background_build_adopts_external_checkpoint() -> Result<(), Box<dyn std::error::Error>>
{
let path = unique_db_path("chio-bg-adopt-external");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 3;
for i in 0..max_batch {
let receipt = sample_receipt_with_keypair(&format!("rcpt-adopt-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
let receipt_bytes = canonical_receipt_bytes(&store, 1, max_batch);
let mut external = build_checkpoint(1, 1, max_batch, &receipt_bytes, &keypair)?;
external.body.issued_at = external.body.issued_at.saturating_add(1_000);
let body_bytes = canonical_json_bytes(&external.body).test_unwrap();
external.signature = keypair.sign(&body_bytes);
store.store_checkpoint(&external)?;
let mut stale_head = VerifiedHead {
latest_checkpoint: None,
chain_frontier: None,
claim_log_count: max_batch,
claim_log_max_seq: max_batch,
};
let signer_a = signer(&keypair, max_batch);
let result = build_due_checkpoints(&store.pool, &mut stale_head, &signer_a);
assert!(
result.is_ok(),
"stale-head background build must adopt the external checkpoint, got {result:?}"
);
assert_eq!(
stale_head.checkpoint_seq(),
1,
"the head must have adopted the externally committed checkpoint"
);
assert!(
store.load_checkpoint_by_seq(2)?.is_none(),
"the already-covered range must not be rebuilt into a second checkpoint"
);
let status = store.receipt_checkpoint_status(Some(max_batch))?;
assert!(status.healthy, "chain must stay healthy: {status:?}");
assert_eq!(status.latest_checkpoint_seq, Some(1));
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn successful_checkpoint_build_clears_stale_error() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-bg-clear-stale-error");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 3;
for i in 0..max_batch {
let receipt = sample_receipt_with_keypair(&format!("rcpt-clear-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
assert!(store.load_checkpoint_by_seq(1)?.is_none());
let health = ReceiptCommitWriterHealth::default();
if let Ok(mut last_error) = health.last_error.lock() {
*last_error = Some("prior background checkpoint build failed".to_string());
}
let mut head = VerifiedHead {
latest_checkpoint: None,
chain_frontier: None,
claim_log_count: max_batch,
claim_log_max_seq: max_batch,
};
build_due_checkpoints_and_record(
&store.pool,
&mut head,
&Some(signer(&keypair, max_batch)),
&health,
);
assert!(
store.load_checkpoint_by_seq(1)?.is_some(),
"the recovery build must persist checkpoint 1"
);
let cleared = health
.last_error
.lock()
.map(|guard| guard.is_none())
.unwrap_or(false);
assert!(
cleared,
"a successful background build must clear the stale writer error"
);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn flush_report_ignores_unverified_checkpoint() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-flush-ignore-forged-ckpt");
let keypair = receipt_test_keypair();
let store_a = SqliteReceiptStore::open(&path)?;
for i in 0..3 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-forge-{i}"), (i + 1) as u64, &keypair);
store_a.append_chio_receipt_returning_seq(&receipt)?;
}
store_a.flush_receipt_writes()?;
let store_b = SqliteReceiptStore::open_existing(&path)?;
store_b.create_next_receipt_checkpoint(3, &keypair)?;
let good = store_a.flush_receipt_writes()?;
assert_eq!(good.latest_checkpoint_seq, Some(1));
assert_eq!(good.latest_checkpointed_entry_seq, 3);
let connection = store_a.connection()?;
connection.execute_batch("DROP TRIGGER IF EXISTS kernel_checkpoints_reject_update;")?;
connection.execute(
"UPDATE kernel_checkpoints SET batch_end_seq = ?1 WHERE checkpoint_seq = 1",
rusqlite::params![9_999_i64],
)?;
drop(connection);
let report = store_a.flush_receipt_writes()?;
assert_ne!(
report.latest_checkpointed_entry_seq, 9_999,
"a forged checkpoint row must not be reported as checkpointed progress"
);
assert_eq!(report.latest_checkpointed_entry_seq, 0);
assert_eq!(report.uncheckpointed_end_seq, Some(3));
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn flush_report_rejects_disconnected_checkpoint() -> Result<(), Box<dyn std::error::Error>> {
let keypair = receipt_test_keypair();
let good_path = unique_db_path("chio-flush-connected-ckpt");
let good_store = SqliteReceiptStore::open(&good_path)?;
for i in 0..6 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-conn-{i}"), (i + 1) as u64, &keypair);
good_store.append_chio_receipt_returning_seq(&receipt)?;
}
good_store.flush_receipt_writes()?;
good_store.create_next_receipt_checkpoint(3, &keypair)?; good_store.create_next_receipt_checkpoint(3, &keypair)?; let good = good_store.flush_receipt_writes()?;
assert_eq!(good.latest_checkpoint_seq, Some(2));
assert_eq!(good.latest_checkpointed_entry_seq, 6);
assert_eq!(good.uncheckpointed_end_seq, None);
let _ = fs::remove_file(good_path);
let bad_path = unique_db_path("chio-flush-disconnected-ckpt");
let bad_store = SqliteReceiptStore::open(&bad_path)?;
for i in 0..6 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-disc-{i}"), (i + 1) as u64, &keypair);
bad_store.append_chio_receipt_returning_seq(&receipt)?;
}
bad_store.flush_receipt_writes()?;
bad_store.create_next_receipt_checkpoint(3, &keypair)?; let checkpoint_one = bad_store
.load_checkpoint_by_seq(1)?
.ok_or("checkpoint 1 missing")?;
let range_bytes = canonical_receipt_bytes(&bad_store, 4, 6);
let mut disconnected_two = build_checkpoint_with_previous(
2,
4,
6,
&range_bytes,
&keypair,
Some(&checkpoint_one),
&[chio_kernel::checkpoint::checkpoint_chain_leaf_hash(
&checkpoint_one.body,
)?],
)?;
disconnected_two.body.previous_checkpoint_sha256 = Some("00".repeat(32));
let body_bytes = canonical_json_bytes(&disconnected_two.body).test_unwrap();
disconnected_two.signature = keypair.sign(&body_bytes);
insert_checkpoint_row(
&bad_store,
&disconnected_two,
disconnected_two.body.batch_end_seq,
);
let report = bad_store.flush_receipt_writes()?;
assert_ne!(
report.latest_checkpointed_entry_seq, 6,
"a chain-disconnected latest checkpoint must not be reported as checkpointed progress"
);
assert_eq!(
report.latest_checkpointed_entry_seq, 3,
"the report must fall back to the last chain-connected checkpoint (cp1)"
);
assert_eq!(report.latest_checkpoint_seq, Some(1));
assert_eq!(report.uncheckpointed_end_seq, Some(6));
let _ = fs::remove_file(bad_path);
Ok(())
}
#[test]
fn flush_report_rejects_checkpoint_foreign_to_local_claim_log(
) -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-flush-foreign-claim-log-ckpt");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
for i in 0..3 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-foreign-{i}"), (i + 1) as u64, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
let foreign_leaves: Vec<Vec<u8>> = (0..6)
.map(|i| format!("foreign-leaf-{i}").into_bytes())
.collect();
let foreign = build_checkpoint(1, 1, 6, &foreign_leaves, &keypair)?;
assert_eq!(foreign.body.batch_end_seq, 6);
assert_eq!(foreign.body.tree_size, 6);
insert_checkpoint_row(&store, &foreign, foreign.body.batch_end_seq);
let report = store.flush_receipt_writes()?;
assert_ne!(
report.latest_checkpointed_entry_seq, 6,
"a checkpoint foreign to the local claim log must not be reported as checkpointed progress"
);
assert_eq!(report.latest_checkpointed_entry_seq, 0);
assert_eq!(report.latest_checkpoint_seq, None);
assert_eq!(report.uncheckpointed_start_seq, Some(1));
assert_eq!(report.uncheckpointed_end_seq, Some(3));
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn chain_connected_rejects_missing_earlier_checkpoint() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-chain-connected-gap");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
for i in 0..4 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-gap-{i}"), (i + 1) as u64, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
for _ in 0..4 {
store.create_next_receipt_checkpoint(1, &keypair)?;
}
let latest = store
.load_checkpoint_by_seq(4)?
.ok_or("checkpoint 4 missing")?;
let connection = store.connection()?;
latest_checkpoint_is_chain_connected(&connection, &latest)?;
connection.execute_batch(
"PRAGMA foreign_keys = OFF; DROP TRIGGER IF EXISTS kernel_checkpoints_reject_delete;",
)?;
let deleted = connection.execute(
"DELETE FROM kernel_checkpoints WHERE checkpoint_seq = 2",
[],
)?;
assert_eq!(deleted, 1, "expected to remove exactly one checkpoint row");
let error = latest_checkpoint_is_chain_connected(&connection, &latest)
.err()
.ok_or("a chain with a missing earlier checkpoint must be rejected")?;
match &error {
ReceiptStoreError::Conflict(message) => {
assert!(
message.contains("prefix is incomplete") && message.contains("chio receipt audit"),
"Conflict must name the incomplete prefix and the audit CLI, got: {message}"
);
}
other => return Err(format!("expected Conflict, got {other}").into()),
}
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn operator_checkpoint_append_reverifies_chain() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-operator-reverify-chain");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
for i in 0..4 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-op-{i}"), (i + 1) as u64, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
store.create_next_receipt_checkpoint(2, &keypair)?;
store.create_next_receipt_checkpoint(2, &keypair)?;
for i in 4..6 {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-op-{i}"), (i + 1) as u64, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
let checkpoint_two = store
.load_checkpoint_by_seq(2)?
.ok_or("checkpoint 2 must exist")?;
let range_bytes = canonical_receipt_bytes(&store, 5, 6);
let checkpoint_one = store
.load_checkpoint_by_seq(1)?
.ok_or("checkpoint 1 must exist")?;
let checkpoint_three = build_checkpoint_with_previous(
3,
5,
6,
&range_bytes,
&keypair,
Some(&checkpoint_two),
&[
chio_kernel::checkpoint::checkpoint_chain_leaf_hash(&checkpoint_one.body)?,
chio_kernel::checkpoint::checkpoint_chain_leaf_hash(&checkpoint_two.body)?,
],
)?;
let connection = store.connection()?;
connection.execute_batch("DROP TRIGGER IF EXISTS kernel_checkpoints_reject_update;")?;
connection.execute(
"UPDATE kernel_checkpoints SET statement_json = replace(statement_json, '\"batch_end_seq\":2', '\"batch_end_seq\":1') WHERE checkpoint_seq = 1",
[],
)?;
drop(connection);
let error = store
.store_checkpoint(&checkpoint_three)
.err()
.ok_or("operator checkpoint append must fail closed on a mid-chain tamper")?;
assert!(
matches!(error, ReceiptStoreError::Conflict(_)),
"expected a fail-closed Conflict, got {error:?}"
);
assert!(
store.load_checkpoint_by_seq(3)?.is_none(),
"the corrupt chain must not be extended with checkpoint 3"
);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn concurrent_valid_checkpoint_is_adopted_not_conflicted() -> Result<(), Box<dyn std::error::Error>>
{
let path = unique_db_path("chio-bg-adopt-valid-winner");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 3;
for i in 0..max_batch {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-adopt-valid-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
store.create_next_receipt_checkpoint(max_batch, &keypair)?;
let winner = store
.load_checkpoint_by_seq(1)?
.ok_or("winner checkpoint 1 missing")?;
let receipt_bytes = canonical_receipt_bytes(&store, 1, max_batch);
let mut loser = build_checkpoint(1, 1, max_batch, &receipt_bytes, &keypair)?;
loser.body.issued_at = winner.body.issued_at.saturating_add(1_000);
let body_bytes = canonical_json_bytes(&loser.body).test_unwrap();
loser.signature = keypair.sign(&body_bytes);
assert_ne!(loser.body.issued_at, winner.body.issued_at);
assert_ne!(loser, winner);
let adopted = {
let mut connection = store.connection()?;
let mut tx =
connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
let savepoint = tx.savepoint()?;
let adopted = insert_checkpoint_incremental_tx(&savepoint, None, &loser)?;
savepoint.commit()?;
tx.commit()?;
adopted
};
assert_eq!(
adopted, winner,
"the loser must adopt the persisted winner, not its own clock-skewed build"
);
let status = store.receipt_checkpoint_status(Some(max_batch))?;
assert!(
status.healthy,
"adopting a valid clock-skew winner must stay healthy: {status:?}"
);
assert_eq!(status.latest_checkpoint_seq, Some(1));
assert!(
store.load_checkpoint_by_seq(2)?.is_none(),
"adoption must not add a second checkpoint row"
);
let _ = fs::remove_file(path);
let bad_path = unique_db_path("chio-bg-adopt-invalid-winner");
let bad_store = SqliteReceiptStore::open(&bad_path)?;
for i in 0..max_batch {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-adopt-invalid-{i}"), i + 1, &keypair);
bad_store.append_chio_receipt_returning_seq(&receipt)?;
}
bad_store.flush_receipt_writes()?;
let bogus_bytes = vec![
b"bogus-receipt-a".to_vec(),
b"bogus-receipt-b".to_vec(),
b"bogus-receipt-c".to_vec(),
];
let forged = build_checkpoint(1, 1, max_batch, &bogus_bytes, &keypair)?;
assert_ne!(forged.body.merkle_root, winner.body.merkle_root);
insert_checkpoint_row(&bad_store, &forged, forged.body.batch_end_seq);
let good_bytes = canonical_receipt_bytes(&bad_store, 1, max_batch);
let good = build_checkpoint(1, 1, max_batch, &good_bytes, &keypair)?;
let mut connection = bad_store.connection()?;
let mut tx = connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
let savepoint = tx.savepoint()?;
let error = insert_checkpoint_incremental_tx(&savepoint, None, &good)
.err()
.ok_or("an invalid persisted checkpoint at the same seq must fail closed")?;
assert!(
matches!(error, ReceiptStoreError::Conflict(_)),
"expected a fail-closed Conflict on an invalid same-seq winner, got {error:?}"
);
drop(savepoint);
drop(tx);
drop(connection);
let _ = fs::remove_file(bad_path);
Ok(())
}
#[test]
fn frontier_rebuild_adopts_concurrent_checkpoint_winner() -> Result<(), Box<dyn std::error::Error>>
{
let (temp_dir, path) = temp_db("chio-bg-frontier-race")?;
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 2;
for i in 0..(max_batch * 2) {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-frontier-race-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
let connection = store.connection()?;
let mut stale_head = seed_verified_head(&connection)?;
assert_eq!(stale_head.checkpoint_seq(), 0);
assert_eq!(
stale_head
.chain_frontier
.as_ref()
.map(CheckpointChainFrontier::leaf_count),
Some(0),
"startup must retain the verified empty frontier"
);
stale_head.chain_frontier = None;
assert!(stale_head.chain_frontier.is_none());
drop(connection);
store.create_next_receipt_checkpoint(max_batch, &keypair)?;
let winner = store
.load_checkpoint_by_seq(1)?
.ok_or("concurrent checkpoint winner missing")?;
let mut connection = store.connection()?;
let advanced = maybe_build_checkpoint(
&mut connection,
&mut stale_head,
&signer(&keypair, max_batch),
)?;
assert!(
advanced,
"winner adoption and catch-up must report progress"
);
assert_eq!(
stale_head.latest_checkpoint.as_ref(),
store.load_checkpoint_by_seq(2)?.as_ref(),
"the cached head must continue through the second owed checkpoint"
);
assert_eq!(
stale_head
.chain_frontier
.as_ref()
.map(CheckpointChainFrontier::leaf_count),
Some(2),
"the reused frontier must cover the adopted winner and its successor"
);
assert_eq!(
store.load_checkpoint_by_seq(1)?.as_ref(),
Some(&winner),
"catch-up must preserve the concurrently committed winner"
);
verify_checkpoint_chain_integrity(&connection)?;
drop(connection);
drop(store);
temp_dir.close()?;
Ok(())
}
#[test]
fn frontier_cache_miss_rejects_disconnected_legacy_prefix_after_failed_build(
) -> Result<(), Box<dyn std::error::Error>> {
let (temp_dir, path) = temp_db("chio-bg-frontier-cache-loss")?;
let keypair = receipt_test_keypair();
let wrong_keypair = Keypair::from_seed(&[0x43; 32]);
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 2;
for i in 0..(max_batch * 3) {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-frontier-cache-loss-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
let mut checkpoint_one = build_checkpoint(
1,
1,
max_batch,
&canonical_receipt_bytes(&store, 1, max_batch),
&keypair,
)?;
checkpoint_one.body.schema = chio_kernel::checkpoint::CHECKPOINT_SCHEMA_V1.to_string();
checkpoint_one.body.chain_root = None;
checkpoint_one.signature = keypair.sign(&canonical_json_bytes(&checkpoint_one.body)?);
insert_checkpoint_row(&store, &checkpoint_one, checkpoint_one.body.batch_end_seq);
let checkpoint_one_leaf =
chio_kernel::checkpoint::checkpoint_chain_leaf_hash(&checkpoint_one.body)?;
let mut checkpoint_two = build_checkpoint_with_previous(
2,
max_batch + 1,
max_batch * 2,
&canonical_receipt_bytes(&store, max_batch + 1, max_batch * 2),
&keypair,
Some(&checkpoint_one),
&[checkpoint_one_leaf],
)?;
checkpoint_two.body.schema = chio_kernel::checkpoint::CHECKPOINT_SCHEMA_V1.to_string();
checkpoint_two.body.chain_root = None;
checkpoint_two.signature = keypair.sign(&canonical_json_bytes(&checkpoint_two.body)?);
insert_checkpoint_row(&store, &checkpoint_two, checkpoint_two.body.batch_end_seq);
let mut connection = store.connection()?;
let mut head = seed_verified_head(&connection)?;
assert_eq!(head.latest_checkpoint.as_ref(), Some(&checkpoint_two));
assert_eq!(
head.chain_frontier
.as_ref()
.map(CheckpointChainFrontier::leaf_count),
Some(2),
"seeding must retain the fully verified legacy frontier"
);
let error = maybe_build_checkpoint(
&mut connection,
&mut head,
&signer(&wrong_keypair, max_batch),
)
.err()
.ok_or("the mismatched signer must fail after consuming the cached frontier")?;
assert!(
matches!(error, ReceiptStoreError::Conflict(_))
&& error
.to_string()
.contains("does not match receipt signer key"),
"expected the mismatched signer to fail closed, got {error:?}"
);
assert_eq!(
head.chain_frontier
.as_ref()
.map(CheckpointChainFrontier::leaf_count),
Some(2),
"the failed build must not mutate the live verified frontier"
);
assert!(
store.load_checkpoint_by_seq(3)?.is_none(),
"the failed build must not persist its candidate"
);
head.chain_frontier = None;
let mut replacement = checkpoint_one.clone();
replacement.body.issued_at = checkpoint_one
.body
.issued_at
.checked_sub(1)
.ok_or("checkpoint timestamp must permit a distinct predecessor")?;
replacement.signature = keypair.sign(&canonical_json_bytes(&replacement.body)?);
chio_kernel::checkpoint::validate_checkpoint(&replacement)?;
assert!(
chio_kernel::checkpoint::validate_checkpoint_predecessor(&replacement, &checkpoint_two)
.is_err(),
"the retained checkpoint 2 must be disconnected from the replacement"
);
let replacement_json = serde_json::to_string(&replacement.body)?;
let replacement_signature = replacement.signature.to_hex();
connection.execute_batch(
r#"
DROP TRIGGER IF EXISTS kernel_checkpoints_reject_update;
DROP TRIGGER IF EXISTS checkpoint_tree_heads_reject_update;
DROP TRIGGER IF EXISTS checkpoint_predecessor_witnesses_reject_update;
DROP TRIGGER IF EXISTS checkpoint_publication_metadata_reject_update;
"#,
)?;
assert_eq!(
connection.execute(
"UPDATE kernel_checkpoints
SET issued_at = ?1, statement_json = ?2, signature = ?3
WHERE checkpoint_seq = 1",
rusqlite::params![
replacement.body.issued_at as i64,
replacement_json,
replacement_signature,
],
)?,
1
);
assert_eq!(
connection.execute(
"UPDATE checkpoint_tree_heads
SET issued_at = ?1, statement_json = ?2, signature = ?3
WHERE checkpoint_seq = 1",
rusqlite::params![
replacement.body.issued_at as i64,
replacement_json,
replacement_signature,
],
)?,
1
);
assert_eq!(
connection.execute(
"UPDATE checkpoint_publication_metadata
SET published_at = ?1
WHERE checkpoint_seq = 1",
rusqlite::params![replacement.body.issued_at as i64],
)?,
1
);
let error = maybe_build_checkpoint(&mut connection, &mut head, &signer(&keypair, max_batch))
.err()
.ok_or("a cache-miss retry must reject the disconnected legacy prefix")?;
assert!(
matches!(error, ReceiptStoreError::Conflict(_))
&& error
.to_string()
.contains("does not match predecessor digest"),
"expected full predecessor verification to reject the retry, got {error:?}"
);
assert!(
load_persisted_checkpoint_row(&connection, 3)?.is_none(),
"a disconnected legacy prefix must not gain a v2 successor"
);
let mut replacement_two = checkpoint_two.clone();
replacement_two.body.previous_checkpoint_sha256 = Some(
chio_kernel::checkpoint::checkpoint_body_sha256(&replacement.body)?,
);
replacement_two.signature = keypair.sign(&canonical_json_bytes(&replacement_two.body)?);
chio_kernel::checkpoint::validate_checkpoint_predecessor(&replacement, &replacement_two)?;
let replacement_two_json = serde_json::to_string(&replacement_two.body)?;
let replacement_two_signature = replacement_two.signature.to_hex();
let replacement_predecessor = replacement_two
.body
.previous_checkpoint_sha256
.as_deref()
.ok_or("replacement checkpoint 2 must retain its predecessor digest")?;
connection.execute_batch(
r#"
DROP TRIGGER IF EXISTS kernel_checkpoints_reject_update;
DROP TRIGGER IF EXISTS checkpoint_tree_heads_reject_update;
DROP TRIGGER IF EXISTS checkpoint_predecessor_witnesses_reject_update;
DROP TRIGGER IF EXISTS checkpoint_publication_metadata_reject_update;
"#,
)?;
assert_eq!(
connection.execute(
"UPDATE kernel_checkpoints
SET statement_json = ?1, signature = ?2
WHERE checkpoint_seq = 2",
rusqlite::params![replacement_two_json, replacement_two_signature],
)?,
1
);
assert_eq!(
connection.execute(
"UPDATE checkpoint_tree_heads
SET previous_checkpoint_sha256 = ?1, statement_json = ?2, signature = ?3
WHERE checkpoint_seq = 2",
rusqlite::params![
replacement_predecessor,
replacement_two_json,
replacement_two_signature,
],
)?,
1
);
assert_eq!(
connection.execute(
"UPDATE checkpoint_predecessor_witnesses
SET previous_checkpoint_sha256 = ?1, witness_statement_json = ?2
WHERE witness_checkpoint_seq = 2",
rusqlite::params![replacement_predecessor, replacement_two_json],
)?,
1
);
assert_eq!(
connection.execute(
"UPDATE checkpoint_publication_metadata
SET previous_checkpoint_sha256 = ?1
WHERE checkpoint_seq = 2",
rusqlite::params![replacement_predecessor],
)?,
1
);
let error = maybe_build_checkpoint(&mut connection, &mut head, &signer(&keypair, max_batch))
.err()
.ok_or("a cache-miss retry must reject a coherent same-length fork")?;
assert!(
matches!(error, ReceiptStoreError::Conflict(_))
&& error
.to_string()
.contains("diverged from the verified head"),
"expected same-length head reconciliation to reject the retry, got {error:?}"
);
assert!(
load_persisted_checkpoint_row(&connection, 3)?.is_none(),
"a coherent replacement prefix must not be joined to the stale head"
);
ensure_checkpoint_transparency_guards(&connection)?;
ensure_transparency_projection_guards(&connection)?;
drop(connection);
drop(store);
temp_dir.close()?;
Ok(())
}
#[test]
fn checkpoint_insert_rejects_prefix_replaced_after_frontier_audit(
) -> Result<(), Box<dyn std::error::Error>> {
let (temp_dir, path) = temp_db("chio-bg-frontier-insert-race")?;
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 2;
for i in 0..(max_batch * 2) {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-frontier-insert-race-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
let mut checkpoint_one = build_checkpoint(
1,
1,
max_batch,
&canonical_receipt_bytes(&store, 1, max_batch),
&keypair,
)?;
checkpoint_one.body.schema = chio_kernel::checkpoint::CHECKPOINT_SCHEMA_V1.to_string();
checkpoint_one.body.chain_root = None;
checkpoint_one.signature = keypair.sign(&canonical_json_bytes(&checkpoint_one.body)?);
insert_checkpoint_row(&store, &checkpoint_one, checkpoint_one.body.batch_end_seq);
let mut builder_connection = store.connection()?;
let old_frontier = rebuild_checkpoint_frontier(&mut builder_connection, Some(&checkpoint_one))?;
let candidate = chio_kernel::checkpoint::build_checkpoint_with_chain_frontier(
2,
max_batch + 1,
max_batch * 2,
&canonical_receipt_bytes(&store, max_batch + 1, max_batch * 2),
&keypair,
Some(&checkpoint_one),
&old_frontier,
)?;
let mut replacement_one = checkpoint_one.clone();
replacement_one.body.issued_at = checkpoint_one
.body
.issued_at
.checked_sub(1)
.ok_or("checkpoint timestamp must permit a distinct predecessor")?;
replacement_one.signature = keypair.sign(&canonical_json_bytes(&replacement_one.body)?);
chio_kernel::checkpoint::validate_checkpoint(&replacement_one)?;
let replacement_one_json = serde_json::to_string(&replacement_one.body)?;
let replacement_one_signature = replacement_one.signature.to_hex();
let peer = store.connection()?;
peer.execute_batch(
r#"
DROP TRIGGER IF EXISTS kernel_checkpoints_reject_update;
DROP TRIGGER IF EXISTS checkpoint_tree_heads_reject_update;
DROP TRIGGER IF EXISTS checkpoint_publication_metadata_reject_update;
"#,
)?;
assert_eq!(
peer.execute(
"UPDATE kernel_checkpoints
SET issued_at = ?1, statement_json = ?2, signature = ?3
WHERE checkpoint_seq = 1",
rusqlite::params![
replacement_one.body.issued_at as i64,
replacement_one_json,
replacement_one_signature,
],
)?,
1
);
assert_eq!(
peer.execute(
"UPDATE checkpoint_tree_heads
SET issued_at = ?1, statement_json = ?2, signature = ?3
WHERE checkpoint_seq = 1",
rusqlite::params![
replacement_one.body.issued_at as i64,
replacement_one_json,
replacement_one_signature,
],
)?,
1
);
assert_eq!(
peer.execute(
"UPDATE checkpoint_publication_metadata
SET published_at = ?1
WHERE checkpoint_seq = 1",
rusqlite::params![replacement_one.body.issued_at as i64],
)?,
1
);
ensure_checkpoint_transparency_guards(&peer)?;
ensure_transparency_projection_guards(&peer)?;
assert_eq!(
verify_checkpoint_chain_integrity(&peer)?.as_ref(),
Some(&replacement_one),
"the peer replacement must be a coherent persisted prefix"
);
drop(peer);
let mut tx =
builder_connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
let savepoint = tx.savepoint()?;
let error = insert_checkpoint_incremental_tx(&savepoint, Some(&checkpoint_one), &candidate)
.err()
.ok_or("a stale candidate must not extend the replacement prefix")?;
assert!(
matches!(error, ReceiptStoreError::Conflict(_))
&& error
.to_string()
.contains("cached predecessor 1 changed before persistence"),
"expected a fail-closed predecessor snapshot conflict, got {error:?}"
);
assert!(
load_persisted_checkpoint_row(&savepoint, 2)?.is_none(),
"the replacement prefix must not gain a disconnected successor"
);
drop(savepoint);
drop(tx);
assert!(
store.load_checkpoint_by_seq(2)?.is_none(),
"the failed transaction must leave checkpoint 2 absent"
);
drop(builder_connection);
drop(store);
temp_dir.close()?;
Ok(())
}
#[test]
fn install_signer_builds_owed_checkpoints() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-install-owed-ckpt");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 3;
for i in 0..max_batch {
let receipt = sample_receipt_with_keypair(&format!("rcpt-install-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"no checkpoint before the signer is installed"
);
store.enable_background_checkpoints(signer(&keypair, max_batch))?;
store.flush_receipt_writes()?;
let checkpoint = store
.load_checkpoint_by_seq(1)?
.ok_or("InstallSigner must build the owed checkpoint at install time")?;
assert_eq!(
(
checkpoint.body.batch_start_seq,
checkpoint.body.batch_end_seq
),
(1, 3)
);
let status = store.receipt_checkpoint_status(Some(max_batch))?;
assert!(status.healthy, "audit after install-time build: {status:?}");
assert_eq!(status.latest_checkpoint_seq, Some(1));
assert_eq!(status.latest_checkpointed_entry_seq, 3);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn install_catch_up_respects_deferred_validation() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-install-deferred");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open_with_options(
&path,
crate::SqliteStoreOptions {
incremental_verification: false,
..crate::SqliteStoreOptions::default()
},
)?;
let max_batch = 3;
for i in 0..max_batch {
let receipt = sample_receipt_with_keypair(&format!("rcpt-deferred-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
store.enable_background_checkpoints(signer(&keypair, max_batch))?;
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"deferred-seed mode must not checkpoint the unvalidated range at install"
);
let receipt = sample_receipt_with_keypair("rcpt-deferred-tail", max_batch + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
store.flush_receipt_writes()?;
let checkpoint = store
.load_checkpoint_by_seq(1)?
.ok_or("the deferred catch-up must build once the full validation has run")?;
assert_eq!(
(
checkpoint.body.batch_start_seq,
checkpoint.body.batch_end_seq
),
(1, 3)
);
let _ = fs::remove_file(path);
Ok(())
}
fn co_drain_flush_with_appends(
store: &SqliteReceiptStore,
keypair: &Keypair,
n: u64,
id_prefix: &str,
) -> Result<Result<(), ReceiptStoreError>, Box<dyn std::error::Error>> {
let sender = store.receipt_commit_actor.sender.clone();
let handle = store.writer_handle();
let (started_tx, started_rx) = std::sync::mpsc::channel::<()>();
let (release_tx, release_rx) = std::sync::mpsc::channel::<()>();
let job = std::thread::spawn(move || {
handle.run_write(move |_connection| -> Result<(), ReceiptStoreError> {
let _ = started_tx.send(());
let _ = release_rx.recv();
Ok(())
})
});
started_rx.recv()?;
let mut append_receivers = Vec::new();
for i in 0..n {
let receipt = sample_receipt_with_keypair(&format!("{id_prefix}-{i}"), i + 1, keypair);
let raw_json = serde_json::to_string(&receipt)?;
let (response, receiver) = std::sync::mpsc::sync_channel(1);
sender
.try_send(ReceiptCommitCommand::Append(Box::new(
ReceiptCommitRequest {
receipt,
raw_json,
ensure_lineage: false,
response,
},
)))
.map_err(|_| "failed to enqueue append")?;
append_receivers.push(receiver);
}
let (flush_response, flush_receiver) = std::sync::mpsc::sync_channel(1);
sender
.try_send(ReceiptCommitCommand::Flush(flush_response))
.map_err(|_| "failed to enqueue flush")?;
release_tx.send(())?;
job.join().map_err(|_| "write job thread panicked")??;
let flush_result = flush_receiver.recv()?;
drop(append_receivers);
Ok(flush_result)
}
#[test]
fn flush_is_a_checkpoint_barrier() -> Result<(), Box<dyn std::error::Error>> {
let keypair = receipt_test_keypair();
let path = unique_db_path("chio-flush-barrier");
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 3;
store.enable_background_checkpoints(signer(&keypair, max_batch))?;
store.flush_receipt_writes()?;
let flush_result = co_drain_flush_with_appends(&store, &keypair, max_batch, "rcpt-barrier")?;
assert!(
flush_result.is_ok(),
"the co-drained flush must succeed: {flush_result:?}"
);
assert!(
store.load_checkpoint_by_seq(1)?.is_some(),
"flush must not return until the co-drained batch's checkpoint is built"
);
let _ = fs::remove_file(path);
let fail_path = unique_db_path("chio-flush-barrier-fail");
let fail_store = SqliteReceiptStore::open(&fail_path)?;
let fail_batch = test_hooks::FAIL_CHECKPOINT_BUILD_MARKER_MAX_BATCH;
fail_store.enable_background_checkpoints(signer(&keypair, fail_batch))?;
fail_store.flush_receipt_writes()?;
test_hooks::FAIL_CHECKPOINT_BUILD.store(true, std::sync::atomic::Ordering::SeqCst);
let flush_result =
co_drain_flush_with_appends(&fail_store, &keypair, fail_batch, "rcpt-barrier-fail")?;
test_hooks::FAIL_CHECKPOINT_BUILD.store(false, std::sync::atomic::Ordering::SeqCst);
let error = flush_result
.err()
.ok_or("a checkpoint-build failure must surface to the flush caller")?;
assert!(
matches!(error, ReceiptStoreError::Conflict(_)),
"flush must receive the build error, got {error:?}"
);
assert!(
fail_store.load_checkpoint_by_seq(1)?.is_none(),
"the failed build must not persist a checkpoint"
);
let _ = fs::remove_file(fail_path);
Ok(())
}
#[test]
fn reseed_builds_owed_checkpoints_after_repair() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-reseed-owed-ckpt");
let keypair = receipt_test_keypair();
let max_batch = 3;
let store = SqliteReceiptStore::open(&path)?;
let mut first_id = String::new();
for i in 0..max_batch {
let receipt =
sample_receipt_with_keypair(&format!("rcpt-reseed-owed-{i}"), i + 1, &keypair);
if i == 0 {
first_id = receipt.id.clone();
}
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"no checkpoint before a signer is installed"
);
let original_raw_json = {
let connection = store.connection()?;
connection
.execute_batch("DROP TRIGGER IF EXISTS claim_receipt_log_entries_reject_update;")?;
let original: String = connection.query_row(
"SELECT raw_json FROM claim_receipt_log_entries WHERE receipt_id = ?1 AND receipt_kind = 'tool_receipt'",
rusqlite::params![first_id],
|row| row.get(0),
)?;
let mut tampered: ChioReceipt = serde_json::from_str(&original)?;
tampered.tool_name = "tampered".to_string();
let tampered_json = serde_json::to_string(&tampered)?;
connection.execute(
"UPDATE claim_receipt_log_entries SET raw_json = ?1 WHERE receipt_id = ?2 AND receipt_kind = 'tool_receipt'",
rusqlite::params![tampered_json, first_id],
)?;
original
};
assert!(
store.reseed_verified_head().is_err(),
"a reseed over a corrupt log must fail closed"
);
store.enable_background_checkpoints(signer(&keypair, max_batch))?;
let _ = store.flush_receipt_writes();
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"no checkpoint may be built while the head is poisoned"
);
{
let connection = store.connection()?;
connection
.execute_batch("DROP TRIGGER IF EXISTS claim_receipt_log_entries_reject_update;")?;
connection.execute(
"UPDATE claim_receipt_log_entries SET raw_json = ?1 WHERE receipt_id = ?2 AND receipt_kind = 'tool_receipt'",
rusqlite::params![original_raw_json, first_id],
)?;
}
store.reseed_verified_head()?;
let checkpoint = store
.load_checkpoint_by_seq(1)?
.ok_or("reseed must build the owed checkpoint without a further append")?;
assert_eq!(
(
checkpoint.body.batch_start_seq,
checkpoint.body.batch_end_seq
),
(1, 3)
);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn manual_recovery_clears_stale_checkpoint_error() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-manual-recovery-clear-error");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open(&path)?;
let max_batch = 3;
for i in 0..max_batch {
let receipt = sample_receipt_with_keypair(&format!("rcpt-recover-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"a due checkpoint (1..=3) is pending but unbuilt (no signer installed)"
);
if let Ok(mut last_error) = store.receipt_commit_actor.health.last_error.lock() {
*last_error = Some("prior background checkpoint build failed".to_string());
}
let unhealthy = store.receipt_store_health()?;
assert!(
!unhealthy.healthy && unhealthy.writer.last_error.is_some(),
"a recorded background-build error must report the store unhealthy: {unhealthy:?}"
);
store.create_next_receipt_checkpoint(max_batch, &keypair)?;
assert!(
store.load_checkpoint_by_seq(1)?.is_some(),
"the manual recovery must persist the missing checkpoint"
);
let recovered = store.receipt_store_health()?;
assert!(
recovered.writer.last_error.is_none(),
"a successful manual recovery must clear the stale checkpoint error: {recovered:?}"
);
assert!(
recovered.healthy,
"the store must report healthy after recovery: {recovered:?}"
);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn metadata_write_does_not_checkpoint_unvalidated_data() -> Result<(), Box<dyn std::error::Error>> {
let path = unique_db_path("chio-metadata-defer-build");
let keypair = receipt_test_keypair();
let store = SqliteReceiptStore::open_with_options(
&path,
crate::SqliteStoreOptions {
incremental_verification: false,
..crate::SqliteStoreOptions::default()
},
)?;
let max_batch = 3;
for i in 0..max_batch {
let receipt = sample_receipt_with_keypair(&format!("rcpt-meta-defer-{i}"), i + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
}
store.flush_receipt_writes()?;
store.enable_background_checkpoints(signer(&keypair, max_batch))?;
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"deferred-seed mode must not checkpoint at install"
);
store
.writer_handle()
.run_write(move |_connection| -> Result<(), ReceiptStoreError> { Ok(()) })?;
store.flush_receipt_writes()?;
assert!(
store.load_checkpoint_by_seq(1)?.is_none(),
"a metadata-only write must not checkpoint the unvalidated range (fail-closed)"
);
let receipt = sample_receipt_with_keypair("rcpt-meta-defer-tail", max_batch + 1, &keypair);
store.append_chio_receipt_returning_seq(&receipt)?;
store.flush_receipt_writes()?;
let checkpoint = store
.load_checkpoint_by_seq(1)?
.ok_or("a receipt-appending write after full validation must build the owed checkpoint")?;
assert_eq!(
(
checkpoint.body.batch_start_seq,
checkpoint.body.batch_end_seq
),
(1, 3)
);
let _ = fs::remove_file(path);
Ok(())
}