use std::path::{Path, PathBuf};
use leviath_core::run_archive;
use leviath_core::run_meta::{ContextSnapshot, RunMeta, StageRecord};
use tokio::io::AsyncWriteExt;
use tokio::sync::mpsc::UnboundedReceiver;
pub struct PersistJob {
pub run_id: String,
pub meta: RunMeta,
pub context: ContextSnapshot,
pub stages: Vec<StageRecord>,
pub output_appends: Vec<(usize, String)>,
pub log_appends: Vec<(usize, String)>,
pub taint_audit: Option<(usize, String)>,
pub final_output: Option<String>,
pub fanout: Option<String>,
pub interactions: Option<String>,
}
pub enum PersistMsg {
Snapshot(Box<PersistJob>),
Append {
run_id: String,
record: Box<leviath_core::run_archive::RunRecord>,
ack: Option<tokio::sync::oneshot::Sender<()>>,
},
StageLines {
run_id: String,
output_appends: Vec<(usize, String)>,
log_appends: Vec<(usize, String)>,
},
}
pub async fn persistence_worker(
runs_dir: Option<PathBuf>,
mut jobs: UnboundedReceiver<PersistMsg>,
) {
let Some(runs_dir) = runs_dir else {
while let Some(msg) = jobs.recv().await {
if let PersistMsg::Append { ack: Some(ack), .. } = msg {
let _ = ack.send(());
}
}
return;
};
let machine_id = load_or_create_machine_id(&runs_dir);
let world_id = generate_id();
let mut last_output: std::collections::HashMap<String, (i64, usize)> =
std::collections::HashMap::new();
let mut last_context: std::collections::HashMap<String, run_archive::ContextDigest> =
std::collections::HashMap::new();
while let Some(first) = jobs.recv().await {
let mut batch = vec![first];
while let Ok(msg) = jobs.try_recv() {
batch.push(msg);
}
let mut newest_snapshot: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for (i, msg) in batch.iter().enumerate() {
if let PersistMsg::Snapshot(job) = msg {
newest_snapshot.insert(job.run_id.clone(), i);
}
}
for (i, msg) in batch.into_iter().enumerate() {
match msg {
PersistMsg::Snapshot(job) => {
if newest_snapshot.get(job.run_id.as_str()) != Some(&i) {
continue; }
let prev = last_context.get(&job.run_id);
let written = last_output.get(&job.run_id).copied();
if let Some(key) =
write_snapshot(&runs_dir, &job, &machine_id, &world_id, prev, written).await
{
last_output.insert(job.run_id.clone(), key);
}
if is_terminal_run(&job.meta.status) {
last_context.remove(&job.run_id);
last_output.remove(&job.run_id);
} else {
last_context.insert(
job.run_id.clone(),
run_archive::digest_context(&job.context),
);
}
}
PersistMsg::Append {
run_id,
record,
ack,
} => {
append_record(&runs_dir, &run_id, &record).await;
if let Some(ack) = ack {
let _ = ack.send(());
}
}
PersistMsg::StageLines {
run_id,
output_appends,
log_appends,
} => {
let dir = runs_dir.join(&run_id);
for (idx, line) in &output_appends {
append_stage_line(&dir, *idx, "output.log", line, &run_id).await;
}
for (idx, line) in &log_appends {
append_stage_line(&dir, *idx, "logs.log", line, &run_id).await;
}
}
}
}
}
}
async fn create_private_dir(path: &Path) -> std::io::Result<()> {
let owned = path.to_path_buf();
tokio::task::spawn_blocking(move || leviath_sys::create_private_dir_all(&owned))
.await
.map_err(vanished_task)
.and_then(|r| r)
}
fn vanished_task(e: tokio::task::JoinError) -> std::io::Error {
std::io::Error::other(e.to_string())
}
async fn open_private_append(path: &Path) -> std::io::Result<tokio::fs::File> {
let owned = path.to_path_buf();
tokio::task::spawn_blocking(move || leviath_sys::open_private_append(&owned))
.await
.map_err(vanished_task)
.and_then(|r| r)
.map(tokio::fs::File::from_std)
}
async fn append_record(
runs_dir: &Path,
run_id: &str,
record: &leviath_core::run_archive::RunRecord,
) {
let path = runs_dir.join(run_id).join("run.lvr");
if !tokio::fs::try_exists(&path).await.unwrap_or(false) {
tracing::warn!(run_id = %run_id, "persistence: record append skipped, no archive yet");
return;
}
let mut buf: Vec<u8> = Vec::new();
leviath_core::run_archive::write_record(&mut buf, record)
.expect("writing to a Vec never fails");
match open_private_append(&path).await {
Ok(mut file) => {
let _ = file.write_all(&buf).await;
let _ = file.flush().await;
}
Err(e) => {
tracing::warn!(run_id = %run_id, error = %e, "persistence: record append failed");
}
}
}
fn is_terminal_run(status: &leviath_core::run_meta::RunStatus) -> bool {
use leviath_core::run_meta::RunStatus;
matches!(
status,
RunStatus::Complete | RunStatus::Error | RunStatus::Cancelled
)
}
fn generate_id() -> String {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
std::time::SystemTime::now().hash(&mut hasher);
std::process::id().hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
fn load_or_create_machine_id(runs_dir: &Path) -> String {
let path = runs_dir.parent().unwrap_or(runs_dir).join("machine-id");
let existing = std::fs::read_to_string(&path)
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
match existing {
Some(id) => id,
None => {
let id = generate_id();
let _ = std::fs::write(&path, &id);
id
}
}
}
async fn write_snapshot(
runs_dir: &Path,
job: &PersistJob,
machine_id: &str,
world_id: &str,
prev_context: Option<&run_archive::ContextDigest>,
written_output: Option<(i64, usize)>,
) -> Option<(i64, usize)> {
let dir = runs_dir.join(&job.run_id);
if let Err(e) = create_private_dir(&dir).await {
tracing::warn!(run_id = %job.run_id, error = %e, "persistence: create run dir failed");
return None;
}
append_run_archive(&dir, job, machine_id, world_id, prev_context).await;
let meta_json = serde_json::to_string_pretty(&job.meta).expect("RunMeta always serializes");
write_bytes_atomic(&dir.join("meta.json"), meta_json.into_bytes(), &job.run_id).await;
let ctx_json = serde_json::to_string(&job.context).expect("ContextSnapshot always serializes");
write_bytes_atomic(
&dir.join("context.json"),
ctx_json.into_bytes(),
&job.run_id,
)
.await;
let submitted = job
.meta
.final_output
.as_ref()
.map(|d| (d.submitted_at, d.bytes));
let mut wrote_output = None;
if let Some(content) = &job.final_output
&& (submitted.is_none() || written_output != submitted)
{
write_bytes_atomic(
&dir.join(leviath_core::FINAL_OUTPUT_FILE),
content.clone().into_bytes(),
&job.run_id,
)
.await;
wrote_output = submitted;
}
if !job.stages.is_empty() {
let stages_json =
serde_json::to_string_pretty(&job.stages).expect("StageRecord slice always serializes");
write_bytes_atomic(
&dir.join("stages.json"),
stages_json.into_bytes(),
&job.run_id,
)
.await;
}
for (idx, line) in &job.output_appends {
append_stage_line(&dir, *idx, "output.log", line, &job.run_id).await;
}
for (idx, line) in &job.log_appends {
append_stage_line(&dir, *idx, "logs.log", line, &job.run_id).await;
}
if let Some((idx, json)) = &job.taint_audit {
let stage_dir = dir.join("stages").join(idx.to_string());
let _ = create_private_dir(&stage_dir).await;
write_bytes_atomic(
&stage_dir.join("taint_audit.json"),
json.clone().into_bytes(),
&job.run_id,
)
.await;
}
let fanout_path = dir.join("fanout.json");
match &job.fanout {
Some(json) => {
write_bytes_atomic(&fanout_path, json.clone().into_bytes(), &job.run_id).await
}
None => {
let _ = tokio::fs::remove_file(&fanout_path).await;
}
}
let interactions_path = dir.join("interactions.json");
match &job.interactions {
Some(json) => {
write_bytes_atomic(&interactions_path, json.clone().into_bytes(), &job.run_id).await
}
None => {
let _ = tokio::fs::remove_file(&interactions_path).await;
}
}
wrote_output
}
async fn append_run_archive(
dir: &Path,
job: &PersistJob,
machine_id: &str,
world_id: &str,
prev_context: Option<&run_archive::ContextDigest>,
) {
use leviath_core::run_archive::{RunIdentity, RunRecord};
let path = dir.join("run.lvr");
let file_exists = tokio::fs::try_exists(&path).await.unwrap_or(false);
let at = job.meta.updated_at;
let mut buf: Vec<u8> = Vec::new();
match prev_context {
Some(prev) => {
let progress = RunRecord::Progress {
meta: Box::new(job.meta.clone()),
delta: run_archive::diff_context_digest(prev, &job.context),
at,
};
run_archive::write_record(&mut buf, &progress).expect("writing to a Vec never fails");
}
None => {
if file_exists {
let owned = RunRecord::OwnershipChanged {
machine_id: machine_id.to_string(),
world_id: world_id.to_string(),
at,
};
run_archive::write_record(&mut buf, &owned).expect("writing to a Vec never fails");
} else {
run_archive::write_archive_start(&mut buf, run_archive::RUN_ARCHIVE_VERSION)
.expect("writing to a Vec never fails");
let header = RunRecord::Header {
identity: RunIdentity {
run_id: job.run_id.clone(),
machine_id: machine_id.to_string(),
world_id: world_id.to_string(),
created_at: job.meta.started_at,
},
meta: Box::new(job.meta.clone()),
};
run_archive::write_record(&mut buf, &header).expect("writing to a Vec never fails");
}
let checkpoint = RunRecord::ContextCheckpoint {
snapshot: job.context.clone(),
at,
};
run_archive::write_record(&mut buf, &checkpoint).expect("writing to a Vec never fails");
}
}
match open_private_append(&path).await {
Ok(mut file) => {
let _ = file.write_all(&buf).await;
let _ = file.flush().await;
}
Err(e) => {
tracing::warn!(run_id = %job.run_id, error = %e, "persistence: run archive append failed");
}
}
}
async fn append_stage_line(run_dir: &Path, stage_idx: usize, file: &str, line: &str, run_id: &str) {
let stage_dir = run_dir.join("stages").join(stage_idx.to_string());
let _ = create_private_dir(&stage_dir).await;
match open_private_append(&stage_dir.join(file)).await {
Ok(mut handle) => {
let mut bytes = line.as_bytes().to_vec();
bytes.push(b'\n');
let _ = handle.write_all(&bytes).await;
let _ = handle.flush().await;
}
Err(e) => {
tracing::warn!(run_id = %run_id, error = %e, "persistence: stage log open failed");
}
}
}
async fn write_bytes_atomic(path: &Path, bytes: Vec<u8>, run_id: &str) {
let tmp = path.with_extension("json.tmp");
let tmp_for_write = tmp.clone();
let written =
tokio::task::spawn_blocking(move || leviath_sys::write_private(&tmp_for_write, &bytes))
.await;
if let Err(e) = written.map_err(vanished_task).and_then(|r| r) {
tracing::warn!(run_id = %run_id, error = %e, "persistence: temp write failed");
return;
}
if let Err(e) = tokio::fs::rename(&tmp, path).await {
tracing::warn!(run_id = %run_id, error = %e, "persistence: rename failed");
let _ = tokio::fs::remove_file(&tmp).await;
}
}
#[cfg(test)]
mod tests {
use super::*;
use leviath_core::run_meta::RunMeta;
use tokio::sync::mpsc;
fn meta(run_id: &str) -> RunMeta {
RunMeta::new(
run_id.to_string(),
"agent".to_string(),
"/path".to_string(),
"task".to_string(),
None,
"/work".to_string(),
1,
)
}
fn context() -> ContextSnapshot {
ContextSnapshot {
stage_name: "s".to_string(),
total_tokens: 0,
max_tokens: 100,
regions: vec![],
}
}
#[tokio::test]
async fn worker_writes_the_sidecar_even_when_the_first_snapshot_is_coalesced_away() {
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = mpsc::unbounded_channel();
let answered = |body: &str| {
let mut m = meta("run-fast");
m.final_output = Some(leviath_core::output::FinalOutputDescriptor {
format: None,
stage: "out".to_string(),
submitted_at: 100,
bytes: body.len(),
truncated: false,
artifacts: Vec::new(),
});
Box::new(PersistJob {
run_id: "run-fast".to_string(),
meta: m,
context: context(),
stages: vec![],
output_appends: vec![],
log_appends: vec![],
taint_audit: None,
fanout: None,
interactions: None,
final_output: Some(body.to_string()),
})
};
tx.send(PersistMsg::Snapshot(answered("the answer")))
.unwrap();
tx.send(PersistMsg::Snapshot(answered("the answer")))
.unwrap();
drop(tx);
persistence_worker(Some(dir.path().to_path_buf()), rx).await;
let sidecar = dir
.path()
.join("run-fast")
.join(leviath_core::FINAL_OUTPUT_FILE);
assert_eq!(
std::fs::read_to_string(&sidecar).expect("sidecar written"),
"the answer",
"a coalesced-away first snapshot must not lose the answer"
);
let back: RunMeta = serde_json::from_str(
&std::fs::read_to_string(dir.path().join("run-fast").join("meta.json")).unwrap(),
)
.unwrap();
assert!(back.final_output.is_some(), "descriptor written too");
}
#[tokio::test]
async fn worker_writes_meta_and_context_then_exits_on_close() {
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = mpsc::unbounded_channel();
tx.send(PersistMsg::Snapshot(Box::new(PersistJob {
run_id: "run-1".to_string(),
meta: meta("run-1"),
context: context(),
stages: vec![],
output_appends: vec![],
log_appends: vec![],
taint_audit: None,
fanout: None,
interactions: None,
final_output: None,
})))
.unwrap();
drop(tx);
persistence_worker(Some(dir.path().to_path_buf()), rx).await;
let run_dir = dir.path().join("run-1");
let meta_json = std::fs::read_to_string(run_dir.join("meta.json")).unwrap();
let back: RunMeta = serde_json::from_str(&meta_json).unwrap();
assert_eq!(back.run_id, "run-1");
assert!(run_dir.join("context.json").exists());
assert!(!run_dir.join("meta.json.tmp").exists());
}
fn job(run_id: &str) -> PersistJob {
PersistJob {
run_id: run_id.to_string(),
meta: meta(run_id),
context: context(),
stages: vec![],
output_appends: vec![],
log_appends: vec![],
taint_audit: None,
fanout: None,
interactions: None,
final_output: None,
}
}
fn job_with_context(run_id: &str, entries: usize) -> PersistJob {
let ctx = ContextSnapshot {
stage_name: "s".to_string(),
total_tokens: entries,
max_tokens: 100,
regions: vec![leviath_core::run_meta::RegionSnapshot {
name: "conv".to_string(),
kind: "clearable".to_string(),
current_tokens: entries,
max_tokens: 100,
entries: (0..entries)
.map(|i| leviath_core::run_meta::RegionEntrySnapshot {
content: format!("line {i}"),
tokens: 1,
kind: leviath_core::region::EntryKind::Text,
metadata: None,
key: None,
taint: Default::default(),
})
.collect(),
}],
};
PersistJob {
context: ctx,
..job(run_id)
}
}
#[tokio::test]
async fn every_persisted_run_file_is_private_to_this_user() {
let dir = tempfile::tempdir().expect("temp dir");
let mut j = job("run-perms");
j.final_output = Some("the answer".to_string());
write_snapshot(dir.path(), &j, "m", "w", None, None).await;
let run_dir = dir.path().join("run-perms");
for name in ["meta.json", "context.json", leviath_core::FINAL_OUTPUT_FILE] {
let path = run_dir.join(name);
assert!(path.exists(), "{name} was written");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(&path)
.expect("written file")
.permissions()
.mode()
& 0o777;
assert_eq!(mode, 0o600, "{name} should be private, got {mode:o}");
}
}
}
#[tokio::test]
async fn the_final_output_sidecar_holds_the_answer_verbatim() {
let dir = tempfile::tempdir().expect("temp dir");
let mut j = job("run-answer");
j.final_output = Some("metric,value\nrows,2\n".to_string());
write_snapshot(dir.path(), &j, "m", "w", None, None).await;
let written = std::fs::read_to_string(
dir.path()
.join("run-answer")
.join(leviath_core::FINAL_OUTPUT_FILE),
)
.expect("sidecar written");
assert_eq!(written, "metric,value\nrows,2\n");
}
#[tokio::test]
async fn a_described_answer_is_written_even_after_an_earlier_job_was_dropped() {
let dir = tempfile::tempdir().expect("temp dir");
let mut j = job("run-coalesced");
j.final_output = Some("the answer".to_string());
j.meta.final_output = Some(leviath_core::output::FinalOutputDescriptor {
format: None,
stage: "out".to_string(),
submitted_at: 100,
bytes: "the answer".len(),
truncated: false,
artifacts: Vec::new(),
});
write_snapshot(dir.path(), &j, "m", "w", None, None).await;
let sidecar = dir
.path()
.join("run-coalesced")
.join(leviath_core::FINAL_OUTPUT_FILE);
assert_eq!(
std::fs::read_to_string(&sidecar).expect("sidecar written"),
"the answer"
);
let meta: leviath_core::run_meta::RunMeta = serde_json::from_str(
&std::fs::read_to_string(dir.path().join("run-coalesced").join("meta.json"))
.expect("meta written"),
)
.expect("meta parses");
assert!(meta.final_output.is_some(), "descriptor present");
}
#[tokio::test]
async fn an_answer_already_written_by_this_lane_is_not_rewritten() {
let dir = tempfile::tempdir().expect("temp dir");
let mut j = job("run-heartbeat");
j.final_output = Some("the answer".to_string());
j.meta.final_output = Some(leviath_core::output::FinalOutputDescriptor {
format: None,
stage: "out".to_string(),
submitted_at: 100,
bytes: "the answer".len(),
truncated: false,
artifacts: Vec::new(),
});
let sidecar = dir
.path()
.join("run-heartbeat")
.join(leviath_core::FINAL_OUTPUT_FILE);
write_snapshot(dir.path(), &j, "m", "w", None, None).await;
assert!(sidecar.exists(), "first write lands");
std::fs::write(&sidecar, "MARKER").expect("marker");
write_snapshot(
dir.path(),
&j,
"m",
"w",
None,
Some((100, "the answer".len())),
)
.await;
assert_eq!(
std::fs::read_to_string(&sidecar).expect("still there"),
"MARKER",
"an answer already on disk is not rewritten"
);
j.meta
.final_output
.as_mut()
.expect("descriptor")
.submitted_at = 200;
write_snapshot(
dir.path(),
&j,
"m",
"w",
None,
Some((100, "the answer".len())),
)
.await;
assert_eq!(
std::fs::read_to_string(&sidecar).expect("rewritten"),
"the answer",
"a newer submission replaces it"
);
}
#[tokio::test]
async fn no_answer_writes_no_sidecar() {
let dir = tempfile::tempdir().expect("temp dir");
write_snapshot(dir.path(), &job("run-silent"), "m", "w", None, None).await;
assert!(
!dir.path()
.join("run-silent")
.join(leviath_core::FINAL_OUTPUT_FILE)
.exists()
);
}
#[tokio::test]
async fn every_file_a_run_writes_is_owner_only() {
let dir = tempfile::tempdir().unwrap();
let run = dir.path().join("run-perms");
write_snapshot(dir.path(), &job("run-perms"), "m", "w", None, None).await;
append_stage_line(&run, 0, "output.log", "a line of agent output", "run-perms").await;
append_stage_line(&run, 0, "logs.log", "a line of tool activity", "run-perms").await;
append_record(
dir.path(),
"run-perms",
&leviath_core::run_archive::RunRecord::OwnershipChanged {
machine_id: "m2".to_string(),
world_id: "w2".to_string(),
at: 1,
},
)
.await;
let walked = walkdir(&run);
for name in ["meta.json", "run.lvr", "stages"] {
assert!(
walked.iter().any(|p| p.ends_with(name)),
"the lane did not write {name}: {walked:?}"
);
}
assert!(
walked.iter().any(|p| p.ends_with("output.log")),
"the stage logs are missing: {walked:?}"
);
#[cfg(unix)]
for entry in &walked {
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(entry).unwrap().permissions().mode() & 0o777;
let expected = if entry.is_dir() { 0o700 } else { 0o600 };
let shown = entry.display().to_string();
assert_eq!(
mode, expected,
"{shown} is {mode:o}, and a copy of this tree would carry that"
);
}
}
#[tokio::test]
async fn a_vanished_blocking_task_becomes_an_io_error() {
let joined = tokio::task::spawn_blocking(|| panic!("the pool task died"))
.await
.expect_err("a panicking task joins as an error");
let mapped = vanished_task(joined);
assert_eq!(mapped.kind(), std::io::ErrorKind::Other);
assert!(
mapped.to_string().contains("panic"),
"the reason has to survive: {mapped}"
);
}
fn walkdir(root: &Path) -> Vec<PathBuf> {
let mut found = vec![root.to_path_buf()];
let mut queue = vec![root.to_path_buf()];
while let Some(dir) = queue.pop() {
for entry in std::fs::read_dir(&dir).into_iter().flatten().flatten() {
let path = entry.path();
if path.is_dir() {
queue.push(path.clone());
}
found.push(path);
}
}
found
}
#[tokio::test]
async fn write_snapshot_creates_a_readable_run_archive() {
use leviath_core::run_archive::{RunRecord, fold, read_archive};
let dir = tempfile::tempdir().unwrap();
write_snapshot(
dir.path(),
&job("run-1"),
"machine-x",
"world-y",
None,
None,
)
.await;
let bytes = std::fs::read(dir.path().join("run-1").join("run.lvr")).unwrap();
let (version, records) = read_archive(&mut bytes.as_slice()).unwrap();
assert_eq!(version, leviath_core::run_archive::RUN_ARCHIVE_VERSION);
assert!(
records
.iter()
.any(|r| matches!(r, RunRecord::Header { .. }))
);
assert!(
records
.iter()
.any(|r| matches!(r, RunRecord::ContextCheckpoint { .. }))
);
let folded = fold(&records).unwrap();
assert_eq!(folded.identity.run_id, "run-1");
assert_eq!(folded.identity.machine_id, "machine-x");
assert_eq!(folded.identity.world_id, "world-y");
assert_eq!(folded.meta.run_id, "run-1");
}
#[tokio::test]
async fn run_archive_stores_subsequent_writes_as_progress_diffs() {
use leviath_core::run_archive::{RunRecord, read_archive, replay_points};
let dir = tempfile::tempdir().unwrap();
let first = job_with_context("run-1", 1);
let second = job_with_context("run-1", 3); write_snapshot(dir.path(), &first, "m", "w", None, None).await;
write_snapshot(
dir.path(),
&second,
"m",
"w",
Some(&run_archive::digest_context(&first.context)),
None,
)
.await;
let bytes = std::fs::read(dir.path().join("run-1").join("run.lvr")).unwrap();
let (_v, records) = read_archive(&mut bytes.as_slice()).unwrap();
let count = |pred: fn(&RunRecord) -> bool| records.iter().filter(|r| pred(r)).count();
assert_eq!(count(|r| matches!(r, RunRecord::Header { .. })), 1);
assert_eq!(
count(|r| matches!(r, RunRecord::ContextCheckpoint { .. })),
1
);
assert_eq!(
count(|r| matches!(r, RunRecord::Progress { .. })),
1,
"the second write is a compact Progress diff, not a full checkpoint"
);
let points = replay_points(&records);
assert_eq!(points.len(), 2);
assert_eq!(points[0].context.regions[0].entries.len(), 1);
assert_eq!(points[1].context.regions[0].entries.len(), 3);
}
#[tokio::test]
async fn run_archive_records_ownership_handoff_on_resume() {
use leviath_core::run_archive::{RunRecord, read_archive};
let dir = tempfile::tempdir().unwrap();
write_snapshot(dir.path(), &job("run-1"), "m1", "w1", None, None).await;
write_snapshot(dir.path(), &job("run-1"), "m2", "w2", None, None).await;
let bytes = std::fs::read(dir.path().join("run-1").join("run.lvr")).unwrap();
let (_v, records) = read_archive(&mut bytes.as_slice()).unwrap();
assert_eq!(
records
.iter()
.filter(|r| matches!(r, RunRecord::Header { .. }))
.count(),
1,
"no second Header on resume"
);
let owned = records
.iter()
.find_map(|r| match r {
RunRecord::OwnershipChanged {
machine_id,
world_id,
..
} => Some((machine_id.clone(), world_id.clone())),
_ => None,
})
.expect("ownership handoff recorded");
assert_eq!(owned, ("m2".to_string(), "w2".to_string()));
}
#[test]
fn is_terminal_run_classifies_statuses() {
use leviath_core::run_meta::RunStatus;
assert!(is_terminal_run(&RunStatus::Complete));
assert!(is_terminal_run(&RunStatus::Error));
assert!(is_terminal_run(&RunStatus::Cancelled));
assert!(!is_terminal_run(&RunStatus::Running));
assert!(!is_terminal_run(&RunStatus::CompleteInteractive));
}
#[tokio::test]
async fn worker_coalesces_queued_snapshots_to_the_newest_per_run() {
use leviath_core::run_archive::{RunRecord, read_archive};
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = mpsc::unbounded_channel();
tx.send(PersistMsg::Snapshot(Box::new(job_with_context("run-1", 1))))
.unwrap();
tx.send(PersistMsg::Snapshot(Box::new(job_with_context("run-1", 2))))
.unwrap();
tx.send(PersistMsg::Snapshot(Box::new(job_with_context("run-1", 3))))
.unwrap();
tx.send(PersistMsg::Snapshot(Box::new(job_with_context("run-2", 1))))
.unwrap();
drop(tx);
persistence_worker(Some(dir.path().to_path_buf()), rx).await;
let bytes = std::fs::read(dir.path().join("run-1").join("run.lvr")).unwrap();
let (_v, records) = read_archive(&mut bytes.as_slice()).unwrap();
let checkpoints: Vec<_> = records
.iter()
.filter_map(|r| match r {
RunRecord::ContextCheckpoint { snapshot, .. } => Some(snapshot),
_ => None,
})
.collect();
assert_eq!(checkpoints.len(), 1, "one write for three queued snapshots");
assert_eq!(
checkpoints[0].regions[0].entries.len(),
3,
"and it carries the NEWEST context"
);
assert_eq!(records.len(), 2);
assert!(dir.path().join("run-2").join("meta.json").exists());
}
#[tokio::test]
async fn worker_appends_stage_lines_without_a_snapshot() {
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = mpsc::unbounded_channel();
tx.send(PersistMsg::Snapshot(Box::new(job("run-1"))))
.unwrap();
tx.send(PersistMsg::StageLines {
run_id: "run-1".to_string(),
output_appends: vec![(0, "an output line".to_string())],
log_appends: vec![(0, "[tool] shell: ls".to_string())],
})
.unwrap();
drop(tx);
let meta_before = {
!dir.path().join("run-1").join("meta.json").exists()
};
assert!(meta_before);
persistence_worker(Some(dir.path().to_path_buf()), rx).await;
let run = dir.path().join("run-1");
let out = std::fs::read_to_string(run.join("stages/0/output.log")).unwrap();
assert!(out.contains("an output line"));
let log = std::fs::read_to_string(run.join("stages/0/logs.log")).unwrap();
assert!(log.contains("[tool] shell: ls"));
let meta: RunMeta =
serde_json::from_str(&std::fs::read_to_string(run.join("meta.json")).unwrap()).unwrap();
assert_eq!(meta.run_id, "run-1");
}
#[tokio::test]
async fn worker_drops_terminal_runs_from_the_context_cache() {
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = mpsc::unbounded_channel();
let mut terminal = job("run-term");
terminal.meta.status = leviath_core::run_meta::RunStatus::Complete;
tx.send(PersistMsg::Snapshot(Box::new(terminal))).unwrap();
drop(tx);
persistence_worker(Some(dir.path().to_path_buf()), rx).await;
assert!(dir.path().join("run-term").join("meta.json").exists());
}
#[tokio::test]
async fn worker_without_a_runs_dir_drains_messages_and_writes_nothing() {
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = mpsc::unbounded_channel();
tx.send(PersistMsg::Snapshot(Box::new(job("run-a"))))
.unwrap();
let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
tx.send(PersistMsg::Append {
run_id: "run-a".to_string(),
record: Box::new(batch_record(0, "c1")),
ack: Some(ack_tx),
})
.unwrap();
tx.send(PersistMsg::Append {
run_id: "run-a".to_string(),
record: Box::new(batch_record(0, "c2")),
ack: None, })
.unwrap();
drop(tx);
persistence_worker(None, rx).await;
assert_eq!(ack_rx.await, Ok(()));
assert_eq!(std::fs::read_dir(dir.path()).unwrap().count(), 0);
}
#[tokio::test]
async fn write_snapshot_writes_then_removes_fanout_json() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("run-1").join("fanout.json");
let mut fo_job = job("run-1");
fo_job.fanout = Some(r#"{"resume":"me"}"#.to_string());
write_snapshot(dir.path(), &fo_job, "m", "w", None, None).await;
assert!(path.exists());
write_snapshot(
dir.path(),
&job("run-1"),
"m",
"w",
Some(&run_archive::digest_context(&fo_job.context)),
None,
)
.await;
assert!(!path.exists());
}
#[tokio::test]
async fn run_archive_open_failure_is_swallowed() {
let dir = tempfile::tempdir().unwrap();
let run_dir = dir.path().join("run-1");
std::fs::create_dir_all(run_dir.join("run.lvr")).unwrap();
write_snapshot(dir.path(), &job("run-1"), "m", "w", None, None).await;
assert!(run_dir.join("meta.json").exists());
}
fn batch_record(iteration: usize, call_id: &str) -> leviath_core::run_archive::RunRecord {
leviath_core::run_archive::RunRecord::ToolBatch {
calls: vec![leviath_core::run_archive::ToolCallRecord {
id: call_id.to_string(),
name: "shell".to_string(),
arguments: "{}".to_string(),
result: None,
thought_signature: None,
}],
at: 1,
stage_index: 0,
iteration,
response: "running".to_string(),
}
}
#[tokio::test]
async fn worker_appends_records_after_a_snapshot_and_acks() {
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = mpsc::unbounded_channel();
tx.send(PersistMsg::Snapshot(Box::new(job("run-1"))))
.unwrap();
let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
tx.send(PersistMsg::Append {
run_id: "run-1".to_string(),
record: Box::new(batch_record(0, "c1")),
ack: Some(ack_tx),
})
.unwrap();
tx.send(PersistMsg::Append {
run_id: "run-1".to_string(),
record: Box::new(leviath_core::run_archive::RunRecord::ToolCallDone {
iteration: 0,
call_id: "c1".to_string(),
result: "ran".to_string(),
at: 2,
}),
ack: None, })
.unwrap();
drop(tx);
persistence_worker(Some(dir.path().to_path_buf()), rx).await;
ack_rx.await.expect("append acked");
let bytes = std::fs::read(dir.path().join("run-1").join("run.lvr")).unwrap();
let (_v, records) = leviath_core::run_archive::read_archive(&mut bytes.as_slice()).unwrap();
let folded = leviath_core::run_archive::fold(&records).unwrap();
let pending = folded.pending_batch.expect("batch folds as pending");
assert_eq!(pending.calls[0].result.as_deref(), Some("ran"));
}
#[tokio::test]
async fn append_without_an_archive_is_skipped_but_still_acks() {
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = mpsc::unbounded_channel();
let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
tx.send(PersistMsg::Append {
run_id: "run-none".to_string(),
record: Box::new(batch_record(0, "c1")),
ack: Some(ack_tx),
})
.unwrap();
drop(tx);
persistence_worker(Some(dir.path().to_path_buf()), rx).await;
ack_rx.await.expect("acked despite the skip");
assert!(!dir.path().join("run-none").join("run.lvr").exists());
}
#[tokio::test]
async fn append_open_failure_is_swallowed() {
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(dir.path().join("run-1").join("run.lvr")).unwrap();
append_record(dir.path(), "run-1", &batch_record(0, "c1")).await;
}
#[test]
fn machine_id_is_persisted_and_reused() {
let dir = tempfile::tempdir().unwrap();
let runs = dir.path().join("runs");
std::fs::create_dir_all(&runs).unwrap();
let first = load_or_create_machine_id(&runs);
assert!(!first.is_empty());
assert_eq!(load_or_create_machine_id(&runs), first);
assert!(dir.path().join("machine-id").exists());
}
#[test]
fn machine_id_regenerates_when_file_is_empty() {
let dir = tempfile::tempdir().unwrap();
let runs = dir.path().join("runs");
std::fs::create_dir_all(&runs).unwrap();
std::fs::write(dir.path().join("machine-id"), " \n").unwrap();
let id = load_or_create_machine_id(&runs);
assert!(!id.is_empty());
}
#[test]
fn generate_id_is_sixteen_hex_chars() {
let id = generate_id();
assert_eq!(id.len(), 16);
assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
}
#[tokio::test]
async fn worker_writes_stages_index_and_appends_output_and_logs() {
let dir = tempfile::tempdir().unwrap();
write_snapshot(
dir.path(),
&PersistJob {
run_id: "r".to_string(),
meta: meta("r"),
context: context(),
stages: vec![StageRecord::new("plan".to_string(), 0)],
output_appends: vec![(0, "the plan".to_string())],
log_appends: vec![(0, "[tool] list_dir: .".to_string())],
taint_audit: None,
fanout: None,
interactions: None,
final_output: None,
},
"machine-test",
"world-test",
None,
None,
)
.await;
let run = dir.path().join("r");
let idx: Vec<StageRecord> =
serde_json::from_str(&std::fs::read_to_string(run.join("stages.json")).unwrap())
.unwrap();
assert_eq!(idx[0].name, "plan");
let out = std::fs::read_to_string(run.join("stages/0/output.log")).unwrap();
assert!(out.contains("the plan"));
let log = std::fs::read_to_string(run.join("stages/0/logs.log")).unwrap();
assert!(log.contains("[tool] list_dir"));
}
#[tokio::test]
async fn worker_writes_taint_audit_to_the_stage_dir() {
let dir = tempfile::tempdir().unwrap();
write_snapshot(
dir.path(),
&PersistJob {
run_id: "r".to_string(),
meta: meta("r"),
context: context(),
stages: vec![],
output_appends: vec![],
log_appends: vec![],
taint_audit: Some((2, r#"[{"tool_name":"shell"}]"#.to_string())),
fanout: None,
interactions: None,
final_output: None,
},
"machine-test",
"world-test",
None,
None,
)
.await;
let audit =
std::fs::read_to_string(dir.path().join("r/stages/2/taint_audit.json")).unwrap();
assert!(audit.contains("shell"));
}
#[tokio::test]
async fn interactions_sidecar_is_written_then_removed() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("r/interactions.json");
write_snapshot(
dir.path(),
&PersistJob {
interactions: Some(r#"{"cursor":0,"round":1,"body":"the plan"}"#.to_string()),
..job("r")
},
"machine-test",
"world-test",
None,
None,
)
.await;
let written = std::fs::read_to_string(&path).unwrap();
assert!(written.contains("the plan"));
write_snapshot(
dir.path(),
&job("r"),
"machine-test",
"world-test",
None,
None,
)
.await;
assert!(!path.exists());
}
#[tokio::test]
async fn empty_stages_are_not_written() {
let dir = tempfile::tempdir().unwrap();
write_snapshot(
dir.path(),
&PersistJob {
run_id: "r".to_string(),
meta: meta("r"),
context: context(),
stages: vec![],
output_appends: vec![],
log_appends: vec![],
taint_audit: None,
fanout: None,
interactions: None,
final_output: None,
},
"machine-test",
"world-test",
None,
None,
)
.await;
assert!(!dir.path().join("r/stages.json").exists());
}
#[tokio::test]
async fn stage_line_open_failure_is_handled() {
crate::test_support::with_tracing(|| {});
let dir = tempfile::tempdir().unwrap();
let run = dir.path().join("r");
std::fs::create_dir_all(run.join("stages/0/output.log")).unwrap();
append_stage_line(&run, 0, "output.log", "line", "r").await;
}
#[tokio::test]
async fn write_is_skipped_when_runs_dir_unwritable() {
crate::test_support::with_tracing(|| {});
let file = tempfile::NamedTempFile::new().unwrap();
write_snapshot(
file.path(), &PersistJob {
run_id: "r".to_string(),
meta: meta("r"),
context: context(),
stages: vec![],
output_appends: vec![],
log_appends: vec![],
taint_audit: None,
fanout: None,
interactions: None,
final_output: None,
},
"machine-test",
"world-test",
None,
None,
)
.await;
}
#[tokio::test]
async fn temp_write_failure_is_handled() {
crate::test_support::with_tracing(|| {});
let dir = tempfile::tempdir().unwrap();
let run_dir = dir.path().join("r");
std::fs::create_dir_all(&run_dir).unwrap();
std::fs::create_dir_all(run_dir.join("meta.json.tmp")).unwrap();
write_snapshot(
dir.path(),
&PersistJob {
run_id: "r".to_string(),
meta: meta("r"),
context: context(),
stages: vec![],
output_appends: vec![],
log_appends: vec![],
taint_audit: None,
fanout: None,
interactions: None,
final_output: None,
},
"machine-test",
"world-test",
None,
None,
)
.await;
assert!(!run_dir.join("meta.json").exists()); assert!(run_dir.join("context.json").exists()); }
#[tokio::test]
async fn rename_failure_is_handled() {
crate::test_support::with_tracing(|| {});
let dir = tempfile::tempdir().unwrap();
let run_dir = dir.path().join("r");
std::fs::create_dir_all(&run_dir).unwrap();
std::fs::create_dir_all(run_dir.join("meta.json")).unwrap();
write_snapshot(
dir.path(),
&PersistJob {
run_id: "r".to_string(),
meta: meta("r"),
context: context(),
stages: vec![],
output_appends: vec![],
log_appends: vec![],
taint_audit: None,
fanout: None,
interactions: None,
final_output: None,
},
"machine-test",
"world-test",
None,
None,
)
.await;
assert!(run_dir.join("context.json").exists());
}
}