Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.

Commit 3b105ef

Browse files
Valentina Bojanclaude
andcommitted
fix: pass validation when guardrail rule references missing field
When a rule references a field that doesn't exist in the data, the evaluator should pass (nothing to validate) instead of failing. Previously, the empty field list caused the loop to be skipped and fall through to the "violation detected" return. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fe4f114 commit 3b105ef

3 files changed

Lines changed: 117 additions & 1 deletion

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-core"
3-
version = "0.5.4"
3+
version = "0.5.5"
44
description = "UiPath Core abstractions"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath/core/guardrails/_evaluators.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ def evaluate_word_rule(
195195
) -> tuple[bool, str]:
196196
"""Evaluate a word rule against input and output data."""
197197
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
198+
if not fields:
199+
return True, "No fields to validate"
200+
198201
operator = _humanize_guardrail_func(rule.detects_violation) or "violation check"
199202
field_paths = ", ".join({field_ref.path for _, field_ref in fields})
200203

@@ -237,6 +240,9 @@ def evaluate_number_rule(
237240
) -> tuple[bool, str]:
238241
"""Evaluate a number rule against input and output data."""
239242
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
243+
if not fields:
244+
return True, "No fields to validate"
245+
240246
operator = _humanize_guardrail_func(rule.detects_violation) or "violation check"
241247
field_paths = ", ".join({field_ref.path for _, field_ref in fields})
242248
for field_value, field_ref in fields:
@@ -281,6 +287,9 @@ def evaluate_boolean_rule(
281287
) -> tuple[bool, str]:
282288
"""Evaluate a boolean rule against input and output data."""
283289
fields = get_fields_from_selector(rule.field_selector, input_data, output_data)
290+
if not fields:
291+
return True, "No fields to validate"
292+
284293
operator = _humanize_guardrail_func(rule.detects_violation) or "violation check"
285294
field_paths = ", ".join({field_ref.path for _, field_ref in fields})
286295
for field_value, field_ref in fields:

tests/guardrails/test_deterministic_guardrails_service.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,3 +1466,110 @@ def _create_guardrail_with_always_rule(
14661466
),
14671467
],
14681468
)
1469+
1470+
1471+
class TestMissingFieldPassesValidation:
1472+
"""Test that rules referencing missing fields pass validation."""
1473+
1474+
def test_word_rule_missing_field_passes(
1475+
self, service: DeterministicGuardrailsService
1476+
) -> None:
1477+
guardrail = DeterministicGuardrail(
1478+
id="test-missing-field",
1479+
name="Missing Field Guardrail",
1480+
description="Test missing field",
1481+
enabled_for_evals=True,
1482+
guardrail_type="custom",
1483+
selector=GuardrailSelector(
1484+
scopes=[GuardrailScope.TOOL], match_names=["test"]
1485+
),
1486+
rules=[
1487+
WordRule(
1488+
rule_type="word",
1489+
field_selector=SpecificFieldsSelector(
1490+
selector_type="specific",
1491+
fields=[
1492+
FieldReference(
1493+
path="sentence2", source=FieldSource.INPUT
1494+
)
1495+
],
1496+
),
1497+
detects_violation=lambda s: s == "",
1498+
rule_description="sentence2 is not empty",
1499+
),
1500+
],
1501+
)
1502+
result = service._evaluate_deterministic_guardrail(
1503+
input_data={"sentence1": "hello"},
1504+
output_data={},
1505+
guardrail=guardrail,
1506+
)
1507+
assert result.result == GuardrailValidationResultType.PASSED
1508+
1509+
def test_number_rule_missing_field_passes(
1510+
self, service: DeterministicGuardrailsService
1511+
) -> None:
1512+
guardrail = DeterministicGuardrail(
1513+
id="test-missing-field",
1514+
name="Missing Field Guardrail",
1515+
description="Test missing field",
1516+
enabled_for_evals=True,
1517+
guardrail_type="custom",
1518+
selector=GuardrailSelector(
1519+
scopes=[GuardrailScope.TOOL], match_names=["test"]
1520+
),
1521+
rules=[
1522+
NumberRule(
1523+
rule_type="number",
1524+
field_selector=SpecificFieldsSelector(
1525+
selector_type="specific",
1526+
fields=[
1527+
FieldReference(path="age", source=FieldSource.INPUT)
1528+
],
1529+
),
1530+
detects_violation=lambda n: n < 0,
1531+
rule_description="age is negative",
1532+
),
1533+
],
1534+
)
1535+
result = service._evaluate_deterministic_guardrail(
1536+
input_data={"name": "test"},
1537+
output_data={},
1538+
guardrail=guardrail,
1539+
)
1540+
assert result.result == GuardrailValidationResultType.PASSED
1541+
1542+
def test_boolean_rule_missing_field_passes(
1543+
self, service: DeterministicGuardrailsService
1544+
) -> None:
1545+
guardrail = DeterministicGuardrail(
1546+
id="test-missing-field",
1547+
name="Missing Field Guardrail",
1548+
description="Test missing field",
1549+
enabled_for_evals=True,
1550+
guardrail_type="custom",
1551+
selector=GuardrailSelector(
1552+
scopes=[GuardrailScope.TOOL], match_names=["test"]
1553+
),
1554+
rules=[
1555+
BooleanRule(
1556+
rule_type="boolean",
1557+
field_selector=SpecificFieldsSelector(
1558+
selector_type="specific",
1559+
fields=[
1560+
FieldReference(
1561+
path="is_active", source=FieldSource.INPUT
1562+
)
1563+
],
1564+
),
1565+
detects_violation=lambda b: b is False,
1566+
rule_description="is_active is false",
1567+
),
1568+
],
1569+
)
1570+
result = service._evaluate_deterministic_guardrail(
1571+
input_data={"name": "test"},
1572+
output_data={},
1573+
guardrail=guardrail,
1574+
)
1575+
assert result.result == GuardrailValidationResultType.PASSED

0 commit comments

Comments
 (0)