feat(cli): shared host-side governance bootstrap for run and debug#1778
feat(cli): shared host-side governance bootstrap for run and debug#1778viswa-uipath wants to merge 1 commit into
Conversation
1a11593 to
04ad14f
Compare
🚨 Heads up:
|
38435f6 to
9d066a2
Compare
9d066a2 to
91a66d9
Compare
91a66d9 to
6d482ee
Compare
|
shouldn t we apply the governance to evals as well? |
|
Overall blocking concern: this PR crosses package boundaries by making the public The clean contract is to add |
6d482ee to
5c8fb05
Compare
There was a problem hiding this comment.
Pull request overview
This PR extracts host-side governance bootstrapping into a shared CLI module and reuses it from both uipath run and uipath debug, so they fetch/compile policy and stamp identical audit + telemetry events. It also adds a CLI-side YAML → PolicyIndex compiler and updates dependencies/tests accordingly.
Changes:
- Added shared governance bootstrap module (
_cli/_governance_bootstrap.py) and wired it into bothcli_runandcli_debug. - Added CLI-side governance helpers (
_cli/_governance/yaml_index.py) to compile YAML policies into a runtimePolicyIndex. - Added comprehensive tests for governance bootstrap and YAML compilation, and bumped package/dependency versions (including
pyyaml).
Reviewed changes
Copilot reviewed 10 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/uipath/uv.lock | Updates lockfile for new/updated dependencies (runtime bump, PyYAML/types, etc.). |
| packages/uipath/pyproject.toml | Bumps uipath version and updates dependencies (incl. uipath-runtime and pyyaml). |
| packages/uipath/src/uipath/_cli/_governance_bootstrap.py | New shared governance bootstrap used by both run/debug paths. |
| packages/uipath/src/uipath/_cli/_governance/init.py | New CLI governance helper package export. |
| packages/uipath/src/uipath/_cli/_governance/yaml_index.py | New YAML policy compiler producing PolicyIndex for the runtime. |
| packages/uipath/src/uipath/_cli/cli_run.py | Wires shared governance bootstrap into uipath run and manages teardown. |
| packages/uipath/src/uipath/_cli/cli_debug.py | Wires shared governance bootstrap into uipath debug and manages teardown. |
| packages/uipath/src/uipath/_cli/_utils/_common.py | Adds shared is_coded_agent() helper used for consistent agent-type labeling. |
| packages/uipath/src/uipath/_cli/_evals/_telemetry.py | Reuses is_coded_agent() for consistent telemetry labeling. |
| packages/uipath/tests/cli/test_governance_bootstrap.py | New tests covering feature-flag gating, policy fetch/compile branches, cleanup contract, and conversational flag read. |
| packages/uipath/tests/cli/_governance/test_yaml_index.py | New tests covering YAML policy parsing and check-type compilation. |
| packages/uipath/tests/cli/_governance/init.py | Initializes governance test package. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
17fff0a to
cd0d458
Compare
8416792 to
1a20051
Compare
Extracts the governance-bootstrap helpers used by ``uipath run`` into a shared ``_cli/_governance_bootstrap.py`` module and wires the same path into ``uipath debug`` so both commands stamp identical audit + telemetry events. Adds a CLI-side YAML -> ``PolicyIndex`` compiler so the runtime never has to depend on ``pyyaml`` or the wire policy format. GovernanceBootstrap dataclass ----------------------------- ``resolve_governance()`` returns a ``GovernanceBootstrap`` (frozen dataclass with ``evaluator`` / ``policy_index`` / ``enforcement_mode`` / ``dispose`` fields plus a ``wrap_runtime()`` method) or ``None`` when governance should not fire. Callers hand the base runtime to ``bootstrap.wrap_runtime(delegate, agent_name=..., runtime_id=...)`` and call ``bootstrap.dispose()`` in ``finally`` -- no manual ``UiPathGovernedRuntime`` construction and no tuple unpacking. Factory-declared identity ------------------------- Framework and agent-type wire labels are advertised by the factory via ``UiPathRuntimeFactorySettings.agent_framework`` and ``UiPathRuntimeFactorySettings.agent_type``. The CLI reads both from ``factory.get_settings()`` and forwards them verbatim to ``GovernanceRuntimeMetadata``; the OSS package no longer classifies projects by filename or hardcoded marker registries. Missing values default to ``"unknown"`` at the metadata boundary. Eval telemetry keeps its historical wire values ----------------------------------------------- ``EvalSetRunCreatedEvent`` carries ``agent_type`` populated from the factory, and ``EvalTelemetrySubscriber._resolve_agent_type()`` normalizes modern factory labels back to the pre-refactor ``"LowCode"`` / ``"Coded"`` strings so Application Insights dashboards that filter on those exact values keep working. When the factory advertises nothing, the subscriber falls back to the pre-refactor entrypoint-based derivation (``agent.json`` -> ``"LowCode"``, else ``"Coded"``). Dispose ordering ---------------- In both ``uipath run`` and ``uipath debug``, runtime-scoped sinks (``LiveTrackingSpanProcessor``, ``GovernanceBootstrap`` track-event dispatcher) drain before ``factory.dispose()`` -- the factory may own transports the sinks push through. Error handling -------------- Governance is optional -- a failing bootstrap must not crash the run. Every step in ``resolve_governance()`` that can raise (policy fetch, policy compile, dispatcher init, compensator / audit-manager / evaluator construction) is caught; the CLI logs a warning and runs un-governed. If a failure lands after the dispatcher spawns its background thread and registers its ``atexit`` hook, the recovery path calls ``dispose()`` to unregister and shut down so no thread leaks. ``dispose()`` runs from CLI ``finally`` blocks, so it swallows and logs any exception rather than propagating. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1a20051 to
7e711b6
Compare
🚨 Heads up:
|
|



Summary
Extracts the governance-bootstrap helpers used by
uipath runinto a shared_cli/_governance_bootstrap.pymodule and wires the same path intouipath debug, so both commands stamp identical audit + telemetry events. Adds a CLI-side YAML →PolicyIndexcompiler so the runtime layer never has to depend onpyyamlor the wire policy format.GovernanceBootstrapdataclassresolve_governance()returns a frozenGovernanceBootstrap(withevaluator/policy_index/enforcement_mode/disposefields plus awrap_runtime()method) orNonewhen governance should not fire. Callers hand the base runtime tobootstrap.wrap_runtime(delegate, agent_name=..., runtime_id=...)and callbootstrap.dispose()from afinallyblock — no manualUiPathGovernedRuntimeconstruction, no tuple unpacking.Factory-declared identity
Wire labels for governance / App Insights come from
UiPathRuntimeFactorySettings— never from the CLI:agent_framework: str | None— factory declares its framework identity (e.g."langgraph")agent_type: str | None— factory declares the coarse type (e.g."uipath_coded","uipath_lowcode")Both flow:
factory.get_settings()→ CLI →GovernanceRuntimeMetadataverbatim. Missing values default to"unknown"at the metadata boundary.is_coded_agentand low-code filename detection are removed entirely.Eval telemetry consumes the same source:
EvalSetRunCreatedEventcarriesagent_typepopulated from the factory;EvalTelemetrySubscriberforwards it verbatim to the App InsightsAgentTypedimension.Error handling
Governance is optional — a failing bootstrap must not crash the run. Every step in
resolve_governance()that can raise (policy fetch, policy compile, dispatcher init, compensator / audit-manager / evaluator construction) is caught; the CLI logs a warning and runs un-governed. If a failure lands after the dispatcher spawns its background thread and registers itsatexithook, the recovery path callsdispose()to unregister and shut down so no thread leaks.dispose()runs from CLIfinallyblocks, so it swallows and debug-logs bothatexit.unregisterand dispatcher-shutdown errors — it must never mask the primary exception. Safe to call more than once: the dispatcher'sshutdownearly-returns on repeat calls andatexit.unregisteris a no-op for missing handlers.cli_debugparitycli_debugcallsresolve_governance()afterfactory.get_settings(), computesgovernance_runtime_idonce (ctx.conversation_id or ctx.job_id or "default"), passes the evaluator intofactory.new_runtimeand wraps viabootstrap.wrap_runtime— matchingcli_run's shape exactly.Runtime dependency
Depends on
uipath-runtimeshippingUiPathRuntimeFactorySettings.agent_frameworkand.agent_type. The pin will be bumped in a follow-up commit.Adapter follow-ups (outside this PR)
Each third-party factory should declare its own wire labels in
get_settings()— until they do, their governance/App Insights emits"unknown":agent_frameworkagent_typeuipath-agents-python(low-code)"uipath_lowcode"uipath-langchain-python"langgraph""uipath_coded"uipath-integrations-python/*"llamaindex","openai_agents","pydantic_ai","google_adk","agent_framework""uipath_coded"Test plan
tests/cli/test_governance_bootstrap.py— feature-flag gate, policy fetch / compile failures, dispose contract,wrap_runtime, error-path cleanup, agent_type / agent_framework forwardingtests/cli/_governance/test_yaml_index.py— every supported check type + pack/rule plumbingtests/cli/eval/test_eval_telemetry.py— agent_type forwarded from event to App Insights🤖 Generated with Claude Code
Development Packages
uipath