feat(governance): enforcement-mode config, policy models, deps#120
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds the initial “governance core” slice in uipath-runtime, introducing runtime-scoped enforcement-mode state, a native policy model, and the baseline dependency/test scaffolding needed to support policy evaluation in subsequent stacked PRs.
Changes:
- Add
EnforcementModeresolution + caching APIs inuipath.runtime.governance.config(defaulting toAUDITwith env/programmatic overrides). - Introduce dataclass-based native policy models (
Rule,Check,Condition,PolicyIndex, etc.) underuipath.runtime.governance.native. - Add governance-related dependencies and baseline tests/fixtures for enforcement-mode behavior.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
uv.lock |
Updates the lockfile to include new governance-related dependencies and bumps uipath-core. |
pyproject.toml |
Adds runtime deps (pyyaml, vaderSentiment, chardet) and dev dep (types-PyYAML), plus mypy overrides for missing stubs. |
src/uipath/runtime/governance/config.py |
New module implementing runtime enforcement-mode resolution/caching. |
src/uipath/runtime/governance/native/models.py |
New dataclass model layer for native governance policies and indexing. |
tests/test_enforcement_mode_default.py |
Adds unit tests validating enforcement-mode resolution order and caching behavior. |
tests/conftest.py |
Adds an autouse fixture intended to reset governance-related process state between tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… forward refs in docstrings, guard backend_client import in conftest Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| name: str | ||
| clause: str | ||
| hook: LifecycleHook | ||
| action: Action |
There was a problem hiding this comment.
we also have an action per check. what happens if
Rule.action = Action.AUDIT
Check.action = Action.DENY
?
There was a problem hiding this comment.
For Rule.action = AUDIT, Check.action = DENY: the matched check's action wins, so the rule resolves to DENY. Then enforcement mode decides: ENFORCE blocks it, AUDIT downgrades it to log-only, DISABLED skips.
There was a problem hiding this comment.
makes sense, thanks.
I would include this in a docstring as well
|
|
||
|
|
||
| @dataclass | ||
| class CheckContext: |
There was a problem hiding this comment.
instead of a dataclass with a large bag of fields we should use hook-specific contexts as pydantic objects
BeforeAgentContext
AfterAgentContext
BeforeModelContext
ToolCallContext
AfterToolContext
and create a union type for them. this will provide out-of-the-box model validation so one can t write stuff like:
CheckContext(
hook=LifecycleHook.TOOL_CALL,
agent_output="some output",
tool_name=""
)
There was a problem hiding this comment.
Splitting CheckContext into per hook Pydantic types plus a union touches multiple files: the models definition, every hook construction site in the evaluator, the places reading the context fields, and the context tests. Since it spans the stack, I'll track it in the parent PR and do it once these get merged.
| agent_input: str = "" | ||
| agent_output: str = "" | ||
| model_input: str = "" | ||
| model_output: str = "" | ||
| model_name: str = ( |
There was a problem hiding this comment.
CheckContext narrows most runtime payloads to str, but runtime inputs/outputs/tool results are often structured objects. should these be Any/dict/message types, or should serialization happen explicitly in the evaluator?
There was a problem hiding this comment.
I'll pick this up together with the per-hook context split, since both touch the same CheckContext types and evaluator serialization. The fields are typed str today but the evaluator already stringifies, so I'll widen them to accept structured payloads and add explicit serialization in the evaluator as part of that refactor.
- config.py: enforcement mode no longer reads the UIPATH_GOVERNANCE_MODE env var directly (the backend /runtime/policy response is the source); default is AUDIT. Mode state lives in a holder object instead of a module global (no `global` statements). The test-only reset helper moved out to tests/_helpers.py so test concerns stay in the test tree. - models.py: Check.logic is now the Logic(str, Enum) instead of a free-form string; tidy the CheckContext.model_name field. - pyproject: add dependency upper bounds (pyyaml<7, vaderSentiment<4, chardet<8); remove the [[tool.mypy.overrides]] block. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| from enum import Enum | ||
|
|
||
|
|
||
| class EnforcementMode(str, Enum): |
There was a problem hiding this comment.
we need to either delete this class now and update the uipath-core dependency once this PR gets merged, or at least leave a ToDo comment here and delete this class in a subsequent PR
There was a problem hiding this comment.
Raising the uipath-core PR: UiPath/uipath-python#1727. I'll raise the runtime changes once the uipath-core changes are merged.
| # The enforcement mode is owned by the backend: the policy loader applies | ||
| # the mode from the ``/runtime/policy`` response via | ||
| # :func:`set_enforcement_mode`. | ||
| _state = _EnforcementModeState() |
There was a problem hiding this comment.
this still leaves enforcement mode as process-global state in this PR, just behind _state instead of a global variable. shouldn t the resolved mode live under the evaluator instance?
There was a problem hiding this comment.
Agreed, I'll move the resolved mode onto the evaluator instance in a follow up
EnforcementMode is a shared governance contract, so it now lives in uipath.core.governance (uipath-core 0.5.19) and is re-exported from config.py — runtime callers keep importing it from one place, but the value type is owned by the lower abstraction level (per radu's review). Bumps the uipath-core floor to 0.5.19. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stacked PR 1/7 — part of splitting
feat/governance-coreinto reviewable slices. Base:feat/agentic-governance. 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.