use assay_core::agent_assertions::{model::TraceAssertion, verify_assertions};
use assay_core::storage::Store;
use assay_core::trace::schema::{EpisodeStart, StepEntry, ToolCallEntry, TraceEvent};
use serde_json::json;
fn store_with_one_call() -> anyhow::Result<(Store, i64, &'static str)> {
let store = Store::memory()?;
store.init_schema()?;
let run_id = store.insert_run("vacuity-suite")?;
let test_id = "agent-under-test";
store.insert_event(
&TraceEvent::EpisodeStart(EpisodeStart {
episode_id: "ep-1".into(),
timestamp: 1000,
input: json!({ "prompt": "hi" }),
meta: json!({}),
}),
Some(run_id),
Some(test_id),
)?;
store.insert_event(
&TraceEvent::Step(StepEntry {
episode_id: "ep-1".into(),
step_id: "s-1".into(),
idx: 0,
timestamp: 1001,
kind: "tool".into(),
name: Some("model".into()),
content: None,
content_sha256: None,
truncations: vec![],
meta: json!({}),
}),
Some(run_id),
Some(test_id),
)?;
store.insert_event(
&TraceEvent::ToolCall(ToolCallEntry {
episode_id: "ep-1".into(),
step_id: "s-1".into(),
timestamp: 1002,
tool_name: "web_search".into(),
call_index: Some(0),
args: json!({ "q": "rust" }),
args_sha256: None,
result: None,
result_sha256: None,
error: None,
truncations: vec![],
}),
Some(run_id),
Some(test_id),
)?;
Ok((store, run_id, test_id))
}
const INEFFECTIVE: &str = "E_ASSERT_INEFFECTIVE";
fn assert_reports_ineffective(
diags: &[assay_core::errors::diagnostic::Diagnostic],
case: &str,
expected_field: &str,
) {
assert!(
!diags.is_empty(),
"{case}: assertion evaluated to nothing and reported no diagnostic, \
which is indistinguishable from a check that ran and held"
);
let found = diags
.iter()
.find(|d| d.code == INEFFECTIVE)
.unwrap_or_else(|| {
panic!(
"{case}: expected a {INEFFECTIVE} diagnostic, got {:?}",
diags.iter().map(|d| &d.code).collect::<Vec<_>>()
)
});
let named = found.context.get("field").and_then(|v| v.as_str());
assert_eq!(
named,
Some(expected_field),
"{case}: diagnostic blamed the wrong field, which points the author at the wrong fix"
);
}
#[test]
fn args_valid_without_test_args_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ArgsValid {
tool: "web_search".into(),
test_args: None,
policy: Some(json!({ "type": "object", "required": ["q"] })),
expect: None,
}],
)?;
assert_reports_ineffective(&diags, "args_valid without test_args", "test_args");
Ok(())
}
#[test]
fn args_valid_with_test_args_and_no_policy_still_diagnoses() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ArgsValid {
tool: "web_search".into(),
test_args: Some(json!({ "q": "rust" })),
policy: None,
expect: None,
}],
)?;
assert!(
!diags.is_empty(),
"a unit-mode args_valid without a policy must stay diagnosed"
);
Ok(())
}
#[test]
fn sequence_valid_policy_without_regex_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::SequenceValid {
test_trace: None,
test_trace_raw: Some(vec![json!({ "tool": "web_search" })]),
policy: Some(json!({ "rules": ["whatever the author meant"] })),
expect: None,
}],
)?;
assert_reports_ineffective(
&diags,
"sequence_valid policy without regex",
"policy.regex",
);
Ok(())
}
#[test]
fn sequence_valid_with_typed_test_trace_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::SequenceValid {
test_trace: Some(vec![]),
test_trace_raw: None,
policy: Some(json!({ "regex": "^web_search$" })),
expect: None,
}],
)?;
assert_reports_ineffective(
&diags,
"sequence_valid using the typed test_trace field",
"test_trace",
);
Ok(())
}
#[test]
fn tool_blocklist_without_blocked_key_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ToolBlocklist {
test_tool_calls: Some(vec!["web_search".into()]),
policy: Some(json!({ "deny": ["rm"] })),
expect: None,
}],
)?;
assert_reports_ineffective(
&diags,
"tool_blocklist policy without a blocked key",
"policy.blocked",
);
Ok(())
}
#[test]
fn tool_blocklist_with_empty_blocked_list_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ToolBlocklist {
test_tool_calls: Some(vec!["web_search".into()]),
policy: Some(json!({ "blocked": [] })),
expect: None,
}],
)?;
assert_reports_ineffective(&diags, "tool_blocklist with blocked: []", "policy.blocked");
Ok(())
}
#[test]
fn tool_blocklist_without_test_tool_calls_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ToolBlocklist {
test_tool_calls: None,
policy: Some(json!({ "blocked": ["rm"] })),
expect: None,
}],
)?;
assert_reports_ineffective(
&diags,
"tool_blocklist without test_tool_calls",
"test_tool_calls",
);
Ok(())
}
#[test]
fn unrecognised_expect_value_is_rejected_not_silently_inverted() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
for spelling in ["Pass", "PASS", "passes", "true", "ok", ""] {
for (site, assertion) in expect_sites(spelling) {
let diags = verify_assertions(&store, run_id, test_id, &[assertion])?;
assert!(
diags.iter().any(|d| d.code == "E_CONFIG_ERROR"),
"{site} with expect: {spelling:?} must be rejected, not read as \
`expect failure`; got {:?}",
diags.iter().map(|d| &d.code).collect::<Vec<_>>()
);
}
}
Ok(())
}
fn expect_sites(spelling: &str) -> Vec<(&'static str, TraceAssertion)> {
let expect = Some(spelling.to_string());
vec![
(
"args_valid",
TraceAssertion::ArgsValid {
tool: "web_search".into(),
test_args: Some(json!({ "q": "rust" })),
policy: Some(json!({ "schema": { "type": "object", "required": ["q"] } })),
expect: expect.clone(),
},
),
(
"sequence_valid",
TraceAssertion::SequenceValid {
test_trace: None,
test_trace_raw: Some(vec![json!({ "tool": "web_search" })]),
policy: Some(json!({ "regex": "^web_search$" })),
expect: expect.clone(),
},
),
(
"tool_blocklist",
TraceAssertion::ToolBlocklist {
test_tool_calls: Some(vec!["web_search".into()]),
policy: Some(json!({ "blocked": ["rm"] })),
expect,
},
),
]
}
#[test]
fn recognised_expect_values_still_work() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let schema = json!({ "schema": { "type": "object", "required": ["q"] } });
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ArgsValid {
tool: "web_search".into(),
test_args: Some(json!({ "q": "rust" })),
policy: Some(schema.clone()),
expect: Some("pass".into()),
}],
)?;
assert!(diags.is_empty(), "expect: pass on valid args must hold");
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ArgsValid {
tool: "web_search".into(),
test_args: Some(json!({ "wrong": 1 })),
policy: Some(schema),
expect: Some("fail".into()),
}],
)?;
assert!(diags.is_empty(), "expect: fail on invalid args must hold");
Ok(())
}
#[test]
fn must_call_tool_with_min_calls_zero_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceMustCallTool {
tool: "web_search".into(),
min_calls: Some(0),
}],
)?;
assert_reports_ineffective(
&diags,
"trace_must_call_tool with min_calls: 0",
"min_calls",
);
Ok(())
}
#[test]
fn tool_sequence_empty_subsequence_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceToolSequence {
sequence: vec![],
allow_other_tools: true,
}],
)?;
assert_reports_ineffective(
&diags,
"trace_tool_sequence with an empty sequence and allow_other_tools",
"sequence",
);
Ok(())
}
#[test]
fn tool_sequence_empty_exact_is_a_real_constraint() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceToolSequence {
sequence: vec![],
allow_other_tools: false,
}],
)?;
assert!(
diags.iter().any(|d| d.code == "E_TRACE_ASSERT_FAIL"),
"an empty exact sequence constrains the trace to no tool calls and must fail here, got {:?}",
diags.iter().map(|d| &d.code).collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn sequence_valid_permissive_regex_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
for pol in [json!({ "regex": "" }), json!({ "regex": 123 })] {
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::SequenceValid {
test_trace: None,
test_trace_raw: Some(vec![json!({ "tool": "web_search" })]),
policy: Some(pol.clone()),
expect: None,
}],
)?;
assert_reports_ineffective(
&diags,
&format!("sequence_valid regex {pol}"),
"policy.regex",
);
}
Ok(())
}
#[test]
fn tool_blocklist_unusable_blocked_value_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
for pol in [
json!({ "blocked": "rm" }),
json!({ "blocked": [42] }),
json!({ "blocked": [{ "name": "rm" }] }),
] {
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ToolBlocklist {
test_tool_calls: Some(vec!["web_search".into()]),
policy: Some(pol.clone()),
expect: None,
}],
)?;
assert_reports_ineffective(
&diags,
&format!("tool_blocklist blocked {pol}"),
"policy.blocked",
);
}
Ok(())
}
#[test]
fn blocked_diagnostic_separates_absent_from_wrongly_typed() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let message_for = |policy: serde_json::Value| -> anyhow::Result<String> {
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ToolBlocklist {
test_tool_calls: Some(vec!["web_search".into()]),
policy: Some(policy),
expect: None,
}],
)?;
Ok(diags
.iter()
.find(|d| d.code == INEFFECTIVE)
.map(|d| d.message.clone())
.unwrap_or_default())
};
let absent = message_for(json!({ "deny": ["rm"] }))?;
let wrong_type = message_for(json!({ "blocked": "rm" }))?;
assert!(
absent.contains("carries no"),
"an absent `blocked` should say so; got {absent:?}"
);
assert!(
wrong_type.contains("not a list"),
"a wrongly-typed `blocked` should say so rather than claiming it is absent; got \
{wrong_type:?}"
);
assert_ne!(
absent, wrong_type,
"two different defects must not share one explanation"
);
Ok(())
}
#[test]
fn tool_blocklist_with_no_calls_to_check_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ToolBlocklist {
test_tool_calls: Some(vec![]),
policy: Some(json!({ "blocked": ["rm"] })),
expect: Some("pass".into()),
}],
)?;
assert_reports_ineffective(
&diags,
"tool_blocklist with an empty test_tool_calls",
"test_tool_calls",
);
Ok(())
}
#[test]
fn tool_blocklist_partially_unusable_blocked_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::ToolBlocklist {
test_tool_calls: Some(vec!["drop_table".into()]),
policy: Some(json!({ "blocked": ["rm", { "name": "drop_table" }] })),
expect: None,
}],
)?;
assert_reports_ineffective(
&diags,
"tool_blocklist with a non-string entry in blocked",
"policy.blocked",
);
Ok(())
}
#[test]
fn sequence_valid_unreadable_trace_entry_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::SequenceValid {
test_trace: None,
test_trace_raw: Some(vec![
json!({ "tool": "web_search" }),
json!({ "toolName": "delete_account" }),
]),
policy: Some(json!({ "regex": "^web_search$" })),
expect: None,
}],
)?;
assert_reports_ineffective(
&diags,
"sequence_valid with an entry naming no tool",
"test_trace_raw",
);
Ok(())
}
#[test]
fn must_not_call_tool_with_empty_name_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceMustNotCallTool {
tool: String::new(),
}],
)?;
assert_reports_ineffective(
&diags,
"trace_must_not_call_tool with an empty tool",
"tool",
);
Ok(())
}
#[test]
fn must_call_tool_with_empty_name_is_not_a_behavioural_failure() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceMustCallTool {
tool: String::new(),
min_calls: Some(1),
}],
)?;
assert_reports_ineffective(&diags, "trace_must_call_tool with an empty tool", "tool");
assert!(
!diags.iter().any(|d| d.code == "E_TRACE_ASSERT_FAIL"),
"an unsatisfiable config must not be reported as the agent misbehaving"
);
Ok(())
}
#[test]
fn max_steps_at_the_ceiling_is_not_a_pass() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceMaxSteps { max: u32::MAX }],
)?;
assert_reports_ineffective(&diags, "trace_max_steps at u32::MAX", "max");
Ok(())
}
#[test]
fn max_steps_at_a_large_but_reachable_bound_is_a_real_constraint() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceMaxSteps { max: 100_000 }],
)?;
assert!(
diags.is_empty(),
"a large but reachable step bound must stay a real constraint, got {:?}",
diags.iter().map(|d| &d.code).collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn max_steps_still_fails_when_exceeded() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceMaxSteps { max: 0 }],
)?;
assert!(
diags.iter().any(|d| d.code == "E_TRACE_ASSERT_FAIL"),
"one step against max: 0 must fail, got {:?}",
diags.iter().map(|d| &d.code).collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn effective_assertion_that_holds_stays_silent() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceMustCallTool {
tool: "web_search".into(),
min_calls: Some(1),
}],
)?;
assert!(
diags.is_empty(),
"an effective assertion that holds must report nothing, got {:?}",
diags.iter().map(|d| &d.code).collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn effective_assertion_that_fails_still_fails() -> anyhow::Result<()> {
let (store, run_id, test_id) = store_with_one_call()?;
let diags = verify_assertions(
&store,
run_id,
test_id,
&[TraceAssertion::TraceMustNotCallTool {
tool: "web_search".into(),
}],
)?;
assert!(
diags.iter().any(|d| d.code == "E_TRACE_ASSERT_FAIL"),
"a genuine violation must stay a failure, got {:?}",
diags.iter().map(|d| &d.code).collect::<Vec<_>>()
);
Ok(())
}