patchshuttle 0.1.0a2__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.
- patchshuttle/__init__.py +98 -0
- patchshuttle/_diff.py +317 -0
- patchshuttle/_process.py +198 -0
- patchshuttle/_version.py +3 -0
- patchshuttle/actions/__init__.py +80 -0
- patchshuttle/actions/constructors.py +211 -0
- patchshuttle/actions/create.py +155 -0
- patchshuttle/actions/modify.py +174 -0
- patchshuttle/audit.py +588 -0
- patchshuttle/backup.py +712 -0
- patchshuttle/checks/__init__.py +37 -0
- patchshuttle/checks/constructors.py +67 -0
- patchshuttle/checks/runner.py +233 -0
- patchshuttle/cli.py +766 -0
- patchshuttle/config.py +247 -0
- patchshuttle/context.py +370 -0
- patchshuttle/errors.py +291 -0
- patchshuttle/execution.py +651 -0
- patchshuttle/formatters/__init__.py +25 -0
- patchshuttle/formatters/runner.py +240 -0
- patchshuttle/identifiers.py +20 -0
- patchshuttle/inventory.py +331 -0
- patchshuttle/logging.py +741 -0
- patchshuttle/models.py +496 -0
- patchshuttle/operations.py +292 -0
- patchshuttle/parser.py +243 -0
- patchshuttle/planner.py +1144 -0
- patchshuttle/policy.py +377 -0
- patchshuttle/py.typed +1 -0
- patchshuttle/registry.py +275 -0
- patchshuttle/resources/AI_GUIDE.md +163 -0
- patchshuttle/resources/AUDIT-EXAMPLE.psh.yaml +10 -0
- patchshuttle/resources/PATCH-EXAMPLE.psh.yaml +17 -0
- patchshuttle/resources/PATCHSHUTTLE_PROTOCOL.md +109 -0
- patchshuttle/resources/__init__.py +1 -0
- patchshuttle/rollback.py +306 -0
- patchshuttle/runner.py +880 -0
- patchshuttle/verification.py +107 -0
- patchshuttle/workspace.py +382 -0
- patchshuttle-0.1.0a2.dist-info/METADATA +535 -0
- patchshuttle-0.1.0a2.dist-info/RECORD +44 -0
- patchshuttle-0.1.0a2.dist-info/WHEEL +4 -0
- patchshuttle-0.1.0a2.dist-info/entry_points.txt +2 -0
- patchshuttle-0.1.0a2.dist-info/licenses/LICENSE +21 -0
patchshuttle/errors.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Stable public errors for loading and validating PatchShuttle jobs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from patchshuttle.audit import AuditActionResult
|
|
11
|
+
from patchshuttle.checks import CheckResult
|
|
12
|
+
from patchshuttle.formatters import FormatterResult
|
|
13
|
+
from patchshuttle.inventory import WorkspaceComparison
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class JobErrorCode(str, Enum):
|
|
17
|
+
"""Stable error codes for protocol input failures."""
|
|
18
|
+
|
|
19
|
+
JOB_EXTENSION_INVALID = "JOB_EXTENSION_INVALID"
|
|
20
|
+
JOB_FILE_NOT_FOUND = "JOB_FILE_NOT_FOUND"
|
|
21
|
+
JOB_FILE_NOT_REGULAR = "JOB_FILE_NOT_REGULAR"
|
|
22
|
+
JOB_FILE_READ_FAILED = "JOB_FILE_READ_FAILED"
|
|
23
|
+
JOB_SIZE_LIMIT_EXCEEDED = "JOB_SIZE_LIMIT_EXCEEDED"
|
|
24
|
+
JOB_ENCODING_INVALID = "JOB_ENCODING_INVALID"
|
|
25
|
+
YAML_INVALID = "YAML_INVALID"
|
|
26
|
+
YAML_ANCHOR_FORBIDDEN = "YAML_ANCHOR_FORBIDDEN"
|
|
27
|
+
YAML_ALIAS_FORBIDDEN = "YAML_ALIAS_FORBIDDEN"
|
|
28
|
+
YAML_TAG_FORBIDDEN = "YAML_TAG_FORBIDDEN"
|
|
29
|
+
YAML_DUPLICATE_KEY = "YAML_DUPLICATE_KEY"
|
|
30
|
+
YAML_MAPPING_KEY_INVALID = "YAML_MAPPING_KEY_INVALID"
|
|
31
|
+
JOB_ROOT_INVALID = "JOB_ROOT_INVALID"
|
|
32
|
+
JOB_SCHEMA_INVALID = "JOB_SCHEMA_INVALID"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WorkspaceErrorCode(str, Enum):
|
|
36
|
+
"""Stable error codes for workspace and configuration failures."""
|
|
37
|
+
|
|
38
|
+
WORKSPACE_NOT_FOUND = "WORKSPACE_NOT_FOUND"
|
|
39
|
+
WORKSPACE_NOT_DIRECTORY = "WORKSPACE_NOT_DIRECTORY"
|
|
40
|
+
WORKSPACE_NOT_INITIALIZED = "WORKSPACE_NOT_INITIALIZED"
|
|
41
|
+
WORKSPACE_READ_FAILED = "WORKSPACE_READ_FAILED"
|
|
42
|
+
WORKSPACE_WRITE_FAILED = "WORKSPACE_WRITE_FAILED"
|
|
43
|
+
NEW_PROJECT_NOT_EMPTY = "NEW_PROJECT_NOT_EMPTY"
|
|
44
|
+
MANAGED_PATH_CONFLICT = "MANAGED_PATH_CONFLICT"
|
|
45
|
+
CONFIG_NOT_FOUND = "CONFIG_NOT_FOUND"
|
|
46
|
+
CONFIG_NOT_REGULAR = "CONFIG_NOT_REGULAR"
|
|
47
|
+
CONFIG_READ_FAILED = "CONFIG_READ_FAILED"
|
|
48
|
+
CONFIG_INVALID = "CONFIG_INVALID"
|
|
49
|
+
PROJECT_ORIGIN_CONFLICT = "PROJECT_ORIGIN_CONFLICT"
|
|
50
|
+
PROJECT_ID_MISMATCH = "PROJECT_ID_MISMATCH"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PolicyErrorCode(str, Enum):
|
|
54
|
+
"""Stable error codes for workspace path and local-policy failures."""
|
|
55
|
+
|
|
56
|
+
PATH_INVALID = "PATH_INVALID"
|
|
57
|
+
PATH_ABSOLUTE = "PATH_ABSOLUTE"
|
|
58
|
+
PATH_TRAVERSAL = "PATH_TRAVERSAL"
|
|
59
|
+
PATH_URL = "PATH_URL"
|
|
60
|
+
PATH_ROOT_FORBIDDEN = "PATH_ROOT_FORBIDDEN"
|
|
61
|
+
PATH_PROTECTED = "PATH_PROTECTED"
|
|
62
|
+
PATH_SYMLINK = "PATH_SYMLINK"
|
|
63
|
+
PATH_NOT_FOUND = "PATH_NOT_FOUND"
|
|
64
|
+
PATH_PARENT_NOT_DIRECTORY = "PATH_PARENT_NOT_DIRECTORY"
|
|
65
|
+
PATH_SPECIAL_FILE = "PATH_SPECIAL_FILE"
|
|
66
|
+
PATH_INSPECTION_FAILED = "PATH_INSPECTION_FAILED"
|
|
67
|
+
PATH_OUTSIDE_WORKSPACE = "PATH_OUTSIDE_WORKSPACE"
|
|
68
|
+
POLICY_PATTERN_INVALID = "POLICY_PATTERN_INVALID"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class PlanningErrorCode(str, Enum):
|
|
72
|
+
"""Stable error codes for read-only planning failures."""
|
|
73
|
+
|
|
74
|
+
ACTION_LIMIT_EXCEEDED = "ACTION_LIMIT_EXCEEDED"
|
|
75
|
+
PATCH_CHECK_REQUIRED = "PATCH_CHECK_REQUIRED"
|
|
76
|
+
PATH_IGNORED = "PATH_IGNORED"
|
|
77
|
+
TARGET_TYPE_INVALID = "TARGET_TYPE_INVALID"
|
|
78
|
+
FILE_SIZE_LIMIT_EXCEEDED = "FILE_SIZE_LIMIT_EXCEEDED"
|
|
79
|
+
FILE_BINARY = "FILE_BINARY"
|
|
80
|
+
FILE_ENCODING_UNSUPPORTED = "FILE_ENCODING_UNSUPPORTED"
|
|
81
|
+
FILE_NEWLINE_UNSUPPORTED = "FILE_NEWLINE_UNSUPPORTED"
|
|
82
|
+
FILE_READ_FAILED = "FILE_READ_FAILED"
|
|
83
|
+
CONTENT_ENCODING_INVALID = "CONTENT_ENCODING_INVALID"
|
|
84
|
+
CONTENT_BINARY_FORBIDDEN = "CONTENT_BINARY_FORBIDDEN"
|
|
85
|
+
CREATE_FILE_CONFLICT = "CREATE_FILE_CONFLICT"
|
|
86
|
+
OCCURRENCE_COUNT_MISMATCH = "OCCURRENCE_COUNT_MISMATCH"
|
|
87
|
+
INSERTION_STATE_CONFLICT = "INSERTION_STATE_CONFLICT"
|
|
88
|
+
VIRTUAL_PATH_CONFLICT = "VIRTUAL_PATH_CONFLICT"
|
|
89
|
+
DIFF_INVALID = "DIFF_INVALID"
|
|
90
|
+
DIFF_PATH_INVALID = "DIFF_PATH_INVALID"
|
|
91
|
+
DIFF_BINARY_FORBIDDEN = "DIFF_BINARY_FORBIDDEN"
|
|
92
|
+
DIFF_HUNK_MISMATCH = "DIFF_HUNK_MISMATCH"
|
|
93
|
+
CHECK_PROFILE_NOT_FOUND = "CHECK_PROFILE_NOT_FOUND"
|
|
94
|
+
CHECK_ARGUMENT_INVALID = "CHECK_ARGUMENT_INVALID"
|
|
95
|
+
DEPENDENCY_NOT_AVAILABLE = "DEPENDENCY_NOT_AVAILABLE"
|
|
96
|
+
PYTEST_ARGUMENT_FORBIDDEN = "PYTEST_ARGUMENT_FORBIDDEN"
|
|
97
|
+
CHECK_PATH_NOT_FOUND = "CHECK_PATH_NOT_FOUND"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ExecutionErrorCode(str, Enum):
|
|
101
|
+
"""Stable errors for the internal transactional execution core."""
|
|
102
|
+
|
|
103
|
+
APPROVAL_REQUIRED = "APPROVAL_REQUIRED"
|
|
104
|
+
USER_DECLINED = "USER_DECLINED"
|
|
105
|
+
KEEP_CHANGES_FORBIDDEN = "KEEP_CHANGES_FORBIDDEN"
|
|
106
|
+
JOB_KIND_UNSUPPORTED = "JOB_KIND_UNSUPPORTED"
|
|
107
|
+
ACTION_UNSUPPORTED = "ACTION_UNSUPPORTED"
|
|
108
|
+
PATCH_ID_CONFLICT = "PATCH_ID_CONFLICT"
|
|
109
|
+
WORKSPACE_LOCKED = "WORKSPACE_LOCKED"
|
|
110
|
+
WORKSPACE_LOCK_FAILED = "WORKSPACE_LOCK_FAILED"
|
|
111
|
+
PLAN_STALE = "PLAN_STALE"
|
|
112
|
+
BACKUP_FAILED = "BACKUP_FAILED"
|
|
113
|
+
ACTION_FAILED = "ACTION_FAILED"
|
|
114
|
+
CHECK_FAILED = "CHECK_FAILED"
|
|
115
|
+
FORMAT_FAILED = "FORMAT_FAILED"
|
|
116
|
+
WORKSPACE_INVENTORY_FAILED = "WORKSPACE_INVENTORY_FAILED"
|
|
117
|
+
UNEXPECTED_WORKSPACE_CHANGE = "UNEXPECTED_WORKSPACE_CHANGE"
|
|
118
|
+
ROLLBACK_FAILED = "ROLLBACK_FAILED"
|
|
119
|
+
OPERATIONAL_RECORD_FAILED = "OPERATIONAL_RECORD_FAILED"
|
|
120
|
+
LOG_NOT_FOUND = "LOG_NOT_FOUND"
|
|
121
|
+
JOB_NOT_FOUND = "JOB_NOT_FOUND"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class JobError(ValueError):
|
|
125
|
+
"""A user-correctable job input error with a stable code and location."""
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
code: JobErrorCode,
|
|
130
|
+
message: str,
|
|
131
|
+
*,
|
|
132
|
+
field_path: str | None = None,
|
|
133
|
+
line: int | None = None,
|
|
134
|
+
column: int | None = None,
|
|
135
|
+
) -> None:
|
|
136
|
+
self.code = code
|
|
137
|
+
self.message = message
|
|
138
|
+
self.field_path = field_path
|
|
139
|
+
self.line = line
|
|
140
|
+
self.column = column
|
|
141
|
+
super().__init__(message)
|
|
142
|
+
|
|
143
|
+
def __str__(self) -> str:
|
|
144
|
+
location = self.field_path or ""
|
|
145
|
+
if self.line is not None and self.column is not None:
|
|
146
|
+
position = f"line {self.line}, column {self.column}"
|
|
147
|
+
location = f"{location} ({position})" if location else position
|
|
148
|
+
prefix = f"[{self.code.value}]"
|
|
149
|
+
return (
|
|
150
|
+
f"{prefix} {location}: {self.message}"
|
|
151
|
+
if location
|
|
152
|
+
else f"{prefix} {self.message}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class WorkspaceError(ValueError):
|
|
157
|
+
"""A user-correctable workspace failure with a stable code and location."""
|
|
158
|
+
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
code: WorkspaceErrorCode,
|
|
162
|
+
message: str,
|
|
163
|
+
*,
|
|
164
|
+
path: str | None = None,
|
|
165
|
+
) -> None:
|
|
166
|
+
self.code = code
|
|
167
|
+
self.message = message
|
|
168
|
+
self.path = path
|
|
169
|
+
super().__init__(message)
|
|
170
|
+
|
|
171
|
+
def __str__(self) -> str:
|
|
172
|
+
prefix = f"[{self.code.value}]"
|
|
173
|
+
return (
|
|
174
|
+
f"{prefix} {self.path}: {self.message}"
|
|
175
|
+
if self.path
|
|
176
|
+
else f"{prefix} {self.message}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class PolicyError(ValueError):
|
|
181
|
+
"""A user-correctable path-policy failure with a stable code."""
|
|
182
|
+
|
|
183
|
+
def __init__(
|
|
184
|
+
self,
|
|
185
|
+
code: PolicyErrorCode,
|
|
186
|
+
message: str,
|
|
187
|
+
*,
|
|
188
|
+
path: str | None = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
self.code = code
|
|
191
|
+
self.message = message
|
|
192
|
+
self.path = path
|
|
193
|
+
super().__init__(message)
|
|
194
|
+
|
|
195
|
+
def __str__(self) -> str:
|
|
196
|
+
prefix = f"[{self.code.value}]"
|
|
197
|
+
return (
|
|
198
|
+
f"{prefix} {self.path}: {self.message}"
|
|
199
|
+
if self.path
|
|
200
|
+
else f"{prefix} {self.message}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class PlanningError(ValueError):
|
|
205
|
+
"""A user-correctable planning failure with a stable location."""
|
|
206
|
+
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
code: PlanningErrorCode,
|
|
210
|
+
message: str,
|
|
211
|
+
*,
|
|
212
|
+
item_id: str | None = None,
|
|
213
|
+
path: str | None = None,
|
|
214
|
+
) -> None:
|
|
215
|
+
self.code = code
|
|
216
|
+
self.message = message
|
|
217
|
+
self.item_id = item_id
|
|
218
|
+
self.path = path
|
|
219
|
+
super().__init__(message)
|
|
220
|
+
|
|
221
|
+
def __str__(self) -> str:
|
|
222
|
+
location = " ".join(value for value in (self.item_id, self.path) if value)
|
|
223
|
+
prefix = f"[{self.code.value}]"
|
|
224
|
+
return (
|
|
225
|
+
f"{prefix} {location}: {self.message}"
|
|
226
|
+
if location
|
|
227
|
+
else f"{prefix} {self.message}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class ExecutionError(RuntimeError):
|
|
232
|
+
"""A transactional failure with rollback and backup context."""
|
|
233
|
+
|
|
234
|
+
def __init__(
|
|
235
|
+
self,
|
|
236
|
+
code: ExecutionErrorCode,
|
|
237
|
+
message: str,
|
|
238
|
+
*,
|
|
239
|
+
item_id: str | None = None,
|
|
240
|
+
path: str | None = None,
|
|
241
|
+
backup_path: Path | None = None,
|
|
242
|
+
rollback_succeeded: bool | None = None,
|
|
243
|
+
rollback_skipped: bool = False,
|
|
244
|
+
changes_kept: bool = False,
|
|
245
|
+
cause_code: ExecutionErrorCode | None = None,
|
|
246
|
+
check_results: tuple[CheckResult, ...] = (),
|
|
247
|
+
audit_results: tuple[AuditActionResult, ...] = (),
|
|
248
|
+
formatting_results: tuple[FormatterResult, ...] = (),
|
|
249
|
+
workspace_comparison: WorkspaceComparison | None = None,
|
|
250
|
+
log_path: Path | None = None,
|
|
251
|
+
archived_job_path: Path | None = None,
|
|
252
|
+
) -> None:
|
|
253
|
+
self.code = code
|
|
254
|
+
self.message = message
|
|
255
|
+
self.item_id = item_id
|
|
256
|
+
self.path = path
|
|
257
|
+
self.backup_path = backup_path
|
|
258
|
+
self.rollback_succeeded = rollback_succeeded
|
|
259
|
+
self.rollback_skipped = rollback_skipped
|
|
260
|
+
self.changes_kept = changes_kept
|
|
261
|
+
self.cause_code = cause_code
|
|
262
|
+
self.check_results = check_results
|
|
263
|
+
self.audit_results = audit_results
|
|
264
|
+
self.formatting_results = formatting_results
|
|
265
|
+
self.workspace_comparison = workspace_comparison
|
|
266
|
+
self.log_path = log_path
|
|
267
|
+
self.archived_job_path = archived_job_path
|
|
268
|
+
super().__init__(message)
|
|
269
|
+
|
|
270
|
+
def __str__(self) -> str:
|
|
271
|
+
location = " ".join(value for value in (self.item_id, self.path) if value)
|
|
272
|
+
prefix = f"[{self.code.value}]"
|
|
273
|
+
return (
|
|
274
|
+
f"{prefix} {location}: {self.message}"
|
|
275
|
+
if location
|
|
276
|
+
else f"{prefix} {self.message}"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
__all__ = [
|
|
281
|
+
"ExecutionError",
|
|
282
|
+
"ExecutionErrorCode",
|
|
283
|
+
"JobError",
|
|
284
|
+
"JobErrorCode",
|
|
285
|
+
"PolicyError",
|
|
286
|
+
"PolicyErrorCode",
|
|
287
|
+
"PlanningError",
|
|
288
|
+
"PlanningErrorCode",
|
|
289
|
+
"WorkspaceError",
|
|
290
|
+
"WorkspaceErrorCode",
|
|
291
|
+
]
|