Skip to content

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

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

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

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