ci(platform): enforce uipath.platform layering with import-linter#1788
ci(platform): enforce uipath.platform layering with import-linter#1788andreiancuta-uipath wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
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-linterlayerscontract to statically forbid upward imports and cycles withinuipath.platform, enforced in CI. - Move interrupt/HITL models to
uipath.platform.resume_triggersand update imports/exports/tests accordingly (breaking pre-1.0 re-export removal fromcommon). - Bump
uipath-platformanduipathversions and lockfiles; addimport-linterdev 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.
c2d23ff to
9984c7e
Compare
🚨 Heads up:
|
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>
9984c7e to
6e36039
Compare
🚨 Heads up:
|
6e36039 to
69c4bfe
Compare
|



Why
Follow-up to #1787.
This PR adds an import-linter
layerscontract overuipath.platform, enforced in CI.exhaustive— new subpackages must be explicitly assigned a layerWhat changed
lint-importsstep inlint-packages.yml; contract inpyproject.toml.resume_triggers:interrupt_models.py(HITL suspend/resume payloads) moved fromcommon/toresume_triggers/, its only consumer.uipath.platform.commontemporarily so consumers can migrate without restoring the eager cycle.guardrails: decoupled from the facade.GuardrailValidatorBase.runnow usesGuardrailsServicedirectly, 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 ofcommonEvery service package imports
common(base service, config, errors) — those downward edges are normal. Butinterrupt_models.pylived insidecommonand imported five service packages back, closing a package-level cyclecommon ⇄ services(masked only by import ordering incommon/__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:#000guardrails: facade cycle broken with a direct service dependencyThe facade imports
GuardrailsServiceat module top, andGuardrailValidatorBase.runlazily imported the facade back — a static two-package cycle_uipath ⇄ guardrails. Validators now cache aGuardrailsServicedirectly, built from environment config on first use, leaving only the one-way_uipath → guardrailsdependency.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:#000Compatibility and migration
uipath.platform.resume_triggers.uipath.platform.commonanduipath.platform.common.interrupt_modelsremain as deprecated lazy aliases and resolve to the canonical objects.UiPath/uipath-langchain-python—docs/human_in_the_loop.md, severalsamples/,tests/hitl/mocks/job_trigger_hitl.pyUiPath/prior-auth-poc-fe,UiPath/fins-vertical-solution— coded-agent POC scriptsUiPath/skills/UiPath/AgenticProductSupport— markdown API referencesValidation