refactor(governance): hoist policy fetch to host; drop PolicyLoader#133
Merged
viswa-uipath merged 2 commits intoJun 26, 2026
Conversation
GovernanceRuntime now takes a resolved PolicyIndex + EnforcementMode at construction. The host (uipath CLI) does the async fetch via the GovernancePolicyProvider, compiles the YAML through build_policy_index_from_yaml, and hands the snapshot in. The runtime becomes a passive consumer; the host owns lifecycle. - Delete PolicyLoader (343 LOC) and its hand-rolled future (threading.Thread + Event). Async I/O belongs to the async host. - Delete StubPolicyProvider test helper + enforcement-mode-default tests (the mode is now a constructor arg, no default needed). - GovernanceRuntime ctor: (delegate, policy_index, enforcement_mode, *, trace_id=None). No more policy_provider / is_conversational parameters. Agent-type selection lives in the host's PolicyContext construction. - Expose build_policy_index_from_yaml from native/__init__.py for the host's compile step. Net: -890 LOC. Addresses architecture-review item Sec 2.4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- runtime.py: drop §2.4 PR ref and historical "staging caveat" language from module/class docstrings; drop downstream LangChain class name from the generic runtime layer; replace defensive getattr(result, "output", None) with result.output (the outer fail-open try/except already covers a malformed delegate). - evaluator.py: fix stale "loader" reference in docstring → GovernanceRuntime. - _audit/traces.py: rewrite three comments referencing the deleted PolicyLoader to describe the per-runtime model. - _audit/base.py: rewrite two docstrings referencing the deleted PolicyLoader. - native/_yaml_to_index.py: fix broken :mod: link to the deleted native.loader module; describe the platform-host compile flow. No behavior change. ruff/mypy clean, 326 passed + 1 skipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
cristipufu
reviewed
Jun 26, 2026
| return str(payload) | ||
|
|
||
|
|
||
| class GovernanceRuntime: |
Member
There was a problem hiding this comment.
let's name it UiPathGovernedRuntime to be consistent with the other runtime names in this repo
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def _serialize_payload(payload: Any) -> str: |
Member
There was a problem hiding this comment.
I think we have serialization helpers in uipath-core and we can get rid of this one
Author
| return self._enforcement_mode | ||
|
|
||
| @property | ||
| def trace_id(self) -> str | None: |
Member
There was a problem hiding this comment.
Not sure why we need to expose these properties (trace_id, enforcement_mode, policy_index)
Author
There was a problem hiding this comment.
policy_index, and enforcement_mode is needed to run the evaluator in runtime. Rules for each hook resides within that.
079569e
into
feat/governance-guardrail-compensation
79 of 84 checks passed
viswa-uipath
added a commit
that referenced
this pull request
Jun 26, 2026
…contextvars Addresses cristipufu's PR #133 review (rename + drop properties + drop local serializer) and the wider point that ``trace_id`` shouldn't live on the generic runtime layer at all. The platform side (uipath-platform / PR #1761) now self-resolves ``GovernRequest.trace_id`` when the runtime sends an empty value, and the compensator preserves live OTel context across its background-pool hop via ``contextvars.copy_context()`` — so the platform-side resolver still sees the agent's live span when the worker calls ``provider.compensate(...)``. Runtime wrapper (``runtime.py``) - Renamed ``GovernanceRuntime`` → ``UiPathGovernedRuntime`` to match the repo's other runtime names (UiPathResumableRuntime, UiPathDebugRuntime, etc.). - Dropped ``trace_id`` ctor arg. - Dropped the ``policy_index`` / ``enforcement_mode`` / ``trace_id`` read-only properties — they were dead surface area; consumers receive the values from the host at construction time and don't need to read them back through the wrapper. - Replaced the bespoke ``_serialize_payload`` (4 branches + nested try/except) with a 9-line version that delegates the complex case to ``uipath.core.serialization.serialize_object``. ``None → ""`` and ``str → passthrough`` stay as governance-scan special cases (the evaluator's regex / contains / sentiment checks would mismatch against ``"null"`` or ``'"hello"'``). Compensator (``guardrail_compensation.py``) - Dropped ``trace_id`` ctor arg. - Dropped the per-call ``trace_id`` arg from ``submit()``. - Deleted the ``_resolve_trace_id(supplied, fallback)`` helper. - Added ``import contextvars``; ``submit()`` snapshots the caller's context (``ctx = contextvars.copy_context()``) and the pool runs the worker as ``pool.submit(ctx.run, _run)``. The worker therefore sees the agent's live OTel span; the platform's ``resolve_trace_id`` resolves correctly on the worker thread. - ``GovernRequest.trace_id="" `` on the wire — platform fills. Evaluator (``native/evaluator.py``) - All six ``evaluate_*`` per-call methods now default ``trace_id: str = ""`` (was required). Callers that already supply a value (e.g. legacy callers passing through resolved ids) continue to work unchanged. - ``_dispatch_compensation`` no longer passes ``trace_id`` to ``compensator.submit(...)``. Tests - ``test_governance_runtime.py``: rewritten for the renamed class + dropped properties + dropped ctor arg. Asserts internal ``_policy_index`` / ``_enforcement_mode`` instead of properties. - ``test_guardrail_compensation.py``: dropped the four ``_resolve_trace_id`` tests + the constructor-trace-id test. Replaced ``test_submit_captures_live_trace_before_thread_hop`` with ``test_submit_propagates_otel_context_to_worker_thread``: now asserts that ``trace.get_current_span()`` *inside the worker callable* returns the agent's live span (proves the contextvars snapshot propagation works end-to-end). 319 passed, 1 skipped. - ``conftest.py`` / ``test_traces_severity.py``: docstring renames only. ruff + mypy clean (10 source files). Test count: 319 passed, 1 skipped (was 357 — drop is the deleted ``_resolve_trace_id`` tests + the ctor-trace-id test). Co-Authored-By: Claude Opus 4.7 (1M context) <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
Addresses architecture-review item Sec 2.4 — "policy fetch belongs to the async host, not the runtime layer."
GovernanceRuntimeis now a pure consumer of a resolved policy snapshot. The host (uipath CLI) does the async fetch viaGovernancePolicyProvider.get_policy_async, compiles the YAML throughbuild_policy_index_from_yaml, and passes the resultingPolicyIndex+EnforcementModeinto the constructor. The runtime layer no longer carries any I/O or background-thread machinery.What changed
PolicyLoader(343 LOC) along with its hand-rolled future onthreading.Thread+threading.Event. Async fetch is the async host's job.GovernanceRuntimeconstructor signature:policy_provider/is_conversationalparams. Agent-type selection lives in the host'sPolicyContextconstruction.build_policy_index_from_yamlfromnative/__init__.pyso the host has a clean import path for the compile step.StubPolicyProvidertest helper andtest_enforcement_mode_default.py— mode is a constructor arg now, no default-resolution path to test.Net diff
`-890 LOC` (mostly the loader, its tests, and the stub provider).
Constraint check
pyproject.tomlalready pinsuipath-core>=0.5.19, <0.6.0. Theget_policy_asyncprotocol method (unreleased 0.5.23) falls within that range. No bump needed in this PR.Stacking
Layered on top of #124 (
feat/governance-evaluator). The host-side wiring inuipath-pythonis a separate follow-up PR — it consumes the new constructor shape.Test plan
🤖 Generated with Claude Code