assay_core/agent_assertions/
mod.rs1pub mod cover;
2pub mod matchers;
3pub mod model;
4
5use crate::errors::diagnostic::Diagnostic;
6use crate::storage::Store;
7
8pub struct EpisodeGraph {
9 pub episode_id: String,
10 pub steps: Vec<crate::storage::rows::StepRow>,
11 pub tool_calls: Vec<crate::storage::rows::ToolCallRow>,
12}
13
14pub struct AssertionOutcome {
20 pub diagnostics: Vec<Diagnostic>,
21 pub not_exercised: Vec<cover::AssertionCover>,
22}
23
24pub fn verify_assertions(
34 store: &Store,
35 run_id: i64,
36 test_id: &str,
37 assertions: &[model::TraceAssertion],
38) -> anyhow::Result<Vec<Diagnostic>> {
39 verify_assertions_with_meta(store, run_id, test_id, assertions, &serde_json::Value::Null)
40 .map(|o| o.diagnostics)
41}
42
43pub fn verify_assertions_with_meta(
44 store: &Store,
45 run_id: i64,
46 test_id: &str,
47 assertions: &[model::TraceAssertion],
48 meta: &serde_json::Value,
49) -> anyhow::Result<AssertionOutcome> {
50 let finish = |graph: &EpisodeGraph| -> anyhow::Result<AssertionOutcome> {
51 let tools = cover::ToolAvailability::observe(meta, graph);
52 Ok(AssertionOutcome {
53 diagnostics: matchers::evaluate(graph, assertions)?,
54 not_exercised: cover::evaluate_cover(graph, &tools, assertions),
55 })
56 };
57
58 let graph_res = store.get_episode_graph(run_id, test_id);
59 match graph_res {
60 Ok(graph) => finish(&graph),
61 Err(e) => {
62 #[expect(
66 clippy::wildcard_enum_match_arm,
67 reason = "an assertion kind with no test-input field cannot be a unit test; a new kind that carries one must be named above or its unit-test form is not recognised"
68 )]
69 let is_unit_test = assertions.iter().all(|a| match a {
70 model::TraceAssertion::ArgsValid { test_args, .. } => test_args.is_some(),
71 model::TraceAssertion::SequenceValid {
72 test_trace,
73 test_trace_raw,
74 ..
75 } => test_trace.is_some() || test_trace_raw.is_some(),
76 model::TraceAssertion::ToolBlocklist {
77 test_tool_calls, ..
78 } => test_tool_calls.is_some(),
79 _ => false,
80 });
81
82 if is_unit_test {
83 let dummy = EpisodeGraph {
85 episode_id: "unit_test_mock".into(),
86 steps: vec![],
87 tool_calls: vec![],
88 };
89 return finish(&dummy);
90 }
91
92 if e.to_string().contains("E_TRACE_EPISODE_MISSING") {
96 match store.get_latest_episode_graph_by_test_id(test_id) {
97 Ok(latest_graph) => return finish(&latest_graph),
98 Err(fallback_err) => {
99 return Err(anyhow::anyhow!("E_TRACE_EPISODE_MISSING: Primary query failed ({}), Fallback failed: {}", e, fallback_err));
100 }
101 }
102 }
103
104 Err(e)
107 }
108 }
109}