@@ -158,6 +158,148 @@ def test_escalate_action_is_mapped_with_app_and_recipient(self) -> None:
158158 assert action .version == 2
159159 assert action .assignee == "admin@example.com"
160160
161+ def test_deterministic_guardrail_with_llm_scope_raises_value_error (self ) -> None :
162+ """DeterministicGuardrails (AgentCustomGuardrails) with LLM scope should raise ValueError."""
163+ guardrail = AgentCustomGuardrail .model_validate (
164+ {
165+ "$guardrailType" : "custom" ,
166+ "id" : "test-llm-scope" ,
167+ "name" : "test-guardrail-llm" ,
168+ "description" : "Test guardrail with LLM scope" ,
169+ "enabledForEvals" : True ,
170+ "selector" : {
171+ "$selectorType" : "scoped" ,
172+ "scopes" : ["llm" ], # LLM scope - should be rejected
173+ "matchNames" : None ,
174+ },
175+ "rules" : [
176+ {
177+ "$ruleType" : "word" ,
178+ "fieldSelector" : {
179+ "$selectorType" : "specific" ,
180+ "fields" : [{"path" : "message.content" , "source" : "input" }],
181+ },
182+ "operator" : "contains" ,
183+ "value" : "forbidden" ,
184+ }
185+ ],
186+ "action" : {"$actionType" : "block" , "reason" : "test" },
187+ }
188+ )
189+
190+ with pytest .raises (
191+ ValueError ,
192+ match = r"Deterministic guardrail 'test-guardrail-llm' can only be used with TOOL scope.*Found invalid scopes.*LLM" ,
193+ ):
194+ build_guardrails_with_actions ([guardrail ])
195+
196+ def test_deterministic_guardrail_with_agent_scope_raises_value_error (self ) -> None :
197+ """DeterministicGuardrails with AGENT scope should raise ValueError."""
198+ guardrail = AgentCustomGuardrail .model_validate (
199+ {
200+ "$guardrailType" : "custom" ,
201+ "id" : "test-agent-scope" ,
202+ "name" : "test-guardrail-agent" ,
203+ "description" : "Test guardrail with AGENT scope" ,
204+ "enabledForEvals" : True ,
205+ "selector" : {
206+ "$selectorType" : "scoped" ,
207+ "scopes" : ["agent" ], # AGENT scope - should be rejected
208+ "matchNames" : None ,
209+ },
210+ "rules" : [
211+ {
212+ "$ruleType" : "word" ,
213+ "fieldSelector" : {
214+ "$selectorType" : "specific" ,
215+ "fields" : [{"path" : "message.content" , "source" : "input" }],
216+ },
217+ "operator" : "contains" ,
218+ "value" : "forbidden" ,
219+ }
220+ ],
221+ "action" : {"$actionType" : "block" , "reason" : "test" },
222+ }
223+ )
224+
225+ with pytest .raises (
226+ ValueError ,
227+ match = r"Deterministic guardrail 'test-guardrail-agent' can only be used with TOOL scope.*Found invalid scopes.*AGENT" ,
228+ ):
229+ build_guardrails_with_actions ([guardrail ])
230+
231+ def test_deterministic_guardrail_with_tool_scope_succeeds (self ) -> None :
232+ """DeterministicGuardrails with TOOL scope should be accepted."""
233+ guardrail = AgentCustomGuardrail .model_validate (
234+ {
235+ "$guardrailType" : "custom" ,
236+ "id" : "test-tool-scope" ,
237+ "name" : "test-guardrail-tool" ,
238+ "description" : "Test guardrail with TOOL scope" ,
239+ "enabledForEvals" : True ,
240+ "selector" : {
241+ "$selectorType" : "scoped" ,
242+ "scopes" : ["tool" ], # TOOL scope - should be accepted
243+ "matchNames" : ["my_tool" ],
244+ },
245+ "rules" : [
246+ {
247+ "$ruleType" : "word" ,
248+ "fieldSelector" : {
249+ "$selectorType" : "specific" ,
250+ "fields" : [{"path" : "message.content" , "source" : "input" }],
251+ },
252+ "operator" : "contains" ,
253+ "value" : "forbidden" ,
254+ }
255+ ],
256+ "action" : {"$actionType" : "block" , "reason" : "test" },
257+ }
258+ )
259+
260+ result = build_guardrails_with_actions ([guardrail ])
261+
262+ assert len (result ) == 1
263+ converted_guardrail , action = result [0 ]
264+ assert isinstance (converted_guardrail , DeterministicGuardrail )
265+ assert converted_guardrail .name == "test-guardrail-tool"
266+ assert isinstance (action , BlockAction )
267+
268+ def test_deterministic_guardrail_with_mixed_scopes_raises_value_error (self ) -> None :
269+ """DeterministicGuardrails with mixed scopes including non-TOOL should raise ValueError."""
270+ guardrail = AgentCustomGuardrail .model_validate (
271+ {
272+ "$guardrailType" : "custom" ,
273+ "id" : "test-mixed-scope" ,
274+ "name" : "test-guardrail-mixed" ,
275+ "description" : "Test guardrail with mixed scopes" ,
276+ "enabledForEvals" : True ,
277+ "selector" : {
278+ "$selectorType" : "scoped" ,
279+ "scopes" : ["tool" , "llm" ], # Mixed scopes - should be rejected
280+ "matchNames" : ["my_tool" ],
281+ },
282+ "rules" : [
283+ {
284+ "$ruleType" : "word" ,
285+ "fieldSelector" : {
286+ "$selectorType" : "specific" ,
287+ "fields" : [{"path" : "message.content" , "source" : "input" }],
288+ },
289+ "operator" : "contains" ,
290+ "value" : "forbidden" ,
291+ }
292+ ],
293+ "action" : {"$actionType" : "block" , "reason" : "test" },
294+ }
295+ )
296+
297+ with pytest .raises (
298+ ValueError ,
299+ match = r"Deterministic guardrail 'test-guardrail-mixed' can only be used with TOOL scope.*Found invalid scopes.*LLM" ,
300+ ):
301+ build_guardrails_with_actions ([guardrail ])
302+
161303
162304class TestCreateWordRuleFunc :
163305 """Tests for _create_word_rule_func."""
0 commit comments