Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions packages/uipath-core/src/uipath/core/governance/config.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
"""Governance configuration.

Process-level feature-flag gate that decides whether the Python
governance checker runs at all. The
:class:`uipath.core.governance.EnforcementMode` value type is defined
in :mod:`uipath.core.governance.models`; the per-policy runtime state
that selects a mode (backend-supplied via the ``/runtime/policy``
client) lives outside this package.
Process-level feature-flag gate that lets direct callers (agents
constructing an evaluator themselves) decide whether to wire up
governance. The :class:`uipath.core.governance.EnforcementMode` value
type is defined in :mod:`uipath.core.governance.models`; the per-policy
runtime state that selects a mode (backend-supplied via the
``/runtime/policy`` client) lives outside this package.
"""

from __future__ import annotations

import warnings

from uipath.core.feature_flags import FeatureFlags

# Feature flag name controlling whether governance runs.
Expand All @@ -22,6 +24,14 @@
def is_governance_enabled() -> bool:
"""Return whether the ``EnablePythonGovernanceChecker`` flag is enabled.

.. deprecated::
The CLI ``run`` / ``debug`` bootstrap no longer consults this
gate — it always fetches policy and lets the backend-supplied
enforcement mode decide. This helper remains only for direct
callers (agents constructing an evaluator themselves) that gate
their own wiring on the flag, and will be removed in a future
major release.

Governance is **off by default** — the flag must be explicitly set
to ``true`` (programmatically via the ``FeatureFlags`` registry, or
via the ``UIPATH_FEATURE_EnablePythonGovernanceChecker`` env var)
Expand All @@ -34,4 +44,10 @@ def is_governance_enabled() -> bool:
gitops) and its own ``UIPATH_FEATURE_<name>`` env-var fallback.
2. Default ``False`` (governance disabled).
"""
warnings.warn(
"is_governance_enabled() is deprecated: the CLI bootstrap no longer "
"gates on it and it will be removed in a future major release.",
DeprecationWarning,
stacklevel=2,
)
return FeatureFlags.is_flag_enabled(GOVERNANCE_FEATURE_FLAG, default=False)
23 changes: 19 additions & 4 deletions packages/uipath-core/tests/governance/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ def test_governance_flag_name_is_stable():
assert GOVERNANCE_FEATURE_FLAG == "EnablePythonGovernanceChecker"


def test_is_governance_enabled_is_deprecated():
"""The helper is retained for external callers but warns on use.

The CLI bootstrap no longer gates on it; direct callers (agents
wiring their own evaluator) may still use it, but should migrate
off before it is removed in a future major release.
"""
with pytest.warns(DeprecationWarning):
is_governance_enabled()

Comment on lines +34 to +36

def test_is_governance_enabled_defaults_to_false():
"""With nothing configured, the gate defaults to disabled.

Expand All @@ -33,22 +44,26 @@ def test_is_governance_enabled_defaults_to_false():
keeps the SDK safe-by-default for callers that haven't yet
integrated with the governance backend.
"""
assert is_governance_enabled() is False
with pytest.warns(DeprecationWarning):
assert is_governance_enabled() is False


def test_is_governance_enabled_respects_programmatic_disable():
"""Programmatic ``False`` flips the gate off."""
FeatureFlags.configure_flags({GOVERNANCE_FEATURE_FLAG: False})
assert is_governance_enabled() is False
with pytest.warns(DeprecationWarning):
assert is_governance_enabled() is False


def test_is_governance_enabled_respects_programmatic_enable():
"""Programmatic ``True`` keeps the gate on."""
FeatureFlags.configure_flags({GOVERNANCE_FEATURE_FLAG: True})
assert is_governance_enabled() is True
with pytest.warns(DeprecationWarning):
assert is_governance_enabled() is True


def test_is_governance_enabled_reads_env_var_fallback(monkeypatch):
"""When nothing is configured programmatically, the env-var fallback wins."""
monkeypatch.setenv(f"UIPATH_FEATURE_{GOVERNANCE_FEATURE_FLAG}", "false")
assert is_governance_enabled() is False
with pytest.warns(DeprecationWarning):
assert is_governance_enabled() is False
4 changes: 0 additions & 4 deletions packages/uipath/src/uipath/_cli/_governance_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from dataclasses import dataclass

from uipath.core.governance import EnforcementMode, PolicyContext
from uipath.core.governance.config import is_governance_enabled
from uipath.platform import UiPath
from uipath.platform.governance import UiPathPlatformGovernanceProvider
from uipath.platform.governance._live_track_event_dispatcher import (
Expand Down Expand Up @@ -91,9 +90,6 @@ async def resolve_governance(
to :class:`PolicyContext` so the backend can select the
conversational or autonomous policy view.
"""
if not is_governance_enabled():
return None

context = PolicyContext(is_conversational=is_conversational)

try:
Expand Down
76 changes: 0 additions & 76 deletions packages/uipath/tests/cli/test_governance_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,6 @@ def _stub_provider(


class TestResolveGovernance:
async def test_returns_none_when_feature_flag_disabled(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: False,
)
assert (
await resolve_governance(
agent_framework="langgraph",
agent_type="uipath_coded",
is_conversational=False,
)
is None
)

@pytest.mark.parametrize("is_conversational", [True, False])
async def test_is_conversational_forwarded_verbatim_to_policy_context(
self,
Expand All @@ -155,10 +139,6 @@ async def test_is_conversational_forwarded_verbatim_to_policy_context(
bootstrap must forward the exact value to :class:`PolicyContext`
so the backend can select the conversational or autonomous
policy view."""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -192,10 +172,6 @@ async def test_returns_none_when_policy_fetch_fails(
monkeypatch: pytest.MonkeyPatch,
cwd: Path,
) -> None:
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand All @@ -218,10 +194,6 @@ async def test_returns_none_when_mode_is_disabled(
monkeypatch: pytest.MonkeyPatch,
cwd: Path,
) -> None:
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -249,10 +221,6 @@ async def test_returns_none_when_mode_is_none(
monkeypatch: pytest.MonkeyPatch,
cwd: Path,
) -> None:
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand All @@ -278,10 +246,6 @@ async def test_returns_none_when_policies_empty(
monkeypatch: pytest.MonkeyPatch,
cwd: Path,
) -> None:
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -313,10 +277,6 @@ async def test_returns_none_when_policy_compilation_fails(
governance skips cleanly rather than propagating a ``YAMLError``
out of ``resolve_governance``.
"""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -352,10 +312,6 @@ async def test_success_returns_bootstrap_with_dispose_contract(
"""Happy path -- named fields populated and dispose unregisters
atexit + shuts the dispatcher down.
"""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -427,10 +383,6 @@ async def test_agent_type_forwarded_verbatim_to_metadata(
the project; the factory does via
:attr:`UiPathRuntimeFactorySettings.agent_type`.
"""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -471,10 +423,6 @@ async def test_agent_framework_none_passes_through_as_unknown(
) -> None:
"""A factory with no ``agent_framework`` opinion emits
``"unknown"`` -- symmetric with the ``agent_type`` fallback."""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -511,10 +459,6 @@ async def test_agent_type_none_passes_through_to_metadata(
) -> None:
"""A factory with no ``agent_type`` opinion yields ``None`` on
the metadata -- the backend decides how to interpret the gap."""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -557,10 +501,6 @@ async def test_wrap_runtime_produces_governed_runtime_with_bootstrap_fields(
the caller's ``agent_name`` / ``runtime_id``. This is the code
path CLI callers replaced their manual construction with.
"""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -607,10 +547,6 @@ async def test_returns_none_when_dispatcher_init_fails(
is optional and a failing bootstrap must not crash the CLI. No
``atexit`` hook should leak.
"""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -667,10 +603,6 @@ async def test_returns_none_and_cleans_up_when_evaluator_setup_fails(
raises, ``resolve_governance`` must unregister the ``atexit`` hook
AND shut the dispatcher down before returning ``None``.
"""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)

class _ExplodingEvaluator:
def __init__(self, *_a: Any, **_kw: Any) -> None:
Expand Down Expand Up @@ -739,10 +671,6 @@ async def test_dispose_swallows_dispatcher_shutdown_errors(
raise, or it will mask the primary exception. A shutdown that
blows up should be logged at debug and swallowed.
"""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down Expand Up @@ -807,10 +735,6 @@ async def test_dispose_atexit_hook_matches_dispatcher_shutdown(
callable that ``atexit.register`` received — otherwise unregister
is a silent no-op and the dispatcher lingers.
"""
monkeypatch.setattr(
"uipath._cli._governance_bootstrap.is_governance_enabled",
lambda: True,
)
_install_fake_runtime_governance(
monkeypatch,
audit_manager_cls=_FakeAuditManager,
Expand Down
Loading