Skip to content

Commit c43e483

Browse files
ci(platform): enforce uipath.platform layering with import-linter
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>
1 parent c5924ec commit c43e483

27 files changed

Lines changed: 1405 additions & 724 deletions

.github/workflows/lint-packages.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ jobs:
139139
working-directory: packages/uipath-platform
140140
run: uv run ruff format --check .
141141

142+
- name: Check import contracts
143+
if: steps.check.outputs.skip != 'true'
144+
working-directory: packages/uipath-platform
145+
run: uv run lint-imports
146+
142147
lint-uipath:
143148
name: Lint uipath
144149
needs: detect-changed-packages

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ wheels/
2828

2929
.cache/*
3030
.cache
31+
.import_linter_cache/
3132
site
3233

3334

packages/uipath-platform/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pytest tests/services/test_assets_service.py # Single test file
1717
ruff check . # Lint
1818
ruff format --check . # Format check
1919
mypy src tests # Type check
20+
uv run lint-imports # Import-graph layering contract
2021
```
2122

2223
No justfile exists for this package — run commands directly.

packages/uipath-platform/pyproject.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ dev = [
4747
"pytest-cov>=4.1.0",
4848
"pytest-mock>=3.11.1",
4949
"pre-commit>=4.1.0",
50+
"import-linter>=2.0",
5051
]
5152

5253
[tool.hatch.build.targets.wheel]
@@ -126,6 +127,31 @@ exclude_lines = [
126127
"@(abc\\.)?abstractmethod",
127128
]
128129

130+
[tool.importlinter]
131+
root_package = "uipath.platform"
132+
exclude_type_checking_imports = true
133+
134+
[[tool.importlinter.contracts]]
135+
id = "platform-layers"
136+
name = "uipath-platform layered architecture"
137+
type = "layers"
138+
containers = ["uipath.platform"]
139+
exhaustive = true
140+
# TODO: Remove this exception in the next breaking-change release.
141+
ignore_imports = [
142+
"uipath.platform.common.interrupt_models -> uipath.platform.resume_triggers.interrupt_models",
143+
]
144+
unmatched_ignore_imports_alerting = "error"
145+
layers = [
146+
"guardrails",
147+
"_uipath : resume_triggers",
148+
"_guardrails_service | agenthub | automation_ops | automation_tracker | connections | context_grounding | entities | external_applications | governance | memory | pii_detection | portal | resource_catalog | semantic_proxy",
149+
"orchestrator",
150+
"action_center | attachments | chat | documents | identity",
151+
"common",
152+
"constants | errors",
153+
]
154+
129155
[tool.uv]
130156
exclude-newer = "2 days"
131157

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Internal UiPath Guardrails service layer."""
2+
3+
from ._models import (
4+
BYO_VALIDATOR_TYPE,
5+
BuiltInValidatorGuardrail,
6+
EnumListParameterValue,
7+
EnumParameterValue,
8+
GuardrailType,
9+
MapEnumParameterValue,
10+
NumberParameterValue,
11+
TextListParameterValue,
12+
TextParameterValue,
13+
ValidatorParameter,
14+
)
15+
from ._service import GuardrailsService
16+
17+
__all__ = [
18+
"BYO_VALIDATOR_TYPE",
19+
"BuiltInValidatorGuardrail",
20+
"EnumListParameterValue",
21+
"EnumParameterValue",
22+
"GuardrailType",
23+
"GuardrailsService",
24+
"MapEnumParameterValue",
25+
"NumberParameterValue",
26+
"TextListParameterValue",
27+
"TextParameterValue",
28+
"ValidatorParameter",
29+
]
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""Models used by the UiPath Guardrails service."""
2+
3+
from enum import Enum
4+
from typing import Annotated, Literal
5+
6+
from pydantic import BaseModel, ConfigDict, Field
7+
from uipath.core.guardrails import BaseGuardrail
8+
9+
10+
class EnumListParameterValue(BaseModel):
11+
"""Enum list parameter value."""
12+
13+
parameter_type: Literal["enum-list"] = Field(alias="$parameterType")
14+
id: str
15+
value: list[str]
16+
17+
model_config = ConfigDict(populate_by_name=True, extra="allow")
18+
19+
20+
class MapEnumParameterValue(BaseModel):
21+
"""Map enum parameter value."""
22+
23+
parameter_type: Literal["map-enum"] = Field(alias="$parameterType")
24+
id: str
25+
value: dict[str, float]
26+
27+
model_config = ConfigDict(populate_by_name=True, extra="allow")
28+
29+
30+
class NumberParameterValue(BaseModel):
31+
"""Number parameter value."""
32+
33+
parameter_type: Literal["number"] = Field(alias="$parameterType")
34+
id: str
35+
value: float
36+
37+
model_config = ConfigDict(populate_by_name=True, extra="allow")
38+
39+
40+
class EnumParameterValue(BaseModel):
41+
"""Single-select enum parameter value."""
42+
43+
parameter_type: Literal["enum"] = Field(alias="$parameterType")
44+
id: str
45+
value: str
46+
47+
model_config = ConfigDict(populate_by_name=True, extra="allow")
48+
49+
50+
class TextParameterValue(BaseModel):
51+
"""Free-text parameter value."""
52+
53+
parameter_type: Literal["text"] = Field(alias="$parameterType")
54+
id: str
55+
value: str
56+
57+
model_config = ConfigDict(populate_by_name=True, extra="allow")
58+
59+
60+
class TextListParameterValue(BaseModel):
61+
"""List-of-text parameter value."""
62+
63+
parameter_type: Literal["text-list"] = Field(alias="$parameterType")
64+
id: str
65+
value: list[str]
66+
67+
model_config = ConfigDict(populate_by_name=True, extra="allow")
68+
69+
70+
ValidatorParameter = Annotated[
71+
EnumListParameterValue
72+
| MapEnumParameterValue
73+
| NumberParameterValue
74+
| EnumParameterValue
75+
| TextParameterValue
76+
| TextListParameterValue,
77+
Field(discriminator="parameter_type"),
78+
]
79+
80+
BYO_VALIDATOR_TYPE = "byo"
81+
82+
83+
class BuiltInValidatorGuardrail(BaseGuardrail):
84+
"""Built-in validator guardrail model."""
85+
86+
guardrail_type: Literal["builtInValidator"] = Field(alias="$guardrailType")
87+
validator_type: str = Field(alias="validatorType")
88+
validator_parameters: list[ValidatorParameter] = Field(
89+
default_factory=list, alias="validatorParameters"
90+
)
91+
byo_validator_name: str | None = Field(default=None, alias="byoValidatorName")
92+
93+
model_config = ConfigDict(populate_by_name=True, extra="allow")
94+
95+
96+
class GuardrailType(str, Enum):
97+
"""Guardrail type enumeration."""
98+
99+
BUILT_IN_VALIDATOR = "builtInValidator"
100+
CUSTOM = "custom"

0 commit comments

Comments
 (0)