use crate::Runtime;
use car_eventlog::tool_receipts::ToolClaim;
use car_ir::ActionProposal;
use car_verify::goal::GoalInputs;
use car_verify::plan_check::{check_plan, PlanCheckRequest};
use car_verify::transaction::check_transaction;
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct GoalGather {
pub claims: Vec<ToolClaim>,
pub proposal_id: Option<String>,
pub plan: Option<PlanCheckRequest>,
pub transaction_proposal: Option<ActionProposal>,
pub state_keys: Vec<String>,
pub command_exits: HashMap<String, i32>,
pub model_verdicts: HashMap<String, bool>,
}
impl Runtime {
pub async fn gather_goal_inputs(&self, gather: &GoalGather) -> GoalInputs {
let receipts_grounded = Some(
self.verify_tool_receipts(&gather.claims, gather.proposal_id.as_deref())
.await
.grounded,
);
let plan_achieved = gather.plan.as_ref().map(|p| check_plan(p).valid);
let full = self.state.snapshot();
let state = if gather.state_keys.is_empty() {
full
} else {
gather
.state_keys
.iter()
.filter_map(|k| full.get(k).map(|v| (k.clone(), v.clone())))
.collect()
};
let state_consistent = gather.transaction_proposal.as_ref().map(|p| {
let (values, versions) = self.state.versioned_snapshot();
check_transaction(p, &versions, Some(&values)).consistent
});
GoalInputs {
receipts_grounded,
plan_achieved,
state_consistent,
state,
command_exits: gather.command_exits.clone(),
model_verdicts: gather.model_verdicts.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ToolExecutor;
use car_eventlog::tool_receipts::{ClaimKind, ToolClaim};
use car_ir::{Action, ActionProposal, ActionType, FailureBehavior};
use car_verify::goal::{evaluate_goal, GoalCondition};
use serde_json::Value;
use std::sync::Arc;
struct EchoExec;
#[async_trait::async_trait]
impl ToolExecutor for EchoExec {
async fn execute(&self, tool: &str, params: &Value) -> Result<Value, String> {
match tool {
"echo" => Ok(params.get("message").cloned().unwrap_or(Value::Null)),
_ => Err(format!("unknown tool: {tool}")),
}
}
}
fn tool_call(tool: &str, params: HashMap<String, Value>) -> Action {
Action {
id: uuid::Uuid::new_v4().simple().to_string()[..12].to_string(),
action_type: ActionType::ToolCall,
tool: Some(tool.to_string()),
parameters: params,
preconditions: vec![],
expected_effects: HashMap::new(),
state_dependencies: vec![],
read_set: vec![],
write_set: vec![],
assumptions: vec![],
invocation_mode: Default::default(),
idempotent: false,
max_retries: 3,
failure_behavior: FailureBehavior::Abort,
timeout_ms: None,
metadata: HashMap::new(),
}
}
fn state_write(key: &str, value: Value) -> Action {
Action {
id: uuid::Uuid::new_v4().simple().to_string()[..12].to_string(),
action_type: ActionType::StateWrite,
tool: None,
parameters: [
("key".to_string(), Value::from(key)),
("value".to_string(), value),
]
.into(),
preconditions: vec![],
expected_effects: HashMap::new(),
state_dependencies: vec![],
read_set: vec![],
write_set: vec![],
assumptions: vec![],
invocation_mode: Default::default(),
idempotent: false,
max_retries: 3,
failure_behavior: FailureBehavior::Abort,
timeout_ms: None,
metadata: HashMap::new(),
}
}
fn proposal(actions: Vec<Action>) -> ActionProposal {
ActionProposal {
id: "goal-test".to_string(),
source: "test".to_string(),
actions,
timestamp: chrono::Utc::now(),
context: HashMap::new(),
}
}
#[tokio::test]
async fn gather_reads_real_receipts_and_state() {
let rt = Runtime::new().with_executor(Arc::new(EchoExec));
rt.register_tool("echo").await;
let p = proposal(vec![
tool_call("echo", [("message".to_string(), Value::from("hi"))].into()),
state_write("tests_passing", Value::Bool(true)),
]);
let r = rt.execute(&p).await;
assert!(r.all_succeeded(), "setup proposal must succeed");
let gather = GoalGather {
claims: vec![ToolClaim {
kind: ClaimKind::Invoked,
tool: "echo".to_string(),
call_id: None,
count: None,
text: None,
}],
proposal_id: Some("goal-test".to_string()),
..Default::default()
};
let inputs = rt.gather_goal_inputs(&gather).await;
assert_eq!(inputs.receipts_grounded, Some(true));
assert_eq!(inputs.state.get("tests_passing"), Some(&Value::Bool(true)));
let cond = GoalCondition::AllOf {
conditions: vec![
GoalCondition::ToolReceiptsGrounded,
GoalCondition::StatePredicate {
key: "tests_passing".into(),
equals: Value::Bool(true),
},
],
};
let verdict = evaluate_goal(&cond, &inputs);
assert!(verdict.met && verdict.grounded, "{}", verdict.reason);
}
#[tokio::test]
async fn fabricated_claim_fails_the_receipts_condition() {
let rt = Runtime::new().with_executor(Arc::new(EchoExec));
rt.register_tool("echo").await;
let r = rt
.execute(&proposal(vec![tool_call(
"echo",
[("message".to_string(), Value::from("hi"))].into(),
)]))
.await;
assert!(r.all_succeeded());
let gather = GoalGather {
claims: vec![ToolClaim {
kind: ClaimKind::Invoked,
tool: "deploy".to_string(),
call_id: None,
count: None,
text: Some("I deployed it".to_string()),
}],
proposal_id: Some("goal-test".to_string()),
..Default::default()
};
let inputs = rt.gather_goal_inputs(&gather).await;
assert_eq!(inputs.receipts_grounded, Some(false));
let verdict = evaluate_goal(&GoalCondition::ToolReceiptsGrounded, &inputs);
assert!(
!verdict.met,
"a hallucinated tool claim must not satisfy the goal"
);
assert!(verdict.reason.contains("hallucinated"));
}
}