mod debris;
mod queue;
use super::*;
use crate::boundary::deposit;
use crate::boundary::dispatch::Deps;
use crate::boundary::tests::snapshot;
use crate::cli_outbound::Cli;
use crate::git_tree::AgentState;
use serde_json::json;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tempfile::tempdir;
fn deps(state_root: &Path) -> Deps {
Deps {
litany: Cli::new("/no/such/litany"),
bl: Cli::new("/no/such/bl"),
state_root: state_root.to_path_buf(),
yog_binary: PathBuf::from("/no/such/yog"),
world: crate::test_support::no_world(),
home: PathBuf::from("/home/x"),
yog_data_root: PathBuf::from("/data"),
balls_state_root: PathBuf::from("/balls"),
snapshot: Arc::new(snapshot(
Path::new("/names/alba"),
"alba",
vec![crate::boundary::tests::agent("c-1", AgentState::Live, 9)],
vec![],
)),
caller: crate::boundary::dispatch::Caller::default(),
}
}
fn ui() -> UiState {
UiState::open(PathBuf::from("/nonexistent/ui.json"))
}
#[test]
fn a_query_deposit_earns_its_reply_and_leaves_the_claimed_audit() {
let root = tempdir().unwrap();
let d = deps(root.path());
deposit::deposit(root.path(), "q-1", &json!({"op": "balls"})).unwrap();
assert_eq!(consume(&d, &mut ui(), "T1", 100), 1);
let reply = deposit::read_reply(root.path(), "q-1").unwrap();
assert_eq!(reply["ok"], true);
assert_eq!(reply["kind"], "balls");
assert!(
deposit::gestures_dir(root.path())
.join("claimed")
.join("q-1.json")
.is_file(),
"the deposit stays, claimed — the audit's other half"
);
assert_eq!(consume(&d, &mut ui(), "T2", 100), 0, "nothing left");
}
#[test]
fn a_refusing_query_deposit_answers_with_a_refusal_not_a_wedge() {
let root = tempdir().unwrap();
let d = deps(root.path());
deposit::deposit(
root.path(),
"q-refuse",
&json!({"op": "config", "target": {"file": "branch", "workspace": "/ws",
"lineage": "default", "path": "providers.yaml", "origin": "advance"}}),
)
.unwrap();
assert_eq!(consume(&d, &mut ui(), "T1", 100), 1);
let reply = deposit::read_reply(root.path(), "q-refuse").unwrap();
assert_eq!(reply["ok"], false, "{reply}");
}
#[test]
fn a_conversations_query_renders_the_same_rows_the_frame_would() {
let root = tempdir().unwrap();
let d = deps(root.path());
deposit::deposit(
root.path(),
"q-2",
&json!({"op": "conversations", "workspace": "alba"}),
)
.unwrap();
consume(&d, &mut ui(), "T1", 100);
let reply = deposit::read_reply(root.path(), "q-2").unwrap();
assert_eq!(reply["rows"][0]["root_id"], "c-1");
assert_eq!(reply["rows"][0]["state"], "live");
}
#[test]
fn a_mangled_deposit_answers_with_a_refusal_not_a_wedge() {
let root = tempdir().unwrap();
let d = deps(root.path());
deposit::deposit(root.path(), "g-raw", &json!({"op": "warp"})).unwrap();
std::fs::write(
deposit::gestures_dir(root.path()).join("g-torn.json"),
b"not json",
)
.unwrap();
assert_eq!(consume(&d, &mut ui(), "T1", 100), 2);
let unknown = deposit::read_reply(root.path(), "g-raw").unwrap();
assert_eq!(unknown["ok"], false);
assert!(unknown["error"].as_str().unwrap().contains("unknown op"));
let torn = deposit::read_reply(root.path(), "g-torn").unwrap();
assert_eq!(torn["ok"], false);
assert!(torn["error"].as_str().unwrap().contains("not JSON"));
}
#[test]
fn a_spawn_failing_action_is_refused_and_its_ops_row_stands() {
let root = tempdir().unwrap();
let d = deps(root.path());
deposit::deposit(
root.path(),
"a-1",
&json!({"op": "scan", "workspace": "alba"}),
)
.unwrap();
assert_eq!(consume(&d, &mut ui(), "T1", 100), 1);
let reply = deposit::read_reply(root.path(), "a-1").unwrap();
assert_eq!(reply["ok"], false, "the spawn never launched");
let ops = crate::opslog::tail(root.path(), 8);
assert_eq!(ops.len(), 1, "the synthetic failure line is durable (§4.2)");
assert_eq!(ops[0].ts, "T1");
}
#[test]
fn the_trail_verbs_land_through_the_boundary() {
let root = tempdir().unwrap();
let d = deps(root.path());
deposit::deposit(root.path(), "a-ack", &json!({"op": "ack"})).unwrap();
assert_eq!(consume(&d, &mut ui(), "T1", 100), 1);
assert_eq!(
deposit::read_reply(root.path(), "a-ack").unwrap()["kind"],
"acked"
);
deposit::deposit(root.path(), "a-clear", &json!({"op": "clear-trail"})).unwrap();
assert_eq!(consume(&d, &mut ui(), "T2", 100), 1);
assert_eq!(
deposit::read_reply(root.path(), "a-clear").unwrap()["kind"],
"trail-cleared"
);
let ops = crate::opslog::tail(root.path(), 8);
assert_eq!(
ops.len(),
1,
"the clear truncated; its own row opens the trail"
);
assert_eq!(ops[0].ts, "T2");
}
#[test]
fn an_action_leaves_an_ops_row_and_a_query_leaves_none() {
let root = tempdir().unwrap();
let d = deps(root.path());
deposit::deposit(root.path(), "q-att", &json!({"op": "attention"})).unwrap();
assert_eq!(consume(&d, &mut ui(), "T1", 100), 1);
assert_eq!(
deposit::read_reply(root.path(), "q-att").map(|r| r["ok"].clone()),
Some(json!(true)),
"the query was answered — it simply left no trace in the trail"
);
assert!(
crate::opslog::tail(root.path(), 8).is_empty(),
"a query reads the world and changes nothing (§4.2)"
);
deposit::deposit(root.path(), "a-ack", &json!({"op": "ack"})).unwrap();
assert_eq!(consume(&d, &mut ui(), "T2", 100), 1);
let ops = crate::opslog::tail(root.path(), 8);
assert_eq!(ops.len(), 1, "one line per attempted action, {ops:?}");
assert_eq!(ops[0].argv, vec!["yog-step", "ack-failures"]);
}
#[test]
fn an_unwritable_reply_leaves_its_own_step_failure_row() {
let root = tempdir().unwrap();
let d = deps(root.path());
deposit::deposit(root.path(), "q-9", &json!({"op": "balls"})).unwrap();
std::fs::write(deposit::gestures_dir(root.path()).join("replies"), b"x").unwrap();
assert_eq!(consume(&d, &mut ui(), "T1", 100), 1);
let ops = crate::opslog::tail(root.path(), 8);
assert!(
ops.iter()
.any(|e| e.argv.get(1).is_some_and(|s| s == "gesture-reply")),
"{ops:?}"
);
}