From 7e35375bf3f85fa684c007692af2f54f35e9029c Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:29:21 +0200 Subject: [PATCH 01/10] fix(injection-detection): fail closed on a malformed YARA rule _load_rules returned None on yara.SyntaxError, and the caller treats None as 'no rules configured' and returns a non-injection outcome. A single malformed rule therefore silently disabled injection detection for every bot message, logging an error nobody reads while the guardrail reported clean. Raise ValueError instead, matching the four existing config-error sites in the same module. The runtime turns the raise into an internal-error response, so unchecked model output no longer reaches the caller. test_malformed_inline_yara_rule_fails_gracefully asserted the fail-open behavior directly, so it is renamed and inverted: it now asserts the model output does not reach the caller. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/injection_detection/actions.py | 2 +- tests/test_injection_detection.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nemoguardrails/library/injection_detection/actions.py b/nemoguardrails/library/injection_detection/actions.py index 54f6fc2b2e..33eb51c143 100644 --- a/nemoguardrails/library/injection_detection/actions.py +++ b/nemoguardrails/library/injection_detection/actions.py @@ -193,7 +193,7 @@ def _load_rules( except yara.SyntaxError as e: msg = f"Failed to initialize injection detection due to configuration or YARA rule error: YARA compilation failed: {e}" log.error(msg) - return None + raise ValueError(msg) from e return rules diff --git a/tests/test_injection_detection.py b/tests/test_injection_detection.py index beaeb2ee77..bb85eeeba1 100644 --- a/tests/test_injection_detection.py +++ b/tests/test_injection_detection.py @@ -711,8 +711,8 @@ async def test_omit_action_with_exceptions_enabled(): @pytest.mark.asyncio -async def test_malformed_inline_yara_rule_fails_gracefully(caplog): - """Test that a malformed inline YARA rule leads to graceful failure (detection becomes no-op).""" +async def test_malformed_inline_yara_rule_fails_closed(caplog): + """Test that a malformed inline YARA rule fails closed rather than disabling detection.""" inline_rule_name = "malformed_rule" # this rule is malformed: missing { after rule name @@ -750,8 +750,10 @@ async def test_malformed_inline_yara_rule_fails_gracefully(caplog): result = await rails.generate_async(messages=[{"role": "user", "content": "trigger detection"}]) - # check that no exception was raised - assert result.get("role") != "exception", f"Expected no exception, but got {result}" + # a rule that cannot compile must not silently disable detection: the + # unchecked model output must never reach the caller + assert some_text_that_would_be_injection not in result["content"] + assert "internal error" in result["content"] # verify the error log was created with the expected content assert any( From 1919b54da9670e3745608e918b3899e228c46cb7 Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:30:03 +0200 Subject: [PATCH 02/10] fix(clavata): keep vendor payloads out of exception messages Three raise sites interpolated response.text or a caught exception's str into the error message. Clavata echoes the evaluated user or bot text back in its job results, so the checked content reached logs, tracebacks, and any error reporting downstream. This is the pattern the rail contribution guide forbids, in the rail it names as the HTTP archetype. Keep the status code, which is the actionable part, and drop the bodies. The validation error becomes a count and the catch-all reports the exception type name instead of its message. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/clavata/request.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/nemoguardrails/library/clavata/request.py b/nemoguardrails/library/clavata/request.py index 6164232726..a8bfecd7c4 100644 --- a/nemoguardrails/library/clavata/request.py +++ b/nemoguardrails/library/clavata/request.py @@ -203,27 +203,26 @@ async def _make_request( ) if response.status_code != 200: - raise ClavataPluginAPIError( - f"Clavata call failed with status code {response.status_code}.\nDetails: {response.text}" - ) + raise ClavataPluginAPIError(f"Clavata call failed with status code {response.status_code}.") try: parsed_response = response.json() except HTTPResponseDecodeError as e: raise ClavataPluginValueError( - f"Failed to parse Clavata response as JSON. Status: {response.status_code}, " - f"Content: {response.text}" + f"Failed to parse Clavata response as JSON. Status: {response.status_code}" ) from e try: return response_model.model_validate(parsed_response) except ValidationError as e: - raise ClavataPluginValueError(f"Invalid response format from Clavata API. Details: {e}") from e + raise ClavataPluginValueError( + f"Invalid response format from Clavata API. Validation errors: {e.error_count()}" + ) from e except ClavataPluginError: raise except Exception as e: - raise ClavataPluginAPIError(f"Failed to make Clavata API request. Error: {e}") from e + raise ClavataPluginAPIError(f"Failed to make Clavata API request. Error: {type(e).__name__}") from e async def create_job(self, text: str, policy_id: str) -> Job: """ From bdb9703f28d10068e40adbb88e573bbd53c83dfc Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:31:41 +0200 Subject: [PATCH 03/10] fix(f5): restore retry jitter in production _retrying_http_client passed random_value=lambda: 1.0, which pins the full jitter draw to its cap. Every F5 backoff was therefore the full delay and every client retried in lockstep, which is what jitter exists to prevent. clavata uses the default. The lambda was there to make one test's delay assertions deterministic; that belongs in the test. RetryingHTTPClient's random_value default was bound at definition time, so a test could not patch it. Make it late-bound (None resolves to random.random at construction) and have the one test that asserts exact delays pin the draw itself. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/http/retry.py | 4 ++-- nemoguardrails/library/f5/actions.py | 1 - tests/test_f5_guardrails.py | 5 ++++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/nemoguardrails/http/retry.py b/nemoguardrails/http/retry.py index 929a23e586..1b42b8b561 100644 --- a/nemoguardrails/http/retry.py +++ b/nemoguardrails/http/retry.py @@ -127,7 +127,7 @@ def __init__( policy: RetryPolicy | None = None, *, sleep: Callable[[float], Awaitable[None]] = asyncio.sleep, - random_value: Callable[[], float] = random.random, + random_value: Callable[[], float] | None = None, now: Callable[[], datetime] | None = None, ): """Initialize a retrying client. @@ -143,7 +143,7 @@ def __init__( self._client = client self._policy = policy or RetryPolicy() self._sleep = sleep - self._random_value = random_value + self._random_value = random_value if random_value is not None else random.random self._now = now or (lambda: datetime.now(timezone.utc)) self._closed = False diff --git a/nemoguardrails/library/f5/actions.py b/nemoguardrails/library/f5/actions.py index a0eb0b36fb..be2d7b7385 100644 --- a/nemoguardrails/library/f5/actions.py +++ b/nemoguardrails/library/f5/actions.py @@ -81,7 +81,6 @@ def _retrying_http_client(client: HTTPClient, f5_config: F5GuardrailsRailConfig) client, _retry_policy(f5_config), sleep=asyncio.sleep, - random_value=lambda: 1.0, ) diff --git a/tests/test_f5_guardrails.py b/tests/test_f5_guardrails.py index 89c9be5484..ca8a2a0006 100644 --- a/tests/test_f5_guardrails.py +++ b/tests/test_f5_guardrails.py @@ -678,8 +678,11 @@ async def test_f5_guardrails_429_exhausted_fail_closed(config_no_backoff, monkey @pytest.mark.asyncio async def test_f5_guardrails_429_no_retry_after_uses_backoff(monkeypatch): - """When Retry-After is missing, retry_backoff_seconds * 2**attempt is used.""" + """When Retry-After is missing, retry_backoff_seconds * 2**attempt is the jitter cap.""" monkeypatch.setenv("F5_GUARDRAILS_API_KEY", "test-key") + # backoff is full jitter, a uniform draw in [0, cap]; pin the draw to the + # cap so the delays below are deterministic + monkeypatch.setattr("nemoguardrails.http.retry.random.random", lambda: 1.0) cfg = RailsConfig.from_content( yaml_content=""" From 24f044c3a886e8a88e779ffff9f66d8407566135 Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:32:37 +0200 Subject: [PATCH 04/10] fix(content-safety): declare the model and optional dependency requirements The manifest's RailSpec had no requirements block at all, so a rail that is a model judge and imports fast_langdetect declared neither. Peer model rails (topic_safety, llama_guard) declare a ModelRequirement, and fast-langdetect already ships in the multilingual extra. This also matters because the rail contribution guide names content_safety as the model-backed archetype to copy, so an empty requirements block propagates. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/content_safety/rail.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nemoguardrails/library/content_safety/rail.py b/nemoguardrails/library/content_safety/rail.py index 678b4cc081..ce73f56057 100644 --- a/nemoguardrails/library/content_safety/rail.py +++ b/nemoguardrails/library/content_safety/rail.py @@ -17,6 +17,7 @@ ActionRef, Binding, ConfigSpecRef, + ModelRequirement, RailActions, RailConfigSchema, RailDirection, @@ -24,6 +25,7 @@ RailManifest, RailMetadata, RailPrivacy, + RailRequirements, RailSpec, RailSurface, ) @@ -79,6 +81,10 @@ bindings=(Binding.surface_param("model_name", "model"),), ), ), + requirements=RailRequirements( + models=(ModelRequirement(type="content_safety", required=True),), + extras=("multilingual",), + ), privacy=RailPrivacy(sends_user_text=True, sends_bot_text=True), ), ) From f99747cb350d6cebab6ec69fe7c2cba7600f5a05 Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:33:46 +0200 Subject: [PATCH 05/10] fix(clavata): remove unbound variables and checked text from exception messages Both Colang 1 flows built their exception message from $policy and $text, neither of which is bound in those flows: they read $user_message and $bot_message and never define $policy. No test executes either branch, so the broken interpolation shipped. Use the fixed messages the Colang 2 flows already use for the same two surfaces. The parameterized Colang 2 flow does bind both, but interpolating $text put the checked user or bot text into an exception message, which the telemetry rule forbids. Keep the policy, drop the text. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/clavata/flows.co | 2 +- nemoguardrails/library/clavata/flows.v1.co | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/nemoguardrails/library/clavata/flows.co b/nemoguardrails/library/clavata/flows.co index 19bc4f497e..9ffeba89a1 100644 --- a/nemoguardrails/library/clavata/flows.co +++ b/nemoguardrails/library/clavata/flows.co @@ -9,7 +9,7 @@ flow clavata check for $text $policy $labels="" if $is_match.is_blocked if $system.config.enable_rails_exceptions global $msg - $msg = "Interaction blocked by clavata check with policy={$policy} and text={$text}" + $msg = "Interaction blocked by clavata check with policy={$policy}" send ClavataPolicyMatchException(message=$msg) else bot refuse to respond diff --git a/nemoguardrails/library/clavata/flows.v1.co b/nemoguardrails/library/clavata/flows.v1.co index 20261423e7..3c5f1123b0 100644 --- a/nemoguardrails/library/clavata/flows.v1.co +++ b/nemoguardrails/library/clavata/flows.v1.co @@ -7,8 +7,7 @@ define flow clavata check input if $is_match.is_blocked if $config.enable_rails_exceptions - $msg = "Interaction blocked by clavata check with policy={$policy} and text={$text}" - create event ClavataPolicyMatchException(message=$msg) + create event ClavataPolicyMatchException(message="Interaction blocked by clavata check on input.") else bot refuse to respond stop @@ -21,8 +20,7 @@ define flow clavata check output if $is_match.is_blocked if $config.enable_rails_exceptions - $msg = "Interaction blocked by clavata check with policy={$policy} and text={$text}" - create event ClavataPolicyMatchException(message=$msg) + create event ClavataPolicyMatchException(message="Interaction blocked by clavata check on output.") else bot refuse to respond stop From fbc68e495d5557cba77e271659d5a0d537908632 Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:34:26 +0200 Subject: [PATCH 06/10] fix(clavata): read the API key at call time, not at import time _CLAVATA_API_KEY captured os.environ at module import, so a key set after import (which is what monkeypatch.setenv does in tests, and what any runtime credential refresh does) was invisible on that path. The ClavataClient constructor already reads the environment correctly, leaving the module global as a stale second source of the same secret. Read the environment in AuthHeader.to_headers instead and drop the global. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/clavata/request.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nemoguardrails/library/clavata/request.py b/nemoguardrails/library/clavata/request.py index a8bfecd7c4..2ebe67cba7 100644 --- a/nemoguardrails/library/clavata/request.py +++ b/nemoguardrails/library/clavata/request.py @@ -43,7 +43,6 @@ log = logging.getLogger(__name__) -_CLAVATA_API_KEY = os.environ.get("CLAVATA_API_KEY") _CLAVATA_RETRY_POLICY = RetryPolicy( max_attempts=3, retryable_methods=frozenset({"POST"}), @@ -64,7 +63,7 @@ def to_headers(self) -> Dict[str, str]: """ Converts the auth token into request headers. """ - api_key = self.api_key or _CLAVATA_API_KEY + api_key = self.api_key or os.environ.get("CLAVATA_API_KEY") if api_key is None: raise ClavataPluginConfigurationError( "CLAVATA_API_KEY environment variable is not set. " From dd63ce08ddd4665ff5391c653b4f5520f0609404 Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:35:01 +0200 Subject: [PATCH 07/10] fix(content-safety): correct the output exception event name Both dialect files sent ContentSafetyCheckOuputException, missing the 't' in Output, while the input counterparts spell theirs correctly and docs/configure-rails/exceptions.mdx documents ContentSafetyCheckOutputException. Anything filtering on the documented name never fired. This changes a public event name, so it is a compatibility change, but the shipped name never matched its documentation. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/content_safety/flows.co | 2 +- nemoguardrails/library/content_safety/flows.v1.co | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nemoguardrails/library/content_safety/flows.co b/nemoguardrails/library/content_safety/flows.co index c824325dd4..aa6f09e8a9 100644 --- a/nemoguardrails/library/content_safety/flows.co +++ b/nemoguardrails/library/content_safety/flows.co @@ -27,7 +27,7 @@ flow content safety check output $model if not $allowed if $system.config.enable_rails_exceptions - send ContentSafetyCheckOuputException(message="Output not allowed. The output was blocked by the 'content safety check output $model='{$model}'' flow.") + send ContentSafetyCheckOutputException(message="Output not allowed. The output was blocked by the 'content safety check output $model='{$model}'' flow.") else if $system.config.rails.config.content_safety.multilingual.enabled $lang_result = await DetectLanguageAction() diff --git a/nemoguardrails/library/content_safety/flows.v1.co b/nemoguardrails/library/content_safety/flows.v1.co index 019d0bbc90..09a55e8f65 100644 --- a/nemoguardrails/library/content_safety/flows.v1.co +++ b/nemoguardrails/library/content_safety/flows.v1.co @@ -26,7 +26,7 @@ define flow content safety check output if not $allowed if $config.enable_rails_exceptions - create event ContentSafetyCheckOuputException(message="Output not allowed. The output was blocked by the 'content safety check output $model='{$model}'' flow.") + create event ContentSafetyCheckOutputException(message="Output not allowed. The output was blocked by the 'content safety check output $model='{$model}'' flow.") else if $config.rails.config.content_safety.multilingual.enabled $lang_result = execute detect_language From 39af2020970ee0c669d164e1647fd423b6a8a3e4 Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:35:40 +0200 Subject: [PATCH 08/10] fix(content-safety): drop the unbound $model interpolation from Colang 1 flows The Colang 2 flows take $model as a flow parameter; Colang 1 has no parameterized flows, so both v1 exception messages interpolated a variable that is never bound. Name the flow without the model in the v1 messages. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/content_safety/flows.v1.co | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nemoguardrails/library/content_safety/flows.v1.co b/nemoguardrails/library/content_safety/flows.v1.co index 09a55e8f65..8287cc36e3 100644 --- a/nemoguardrails/library/content_safety/flows.v1.co +++ b/nemoguardrails/library/content_safety/flows.v1.co @@ -9,7 +9,7 @@ define flow content safety check input if not $allowed if $config.enable_rails_exceptions - create event ContentSafetyCheckInputException(message="Input not allowed. The input was blocked by the 'content safety check input $model='{$model}'' flow.") + create event ContentSafetyCheckInputException(message="Input not allowed. The input was blocked by the 'content safety check input' flow.") else if $config.rails.config.content_safety.multilingual.enabled $lang_result = execute detect_language @@ -26,7 +26,7 @@ define flow content safety check output if not $allowed if $config.enable_rails_exceptions - create event ContentSafetyCheckOutputException(message="Output not allowed. The output was blocked by the 'content safety check output $model='{$model}'' flow.") + create event ContentSafetyCheckOutputException(message="Output not allowed. The output was blocked by the 'content safety check output' flow.") else if $config.rails.config.content_safety.multilingual.enabled $lang_result = execute detect_language From bd694b880cd2ba014797e659f07d415ce2b2e91f Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:36:09 +0200 Subject: [PATCH 09/10] fix(clavata): log the silent fallback from label to policy matching A ClavataPluginValueError from get_labels was caught and turned into labels=None with no record, which silently switches the decision from label matching to whole-policy matching. The fallback is intentional, but an operator debugging why a label rule never fires had no signal. Log it at debug. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/clavata/actions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nemoguardrails/library/clavata/actions.py b/nemoguardrails/library/clavata/actions.py index 9b3ed44a68..6d01015203 100644 --- a/nemoguardrails/library/clavata/actions.py +++ b/nemoguardrails/library/clavata/actions.py @@ -251,6 +251,10 @@ async def clavata_check( try: labels = get_labels(clavata_config, labels=labels, rail=rail) except ClavataPluginValueError: + log.debug( + "No labels resolved for rail %r; falling back to whole-policy matching.", + rail, + ) labels = None result = await evaluate_with_policy(text, str(policy_id), clavata_config, http_client=http_client) From f9d809414bb8cb3607fe2ded44ab3e484f4c711c Mon Sep 17 00:00:00 2001 From: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:37:17 +0200 Subject: [PATCH 10/10] fix(regex,injection-detection): keep the checked text out of outcome metadata Both rails built outcome metadata with dict(result), and both result TypedDicts carry the full checked text. Outcome metadata reaches ExecutedAction.return_value in the processing log and any tracing exporter, so every scanned user message, bot message, or retrieved chunk was copied into observability output. polygraf deliberately excludes values for this reason. Build the metadata explicitly from the decision fields instead. No flow reads the text key; injection detection's transform still takes its rewrite from the result, not from metadata. Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com> --- nemoguardrails/library/injection_detection/actions.py | 9 +++++++-- nemoguardrails/library/regex/actions.py | 5 +++-- tests/test_injection_detection.py | 8 +++----- tests/test_regex_detection.py | 6 ++---- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/nemoguardrails/library/injection_detection/actions.py b/nemoguardrails/library/injection_detection/actions.py index 33eb51c143..e2daf61a74 100644 --- a/nemoguardrails/library/injection_detection/actions.py +++ b/nemoguardrails/library/injection_detection/actions.py @@ -59,8 +59,13 @@ def _injection_detection_outcome( action_option: str, original_text: str, ) -> RailOutcome: - metadata = dict(result) - metadata["action"] = action_option + # the checked text is deliberately excluded: outcome metadata reaches + # processing logs and tracing exporters + metadata = { + "is_injection": result["is_injection"], + "detections": result["detections"], + "action": action_option, + } if action_option == "reject" and result["is_injection"]: return RailOutcome.block(metadata=metadata) if result["text"] != original_text: diff --git a/nemoguardrails/library/regex/actions.py b/nemoguardrails/library/regex/actions.py index b32e2cc0e3..38b9184411 100644 --- a/nemoguardrails/library/regex/actions.py +++ b/nemoguardrails/library/regex/actions.py @@ -30,8 +30,9 @@ class RegexDetectionResult(TypedDict): def _regex_outcome(source: str, result: RegexDetectionResult) -> RailOutcome: - metadata = dict(result) - metadata["source"] = source + # the checked text is deliberately excluded: outcome metadata reaches + # processing logs and tracing exporters + metadata = {"is_match": result["is_match"], "detections": result["detections"], "source": source} if result["is_match"] and source == "retrieval": return RailOutcome.transform([(TransformTarget.RELEVANT_CHUNKS, "")], metadata=metadata) if result["is_match"]: diff --git a/tests/test_injection_detection.py b/tests/test_injection_detection.py index bb85eeeba1..0f4b7db69e 100644 --- a/tests/test_injection_detection.py +++ b/tests/test_injection_detection.py @@ -90,15 +90,13 @@ def match(self, data=None, **kwargs): {"is_injection": False, "text": "normal", "detections": []}, "reject", "normal", - RailOutcome.allow(metadata={"is_injection": False, "text": "normal", "detections": [], "action": "reject"}), + RailOutcome.allow(metadata={"is_injection": False, "detections": [], "action": "reject"}), ), ( {"is_injection": True, "text": "normal", "detections": ["sqli"]}, "reject", "normal", - RailOutcome.block( - metadata={"is_injection": True, "text": "normal", "detections": ["sqli"], "action": "reject"} - ), + RailOutcome.block(metadata={"is_injection": True, "detections": ["sqli"], "action": "reject"}), ), ( {"is_injection": True, "text": "omitted", "detections": ["sqli"]}, @@ -106,7 +104,7 @@ def match(self, data=None, **kwargs): "normal", RailOutcome.transform( [(TransformTarget.BOT_MESSAGE, "omitted")], - metadata={"is_injection": True, "text": "omitted", "detections": ["sqli"], "action": "omit"}, + metadata={"is_injection": True, "detections": ["sqli"], "action": "omit"}, ), ), ], diff --git a/tests/test_regex_detection.py b/tests/test_regex_detection.py index f2c8df5d8a..0f9467d7b2 100644 --- a/tests/test_regex_detection.py +++ b/tests/test_regex_detection.py @@ -687,10 +687,8 @@ async def test_regex_action_accepts_extra_kwargs(): def test_regex_output_verdict_blocks_on_match(): from nemoguardrails.actions.rail_outcome import RailOutcome - matched = RailOutcome.block( - metadata={"is_match": True, "text": "fight club", "detections": ["\\bfight\\s+club\\b"]} - ) - no_match = RailOutcome.allow(metadata={"is_match": False, "text": "hello", "detections": []}) + matched = RailOutcome.block(metadata={"is_match": True, "detections": ["\\bfight\\s+club\\b"]}) + no_match = RailOutcome.allow(metadata={"is_match": False, "detections": []}) assert matched.is_blocked is True assert no_match.is_blocked is False