Skip to content

Commit b5baa87

Browse files
authored
Validate expected_goal for goal_integrity assertions
Rejects goal_integrity assertions during scenario validation when expected_goal is missing, blank, or not a string. Adds regression tests covering valid, missing, blank, non-string, and unrelated assertion cases. Closes #37.
2 parents 27c23dc + f2e7556 commit b5baa87

2 files changed

Lines changed: 75 additions & 2 deletions

File tree

src/agent_harness/scenario.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,19 @@ def validate_scenario_data(data: Any) -> Scenario:
124124
f"assertions[{index}].type must be a non-empty string"
125125
)
126126

127+
assertion_type = assertion_type.strip()
128+
if assertion_type == "goal_integrity":
129+
expected_goal = assertion.get("expected_goal")
130+
if not isinstance(expected_goal, str) or not expected_goal.strip():
131+
raise ScenarioValidationError(
132+
f"assertions[{index}].expected_goal must be a non-empty string "
133+
"for goal_integrity assertions"
134+
)
135+
127136
return Scenario(
128137
id=scenario_id,
129138
title=title,
130139
category=category,
131140
severity=severity,
132141
raw=data,
133-
)
142+
)

tests/test_scenarios.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,27 @@
44

55
from pathlib import Path
66

7-
from agent_harness.scenario import load_scenario
7+
import pytest
8+
9+
from agent_harness.scenario import (
10+
ScenarioValidationError,
11+
load_scenario,
12+
validate_scenario_data,
13+
)
14+
15+
16+
def _minimal_scenario(assertions):
17+
return {
18+
"id": "goal-hijack-basic",
19+
"title": "Goal hijack basic",
20+
"category": "goal_hijack",
21+
"severity": "medium",
22+
"target": {},
23+
"input": {},
24+
"expected": {},
25+
"assertions": assertions,
26+
}
27+
828

929
def test_all_bundled_scenarios_validate():
1030
scenario_paths = sorted(Path("scenarios").rglob("*.yaml"))
@@ -15,5 +35,49 @@ def test_all_bundled_scenarios_validate():
1535
load_scenario(scenario_path)
1636

1737

38+
def test_goal_integrity_assertion_validates_with_expected_goal():
39+
scenario = validate_scenario_data(
40+
_minimal_scenario(
41+
[{"type": "goal_integrity", "expected_goal": "summarize_document"}]
42+
)
43+
)
44+
45+
assert scenario.id == "goal-hijack-basic"
46+
47+
48+
def test_goal_integrity_assertion_requires_expected_goal():
49+
with pytest.raises(
50+
ScenarioValidationError,
51+
match="expected_goal must be a non-empty string",
52+
):
53+
validate_scenario_data(_minimal_scenario([{"type": "goal_integrity"}]))
54+
55+
56+
def test_goal_integrity_assertion_rejects_blank_expected_goal():
57+
with pytest.raises(
58+
ScenarioValidationError,
59+
match="expected_goal must be a non-empty string",
60+
):
61+
validate_scenario_data(
62+
_minimal_scenario([{"type": "goal_integrity", "expected_goal": " "}])
63+
)
64+
65+
66+
def test_goal_integrity_assertion_rejects_non_string_expected_goal():
67+
with pytest.raises(
68+
ScenarioValidationError,
69+
match="expected_goal must be a non-empty string",
70+
):
71+
validate_scenario_data(
72+
_minimal_scenario([{"type": "goal_integrity", "expected_goal": 123}])
73+
)
74+
75+
76+
def test_other_assertion_types_do_not_require_expected_goal():
77+
scenario = validate_scenario_data(
78+
_minimal_scenario([{"type": "no_denied_tool_call"}])
79+
)
80+
81+
assert scenario.id == "goal-hijack-basic"
1882

1983

0 commit comments

Comments
 (0)