velophex 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. velophex/__init__.py +38 -0
  2. velophex/_json.py +297 -0
  3. velophex/agents/__init__.py +130 -0
  4. velophex/agents/_errors.py +75 -0
  5. velophex/agents/_loop.py +436 -0
  6. velophex/agents/_tools.py +168 -0
  7. velophex/agents/_tracing.py +87 -0
  8. velophex/agents/testing.py +249 -0
  9. velophex/cli/__init__.py +5 -0
  10. velophex/cli/main.py +548 -0
  11. velophex/cli/publish.py +152 -0
  12. velophex/cli/sync.py +90 -0
  13. velophex/cli/templates.py +126 -0
  14. velophex/contracts/__init__.py +103 -0
  15. velophex/contracts/_model.py +26 -0
  16. velophex/contracts/agents.py +110 -0
  17. velophex/contracts/diagnostics.py +157 -0
  18. velophex/contracts/executor.py +1921 -0
  19. velophex/contracts/package.py +391 -0
  20. velophex/contracts/project.py +1008 -0
  21. velophex/contracts/runtimes.py +226 -0
  22. velophex/contracts/signin.py +53 -0
  23. velophex/contracts/validation.py +158 -0
  24. velophex/entrypoints.py +329 -0
  25. velophex/errors.py +44 -0
  26. velophex/packaging.py +465 -0
  27. velophex/py.typed +0 -0
  28. velophex/runtime/__init__.py +471 -0
  29. velophex/runtime/_session.py +192 -0
  30. velophex/runtime/client.py +957 -0
  31. velophex/runtime/models.py +326 -0
  32. velophex/signing.py +425 -0
  33. velophex/worker/__init__.py +27 -0
  34. velophex/worker/__main__.py +118 -0
  35. velophex/worker/framing.py +248 -0
  36. velophex/worker/loader.py +105 -0
  37. velophex/worker/outbox.py +147 -0
  38. velophex/worker/session.py +519 -0
  39. velophex-0.2.0.dist-info/METADATA +313 -0
  40. velophex-0.2.0.dist-info/RECORD +43 -0
  41. velophex-0.2.0.dist-info/WHEEL +4 -0
  42. velophex-0.2.0.dist-info/entry_points.txt +2 -0
  43. velophex-0.2.0.dist-info/licenses/LICENSE +21 -0
velophex/cli/sync.py ADDED
@@ -0,0 +1,90 @@
1
+ """Keeps ``project.json`` in step with the decorated handlers.
2
+
3
+ The entry point schemas in ``project.json`` are what the Orchestrator validates job inputs
4
+ against and what the start form is generated from, so they must not be hand-maintained: a
5
+ schema that has drifted from the handler fails on the robot, not in the editor. ``velophex
6
+ pack`` therefore imports each handler and rewrites its entry point before it packs.
7
+
8
+ Importing runs the author's own module, which is the same thing ``velophex run`` does; the
9
+ synchronisation is skipped with ``--no-sync`` for a project whose handlers cannot be imported
10
+ on the machine that packs (a Windows-only automation packed on a build agent, say).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+
18
+ from velophex.contracts.project import EntryPoint, ProjectFiles, ProjectManifest, RuntimeSpec
19
+ from velophex.contracts.runtimes import RuntimeKind
20
+ from velophex.worker.loader import PackageLoadError, load_entry_point
21
+
22
+ __all__ = ["DEFAULT_PYTHON_VERSION", "SyncResult", "synchronize"]
23
+
24
+ #: ADR-P008: the platform ships CPython 3.12 and nothing else.
25
+ DEFAULT_PYTHON_VERSION = ">=3.12,<3.13"
26
+
27
+
28
+ @dataclass(slots=True)
29
+ class SyncResult:
30
+ """The manifest after synchronisation, and what could not be refreshed."""
31
+
32
+ manifest: ProjectManifest
33
+ warnings: list[str]
34
+ changed: bool
35
+
36
+
37
+ def synchronize(
38
+ project_directory: str | Path,
39
+ manifest: ProjectManifest,
40
+ *,
41
+ import_handlers: bool = True,
42
+ ) -> SyncResult:
43
+ """Return *manifest* with the Python runtime settings and entry point schemas refreshed."""
44
+ warnings: list[str] = []
45
+ runtime = _runtime(manifest.runtime)
46
+ entry_points = list(manifest.entry_points)
47
+
48
+ if import_handlers:
49
+ for index, entry in enumerate(entry_points):
50
+ refreshed, warning = _refresh(project_directory, entry)
51
+ entry_points[index] = refreshed
52
+
53
+ if warning:
54
+ warnings.append(warning)
55
+
56
+ updated = manifest.model_copy(update={"runtime": runtime, "entry_points": entry_points}).normalized()
57
+
58
+ return SyncResult(manifest=updated, warnings=warnings, changed=updated != manifest.normalized())
59
+
60
+
61
+ def _runtime(runtime: RuntimeSpec) -> RuntimeSpec:
62
+ return runtime.model_copy(
63
+ update={
64
+ "kind": RuntimeKind.PYTHON,
65
+ "python_version": runtime.python_version or DEFAULT_PYTHON_VERSION,
66
+ "lock_file": runtime.lock_file or ProjectFiles.PYTHON_LOCK_FILE_NAME,
67
+ }
68
+ )
69
+
70
+
71
+ def _refresh(project_directory: str | Path, entry: EntryPoint) -> tuple[EntryPoint, str | None]:
72
+ try:
73
+ loaded = load_entry_point(project_directory, entry.id)
74
+ except PackageLoadError as error:
75
+ return entry, f"Entry point '{entry.id}' was left as it is: {error}"
76
+
77
+ definition = loaded.definition
78
+
79
+ return (
80
+ entry.model_copy(
81
+ update={
82
+ "handler": definition.handler,
83
+ "display_name": entry.display_name or definition.display_name,
84
+ "description": entry.description or definition.description,
85
+ "input": definition.input_schema,
86
+ "output": definition.output_schema,
87
+ }
88
+ ),
89
+ None,
90
+ )
@@ -0,0 +1,126 @@
1
+ """The project ``velophex init`` writes.
2
+
3
+ A new automation is a uv project with a ``src/`` layout, one decorated handler and a
4
+ ``project.json`` that already says what the platform needs to place and run it. It is
5
+ deliberately small: the schemas come from the handler's signature, so there is nothing in
6
+ ``project.json`` for the author to keep in step by hand.
7
+
8
+ ``uv.lock`` is *not* written here. It is produced by ``uv sync``, which is also what pins the
9
+ ``velophex`` SDK into the lock file - and a Python package without ``uv.lock`` is refused both
10
+ by ``velophex pack`` and by the Orchestrator inspector (ADR-P008).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from uuid import UUID, uuid4
16
+
17
+ from velophex.cli.sync import DEFAULT_PYTHON_VERSION
18
+ from velophex.contracts.project import (
19
+ CURRENT_SCHEMA_VERSION,
20
+ EntryPoint,
21
+ ExecutionSettings,
22
+ ProjectManifest,
23
+ RuntimeSpec,
24
+ )
25
+ from velophex.contracts.runtimes import RuntimeKind
26
+
27
+ __all__ = ["handler_module", "module_name", "new_project_manifest", "pyproject_toml", "python_version_marker"]
28
+
29
+
30
+ def module_name(project_name: str) -> str:
31
+ """The importable module name for a project name such as ``Invoice.Ocr``."""
32
+ cleaned = "".join(character if character.isalnum() else "_" for character in project_name).strip("_").lower()
33
+ return cleaned or "automation"
34
+
35
+
36
+ def python_version_marker() -> str:
37
+ """The ``requires-python`` marker the platform supports."""
38
+ return DEFAULT_PYTHON_VERSION
39
+
40
+
41
+ def new_project_manifest(name: str, version: str = "1.0.0", project_id: UUID | None = None) -> ProjectManifest:
42
+ """The ``project.json`` of a new Python automation."""
43
+ module = module_name(name)
44
+
45
+ return ProjectManifest(
46
+ schemaVersion=CURRENT_SCHEMA_VERSION,
47
+ projectId=project_id or uuid4(),
48
+ name=name,
49
+ version=version,
50
+ description=f"{name} automation.",
51
+ outputType="process",
52
+ runtime=RuntimeSpec(kind=RuntimeKind.PYTHON, pythonVersion=DEFAULT_PYTHON_VERSION, lockFile="uv.lock"),
53
+ main=f"src/{module}/main.py",
54
+ entryPoints=[
55
+ EntryPoint(id="main", path=f"src/{module}/main.py", handler=f"{module}.main:run"),
56
+ ],
57
+ execution=ExecutionSettings(),
58
+ )
59
+
60
+
61
+ def pyproject_toml(name: str, version: str, sdk_specifier: str, sdk_wheel: str | None = None) -> str:
62
+ """The ``pyproject.toml`` of a new automation.
63
+
64
+ With *sdk_wheel* (a path relative to the project) the SDK comes from that vendored wheel
65
+ instead of an index: ``uv.lock`` then records the relative path and the wheel's SHA-256,
66
+ and ``velophex pack`` carries the wheel inside the package, so a Robot's
67
+ ``uv sync --frozen`` restores it from the package itself and never asks an index for a
68
+ package named ``velophex`` (PY-01, PY-02).
69
+ """
70
+ module = module_name(name)
71
+ sources = (
72
+ f"""
73
+ [tool.uv.sources]
74
+ # Vendored: packed with the automation, so a Robot restores the SDK without any index.
75
+ velophex = {{ path = "{sdk_wheel}" }}
76
+ """
77
+ if sdk_wheel
78
+ else ""
79
+ )
80
+
81
+ return f"""[project]
82
+ name = "{module}"
83
+ version = "{version}"
84
+ description = "{name} automation."
85
+ requires-python = "{DEFAULT_PYTHON_VERSION}"
86
+ dependencies = [
87
+ "velophex{sdk_specifier}",
88
+ ]
89
+
90
+ [build-system]
91
+ requires = ["hatchling"]
92
+ build-backend = "hatchling.build"
93
+
94
+ [tool.hatch.build.targets.wheel]
95
+ packages = ["src/{module}"]
96
+ {sources}"""
97
+
98
+
99
+ def handler_module(name: str) -> str:
100
+ """The starting handler of a new automation."""
101
+ return f'''"""Entry points of the {name} automation."""
102
+
103
+ import logging
104
+
105
+ import velophex
106
+ from velophex import runtime
107
+
108
+ log = logging.getLogger(__name__)
109
+
110
+
111
+ @velophex.entrypoint(id="main", display_name="Run")
112
+ def run(message: str = "Hello from VeloPhex") -> dict[str, str]:
113
+ """Say hello.
114
+
115
+ Every parameter becomes an input in project.json, typed from its annotation, and the
116
+ return annotation becomes the output schema - there is nothing to keep in step by hand.
117
+ """
118
+ log.info("%s", message)
119
+
120
+ # Long-running work polls this so a Stop from the Orchestrator ends the run cleanly
121
+ # instead of waiting for the kill.
122
+ if runtime.stop_requested():
123
+ raise velophex.BusinessError("The run was stopped before it did anything.")
124
+
125
+ return {{"message": message}}
126
+ '''
@@ -0,0 +1,103 @@
1
+ """Python models of the VeloPhex platform contracts.
2
+
3
+ One definition per contract, mirroring ``VeloPhex.Platform.Contracts`` and the JSON Schemas
4
+ in ``VeloPhex.Platform.Contracts/schemas``:
5
+
6
+ * :mod:`velophex.contracts.project` - ``project.json`` (ADR-P002)
7
+ * :mod:`velophex.contracts.package` - ``velophex/package-manifest.json`` v2 (ADR-P003)
8
+ * :mod:`velophex.contracts.executor` - the executor protocol messages (ADR-P005)
9
+ * :mod:`velophex.contracts.runtimes` - runtime kinds, phases, levels and fault kinds
10
+ * :mod:`velophex.contracts.signin` - the states a sign-in passes through (D1)
11
+ * :mod:`velophex.contracts.validation` - the lexical rules the schemas express
12
+
13
+ Contracts change only through the ``velophex-contract-change`` skill, which updates every
14
+ consumer together; nothing here may drift on its own.
15
+ """
16
+
17
+ from velophex.contracts.diagnostics import (
18
+ ContractError,
19
+ Diagnostic,
20
+ PackageDiagnosticCodes,
21
+ ProjectDiagnosticCodes,
22
+ Severity,
23
+ has_errors,
24
+ )
25
+ from velophex.contracts.package import (
26
+ PackageFile,
27
+ PackageLayout,
28
+ PackageManifest,
29
+ create_package_manifest,
30
+ describe_file,
31
+ read_package_manifest,
32
+ sha512_hex,
33
+ validate_package_manifest,
34
+ write_package_manifest,
35
+ )
36
+ from velophex.contracts.project import (
37
+ AutomationDependency,
38
+ DesignerSettings,
39
+ EntryPoint,
40
+ ExecutionSettings,
41
+ ExpressionLanguages,
42
+ OutputTypes,
43
+ ProjectFiles,
44
+ ProjectManifest,
45
+ RuntimeSpec,
46
+ read_project_manifest,
47
+ validate_project_manifest,
48
+ write_project_manifest,
49
+ )
50
+ from velophex.contracts.runtimes import (
51
+ AttemptOutcomes,
52
+ ExecutionPhases,
53
+ FaultCodes,
54
+ FaultKinds,
55
+ LogLevels,
56
+ LogStages,
57
+ PhaseStatuses,
58
+ RuntimeKind,
59
+ RuntimeStatuses,
60
+ SignalReasons,
61
+ )
62
+ from velophex.contracts.signin import SignInStates
63
+
64
+ __all__ = [
65
+ "AttemptOutcomes",
66
+ "AutomationDependency",
67
+ "ContractError",
68
+ "DesignerSettings",
69
+ "Diagnostic",
70
+ "EntryPoint",
71
+ "ExecutionPhases",
72
+ "ExecutionSettings",
73
+ "ExpressionLanguages",
74
+ "FaultCodes",
75
+ "FaultKinds",
76
+ "LogLevels",
77
+ "LogStages",
78
+ "OutputTypes",
79
+ "PackageDiagnosticCodes",
80
+ "PackageFile",
81
+ "PackageLayout",
82
+ "PackageManifest",
83
+ "PhaseStatuses",
84
+ "ProjectDiagnosticCodes",
85
+ "ProjectFiles",
86
+ "ProjectManifest",
87
+ "RuntimeKind",
88
+ "RuntimeSpec",
89
+ "RuntimeStatuses",
90
+ "Severity",
91
+ "SignInStates",
92
+ "SignalReasons",
93
+ "create_package_manifest",
94
+ "describe_file",
95
+ "has_errors",
96
+ "read_package_manifest",
97
+ "read_project_manifest",
98
+ "sha512_hex",
99
+ "validate_package_manifest",
100
+ "validate_project_manifest",
101
+ "write_package_manifest",
102
+ "write_project_manifest",
103
+ ]
@@ -0,0 +1,26 @@
1
+ """Shared base for contract models.
2
+
3
+ Contract documents travel between five codebases, so an unknown member is data from a newer
4
+ writer rather than a mistake: models keep it (``extra="allow"``) and emit it again after the
5
+ members they know, which is where ``[JsonExtensionData]`` puts it in .NET.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from pydantic import BaseModel, ConfigDict
13
+
14
+ from velophex._json import omit_none
15
+
16
+ __all__ = ["ContractModel", "omit_none"]
17
+
18
+
19
+ class ContractModel(BaseModel):
20
+ """Base for contract models: camelCase on the wire, unknown members preserved."""
21
+
22
+ model_config = ConfigDict(populate_by_name=True, extra="allow", arbitrary_types_allowed=True)
23
+
24
+ def extras(self) -> dict[str, Any]:
25
+ """Members this version of the SDK does not know, in the order they were read."""
26
+ return dict(self.__pydantic_extra__ or {})
@@ -0,0 +1,110 @@
1
+ """The agent definition, version 1 (``schemas/agent-definition.v1.schema.json``, contracts 0.8.37).
2
+
3
+ Mirrors ``VeloPhex.Platform.Contracts.Agents``. Version 1 changes additively only, so members a
4
+ newer writer adds are kept (``extra="allow"``) rather than refused, and an absent member means the
5
+ default below.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Final
11
+
12
+ from pydantic import Field
13
+
14
+ from velophex.contracts._model import ContractModel
15
+
16
+ __all__ = [
17
+ "DEFAULT_BINDING",
18
+ "DEFAULT_MAX_STEPS",
19
+ "DEFAULT_MAX_TOKENS",
20
+ "DEFAULT_TEMPERATURE",
21
+ "DEFAULT_TIMEOUT_SECONDS",
22
+ "SPEC_VERSION",
23
+ "AgentDefinition",
24
+ "AgentGuardrails",
25
+ "AgentModel",
26
+ "AgentModelParameters",
27
+ "AgentTool",
28
+ "AgentToolApprovals",
29
+ "AgentToolKinds",
30
+ ]
31
+
32
+ SPEC_VERSION: Final = 1
33
+ DEFAULT_MAX_STEPS: Final = 12
34
+ DEFAULT_MAX_TOKENS: Final = 50_000
35
+ DEFAULT_TIMEOUT_SECONDS: Final = 900
36
+ DEFAULT_TEMPERATURE: Final = 0.2
37
+ DEFAULT_BINDING: Final = "agent:default"
38
+
39
+
40
+ class AgentToolKinds:
41
+ """The ``kind`` of an agent tool."""
42
+
43
+ PROCESS: Final = "process"
44
+ MCP: Final = "mcp"
45
+ HTTP: Final = "http"
46
+ VECTOR_SEARCH: Final = "vector_search"
47
+ ACTION_TASK: Final = "action_task"
48
+
49
+
50
+ class AgentToolApprovals:
51
+ """The ``approval`` of an agent tool."""
52
+
53
+ NONE: Final = "none"
54
+ BEFORE_CALL: Final = "before_call"
55
+
56
+
57
+ class AgentModelParameters(ContractModel):
58
+ """Sampling parameters of every model call."""
59
+
60
+ temperature: float | None = None
61
+ max_tokens: int | None = Field(default=None, alias="maxTokens")
62
+ top_p: float | None = Field(default=None, alias="topP")
63
+
64
+
65
+ class AgentModel(ContractModel):
66
+ """The model binding (``<purpose>:<name>``) and its parameters."""
67
+
68
+ binding: str = DEFAULT_BINDING
69
+ parameters: AgentModelParameters | None = None
70
+
71
+
72
+ class AgentTool(ContractModel):
73
+ """One tool the model may call."""
74
+
75
+ kind: str
76
+ ref: str
77
+ name: str
78
+ description: str
79
+ entry_point: str | None = Field(default=None, alias="entryPoint")
80
+ input_schema: dict[str, Any] | None = Field(default=None, alias="inputSchema")
81
+ approval: str = AgentToolApprovals.NONE
82
+ top_k: int | None = Field(default=None, alias="topK")
83
+
84
+
85
+ class AgentGuardrails(ContractModel):
86
+ """Limits of one run."""
87
+
88
+ max_steps: int = Field(default=DEFAULT_MAX_STEPS, alias="maxSteps")
89
+ max_tokens: int = Field(default=DEFAULT_MAX_TOKENS, alias="maxTokens")
90
+ max_cost: float | None = Field(default=None, alias="maxCost")
91
+ timeout_seconds: int = Field(default=DEFAULT_TIMEOUT_SECONDS, alias="timeoutSeconds")
92
+ blocked_tools: list[str] = Field(default_factory=list, alias="blockedTools")
93
+ output_validation: str = Field(default="schema", alias="outputValidation")
94
+
95
+
96
+ class AgentDefinition(ContractModel):
97
+ """What an agent is: instructions, model, schemas, tools and limits."""
98
+
99
+ spec_version: int = Field(default=SPEC_VERSION, alias="specVersion")
100
+ key: str
101
+ name: str
102
+ description: str | None = None
103
+ instructions: str
104
+ model: AgentModel = Field(default_factory=AgentModel)
105
+ input: dict[str, Any] | None = None
106
+ output: dict[str, Any] | None = None
107
+ tools: list[AgentTool] = Field(default_factory=list)
108
+ guardrails: AgentGuardrails = Field(default_factory=AgentGuardrails)
109
+ memory: dict[str, Any] | None = None
110
+ loop: str | None = None
@@ -0,0 +1,157 @@
1
+ """Contract diagnostics, with the same codes and JSON pointers the .NET validators emit.
2
+
3
+ A developer who sees ``VPC1012`` from ``velophex pack`` and ``VPC1012`` from the Orchestrator
4
+ upload is looking at the same rule, so the codes are copied from
5
+ ``VeloPhex.Platform.Contracts`` rather than invented here. Codes are stable; a retired code
6
+ is never reused.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from enum import Enum
13
+ from typing import Final
14
+
15
+ __all__ = [
16
+ "ContractError",
17
+ "Diagnostic",
18
+ "PackageDiagnosticCodes",
19
+ "ProjectDiagnosticCodes",
20
+ "Severity",
21
+ "has_errors",
22
+ ]
23
+
24
+
25
+ # Not StrEnum: that changes str(Severity.ERROR) from "Severity.ERROR" to "Error", which is a
26
+ # behaviour change for anything that formats the member rather than its value.
27
+ class Severity(str, Enum): # noqa: UP042
28
+ """How badly a diagnostic breaks the contract."""
29
+
30
+ ERROR = "Error"
31
+ WARNING = "Warning"
32
+ INFORMATION = "Information"
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class Diagnostic:
37
+ """One rule violation, located by a JSON pointer-ish path such as ``$.entryPoints[0].id``."""
38
+
39
+ code: str
40
+ path: str
41
+ message: str
42
+ severity: Severity = Severity.ERROR
43
+
44
+ @classmethod
45
+ def error(cls, code: str, path: str, message: str) -> Diagnostic:
46
+ """Create an error diagnostic."""
47
+ return cls(code, path, message, Severity.ERROR)
48
+
49
+ @classmethod
50
+ def warning(cls, code: str, path: str, message: str) -> Diagnostic:
51
+ """Create a warning diagnostic."""
52
+ return cls(code, path, message, Severity.WARNING)
53
+
54
+ def __str__(self) -> str:
55
+ return f"{self.severity.value} {self.code} at {self.path}: {self.message}"
56
+
57
+
58
+ def has_errors(diagnostics: list[Diagnostic]) -> bool:
59
+ """True when at least one diagnostic is an error."""
60
+ return any(diagnostic.severity is Severity.ERROR for diagnostic in diagnostics)
61
+
62
+
63
+ class ContractError(Exception):
64
+ """Raised when a contract document cannot be read or does not validate."""
65
+
66
+ def __init__(self, diagnostics: list[Diagnostic]) -> None:
67
+ super().__init__("; ".join(str(diagnostic) for diagnostic in diagnostics) or "The document is invalid.")
68
+ self.diagnostics = diagnostics
69
+
70
+
71
+ class ProjectDiagnosticCodes:
72
+ """Diagnostic codes for ``project.json`` (mirrors ``ProjectDiagnosticCodes`` in .NET)."""
73
+
74
+ INVALID_JSON: Final = "VPC1000"
75
+ UNSUPPORTED_SCHEMA_VERSION: Final = "VPC1001"
76
+ MISSING_PROJECT_ID: Final = "VPC1002"
77
+ INVALID_NAME: Final = "VPC1003"
78
+ INVALID_VERSION: Final = "VPC1004"
79
+ UNKNOWN_OUTPUT_TYPE: Final = "VPC1005"
80
+ UNKNOWN_RUNTIME: Final = "VPC1006"
81
+ UNSUPPORTED_EXPRESSION_LANGUAGE: Final = "VPC1007"
82
+ MISSING_PYTHON_VERSION: Final = "VPC1008"
83
+ MISSING_LOCK_FILE: Final = "VPC1009"
84
+ INVALID_WORKFLOW_ENTRY_PATH: Final = "VPC1010"
85
+ UNEXPECTED_HANDLER: Final = "VPC1011"
86
+ INVALID_PYTHON_HANDLER: Final = "VPC1012"
87
+ MISSING_ENTRY_POINTS: Final = "VPC1013"
88
+ INVALID_ENTRY_POINT_ID: Final = "VPC1014"
89
+ UNSAFE_PATH: Final = "VPC1015"
90
+ INVALID_ARGUMENT_SCHEMA: Final = "VPC1016"
91
+ MAIN_NOT_AN_ENTRY_POINT: Final = "VPC1017"
92
+ INVALID_DEPENDENCY: Final = "VPC1018"
93
+ INVALID_AUTOMATION_DEPENDENCY: Final = "VPC1019"
94
+ SELF_DEPENDENCY: Final = "VPC1020"
95
+ INVALID_TIMEOUT: Final = "VPC1021"
96
+ INVALID_CAPABILITY: Final = "VPC1022"
97
+ DOCUMENT_TOO_LARGE: Final = "VPC1023"
98
+ DUPLICATE_ENTRY_POINT: Final = "VPC1024"
99
+ NO_RUN_MODE_ALLOWED: Final = "VPC1025"
100
+
101
+ #: A dependency's value is present but is not a NuGet version range.
102
+ INVALID_DEPENDENCY_VERSION_RANGE: Final = "VPC1026"
103
+
104
+ #: An automation dependency's version is present but is not a NuGet version range.
105
+ INVALID_AUTOMATION_DEPENDENCY_VERSION_RANGE: Final = "VPC1027"
106
+
107
+ #: The version is SemVer 2.0 but carries build metadata (``1.0.0+build.7``), which NuGet drops
108
+ #: from a package's identity; the manifest and the feed would name different versions.
109
+ VERSION_HAS_BUILD_METADATA: Final = "VPC1028"
110
+
111
+ #: ``expressions`` names an unknown policy, or an extra type that is not a type name or is on
112
+ #: the expression floor (engine ADR 0036).
113
+ INVALID_EXPRESSION_POLICY: Final = "VPC1029"
114
+
115
+ #: A global or constant has a name that is not an identifier, repeats another global's or
116
+ #: constant's name (ignoring case, as Visual Basic does), has no type, or a constant has no
117
+ #: value (contracts 0.8.11).
118
+ INVALID_PROJECT_SYMBOL: Final = "VPC1030"
119
+
120
+ #: ``exceptionHandlerWorkflow`` is not a safe relative ``.xaml`` path, or the project is not a
121
+ #: VeloPhex workflow project (engine ADR 0037 section 5, contracts 0.8.16).
122
+ INVALID_EXCEPTION_HANDLER_WORKFLOW: Final = "VPC1031"
123
+
124
+ #: An entry point's ``testCase`` is in a ``library`` project, or its ``dataSource`` is not a
125
+ #: safe relative ``.csv``, ``.json`` or ``.xlsx[#Sheet]`` path (0.8.30), or names one in a
126
+ #: project that is not a VeloPhex workflow project (contracts 0.8.25).
127
+ INVALID_TEST_CASE: Final = "VPC1032"
128
+
129
+ #: ``icon`` is not a safe relative ``.png`` or ``.svg`` path (contracts 0.8.36). The file's
130
+ #: presence and size are checked when the package is built.
131
+ INVALID_ICON: Final = "VPC1033"
132
+
133
+
134
+ class PackageDiagnosticCodes:
135
+ """Diagnostic codes for automation packages (mirrors ``PackageDiagnosticCodes`` in .NET)."""
136
+
137
+ INVALID_JSON: Final = "VPC2000"
138
+ UNSUPPORTED_FORMAT: Final = "VPC2001"
139
+ UNSUPPORTED_MANIFEST_VERSION: Final = "VPC2002"
140
+ PACKAGE_ID_MISMATCH: Final = "VPC2003"
141
+ PACKAGE_VERSION_MISMATCH: Final = "VPC2004"
142
+ RUNTIME_MISMATCH: Final = "VPC2005"
143
+ REQUIRED_RUNTIMES_MISMATCH: Final = "VPC2006"
144
+ INVALID_FILE_ENTRY: Final = "VPC2007"
145
+ DUPLICATE_FILE: Final = "VPC2008"
146
+ FILES_NOT_SORTED: Final = "VPC2009"
147
+ DOCUMENT_TOO_LARGE: Final = "VPC2010"
148
+ MISSING_PROJECT_MANIFEST: Final = "VPC2011"
149
+ MISSING_FILE: Final = "VPC2012"
150
+ UNLISTED_FILE: Final = "VPC2013"
151
+ HASH_MISMATCH: Final = "VPC2014"
152
+ SIZE_MISMATCH: Final = "VPC2015"
153
+ MISSING_ENTRY_POINT_FILE: Final = "VPC2016"
154
+ MISSING_PYTHON_LOCK_FILE: Final = "VPC2017"
155
+
156
+ #: ``expressionPolicy`` is invalid, has no tier version, or does not carry its own fingerprint.
157
+ INVALID_EXPRESSION_POLICY: Final = "VPC2018"