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.
Files changed (44) hide show
  1. patchshuttle/__init__.py +98 -0
  2. patchshuttle/_diff.py +317 -0
  3. patchshuttle/_process.py +198 -0
  4. patchshuttle/_version.py +3 -0
  5. patchshuttle/actions/__init__.py +80 -0
  6. patchshuttle/actions/constructors.py +211 -0
  7. patchshuttle/actions/create.py +155 -0
  8. patchshuttle/actions/modify.py +174 -0
  9. patchshuttle/audit.py +588 -0
  10. patchshuttle/backup.py +712 -0
  11. patchshuttle/checks/__init__.py +37 -0
  12. patchshuttle/checks/constructors.py +67 -0
  13. patchshuttle/checks/runner.py +233 -0
  14. patchshuttle/cli.py +766 -0
  15. patchshuttle/config.py +247 -0
  16. patchshuttle/context.py +370 -0
  17. patchshuttle/errors.py +291 -0
  18. patchshuttle/execution.py +651 -0
  19. patchshuttle/formatters/__init__.py +25 -0
  20. patchshuttle/formatters/runner.py +240 -0
  21. patchshuttle/identifiers.py +20 -0
  22. patchshuttle/inventory.py +331 -0
  23. patchshuttle/logging.py +741 -0
  24. patchshuttle/models.py +496 -0
  25. patchshuttle/operations.py +292 -0
  26. patchshuttle/parser.py +243 -0
  27. patchshuttle/planner.py +1144 -0
  28. patchshuttle/policy.py +377 -0
  29. patchshuttle/py.typed +1 -0
  30. patchshuttle/registry.py +275 -0
  31. patchshuttle/resources/AI_GUIDE.md +163 -0
  32. patchshuttle/resources/AUDIT-EXAMPLE.psh.yaml +10 -0
  33. patchshuttle/resources/PATCH-EXAMPLE.psh.yaml +17 -0
  34. patchshuttle/resources/PATCHSHUTTLE_PROTOCOL.md +109 -0
  35. patchshuttle/resources/__init__.py +1 -0
  36. patchshuttle/rollback.py +306 -0
  37. patchshuttle/runner.py +880 -0
  38. patchshuttle/verification.py +107 -0
  39. patchshuttle/workspace.py +382 -0
  40. patchshuttle-0.1.0a2.dist-info/METADATA +535 -0
  41. patchshuttle-0.1.0a2.dist-info/RECORD +44 -0
  42. patchshuttle-0.1.0a2.dist-info/WHEEL +4 -0
  43. patchshuttle-0.1.0a2.dist-info/entry_points.txt +2 -0
  44. 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
+ ]