use car_memgine::MemgineEngine;
use car_server_core::{run_dispatch, ServerState, ServerStateConfig};
use futures::{SinkExt, StreamExt};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::sync::Arc;
use tempfile::TempDir;
use tokio::net::TcpListener;
use tokio::sync::Mutex;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::{accept_async, connect_async, MaybeTlsStream, WebSocketStream};
fn shared_state(journal_dir: std::path::PathBuf) -> Arc<ServerState> {
let engine = Arc::new(Mutex::new(MemgineEngine::new(None)));
let approvals = journal_dir.join("approvals.jsonl");
let cfg = ServerStateConfig::new(journal_dir)
.with_shared_memgine(engine)
.with_approval_journal(approvals);
Arc::new(ServerState::with_config(cfg))
}
async fn connect(
state: &Arc<ServerState>,
) -> WebSocketStream<MaybeTlsStream<tokio::net::TcpStream>> {
let listener = TcpListener::bind(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::new(127, 0, 0, 1),
0,
)))
.await
.expect("bind loopback");
let addr = listener.local_addr().expect("local_addr");
let st = state.clone();
tokio::spawn(async move {
let (stream, peer) = listener.accept().await.expect("accept");
let ws = accept_async(stream).await.expect("ws handshake");
let (write, read) = ws.split();
let _ = run_dispatch(read, Box::pin(write), peer.to_string(), st).await;
});
let url = format!("ws://{}", addr);
let (ws, _resp) = connect_async(&url).await.expect("ws client connect");
ws
}
async fn send_recv(
ws: &mut WebSocketStream<MaybeTlsStream<tokio::net::TcpStream>>,
request: serde_json::Value,
) -> serde_json::Value {
let body = serde_json::to_string(&request).expect("request to_string");
ws.send(Message::Text(body.into())).await.expect("send");
let resp = ws.next().await.expect("frame").expect("frame ok");
let text = match resp {
Message::Text(t) => t.to_string(),
other => panic!("expected Text frame, got {:?}", other),
};
serde_json::from_str(&text).expect("parse response JSON")
}
async fn rpc(state: &Arc<ServerState>, request: serde_json::Value) -> serde_json::Value {
let mut ws = connect(state).await;
send_recv(&mut ws, request).await
}
fn three_tier_policy(target: f64, budget: f64) -> serde_json::Value {
serde_json::json!({
"confidence_target": target,
"budget": budget,
"tiers": [
{ "tier": "self_reflect", "cost": 1.0, "expected_confidence": 0.0 },
{ "tier": "tool_verify", "cost": 5.0, "expected_confidence": 0.0 },
{ "tier": "human_expert", "cost": 50.0, "expected_confidence": 0.0 }
]
})
}
#[tokio::test]
async fn cascade_escalates_to_tool_verify_on_observed_confidence() {
let tmp = TempDir::new().unwrap();
let state = shared_state(tmp.path().join("journals"));
let resp = rpc(
&state,
serde_json::json!({
"jsonrpc": "2.0", "id": "c1", "method": "cascade.run",
"params": {
"current_confidence": 0.2,
"policy": three_tier_policy(0.8, 100.0),
"observed": {
"self_reflect": { "confidence": 0.4 },
"tool_verify": { "confidence": 0.85, "knowledge": "verified via search" }
},
"claim": "Paris is the capital of France"
}
}),
)
.await;
let result = resp.get("result").expect("result");
let run = result.get("run").expect("run");
assert_eq!(
run.get("accepted_tier").and_then(|v| v.as_str()),
Some("tool_verify")
);
assert_eq!(
run.get("steps").and_then(|v| v.as_array()).map(|a| a.len()),
Some(2)
);
assert_eq!(
run.get("knowledge").and_then(|v| v.as_str()),
Some("verified via search")
);
assert!(result.get("pending_approval").is_none());
}
#[tokio::test]
async fn cascade_human_tier_is_gated_on_ledger_not_caller_confidence() {
let tmp = TempDir::new().unwrap();
let state = shared_state(tmp.path().join("journals"));
let resp = rpc(
&state,
serde_json::json!({
"jsonrpc": "2.0", "id": "c2", "method": "cascade.run",
"params": {
"current_confidence": 0.1,
"policy": three_tier_policy(0.8, 100.0),
"observed": {
"self_reflect": { "confidence": 0.3 },
"tool_verify": { "confidence": 0.4 },
"human_expert": { "confidence": 0.99 }
},
"claim": "Our Q3 revenue exceeded plan"
}
}),
)
.await;
let result = resp.get("result").expect("result");
let run = result.get("run").expect("run");
assert!(
run.get("accepted_tier")
.map(|v| v.is_null())
.unwrap_or(true),
"human_expert must not be accepted without a ledger decision: {run}"
);
let pending = result
.get("pending_approval")
.expect("pending_approval present");
assert_eq!(
pending.get("fingerprint").and_then(|v| v.as_str()),
Some("cascade:Our Q3 revenue exceeded plan")
);
}
#[tokio::test]
async fn cascade_rejects_blank_claim_with_human_tier() {
let tmp = TempDir::new().unwrap();
let state = shared_state(tmp.path().join("journals"));
let resp = rpc(
&state,
serde_json::json!({
"jsonrpc": "2.0", "id": "c3", "method": "cascade.run",
"params": {
"current_confidence": 0.1,
"policy": three_tier_policy(0.99, 100.0),
"observed": { "human_expert": { "confidence": 0.99 } }
}
}),
)
.await;
assert!(
resp.get("result").is_none(),
"expected an error, got {resp}"
);
assert!(
resp.get("error").is_some(),
"expected a JSON-RPC error: {resp}"
);
}
#[tokio::test]
async fn cascade_accepts_human_after_ledger_approval() {
let tmp = TempDir::new().unwrap();
let state = shared_state(tmp.path().join("journals"));
let mut ws = connect(&state).await;
let approve = send_recv(
&mut ws,
serde_json::json!({
"jsonrpc": "2.0", "id": "a1", "method": "permission.approve",
"params": { "fingerprint": "cascade:gravity is real", "reason": "obvious" }
}),
)
.await;
assert!(approve.get("result").is_some(), "approve failed: {approve}");
let resp = send_recv(
&mut ws,
serde_json::json!({
"jsonrpc": "2.0", "id": "r1", "method": "cascade.run",
"params": {
"current_confidence": 0.1,
"policy": three_tier_policy(0.95, 100.0),
"observed": {
"self_reflect": { "confidence": 0.3 },
"tool_verify": { "confidence": 0.4 },
"human_expert": { "confidence": 0.99 }
},
"claim": "gravity is real"
}
}),
)
.await;
let result = resp.get("result").expect("result");
let run = result.get("run").expect("run");
assert_eq!(
run.get("accepted_tier").and_then(|v| v.as_str()),
Some("human_expert"),
"approved human tier should be accepted: {run}"
);
assert!(result.get("pending_approval").is_none());
}
#[tokio::test]
async fn cascade_dry_run_skips_reflect_side_effect() {
let tmp = TempDir::new().unwrap();
let state = shared_state(tmp.path().join("journals"));
let resp = rpc(
&state,
serde_json::json!({
"jsonrpc": "2.0", "id": "d1", "method": "cascade.run",
"params": {
"current_confidence": 0.2,
"dry_run": true,
"policy": three_tier_policy(0.8, 100.0),
"observed": { "self_reflect": { "confidence": 0.85 } },
"claim": "x"
}
}),
)
.await;
let run = resp.get("result").and_then(|r| r.get("run")).expect("run");
assert_eq!(
run.get("accepted_tier").and_then(|v| v.as_str()),
Some("self_reflect")
);
assert_eq!(
run.get("knowledge").and_then(|v| v.as_str()),
Some("self_reflect: dry_run (no reflect)")
);
}