Skip to content

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

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

ci(platform): enforce uipath.platform layering with import-linter#1788
andreiancuta-uipath wants to merge 9 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

  • New 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.
    • The new imports are canonical, while deprecated lazy exports remain in uipath.platform.common temporarily so consumers can migrate without restoring the eager cycle.
  • guardrails: decoupled from the facade. GuardrailValidatorBase.run now uses GuardrailsService directly, with a default service built from env config.
    • common.auth.build_api_config: code extracted from _uipath.py, shared by the facade and the default guardrails service.

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).

The models now live canonically in resume_triggers.

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 — canonical 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)<br/>temporary lazy aliases"]
        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
        A_COMMON -. "deprecated compatibility<br/>(temporary exception)" .-> A_IM
    end
    style B_IM fill:#ffdddd,stroke:#cc0000,color:#000
    style A_IM fill:#ddffdd,stroke:#00aa00,color:#000
    style A_COMMON fill:#fff3cd,stroke:#b8860b,color:#000
Loading

guardrails: facade cycle broken with a direct service dependency

The facade imports GuardrailsService at module top, and GuardrailValidatorBase.run lazily imported the facade back — a static two-package cycle _uipath ⇄ guardrails. Validators now cache a GuardrailsService directly, built from environment config on first use, leaving only the one-way _uipath → guardrails dependency.

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_FACTORY["default_guardrails_service()"]
            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 -- "lazy default" --> GA_FACTORY
        GA_FACTORY --> GA_SVC
        GA_FACTORY --> GA_AUTH
    end
    style GB_G fill:#ffdddd,stroke:#cc0000,color:#000
    style GA_FACTORY fill:#ddffdd,stroke:#00aa00,color:#000
    style GA_SVC fill:#ddffdd,stroke:#00aa00,color:#000
Loading

Compatibility and migration

  • Interrupt models should now be imported from uipath.platform.resume_triggers.
  • Existing imports from uipath.platform.common and uipath.platform.common.interrupt_models remain as deprecated lazy aliases and resolve to the canonical objects.
  • The bridge and its single import-linter exception are temporary and will be removed after consumers migrate.
  • Known consumers to update:
    • 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

  • The layers contract passes with one documented temporary compatibility exception.
  • Fresh-interpreter coverage verifies legacy-first and canonical import order without recreating the cycle.
  • Compatibility tests verify old imports resolve to the canonical model objects and emit a deprecation warning.

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 →

@andreiancuta-uipath andreiancuta-uipath marked this pull request as draft July 7, 2026 07:52
andreiancuta-uipath and others added 5 commits July 14, 2026 14:21
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>
@andreiancuta-uipath andreiancuta-uipath force-pushed the feat/platform-import-linter branch from 9984c7e to 6e36039 Compare July 14, 2026 15:35
@github-actions

Copy link
Copy Markdown

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

Your changes may break the uipath-langchain-python integration.

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

🔍 Inspect the failed run →

@andreiancuta-uipath andreiancuta-uipath force-pushed the feat/platform-import-linter branch from 6e36039 to 69c4bfe Compare July 14, 2026 16:11
@sonarqubecloud

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