feat(governance): policy backend client, YAML compiler, loader#121
feat(governance): policy backend client, YAML compiler, loader#121aditik0303 wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds the “native” governance policy ingestion path: fetch policy YAML + enforcement mode from the backend, compile YAML into an in-memory PolicyIndex, and cache/prefetch it at runtime startup.
Changes:
- Introduces a governance backend client + policy API client for one-shot policy fetches (fail-open).
- Adds a YAML →
PolicyIndexcompiler that tolerates partial/malformed packs by skipping invalid rules/checks. - Implements a cached loader with optional background prefetch plus extensive unit tests covering fetch/parse/load behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_yaml_to_index.py | Comprehensive tests for YAML → PolicyIndex compilation across supported check types and edge cases. |
| tests/test_policy_api_client.py | Tests for policy fetch skip paths, HTTP failure handling, and JSON body parsing. |
| tests/test_policy_agent_type.py | Tests agent-type selector behavior and URL query parameter composition. |
| tests/test_loader.py | Tests loader caching, prefetch coordination, enforcement mode application, and empty-index diagnostics. |
| src/uipath/runtime/governance/native/policy_api_client.py | Implements policy URL building, one-shot GET, and backend response parsing into PolicyResponse. |
| src/uipath/runtime/governance/native/loader.py | Adds cached loader + background prefetch coordination and enforcement-mode application. |
| src/uipath/runtime/governance/native/backend_client.py | Shared backend URL/header composition, org/tenant resolution, agent-type selector, and safe-call helper. |
| src/uipath/runtime/governance/native/_yaml_to_index.py | YAML compiler from packs/rules/checks into native governance models. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
418fd8f to
14bd3cc
Compare
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… worker failure, default explicit conditions to AND, policy_chars label, importorskip wrapper in agent-type test Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- backend_client/policy_api_client/loader read org/tenant (+ job context) from the environment via runtime-local ENV_* constants instead of importing UiPathConfig. Adds ENV_TRACE_ID. Diagnostic/log messages no longer reference uipath-platform. - _yaml_to_index: convert the parsed logic string to the Logic enum (Check.logic is now typed Logic). - test_loader: assert on env-var names; import reset helper from tests._helpers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
14bd3cc to
acfa5b5
Compare
…their definition site) loader.py imported ENV_ORGANIZATION_ID/ENV_TENANT_ID/resolve_organization_id/ resolve_tenant_id from policy_api_client, which only re-imports them from backend_client — tripping mypy's no_implicit_reexport (4 attr-defined errors). Import them directly from backend_client where they're defined. No runtime change; clears mypy across the stack. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
This PR is crossing the library boundaries.
I raised the same concern on the first PR in this stack: uipath-runtime should stay at the runtime abstraction layer and should not own platform-specific concerns like auth, tenant/org resolution, access tokens, base URL handling, HTTP transport, headers, retries/timeouts or UiPath platform endpoint details.
In this PR, /runtime/policy transport and platform request context are implemented inside uipath-runtime. that duplicates logic already owned by uipath-platform and creates another place where platform auth/config behavior has to be maintained.
uipath-runtime should not own the /runtime/policy transport at all. runtime should only consume a policy source abstraction or already-resolved policy content.
My proposed approach:
- define a small protocol/model in
uipath-core, e.g.GovernancePolicyProvider/GovernancePolicyResponse uipath-runtimedepends only on that protocol and turnspoliciesintoPolicyIndex- uipath-platform` implements the provider using existing platform auth/config/HTTP services (check how the other services are implemented)
- the top-level
uipathCLI wires the platform provider into runtime construction
As a general note:
uipath-core defines contracts
uipath-platform talks to UiPath platform
uipath-runtime handles runtime policy parsing/evaluation
uipath (CLI) composes them
…ovider
Replaces the direct backend HTTP fetch with a GovernancePolicyProvider
indirection so the runtime no longer owns transport, auth, or wire
format. Adds the GovernanceRuntime wrapper and the architecture doc.
- src/uipath/runtime/governance/runtime.py: new GovernanceRuntime(delegate,
policy_provider). Extracts delegate._agent_definition.is_conversational
(depth-capped chain walk), registers the provider, kicks off prefetch.
Passthrough at execute/stream/get_schema/dispose — policy loading only,
no enforcement yet (evaluator slice lands separately).
- src/uipath/runtime/governance/native/loader.py: provider-only loader.
set_policy_provider, set_agent_conversational, prefetch_policy_index,
get_policy_index, clear_policy_cache. Cached PolicyIndex; fail-open on
every failure path (raise / empty / malformed / zero rules / timeout).
- src/uipath/runtime/governance/native/_yaml_to_index.py: drop hardcoded
default clause-id messages ("A.7.4" / "A.8.4" / "A.10.4"); messages
now come from YAML, defaulting to "".
- src/uipath/runtime/governance/config.py: docstrings reworded for the
provider-supplied enforcement mode (no endpoint references).
- Removed src/uipath/runtime/governance/native/policy_api_client.py and
src/uipath/runtime/governance/native/backend_client.py — direct HTTP
fetcher and its shared helpers. Selector + timeout moved into loader.py.
- pyproject.toml: bump uipath-core to ==0.5.21.
- tests: new tests/test_governance_runtime.py (extraction, fail-open,
selector-overwrite regression, prefetch integration), rewritten
tests/test_loader.py for the provider contract, shared StubPolicyProvider
in tests/_helpers.py.
- docs/governance-architecture.md: provider-only design with explicit
'policy loading only, no enforcement yet' staging caveat, module map,
lifecycle diagram, failure-mode table.
ruff / mypy clean, 197 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
55510f9 to
e708f4f
Compare
…sational Addresses radu's review on PR #121 — collapses three architectural boundary concerns into the loader/runtime layers. 1. PolicyLoader is now instance-scoped, not module-globals. Each GovernanceRuntime constructs its own loader carrying its own provider, cache, prefetch state, and conversational selector. uipath eval can spin up multiple runtimes in parallel without them clobbering each other's policy state. 2. is_governance_enabled() reads removed from the runtime layer. The decision "should governance attach?" belongs to the wiring layer (uipath CLI) — it chooses whether to construct GovernanceRuntime at all. Inside the loader the contract is purely "provider present → load policies; provider missing → empty PolicyIndex". The feature flag itself stays in uipath-core. 3. _extract_is_conversational and its delegate-walking deleted. GovernanceRuntime now takes is_conversational explicitly as a keyword arg; the wiring layer (which knows the agent type) passes it in. Runtime no longer reaches into _delegate._agent_definition private attrs. Plus two correctness fixes called out in the readiness re-check: - clear_cache() vs in-flight prefetch worker race: worker now checks _prefetch_event is event before publishing self._policy_index so an orphaned worker can't clobber the just-cleared cache. - _load_from_provider takes the narrowed provider as a parameter instead of asserting self._provider is not None — the bandit B101 "assert stripped under -O" finding is now gone. Tests rewritten around PolicyLoader instances; cross-instance isolation pinned; orphan-worker race regression test added; conftest autouse reset fixture removed (no module state to clean). 187 pass, ruff/mypy/bandit clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses radu's follow-up on PR #121 (discussion r3465934815): the enforcement mode was still process-level scoped via config._state, defeating the point of the loader instance-scoping when uipath eval runs parallel runtimes with mixed-mode policies. - PolicyLoader now owns _enforcement_mode and exposes it via the enforcement_mode property (defaults to AUDIT when no provider response has supplied a mode) - _load_from_provider writes the instance field instead of calling the global set_enforcement_mode - config.py deleted entirely: _state / _EnforcementModeState / get_enforcement_mode / set_enforcement_mode are gone. No production consumers outside the loader; canonical EnforcementMode lives in uipath.core.governance Tests: - _helpers.reset_enforcement_mode dropped (no global to reset) - test_enforcement_mode_default rewritten around PolicyLoader.enforcement_mode; new test_two_loaders_carry_independent_enforcement_modes pins the cross-instance isolation invariant - test_governance_runtime / test_loader drop the reset fixture and the get/set imports; mode-persistence test exercises two consecutive loads on a single loader 188 passed, ruff/mypy/bandit clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stacked PR 2/7 — part of splitting
feat/governance-coreinto reviewable slices. Base:feat/governance-foundation. One logical slice (branch is cumulative so CI is green). Merge in order #1 → #7 and delete each branch on merge so the next PR auto-retargets ontofeat/agentic-governance.feat/governance-corekept untouched as backup.