feat: Rego WASM governance evaluator (v2)#147
Draft
evanbijoy251 wants to merge 12 commits into
Draft
Conversation
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>
- 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>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
feat/rego-evaluatorbranch, rebased on main's DI-first architecture (no globals, no FF checks inside the class)governance/features/) shared between native and Rego evaluatorsRegoEvaluator— oneOPAPolicyengine perLifecycleHook, receivesAuditManagerandEnforcementModevia DI constructor paramsbuild_rego_evaluator_async(service)for platform-service-driven bootstrap — single async call, no background threads at startup, mirrors native evaluator bootstrap pattern exactlyUiPathGovernedRuntimevia the existingrego_evaluatorkwarg and sequential-merge pattern (both evaluators run per hook; firstGovernanceBlockExceptionraised)Commits
feat: port Rego evaluator to feat/rego-evaluator-v2— full port of evaluator, features, bundle cache, api client, loaderfix: remove native policy loading files— native policy loading moved to uipath-python under uipath-platformfeat: add build_rego_evaluator_async to rego loader— async bootstrap builder; bumps to 0.12.3Dependencies
Depends on
UiPath/uipath-python#feat/rego-evaluatorfor:HookBundle/AllPoliciesResponsewire models (uipath.core.governance)GovernanceService.retrieve_all_policies_async()/download_bundle_async()REGO_FEATURE_FLAG/is_rego_enabled()Test plan
uv run pytestpassesUIPATH_FEATURE_EnablePythonGovernanceRegoEvaluator=truewith a valid bundle triggers Rego evaluationGovernanceBlockExceptionfrom Rego returnsFAULTEDresult withGovernance.PolicyViolationcodeGenerated with Claude Code
Development Package