Skip to content

feat: Rego WASM governance evaluator (v2)#147

Draft
evanbijoy251 wants to merge 12 commits into
mainfrom
feat/rego-evaluator-v2
Draft

feat: Rego WASM governance evaluator (v2)#147
evanbijoy251 wants to merge 12 commits into
mainfrom
feat/rego-evaluator-v2

Conversation

@evanbijoy251

@evanbijoy251 evanbijoy251 commented Jul 10, 2026

Copy link
Copy Markdown

Summary

  • Ports the Rego/WASM governance evaluator from the old feat/rego-evaluator branch, rebased on main's DI-first architecture (no globals, no FF checks inside the class)
  • Adds feature signal extractors (governance/features/) shared between native and Rego evaluators
  • Adds RegoEvaluator — one OPAPolicy engine per LifecycleHook, receives AuditManager and EnforcementMode via DI constructor params
  • Adds build_rego_evaluator_async(service) for platform-service-driven bootstrap — single async call, no background threads at startup, mirrors native evaluator bootstrap pattern exactly
  • Integrates into UiPathGovernedRuntime via the existing rego_evaluator kwarg and sequential-merge pattern (both evaluators run per hook; first GovernanceBlockException raised)

Commits

  • feat: port Rego evaluator to feat/rego-evaluator-v2 — full port of evaluator, features, bundle cache, api client, loader
  • fix: remove native policy loading files — native policy loading moved to uipath-python under uipath-platform
  • feat: add build_rego_evaluator_async to rego loader — async bootstrap builder; bumps to 0.12.3

Dependencies

Depends on UiPath/uipath-python#feat/rego-evaluator for:

  • HookBundle / AllPoliciesResponse wire models (uipath.core.governance)
  • GovernanceService.retrieve_all_policies_async() / download_bundle_async()
  • REGO_FEATURE_FLAG / is_rego_enabled()

Test plan

  • uv run pytest passes
  • UIPATH_FEATURE_EnablePythonGovernanceRegoEvaluator=true with a valid bundle triggers Rego evaluation
  • Missing bundles → fail-open (agent runs without Rego evaluation, warning logged)
  • GovernanceBlockException from Rego returns FAULTED result with Governance.PolicyViolation code

Generated with Claude Code

Development Package

  • Add this package as a dependency in your pyproject.toml:
[project]
dependencies = [
  # Exact version:
  "uipath-runtime==0.12.3.dev1001470653",

  # Any version from PR
  "uipath-runtime>=0.12.3.dev1001470000,<0.12.3.dev1001480000"
]

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

[tool.uv.sources]
uipath-runtime = { index = "testpypi" }

evanbijoy251 and others added 3 commits July 9, 2026 15:20
Adds WASM-based Rego evaluation alongside the native (YAML/regex)
evaluator, aligned with main's DI-first, FF-free architecture:

- governance/features/ — shared NLP/regex signal extractors (moved from
  rego/features/ so both evaluators can reuse them); commitment, encoding,
  incident, sentiment, text_stats extractors registered via @register()
- governance/rego/ — RegoEvaluator (opa-wasmtime), bundle cache, api_client,
  loader with ETag-driven disk cache + 30s background refresh
- governance/native/ — backend_client, policy_api_client, _yaml_to_index,
  loader (prefetch + background load, no FF gating)
- governance/config.py — re-exports EnforcementMode from uipath.core.governance,
  keeps get/set/reset_enforcement_mode for process-level state
- governance/delegation_guard.py — ASI-02 recursion depth guard
- governance/_audit/console.py — human-readable logging sink
- governance/_audit/factory.py — add "console" sink registration

Architecture alignment with main:
- RegoEvaluator accepts audit_manager + enforcement_mode via DI (no globals)
- emit_rule_evaluation uses policy_id= (main API), enforcement_mode as object
- No FF gating anywhere; enable/disable is server-side via EnforcementMode
- sequential-merge: both evaluators run, violations collected before raise

runtime.py changes:
- rego_evaluator kwarg added to UiPathGovernedRuntime
- _fire_before/after_agent: sequential-merge pattern (native then rego)
- execute()/stream(): catch GovernanceBlockException → FAULTED result
  with error code Governance.PolicyViolation
- _find_governance_block(): walks __cause__/__context__ chain to unwrap
  framework-wrapped GovernanceBlockExceptions

Tests updated: two tests now assert FAULTED result instead of raised exception.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
The /runtime/policy fetch (YAML policy loading, PolicyIndex
construction) lives in uipath-python under platform, not here.
Removes native/loader.py, native/policy_api_client.py, and
native/_yaml_to_index.py that were incorrectly ported.

native/backend_client.py stays — rego/api_client.py uses it for
URL building and headers when fetching WASM bundles from
/all-policies.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Adds build_rego_evaluator_async(service) — a single async call that
fetches /all-policies metadata via the injected platform service,
downloads changed bundles to disk, and returns a RegoEvaluator or
None. Mirrors the native evaluator bootstrap pattern (no background
threads at startup). Bumps version to 0.12.3.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
evanbijoy251 and others added 9 commits July 12, 2026 23:25
- Remove api_client.py and models.py (dead code since build_rego_evaluator_async
  uses the platform service layer; api_client also used wrong URL pattern)
- Remove prefetch/background-refresh/get_rego_evaluator from loader.py
- Fix AuditRecord construction: trace_id is not a field (reliability bug)
- Add 40 tests covering bundle_cache, loader, and evaluator

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
The backend produces bundles where the outer bundle.tar.gz contains a
'policy.wasm' that is itself a gzip-compressed inner tar bundle. The
actual WASM binary lives at /policy.wasm inside that inner tar.

_extract_wasm_from_bundle now handles three levels:
1. Direct WASM in outer bundle (rare)
2. Gzip-compressed WASM in outer bundle
3. Gzip-compressed inner tar → /policy.wasm (current backend format)

Also adds warning log when all-policies returns no bundles and adds
the governance-rego optional dependency group to pyproject.toml.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
[project.optional-dependencies] section boundary caused classifiers
and maintainers to be parsed as optional dependency lists.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Rego WASM violations were evaluated but never emitted as trace spans
because the loader constructed RegoEvaluator without an AuditManager.
Adds an optional audit_manager param to build_rego_evaluator_async and
_build_rego_evaluator_async_inner, passing it through to RegoEvaluator
so callers can wire the shared AuditManager and get Rego rule violations
surfaced alongside native checker events in LLMOps traces.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Without an AuditManager, Rego WASM violations were silently dropped —
evaluated correctly but never emitted as trace spans. Now the evaluator
self-provisions a default AuditManager (traces + track_events sinks)
when the caller passes None, so Rego rule violations surface in LLMOps
traces alongside native checker events without requiring any changes in
caller repos (e.g. uipath-langchain).

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
The langchain adapter holds a reference to GovernanceEvaluator and calls
evaluate_before_model / after_model / tool_call / after_tool on it for
per-step lifecycle hooks. The Rego evaluator was only wired to
UiPathGovernedRuntime._fire_before_agent / _fire_after_agent, so WASM
rules scoped to before_model (e.g. SSN detection) never ran during actual
agent execution and produced no trace spans.

Fix: add set_rego_evaluator() to GovernanceEvaluator. Each per-step hook
method now calls rego_evaluator.evaluate_*() after native evaluation when
a Rego evaluator is wired. UiPathGovernedRuntime.__init__ calls
evaluator.set_rego_evaluator(rego_evaluator) when both are provided,
which mutates the shared object in place so the callback handler already
holding the evaluator reference automatically picks up Rego evaluation
without any changes to the framework adapter.

BEFORE_AGENT / AFTER_AGENT are intentionally excluded — those remain
owned by UiPathGovernedRuntime._fire_before_agent / _fire_after_agent
to avoid double-firing.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
WASM rules like block_ssn_in_prompts check input.agent_input for
user prompt text. The BEFORE_MODEL callback path only populates
model_input, leaving agent_input empty and causing SSN/PII rules
to silently miss violations.

Fix: context_to_input copies model_input to agent_input when
agent_input is empty and hook is BEFORE_MODEL. Both fields
represent the same user content so this normalization is safe.

Also adds INFO logging of raw WASM result for cloud diagnostics.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
The INFO log was including agent_input/model_input text which can
contain PII (SSNs, etc.). Replace with field lengths — enough to
confirm data was populated without exposing content.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Production WASM rules (e.g. block_ssn_in_prompts) check
input.messages[].content (plain string) rather than the flat
model_input field. The callback-handler path never passes messages,
so context.messages is always [] and rules silently miss violations.

Fix: context_to_input now builds a synthetic [{"role": "user",
"content": model_input}] entry when messages is empty for
BEFORE_MODEL. Verified against the production bundle — the SSN rule
now fires: fired_deny=["Message contains a value matching the SSN
pattern (###-##-####)"], deny=true.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
49.8% Coverage on New Code (required ≥ 90%)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant