Skip to content

ci(platform): enforce uipath.platform layering with import-linter#1788

Draft
andreiancuta-uipath wants to merge 6 commits into
mainfrom
feat/platform-import-linter
Draft

ci(platform): enforce uipath.platform layering with import-linter#1788
andreiancuta-uipath wants to merge 6 commits into
mainfrom
feat/platform-import-linter

Conversation

@andreiancuta-uipath

@andreiancuta-uipath andreiancuta-uipath commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Why

Follow-up to #1787.
This PR adds an import-linter layers contract over uipath.platform, enforced in CI.

  • Statically forbids both cycles and upward imports
  • exhaustive — new subpackages must be explicitly assigned a layer
  • Type-checking-only imports excluded

What changed

  • CI gate: lint-imports step in lint-packages.yml; contract in pyproject.toml.
  • resume_triggers: interrupt_models.py (HITL suspend/resume payloads) moved from common/ to resume_triggers/, its only consumer. All seven formerly-upward edges now point downward; models no longer re-exported from uipath.platform.common. Breaking change
  • guardrails: decoupled from the facade. GuardrailValidatorBase.run no longer lazily constructs UiPath() — it depends on a GuardrailEvaluator protocol, with DefaultGuardrailEvaluator building a GuardrailsService directly from env config.
  • common.auth.build_api_config: pydantic-to-typed-error translation extracted from _uipath.py, shared by the facade and the new evaluator.
  • Only resume_triggers keeps the deliberate call-back-into-facade pattern, so the top layer is _uipath : resume_triggers; guardrails sits in the plain services layer.

How the dependencies were refactored

resume_triggers: interrupt models move out of common

Every service package imports common (base service, config, errors) — those downward edges are normal. But interrupt_models.py lived inside common and imported five service packages back, closing a package-level cycle common ⇄ services (masked only by import ordering in common/__init__, the same class as the 0.1.89 incident). Moving the models into resume_triggers removes the upward half of the loop.

flowchart TB
    subgraph BEFORE["❌ Before — cycle: common ⇄ service layers"]
        direction TB
        B_RT["resume_triggers/_protocol.py"]
        subgraph B_SVC["service layers"]
            B_ORC["orchestrator"]
            B_AC["action_center"]
            B_AT["attachments"]
            B_CG["context_grounding"]
            B_DOC["documents"]
        end
        subgraph B_COMMON["common  (bottom layer)"]
            B_IM["interrupt_models.py"]
            B_BASE["_base_service.py, _config.py, errors, …"]
        end
        B_RT --> B_IM
        B_IM -. "upward ⚠️" .-> B_ORC
        B_IM -. "upward ⚠️" .-> B_AC
        B_IM -. "upward ⚠️" .-> B_AT
        B_IM -. "upward ⚠️" .-> B_CG
        B_IM -. "upward ⚠️" .-> B_DOC
        B_SVC -- "normal downward imports<br/>(closes the cycle)" --> B_BASE
    end
    subgraph AFTER["✅ After — interrupt models in resume_triggers"]
        direction TB
        subgraph A_RT["resume_triggers  (top layer)"]
            A_P["_protocol.py"]
            A_IM["interrupt_models.py"]
        end
        subgraph A_SVC["service layers"]
            A_ORC["orchestrator"]
            A_AC["action_center"]
            A_AT["attachments"]
            A_CG["context_grounding"]
            A_DOC["documents"]
        end
        A_COMMON["common  (bottom layer)"]
        A_P --> A_IM
        A_IM --> A_ORC
        A_IM --> A_AC
        A_IM --> A_AT
        A_IM --> A_CG
        A_IM --> A_DOC
        A_SVC --> A_COMMON
    end
    style B_IM fill:#ffdddd,stroke:#cc0000,color:#000
    style A_IM fill:#ddffdd,stroke:#00aa00,color:#000
Loading

guardrails: facade cycle broken with an evaluator protocol

The facade imports GuardrailsService at module top, and GuardrailValidatorBase.run lazily imported the facade back — a static two-package cycle _uipath ⇄ guardrails. The evaluator protocol removes the upward edge, leaving only the one-way _uipath → guardrails.

flowchart TB
    subgraph GBEFORE["❌ Before — cycle: _uipath ⇄ guardrails"]
        direction TB
        GB_U["_uipath.py  (UiPath facade)"]
        GB_G["guardrails<br/>(GuardrailsService, GuardrailValidatorBase)"]
        GB_U -- "imports GuardrailsService" --> GB_G
        GB_G -. "run() lazily imports UiPath ⚠️" .-> GB_U
    end
    subgraph GAFTER["✅ After — one-way, facade-free"]
        direction TB
        GA_U["_uipath.py  (UiPath facade)"]
        subgraph GA_G["guardrails"]
            GA_BASE["GuardrailValidatorBase.run()"]
            GA_PROTO["GuardrailEvaluator  (Protocol)"]
            GA_DEF["DefaultGuardrailEvaluator"]
            GA_SVC["GuardrailsService"]
        end
        subgraph GA_COMMON["common"]
            GA_AUTH["auth.build_api_config() + resolve_config_from_env()"]
        end
        GA_U --> GA_SVC
        GA_U --> GA_AUTH
        GA_BASE --> GA_PROTO
        GA_DEF -- "implements" --> GA_PROTO
        GA_BASE -- "lazy default" --> GA_DEF
        GA_DEF --> GA_SVC
        GA_DEF --> GA_AUTH
    end
    style GB_G fill:#ffdddd,stroke:#cc0000,color:#000
    style GA_PROTO fill:#ddffdd,stroke:#00aa00,color:#000
    style GA_DEF fill:#ddffdd,stroke:#00aa00,color:#000
Loading

Breaking change (0.1.x -> 0.2.0)

  • Interrupt models must now be imported from uipath.platform.resume_triggers (no longer re-exported from uipath.platform.common)
  • No shim: an eager one would recreate the cycle, a lazy one still violates the contract
  • Org-wide code search found no published package sources using the old path; coordinated updates needed in:
    • UiPath/uipath-langchain-pythondocs/human_in_the_loop.md, several samples/, tests/hitl/mocks/job_trigger_hitl.py
    • UiPath/prior-auth-poc-fe, UiPath/fins-vertical-solution — coded-agent POC scripts
    • UiPath/skills / UiPath/AgenticProductSupport — markdown API references

Validation

  • Negative test: re-adding from ..identity import IdentityService to common/_base_service.py breaks the contract (reproduces the 0.1.89 class)
  • Fresh-interpreter imports of uipath.platform.identity, uipath.platform.resume_triggers, and the facade all clean
  • Versions bumped for the breaking re-export removal: uipath-platform 0.2.0, uipath 2.13.0

🤖 Generated with Claude Code

andreiancuta-uipath and others added 2 commits July 3, 2026 13:46
Add an import-linter layers contract over uipath.platform, enforced as a
CI step in the lint-uipath-platform job. The contract statically forbids
import cycles and upward imports between domain packages — the class of
bug behind the 0.1.89 identity/common incident (#1787). Type-checking
imports are excluded; the contract is exhaustive so new subpackages must
be assigned a layer explicitly.

The contract is intentionally committed without ignores: lint-imports
fails at this commit on the pre-existing upward imports from
common/interrupt_models.py, fixed in the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
common/interrupt_models.py imported action_center, attachments,
context_grounding, documents, and orchestrator at runtime, forming a
cycle cluster around common — the same layering inversion class that
broke 0.1.89 (only masked by import ordering). The models are HITL
suspend/resume payloads consumed solely by resume_triggers, so they move
into that package; all former upward edges now point downward and the
import-linter contract passes with zero ignores.

The models are importable from uipath.platform.resume_triggers. No
re-export shim in common — an eager one recreates the cycle and a lazy
one still violates the layering contract. Known external usages
(uipath-langchain-python docs/samples/test mocks, POC repos) import from
uipath.platform.common and need a coordinated update; none are published
package sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 10:54
@github-actions github-actions Bot added test:uipath-langchain Triggers tests in the uipath-langchain-python repository test:uipath-integrations labels Jul 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds CI enforcement for a layered architecture within uipath.platform using import-linter, and resolves an existing layering inversion by relocating HITL interrupt/resume models from common to resume_triggers (with corresponding import path updates and version bumps).

Changes:

  • Add an import-linter layers contract to statically forbid upward imports and cycles within uipath.platform, enforced in CI.
  • Move interrupt/HITL models to uipath.platform.resume_triggers and update imports/exports/tests accordingly (breaking pre-1.0 re-export removal from common).
  • Bump uipath-platform and uipath versions and lockfiles; add import-linter dev dependency and ignore its cache.

Reviewed changes

Copilot reviewed 8 out of 12 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/uipath/uv.lock Bumps uipath and uipath-platform versions; adds import-linter to dev metadata.
packages/uipath/pyproject.toml Bumps uipath version and raises minimum uipath-platform version.
packages/uipath-platform/uv.lock Bumps uipath-platform version; locks import-linter and its transitive deps.
packages/uipath-platform/tests/services/test_hitl.py Updates tests to import interrupt models from resume_triggers instead of common.
packages/uipath-platform/src/uipath/platform/resume_triggers/interrupt_models.py Introduces relocated interrupt/HITL models under resume_triggers.
packages/uipath-platform/src/uipath/platform/resume_triggers/_protocol.py Switches protocol implementation to import models from local resume_triggers.
packages/uipath-platform/src/uipath/platform/resume_triggers/init.py Re-exports interrupt models from resume_triggers as the new public import path.
packages/uipath-platform/src/uipath/platform/common/init.py Removes re-exports of interrupt models from common (breaking change as intended).
packages/uipath-platform/pyproject.toml Adds import-linter dev dependency and defines the platform-layers contract.
packages/uipath-platform/CLAUDE.md Documents the new lint-imports command for local validation.
.gitignore Ignores .import_linter_cache/.
.github/workflows/lint-packages.yml Adds CI step to run uv run lint-imports for uipath-platform.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@andreiancuta-uipath andreiancuta-uipath force-pushed the feat/platform-import-linter branch from c2d23ff to 9984c7e Compare July 6, 2026 11:08
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

🚨 Heads up: uipath-integrations cross-tests are FAILING 🚨

Your changes may break one or more integrations in uipath-integrations-python:

  • uipath-openai-agents
  • uipath-google-adk
  • uipath-agent-framework
  • uipath-llamaindex
  • uipath-pydantic-ai

⚠️ These checks are NOT enforced by branch protection rules. Please review the failures before merging.

🔍 Inspect the failed run →

@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@andreiancuta-uipath andreiancuta-uipath marked this pull request as draft July 7, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:uipath-integrations test:uipath-langchain Triggers tests in the uipath-langchain-python repository

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants