feat(platform): add GovernanceService for agenticgovernance_ ingress#1738
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new GovernanceService to uipath-platform to wrap the org-scoped agenticgovernance_ ingress (policy retrieval + compensating govern call), and exposes a new resolve_trace_id() helper so callers can capture the effective trace id before moving work off-thread.
Changes:
- Introduces
uipath.platform.governance.GovernanceServicewith sync/asyncretrieve_policy()andcompensate()APIs, plus Pydantic request/response models. - Adds public
resolve_trace_id(fallback)helper touipath.platform.commonfor retrieving the current trace id as a normalized 32-char hex string. - Adds a comprehensive test suite for governance + trace-id resolution and bumps
uipath-platformversion0.1.71→0.1.72.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/uipath-platform/tests/services/test_governance_service.py | Adds coverage for governance policy retrieval, compensation payload behavior, and resolve_trace_id() normalization/fallbacks. |
| packages/uipath-platform/src/uipath/platform/governance/policy.py | Defines PolicyResponse response model with safe defaults and extra-field tolerance. |
| packages/uipath-platform/src/uipath/platform/governance/compensate.py | Defines FiredRule and GovernRequest models with wire-format aliases for compensation POST payloads. |
| packages/uipath-platform/src/uipath/platform/governance/_governance_service.py | Implements the GovernanceService API, URL/header composition, agentType param mapping, and job-context auto-fill. |
| packages/uipath-platform/src/uipath/platform/governance/init.py | Exposes governance service/models as a public module. |
| packages/uipath-platform/src/uipath/platform/common/_base_service.py | Adds resolve_trace_id() helper alongside existing trace header injection logic. |
| packages/uipath-platform/src/uipath/platform/common/init.py | Re-exports resolve_trace_id from uipath.platform.common. |
| packages/uipath-platform/src/uipath/platform/_uipath.py | Adds UiPath.governance cached_property for convenient access from the main client. |
| packages/uipath-platform/pyproject.toml | Bumps package version to 0.1.72. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…nsate - Regenerate uv.lock in uipath-platform and uipath after the 0.1.71 -> 0.1.72 bump so `uv sync --locked` (CI lint check) passes. - In `_build_govern_payload`, use `wire_key in payload` instead of truthiness — `exclude_none=True` already drops caller-None fields, so presence is the right "was it supplied?" signal. The previous truthiness check would silently replace a caller-supplied empty string with the env-backed UiPathConfig value, violating the documented precedence (caller > UiPathConfig > omit). Caught by Copilot review on PR #1738. - Add regression test pinning the empty-string contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
27d012b to
cfc92c0
Compare
cfc92c0 to
cf5c49e
Compare
🚨 Heads up:
|
cf5c49e to
c49cfc6
Compare
41bd919 to
08e0d09
Compare
08e0d09 to
6b9a74a
Compare
6b9a74a to
eb9ec46
Compare
eb9ec46 to
acec4dd
Compare
…governance_ ingress Declare GovernancePolicyProvider / GovernanceCompensationProvider in uipath-core alongside EvaluatorProtocol — runtime consumers depend on the lowest layer; concrete providers live outside this package. Add GovernanceService in uipath-platform for the agenticgovernance_ ingress: - retrieve_policy(*, is_conversational=None) — GET the tenant policy pack. - compensate(*, hook, validators, rules, data, trace_id, ...) — POST the compensating /runtime/govern call when a guardrail_fallback rule matches; job-context fields auto-fill from UiPathConfig with caller values (including empty strings) taking precedence over the env-backed fallback. - Honors UIPATH_SERVICE_URL_AGENTICGOVERNANCE for local dev — the override path injects routing headers since the platform router is bypassed. Add UiPathPlatformGovernanceProvider as the dedicated adapter that satisfies both core protocols by delegating to the service; runtime consumers receive this object via constructor injection without importing uipath-platform directly. Add resolve_trace_id() helper in platform common to capture the canonical trace id before hopping off the OTel-context-owning thread. The runtime-side wrapper that consumes these protocols is intentionally not in this PR — it will land in uipath-runtime in a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
acec4dd to
39bfaba
Compare
|
🚨 Heads up:
|
…tocol Delete the AdapterRegistry / BaseAdapter / GovernedAgentBase plumbing from uipath-core plus the uipath.governance.adapters entry-point group and the get_adapter_registry / reset_adapter_registry helpers. Keep EvaluatorProtocol — the one contract framework plugins still consume. The adapter registry was a second plugin-discovery system keyed on the same fact (which framework is active) that UiPathRuntimeFactoryRegistry already resolves statically. It existed to support an open-PR pattern where the runtime layer would sniff the framework off an opaque agent; that approach is being abandoned in favor of framework plugins wiring governance at their own native seam, so the registry becomes dead weight. Grep across the monorepo confirms zero remaining consumers outside the deleted files. Companion changes landing in the same PR ---------------------------------------- - EvaluatorProtocol returns are typed as AuditRecord (was an untyped list); the trace_id field is dropped from AuditRecord since per-evaluation trace ids aren't part of the contract. - GovernancePolicyProvider grows get_policy_async so async hosts can fetch the policy pack without thread-pool offloading. - GovernanceService self-resolves the compensation trace_id when the caller leaves it empty (same fallback chain track_event uses); the new _resolve_request_trace_id helper lets the runtime layer stay env-free. - uipath-core governance docstrings drop all uipath-platform / uipath-runtime name-drops (per radu's PR #1738 layering rule) — AuditRecord, GovernRequest, the module __init__ and config.py now describe the contracts without referencing higher-layer packages. Version bumps and pins ---------------------- - uipath-core 0.5.22 → 0.5.24 - uipath-platform 0.1.7x → 0.1.79; pin raised to uipath-core>=0.5.24 - uipath is unchanged (no source-level edits in this PR); its uv.lock refreshes only to pick up the bumped uipath-core / uipath-platform versions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tocol Delete the AdapterRegistry / BaseAdapter / GovernedAgentBase plumbing from uipath-core plus the uipath.governance.adapters entry-point group and the get_adapter_registry / reset_adapter_registry helpers. Keep EvaluatorProtocol — the one contract framework plugins still consume. The adapter registry was a second plugin-discovery system keyed on the same fact (which framework is active) that UiPathRuntimeFactoryRegistry already resolves statically. It existed to support an open-PR pattern where the runtime layer would sniff the framework off an opaque agent; that approach is being abandoned in favor of framework plugins wiring governance at their own native seam, so the registry becomes dead weight. Grep across the monorepo confirms zero remaining consumers outside the deleted files. Companion changes landing in the same PR ---------------------------------------- - EvaluatorProtocol returns are typed as AuditRecord (was an untyped list); the trace_id field is dropped from AuditRecord since per-evaluation trace ids aren't part of the contract. - GovernancePolicyProvider grows get_policy_async so async hosts can fetch the policy pack without thread-pool offloading. - GovernRequest.trace_id models absence as str | None (default None) instead of a magic empty-string sentinel — core no longer encodes the "empty means resolve at the platform boundary" platform-side convention. ``""`` is now a legitimate caller value distinct from absence; ``None`` lets the concrete provider fill it. - GovernanceService.compensate self-resolves trace_id only when the caller passes None; ``""`` and any other value pass through. The _resolve_request_trace_id helper switched from a truthiness check to an explicit `is not None` check. - uipath-core governance docstrings drop all uipath-platform / uipath-runtime name-drops (per radu's PR #1738 layering rule) — AuditRecord, GovernRequest, the module __init__ and config.py now describe the contracts without referencing higher-layer packages. Version bumps and pins ---------------------- - uipath-core 0.5.22 → 0.5.24 - uipath-platform 0.1.7x → 0.1.79; pin raised to uipath-core>=0.5.24 - uipath is unchanged (no source-level edits in this PR); its uv.lock refreshes only to pick up the bumped uipath-core / uipath-platform versions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tocol Delete the AdapterRegistry / BaseAdapter / GovernedAgentBase plumbing from uipath-core plus the uipath.governance.adapters entry-point group and the get_adapter_registry / reset_adapter_registry helpers. Keep EvaluatorProtocol — the one contract framework plugins still consume. The adapter registry was a second plugin-discovery system keyed on the same fact (which framework is active) that UiPathRuntimeFactoryRegistry already resolves statically. It existed to support an open-PR pattern where the runtime layer would sniff the framework off an opaque agent; that approach is being abandoned in favor of framework plugins wiring governance at their own native seam, so the registry becomes dead weight. Grep across the monorepo confirms zero remaining consumers outside the deleted files. Companion changes landing in the same PR ---------------------------------------- - EvaluatorProtocol returns are typed as AuditRecord (was an untyped list); the trace_id field is dropped from AuditRecord since per-evaluation trace ids aren't part of the contract. - GovernancePolicyProvider grows get_policy_async so async hosts can fetch the policy pack without thread-pool offloading. - GovernRequest.trace_id models absence as str | None (default None) instead of a magic empty-string sentinel — core no longer encodes the "empty means resolve at the platform boundary" platform-side convention. ``""`` is now a legitimate caller value distinct from absence; ``None`` lets the concrete provider fill it. - GovernanceService.compensate self-resolves trace_id only when the caller passes None; ``""`` and any other value pass through. The _resolve_request_trace_id helper switched from a truthiness check to an explicit `is not None` check. - uipath-core governance docstrings drop all uipath-platform / uipath-runtime name-drops (per radu's PR #1738 layering rule) — AuditRecord, GovernRequest, the module __init__ and config.py now describe the contracts without referencing higher-layer packages. Version bumps and pins ---------------------- - uipath-core 0.5.22 → 0.5.24 - uipath-platform 0.1.7x → 0.1.79; pin raised to uipath-core>=0.5.24 - uipath is unchanged (no source-level edits in this PR); its uv.lock refreshes only to pick up the bumped uipath-core / uipath-platform versions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tocol Delete the AdapterRegistry / BaseAdapter / GovernedAgentBase plumbing from uipath-core plus the uipath.governance.adapters entry-point group and the get_adapter_registry / reset_adapter_registry helpers. Keep EvaluatorProtocol — the one contract framework plugins still consume. The adapter registry was a second plugin-discovery system keyed on the same fact (which framework is active) that UiPathRuntimeFactoryRegistry already resolves statically. It existed to support an open-PR pattern where the runtime layer would sniff the framework off an opaque agent; that approach is being abandoned in favor of framework plugins wiring governance at their own native seam, so the registry becomes dead weight. Grep across the monorepo confirms zero remaining consumers outside the deleted files. Companion changes landing in the same PR ---------------------------------------- - EvaluatorProtocol returns are typed as AuditRecord (was an untyped list); the trace_id field is dropped from AuditRecord since per-evaluation trace ids aren't part of the contract. - GovernancePolicyProvider grows get_policy_async so async hosts can fetch the policy pack without thread-pool offloading. - GovernRequest.trace_id models absence as str | None (default None) instead of a magic empty-string sentinel — core no longer encodes the "empty means resolve at the platform boundary" platform-side convention. ``""`` is now a legitimate caller value distinct from absence; ``None`` lets the concrete provider fill it. - GovernanceService.compensate self-resolves trace_id only when the caller passes None; ``""`` and any other value pass through. The _resolve_request_trace_id helper switched from a truthiness check to an explicit `is not None` check. - uipath-core governance docstrings drop all uipath-platform / uipath-runtime name-drops (per radu's PR #1738 layering rule) — AuditRecord, GovernRequest, the module __init__ and config.py now describe the contracts without referencing higher-layer packages. Version bumps and pins ---------------------- - uipath-core 0.5.22 → 0.5.24 - uipath-platform 0.1.7x → 0.1.79; pin raised to uipath-core>=0.5.24 - uipath is unchanged (no source-level edits in this PR); its uv.lock refreshes only to pick up the bumped uipath-core / uipath-platform versions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>



Summary
Platform-side foundation for UiPath agent governance — protocols in
uipath-coreand a concreteGovernanceService+ provider adapter inuipath-platform. The runtime-side wrapper that consumes these will land inuipath-runtimein a follow-up PR.uipath-core— declaresGovernancePolicyProvider/GovernanceCompensationProviderprotocols (runtime_checkable) alongsideEvaluatorProtocol. Owns the wire-format modelsPolicyContext,PolicyResponse,FiredRule,GovernRequest. No registry or concrete provider —uipath-coredoes not depend onuipath-platformoruipath-runtime.uipath-platform— addsGovernanceService(sdk.governance) for the org-scopedagenticgovernance_ingress with public ergonomic methods:retrieve_policy(*, is_conversational=None)+ async — GET the tenant policy pack.compensate(*, hook, validators, rules, data, trace_id, src_timestamp, agent_name, runtime_id, folder_key=None, job_key=None, process_key=None, reference_id=None, agent_version=None)+ async — POST the compensating/runtime/governcall; buildsGovernRequestinternally; job-context fields auto-fill fromUiPathConfigwith caller values (including empty strings) taking precedence over the env-backed fallback.UIPATH_SERVICE_URL_AGENTICGOVERNANCEfor local dev — the override path injects routing headers since the platform router is bypassed.uipath-platform— addsUiPathPlatformGovernanceProvideras the dedicated adapter that satisfies both core protocols by delegating to the service. Runtime consumers receive this object via constructor injection without importinguipath-platformdirectly. Construct viaUiPathPlatformGovernanceProvider(service=UiPath().governance)or by passingconfig/execution_context.uipath-platform— adds publicresolve_trace_id(fallback)helper incommon/_base_service.pyso callers can capture the trace id on the hook thread before hopping to a background pool.Bumps:
uipath-core0.5.20 → 0.5.21,uipath-platform0.1.71 → 0.1.73,uipath2.11.6 → 2.11.9.Motivation
Replaces hand-rolled
urllibHTTP inuipath-runtime-pythonPRs #121 and #123 per radu-mocanu's review comment: "tenant/org/access token etc. are platform concerns. The runtime's concern is just managing the policies."The runtime can't import
uipath-platform(dependency direction is the other way), so the protocols live inuipath-core. The runtime layer will accept the provider via constructor injection — no global registry, no hidden imports across the layer boundary.After this lands (and the follow-up runtime PR), the runtime gives up
urllib, the browser-UA WAF workaround, manualUIPATH_ACCESS_TOKENreads, manual org/tenant resolution, and the import-timeuipath-platformdependency —BaseServiceprovides all of that, and the runtime depends only onuipath-coreprotocols + a constructor-injected provider.Design choices
uipath-core, concrete provider inuipath-platform— keepsuipath-coreas the lowest layer with no upward dependencies; runtime consumers depend only on the protocols. No module-level provider registry — providers move via constructor injection.runtime_checkableProtocols — any object exposingget_policy(context)/compensate(request)satisfies the contract.compensate(*, hook, ...)is the ergonomic SDK shape that matchesretrieve_policy's pattern and stays consistent with the rest of the platform services. The internal_compensate(request)is the worker the provider adapter calls to satisfy the protocol shape without unpacking.UiPathPlatformGovernanceProvideris the dedicated adapter — implements both core protocols by delegating toGovernanceService. The service is the SDK surface; the provider is the runtime-facing contract.RequestSpec+Endpoint): theagenticgovernance_ingress uses{origin}/{org_id_uuid}/agenticgovernance_/...— the org UUID, not the slugUiPathUrl.scope_urlproduces. Documented in_build_org_scoped_request.UIPATH_SERVICE_URL_AGENTICGOVERNANCEfor local dev —_build_org_scoped_requestresolves the override itself (mirroring whatBaseService.requestdoes for paths that fitscope_url) and injects routing headers, because the org-UUID URL shape can't go through the standardscope_urlflow.is_conversational: bool | Nonerather than an enum: matches the runtime's existing_agent_is_conversationalflag. TheTrue/False/None → conversational/autonomous/omitwire mapping is an implementation detail of the service.resolve_trace_idreturns the value, doesn't mutate headers: the existing_inject_trace_contextonly injects the header. Compensation needs the id in the request body, and must resolve on the hook thread before the pool hop because OpenTelemetry context is thread-local.@cached_propertyonsdk.governance: matchesbuckets/attachments/folders. Avoids freshhttpx.Client/AsyncClientper access in the background compensation loop.PolicyResponse.mode: EnforcementMode | Nonewith lenient coercion: a@field_validator(mode="before")maps unknown wire values toNoneinstead of raising, so a server-side mode addition can't break agent startup.Out of scope (follow-up PRs)
GovernanceRuntime(callsprovider.get_policy()at execute/stream start, exposescompensate()to agent code) will land inuipath-runtime, not here. CLI wiring (cli_run/cli_debugconstructing the runtime with the injected provider) lives there too.EnablePythonGovernanceCheckerfeature flag: kept in place until test-pypi rollout finishes; will be removed in a separate PR.Test plan
ruff check ./ruff format --check .— clean across all three packagesmypy src tests— cleanpytest --no-cov— full suite greentests/governance/test_providers.py): protocol conformance (runtime_checkableaccept/reject), wire-format models (defaults, camelCase aliasing + intentional snake_casesrc_timestamp, optional job-context excluded whenNone, lenientEnforcementModecoercion).tests/services/test_governance_service.py(28) andtests/services/test_governance_provider.py(9): policy fetch happy path / defaults / header + bearer wiring /agentTypequery param (parameterized True/False) / missing org-tenant / HTTP errors / async; compensate payload aliasing / job-context auto-fill from env / caller override precedence / caller empty-string preserved over env / omitted keys / HTTP errors / async;GovernanceService.get_policysatisfies the policy protocol;UiPathPlatformGovernanceProviderconstruction (existing service, built-from-config, requires service or full kwargs) + both-protocol conformance + delegation;UIPATH_SERVICE_URL_AGENTICGOVERNANCEredirects both policy fetch and compensate POST; 5resolve_trace_idcases.uipath-runtime-pythontracer: fix lint #121/cli: pack should include mermaid files #123) to be refactored to consume the constructor-injected provider — separate PRs.🤖 Generated with Claude Code
Development Packages
uipath-core
uipath
uipath-platform