#![cfg(unix)]
use khive_runtime::{KhiveRuntime, Namespace, RuntimeConfig};
use khive_storage::types::SqlStatement;
use serial_test::serial;
fn file_backed_config(db_path: std::path::PathBuf) -> RuntimeConfig {
RuntimeConfig {
db_path: Some(db_path),
embedding_model: None,
additional_embedding_models: vec![],
..RuntimeConfig::default()
}
}
fn run_one_cold_boot(db_path: std::path::PathBuf, writer_label: &'static str, count: usize) {
let guard =
khive_runtime::daemon::acquire_recovery_lock().expect("acquire recovery/boot guard");
let rt_handle = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("build per-thread tokio runtime");
rt_handle.block_on(async {
let rt =
KhiveRuntime::new(file_backed_config(db_path)).expect("cold-boot migrations succeed");
let token = rt
.authorize(Namespace::local())
.expect("authorize local namespace");
for i in 0..count {
rt.create_note(
&token,
"memo",
None,
&format!("{writer_label} note {i} — cold boot race marker"),
None,
None,
vec![],
)
.await
.expect("note write must succeed inside the guarded boot window");
}
});
drop(guard);
}
fn verify_no_corruption(db_path: std::path::PathBuf, expected_count: usize) {
let rt_handle = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("build verification tokio runtime");
rt_handle.block_on(async {
let verify_rt = KhiveRuntime::new(file_backed_config(db_path))
.expect("post-race runtime opens cleanly");
let token = verify_rt
.authorize(Namespace::local())
.expect("authorize local namespace");
let hits = verify_rt
.search_notes(
&token,
"cold boot race marker",
None,
100,
None,
false,
&[],
None,
)
.await
.expect("FTS search over notes must succeed, not error on a corrupted index");
assert_eq!(
hits.len(),
expected_count,
"every planted note must be present and FTS-searchable — a \
corrupted/partial index would drop or duplicate rows: {hits:?}"
);
let sql = verify_rt.sql();
let mut reader = sql.reader().await.expect("sql reader");
let integrity = reader
.query_scalar(SqlStatement {
sql: "PRAGMA integrity_check".into(),
params: vec![],
label: Some("cold_boot_race_integrity_check".into()),
})
.await
.expect("PRAGMA integrity_check must run")
.expect("integrity_check returns a row");
let integrity_text = format!("{integrity:?}");
assert!(
integrity_text.to_lowercase().contains("ok"),
"sqlite integrity_check must report ok after the cold-boot race, \
got: {integrity_text}"
);
});
}
#[test]
#[serial]
fn concurrent_cold_boots_do_not_corrupt_notes_fts_index() {
let dir = tempfile::tempdir().expect("tempdir");
let lock_file = dir.path().join("khived.recovery.lock");
std::env::set_var("KHIVE_LOCK", &lock_file);
let db_path = dir.path().join("cold_boot_race.db3");
const PER_WRITER: usize = 10;
let path_a = db_path.clone();
let path_b = db_path.clone();
let t_a = std::thread::spawn(move || run_one_cold_boot(path_a, "writer-a", PER_WRITER));
let t_b = std::thread::spawn(move || run_one_cold_boot(path_b, "writer-b", PER_WRITER));
t_a.join().expect("boot thread A must not panic");
t_b.join().expect("boot thread B must not panic");
verify_no_corruption(db_path, PER_WRITER * 2);
std::env::remove_var("KHIVE_LOCK");
}
#[test]
#[serial]
fn sequential_cold_boots_against_same_file_are_idempotent() {
let dir = tempfile::tempdir().expect("tempdir");
let lock_file = dir.path().join("khived.recovery.lock");
std::env::set_var("KHIVE_LOCK", &lock_file);
let db_path = dir.path().join("sequential_boot.db3");
run_one_cold_boot(db_path.clone(), "first-boot", 3);
run_one_cold_boot(db_path.clone(), "second-boot", 2);
verify_no_corruption(db_path, 5);
std::env::remove_var("KHIVE_LOCK");
}