Skip to content

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

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

ci(platform): enforce uipath.platform layering with import-linter#1788
andreiancuta-uipath wants to merge 2 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: split the public decorator API from the lower-level service and platform-model implementation.
    • GuardrailsService and its platform models now live in _guardrails_service, which the facade can import without loading the public guardrails package.
    • The public guardrails package remains the public API, sits above _uipath, and retains its service/model exports.
    • Built-in validators lazily create and cache UiPath, leaving an acyclic guardrails → _uipath → _guardrails_service dependency chain.
  • Release: bumped uipath-platform from 0.2.9 to 0.2.10 and refreshed the lockfiles.

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"]
            direction LR
            B_ORC["orchestrator"]
            B_AC["action_center"]
            B_AT["attachments"]
            B_CG["context_grounding"]
            B_DOC["documents"]
            B_ORC ~~~ B_AC ~~~ B_AT ~~~ B_CG ~~~ B_DOC
        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 imports to five packages ⚠️" .-> B_SVC
        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"]
            direction LR
            A_ORC["orchestrator"]
            A_AC["action_center"]
            A_AT["attachments"]
            A_CG["context_grounding"]
            A_DOC["documents"]
            A_ORC ~~~ A_AC ~~~ A_AT ~~~ A_CG ~~~ A_DOC
        end
        A_COMMON["common  (bottom layer)<br/>temporary lazy aliases"]
        A_P --> A_IM
        A_IM -- "model dependencies" --> A_SVC
        A_SVC --> A_COMMON
        A_COMMON -. "deprecated compatibility<br/>(temporary exception)" .-> A_IM
    end
    BEFORE ~~~ AFTER
    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: public API split from the service layer

  • The facade previously imported the public guardrails package for GuardrailsService.
  • Built-in validators lazily imported UiPath back through the platform package, producing _uipath ⇄ guardrails.
  • Service code and platform guardrail models now live in the lower-layer _guardrails_service package.
  • The public guardrails package sits above the facade and retains its public exports.
  • _uipath now depends only on _guardrails_service.
  • The resulting dependencies are acyclic: guardrails → _uipath → _guardrails_service → common, plus guardrails → _guardrails_service.
flowchart TB
    subgraph GBEFORE["❌ Before — cycle: _uipath ⇄ guardrails"]
        direction TB
        GB_U["_uipath.py  (UiPath facade)"]
        GB_G["guardrails<br/>(service, models, decorators)"]
        GB_U -- "imports GuardrailsService" --> GB_G
        GB_G -. "run() lazily imports UiPath ⚠️" .-> GB_U
    end
    subgraph GAFTER["✅ After — split public API from service layer"]
        direction TB
        GA_G["guardrails<br/>(public API + decorators, top layer)"]
        GA_U["_uipath.py  (UiPath facade)"]
        GA_INTERNAL["_guardrails_service<br/>(service + platform models)"]
        GA_COMMON["common"]
        GA_G -- "validators lazily create UiPath" --> GA_U
        GA_G -- "re-exports service/models" --> GA_INTERNAL
        GA_U -- "constructs GuardrailsService" --> GA_INTERNAL
        GA_INTERNAL --> GA_COMMON
    end
    GBEFORE ~~~ GAFTER
    style GB_G fill:#ffdddd,stroke:#cc0000,color:#000
    style GA_INTERNAL 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.
    • After consumers are updated, I'll remove these aliases and bump the minor version.
  • Public guardrails service/model imports remain available from uipath.platform.guardrails;
    • NumberParameterValue also remains temporarily available from its existing uipath.platform.guardrails.guardrails path.
  • 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 interrupt-model import order without recreating the cycle.
  • Compatibility tests verify old interrupt-model imports resolve to the canonical objects and emit a deprecation warning.
  • Fresh-interpreter coverage verifies importing _guardrails_service does not load the public guardrails package.

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
andreiancuta-uipath force-pushed the feat/platform-import-linter branch 3 times, most recently from 69c4bfe to b5babed Compare July 15, 2026 10:01
@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 and others added 2 commits July 15, 2026 14:44
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>
@andreiancuta-uipath
andreiancuta-uipath force-pushed the feat/platform-import-linter branch from b5babed to e441141 Compare July 15, 2026 11:45
@sonarqubecloud

Copy link
Copy Markdown

@andreiancuta-uipath
andreiancuta-uipath marked this pull request as ready for review July 15, 2026 12:17
@andreiancuta-uipath
andreiancuta-uipath marked this pull request as draft July 15, 2026 12:51
@andreiancuta-uipath
andreiancuta-uipath marked this pull request as ready for review July 15, 2026 12:51
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