ultra-builder-pro-cli 0.25.1 → 0.26.0

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 (241) hide show
  1. package/.ultra-template/contexts/TEMPLATE.md +87 -0
  2. package/.ultra-template/decisions/.gitkeep +1 -0
  3. package/.ultra-template/evidence/.gitkeep +1 -0
  4. package/.ultra-template/north-star.md +46 -0
  5. package/.ultra-template/research/.gitkeep +1 -0
  6. package/.ultra-template/specs/discovery.md +2 -2
  7. package/.ultra-template/tasks.json +3 -0
  8. package/.ultra-template/test-report.json +27 -0
  9. package/CHANGELOG.md +40 -0
  10. package/README.md +217 -579
  11. package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
  12. package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
  13. package/adapters/_shared/file-ops.cjs +76 -2
  14. package/adapters/_shared/host-profile.cjs +97 -0
  15. package/adapters/_shared/plugin-core.cjs +204 -0
  16. package/adapters/_shared/runtime-assets.cjs +31 -96
  17. package/adapters/claude.js +92 -291
  18. package/adapters/codex.js +309 -842
  19. package/adapters/grok.js +143 -1469
  20. package/adapters/kimi.js +124 -1053
  21. package/adapters/opencode.js +230 -742
  22. package/bin/delegate-worker.cjs +323 -0
  23. package/bin/delegate.cjs +456 -0
  24. package/bin/install.js +38 -11
  25. package/docs/ARCHITECTURE.md +122 -216
  26. package/docs/ARTIFACT-AUTHORITY.md +90 -268
  27. package/docs/DECISIONS.md +75 -87
  28. package/docs/PHILOSOPHY.md +177 -0
  29. package/docs/PLUGIN-ISOLATION-CONTRACT.md +62 -39
  30. package/docs/RUNTIME-COMPAT-MATRIX.md +99 -156
  31. package/docs/SKILL-AUTHORING.md +94 -73
  32. package/docs/WORKFLOW-LIFECYCLE.md +93 -189
  33. package/hooks/README.md +17 -37
  34. package/hooks/_common.py +113 -0
  35. package/hooks/adapters/codex.py +9 -16
  36. package/hooks/adapters/grok.py +5 -5
  37. package/hooks/adapters/kimi.py +8 -14
  38. package/hooks/block_dangerous_commands.py +95 -0
  39. package/hooks/compact_context.py +65 -0
  40. package/hooks/mid_workflow_recall.py +25 -0
  41. package/hooks/post_edit_guard.py +105 -0
  42. package/hooks/session_context.py +30 -0
  43. package/package.json +17 -52
  44. package/skills/ultra-change/SKILL.md +97 -50
  45. package/skills/ultra-change/references/change-contract.md +40 -1
  46. package/skills/ultra-delegate/SKILL.md +82 -0
  47. package/skills/ultra-delegate/references/delegation-contract.md +51 -0
  48. package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
  49. package/skills/ultra-deliver/SKILL.md +61 -42
  50. package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
  51. package/skills/ultra-dev/SKILL.md +96 -62
  52. package/skills/ultra-dev/references/debugging.md +22 -0
  53. package/skills/ultra-domain-modeling/SKILL.md +79 -0
  54. package/skills/ultra-grilling/SKILL.md +79 -0
  55. package/skills/ultra-init/SKILL.md +94 -37
  56. package/skills/ultra-init/references/brownfield-adoption.md +33 -83
  57. package/skills/ultra-init/scripts/init_project.cjs +84 -0
  58. package/skills/ultra-plan/SKILL.md +88 -68
  59. package/skills/ultra-research/SKILL.md +62 -48
  60. package/skills/ultra-research/references/00-problem-validation.md +5 -5
  61. package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
  62. package/skills/ultra-research/references/02-market-assessment.md +5 -4
  63. package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
  64. package/skills/ultra-research/references/04-product-strategy.md +3 -3
  65. package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
  66. package/skills/ultra-research/references/10-user-personas.md +3 -2
  67. package/skills/ultra-research/references/11-user-scenarios.md +3 -3
  68. package/skills/ultra-research/references/20-user-stories.md +4 -3
  69. package/skills/ultra-research/references/21-features-scope.md +3 -2
  70. package/skills/ultra-research/references/22-success-metrics.md +3 -3
  71. package/skills/ultra-research/references/30-architecture-context.md +3 -3
  72. package/skills/ultra-research/references/31-solution-strategy.md +3 -3
  73. package/skills/ultra-research/references/32-building-blocks.md +3 -3
  74. package/skills/ultra-research/references/40-deployment.md +3 -3
  75. package/skills/ultra-research/references/41-quality-risks.md +3 -3
  76. package/skills/ultra-research/references/99-synthesis.md +7 -7
  77. package/skills/ultra-review/SKILL.md +59 -32
  78. package/skills/ultra-review/references/code.md +17 -0
  79. package/skills/ultra-review/references/comments.md +12 -0
  80. package/skills/ultra-review/references/design.md +12 -0
  81. package/skills/ultra-review/references/errors.md +13 -0
  82. package/skills/ultra-review/references/spec.md +13 -0
  83. package/skills/ultra-review/references/tests.md +12 -0
  84. package/skills/ultra-review/references/worker-packet.md +40 -0
  85. package/skills/ultra-review/scripts/review_wait.py +26 -15
  86. package/skills/ultra-status/SKILL.md +61 -36
  87. package/skills/ultra-tdd/SKILL.md +81 -0
  88. package/skills/ultra-tdd/references/templates/README.md +20 -0
  89. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
  90. package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
  91. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
  92. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
  93. package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
  94. package/skills/ultra-tdd/references/test-execution.md +18 -0
  95. package/skills/ultra-test/SKILL.md +70 -30
  96. package/skills/ultra-test/references/export-syntax.md +18 -0
  97. package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
  98. package/skills/ultra-think/SKILL.md +49 -27
  99. package/skills/ultra-think/references/autonomy-boundary.md +48 -0
  100. package/skills/ultra-think/references/interaction-boundary.md +18 -33
  101. package/.ultra-template/docs/research/README.md +0 -55
  102. package/.ultra-template/reports/templates/delivery-report.json +0 -17
  103. package/.ultra-template/reports/templates/test-report.json +0 -18
  104. package/.ultra-template/tasks/tasks.json +0 -15
  105. package/.ultra-template/templates/task-context.md +0 -19
  106. package/adapters/_shared/codex-assets.cjs +0 -725
  107. package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
  108. package/adapters/_shared/interaction-contract.cjs +0 -129
  109. package/adapters/_shared/path-rewrite.cjs +0 -79
  110. package/adapters/_shared/runtime-native.cjs +0 -132
  111. package/adapters/_shared/settings-merge.cjs +0 -76
  112. package/agents/code-reviewer.md +0 -42
  113. package/agents/debugger.md +0 -40
  114. package/agents/review-code.md +0 -49
  115. package/agents/review-comments.md +0 -38
  116. package/agents/review-coordinator.md +0 -56
  117. package/agents/review-design.md +0 -40
  118. package/agents/review-errors.md +0 -43
  119. package/agents/review-spec.md +0 -39
  120. package/agents/review-tests.md +0 -40
  121. package/agents/tdd-runner.md +0 -38
  122. package/bin/orchestrator.js +0 -383
  123. package/commands/ultra-change.md +0 -10
  124. package/commands/ultra-deliver.md +0 -10
  125. package/commands/ultra-dev.md +0 -10
  126. package/commands/ultra-doctor.md +0 -10
  127. package/commands/ultra-init.md +0 -10
  128. package/commands/ultra-plan.md +0 -10
  129. package/commands/ultra-research.md +0 -10
  130. package/commands/ultra-review.md +0 -10
  131. package/commands/ultra-status.md +0 -10
  132. package/commands/ultra-test.md +0 -10
  133. package/commands/ultra-think.md +0 -10
  134. package/docs/AGENT-CONTEXT.md +0 -191
  135. package/docs/COMMIT-HASH-BACKFILL.md +0 -51
  136. package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
  137. package/docs/ROADMAP.md +0 -101
  138. package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
  139. package/hooks/active_task_context.py +0 -152
  140. package/hooks/context_envelope.py +0 -100
  141. package/hooks/health_check.py +0 -196
  142. package/hooks/pre_stop_check.py +0 -66
  143. package/hooks/runtime_paths.py +0 -396
  144. package/hooks/subagent_tracker.py +0 -88
  145. package/hooks/workflow_checkpoint.py +0 -76
  146. package/hooks/workflow_context.py +0 -48
  147. package/hooks/workflow_resume.py +0 -84
  148. package/mcp-server/hook-context.cjs +0 -38
  149. package/mcp-server/hook-event.cjs +0 -34
  150. package/mcp-server/lib/archive-journal.cjs +0 -1119
  151. package/mcp-server/lib/archive-mutation-worker.py +0 -365
  152. package/mcp-server/lib/artifact-registry.cjs +0 -1232
  153. package/mcp-server/lib/baseline-workflow.cjs +0 -1193
  154. package/mcp-server/lib/canonical-json.cjs +0 -19
  155. package/mcp-server/lib/change-authority.cjs +0 -26
  156. package/mcp-server/lib/change-packet.cjs +0 -790
  157. package/mcp-server/lib/change-workflow.cjs +0 -65
  158. package/mcp-server/lib/context-envelope.cjs +0 -581
  159. package/mcp-server/lib/context-paths.cjs +0 -142
  160. package/mcp-server/lib/decision-records.cjs +0 -423
  161. package/mcp-server/lib/delivery-transaction.cjs +0 -471
  162. package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
  163. package/mcp-server/lib/doctor.cjs +0 -627
  164. package/mcp-server/lib/git-bootstrap.cjs +0 -651
  165. package/mcp-server/lib/init-project.cjs +0 -1021
  166. package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
  167. package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
  168. package/mcp-server/lib/managed-file-write.cjs +0 -172
  169. package/mcp-server/lib/plan-checkpoint.cjs +0 -177
  170. package/mcp-server/lib/plan-publication-worker.cjs +0 -830
  171. package/mcp-server/lib/plan-store.cjs +0 -794
  172. package/mcp-server/lib/prd-parser.cjs +0 -83
  173. package/mcp-server/lib/pricing.cjs +0 -44
  174. package/mcp-server/lib/project-context.cjs +0 -178
  175. package/mcp-server/lib/projector.cjs +0 -386
  176. package/mcp-server/lib/provider-refs.cjs +0 -42
  177. package/mcp-server/lib/runtime-paths.cjs +0 -1714
  178. package/mcp-server/lib/runtime-state.cjs +0 -222
  179. package/mcp-server/lib/safe-project-file.cjs +0 -385
  180. package/mcp-server/lib/stage-checkpoints.cjs +0 -463
  181. package/mcp-server/lib/state-authority.cjs +0 -95
  182. package/mcp-server/lib/state-db.cjs +0 -1580
  183. package/mcp-server/lib/state-ops.cjs +0 -1428
  184. package/mcp-server/lib/task-contract.cjs +0 -404
  185. package/mcp-server/lib/task-expander.cjs +0 -105
  186. package/mcp-server/lib/task-ledger.cjs +0 -1964
  187. package/mcp-server/lib/telemetry.cjs +0 -85
  188. package/mcp-server/lib/topo.cjs +0 -131
  189. package/mcp-server/lib/ultra-facade.cjs +0 -2684
  190. package/mcp-server/lib/worker-packet.cjs +0 -358
  191. package/mcp-server/server.cjs +0 -437
  192. package/orchestrator/auto-merge.cjs +0 -128
  193. package/orchestrator/daemon.cjs +0 -264
  194. package/orchestrator/dispatch-rules.cjs +0 -78
  195. package/orchestrator/parallel-orchestrator.cjs +0 -903
  196. package/orchestrator/planner/plan-builder.cjs +0 -125
  197. package/orchestrator/recovery.cjs +0 -188
  198. package/orchestrator/session-close-journal-worker.cjs +0 -5
  199. package/orchestrator/session-close-journal.cjs +0 -1014
  200. package/orchestrator/session-runner.cjs +0 -1153
  201. package/orchestrator/worktree-manager.cjs +0 -282
  202. package/settings.json +0 -7
  203. package/skills/cc-collab/SKILL.md +0 -42
  204. package/skills/code-review-expert/SKILL.md +0 -43
  205. package/skills/code-review-expert/references/correctness-reliability.md +0 -30
  206. package/skills/code-review-expert/references/design-boundaries.md +0 -26
  207. package/skills/code-review-expert/references/removal-plan.md +0 -24
  208. package/skills/codex-collab/SKILL.md +0 -49
  209. package/skills/codex-collab/evals/evals.json +0 -35
  210. package/skills/integration-rules/SKILL.md +0 -32
  211. package/skills/security-rules/SKILL.md +0 -38
  212. package/skills/testing-rules/SKILL.md +0 -32
  213. package/skills/ultra-doctor/SKILL.md +0 -41
  214. package/skills/ultra-plan/references/semantic-preflight.md +0 -20
  215. package/skills/ultra-verify/SKILL.md +0 -46
  216. package/skills/ultra-verify/evals/evals.json +0 -46
  217. package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
  218. package/skills/ultra-verify/references/evidence-status.md +0 -21
  219. package/skills/ultra-verify/scripts/verify_wait.py +0 -94
  220. package/spec/cli-protocol.md +0 -102
  221. package/spec/mcp-tools.yaml +0 -312
  222. package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
  223. package/spec/schemas/change-delta.v1.schema.json +0 -153
  224. package/spec/schemas/command-manifest.schema.json +0 -29
  225. package/spec/schemas/context-file.v4.5.schema.json +0 -54
  226. package/spec/schemas/delivery-report.v1.schema.json +0 -66
  227. package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
  228. package/spec/schemas/mcp-tools.schema.json +0 -136
  229. package/spec/schemas/skill-manifest.schema.json +0 -24
  230. package/spec/schemas/state-db.sql +0 -641
  231. package/spec/schemas/task-ledger.v2.schema.json +0 -296
  232. package/spec/schemas/tasks.v4.5.schema.json +0 -96
  233. package/spec/schemas/test-report.v1.schema.json +0 -225
  234. package/ultra-tools/cli.cjs +0 -129
  235. package/ultra-tools/commands/db.cjs +0 -226
  236. package/ultra-tools/commands/legacy-memory.cjs +0 -159
  237. package/ultra-tools/commands/migrate.cjs +0 -1386
  238. package/ultra-tools/commands/session.cjs +0 -213
  239. package/ultra-tools/commands/status.cjs +0 -559
  240. package/ultra-tools/commands/system.cjs +0 -498
  241. package/ultra-tools/commands/task.cjs +0 -105
@@ -1,365 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Perform bounded archive mutations relative to inherited directory fds."""
3
-
4
- import base64
5
- import json
6
- import os
7
- import stat
8
- import sys
9
-
10
-
11
- MAX_REQUEST_BYTES = 16 * 1024 * 1024
12
- MAX_DATA_BYTES = 8 * 1024 * 1024
13
- MAX_BASENAME_BYTES = 255
14
- ARCHIVE_FILES = frozenset((
15
- ".archive-intent.json",
16
- ".archive-rebind.json",
17
- "archive-summary.md",
18
- ))
19
-
20
- OPERATION_FIELDS = {
21
- "mkdir_dir": frozenset((
22
- "operation", "directory_fd", "directory_identity", "name",
23
- )),
24
- "write_atomic": frozenset((
25
- "operation", "directory_fd", "directory_identity", "name",
26
- "temp_name", "replace", "data_base64",
27
- )),
28
- "write_rebind_atomic": frozenset((
29
- "operation", "directory_fd", "directory_identity", "name",
30
- "temp_name", "replace", "data_base64",
31
- )),
32
- "unlink_regular": frozenset((
33
- "operation", "directory_fd", "directory_identity", "name",
34
- "allow_missing",
35
- )),
36
- "rename_dir": frozenset((
37
- "operation", "source_parent_fd", "destination_parent_fd",
38
- "guard_parent_fd", "source_parent_identity",
39
- "destination_parent_identity", "guard_parent_identity",
40
- "source_parent_name", "destination_parent_name", "source_name",
41
- "destination_name", "source_identity",
42
- )),
43
- }
44
-
45
-
46
- class MutationError(Exception):
47
- code = "ARCHIVE_PATH_UNSAFE"
48
-
49
-
50
- class ProtocolError(MutationError):
51
- code = "ARCHIVE_MUTATION_INVALID"
52
-
53
-
54
- class RuntimePrerequisiteError(MutationError):
55
- code = "ARCHIVE_RUNTIME_UNAVAILABLE"
56
-
57
-
58
- def identity(value):
59
- return {
60
- "dev": str(value.st_dev),
61
- "ino": str(value.st_ino),
62
- "mode": str(value.st_mode),
63
- }
64
-
65
-
66
- def require_identity_shape(value, label):
67
- if (
68
- not isinstance(value, dict)
69
- or set(value) != {"dev", "ino", "mode"}
70
- or any(
71
- not isinstance(value[field], str) or not value[field].isdigit()
72
- for field in ("dev", "ino", "mode")
73
- )
74
- ):
75
- raise ProtocolError(f"{label} has an invalid identity")
76
-
77
-
78
- def require_identity(actual, expected, label):
79
- if identity(actual) != expected:
80
- raise MutationError(f"{label} changed identity")
81
-
82
-
83
- def require_fd(value, label):
84
- if isinstance(value, bool) or not isinstance(value, int) or value < 3 or value > 5:
85
- raise ProtocolError(f"{label} is outside the inherited descriptor boundary")
86
-
87
-
88
- def require_basename(value, label):
89
- if (
90
- not isinstance(value, str)
91
- or not value
92
- or value in (".", "..")
93
- or "/" in value
94
- or "\\" in value
95
- or "\x00" in value
96
- or len(os.fsencode(value)) > MAX_BASENAME_BYTES
97
- ):
98
- raise ProtocolError(f"{label} must be one bounded basename")
99
-
100
-
101
- def require_platform_support():
102
- if os.name != "posix":
103
- raise RuntimePrerequisiteError("archive mutations require a POSIX Python runtime")
104
- required_dir_fd = (os.open, os.stat, os.rename, os.unlink, os.mkdir)
105
- if any(operation not in os.supports_dir_fd for operation in required_dir_fd):
106
- raise RuntimePrerequisiteError(
107
- "Python dir_fd operations required by archive mutations are unavailable"
108
- )
109
- if os.stat not in os.supports_follow_symlinks or not hasattr(os, "O_NOFOLLOW"):
110
- raise RuntimePrerequisiteError(
111
- "Python no-follow filesystem operations required by archive mutations are unavailable"
112
- )
113
-
114
-
115
- def validate_request(request):
116
- if not isinstance(request, dict):
117
- raise ProtocolError("archive mutation request must be an object")
118
- operation = request.get("operation")
119
- expected = OPERATION_FIELDS.get(operation)
120
- if expected is None or set(request) != expected:
121
- raise ProtocolError("archive mutation operation or fields are not allowlisted")
122
-
123
- for field in (name for name in expected if name.endswith("_fd")):
124
- require_fd(request[field], field)
125
- for field in (name for name in expected if name.endswith("_identity")):
126
- require_identity_shape(request[field], field)
127
- for field in (
128
- name for name in expected
129
- if name == "name" or name.endswith("_name")
130
- ):
131
- require_basename(request[field], field)
132
-
133
- if operation == "mkdir_dir" and request["name"] != "archive":
134
- raise ProtocolError("archive mkdir can create only the archive root")
135
- if operation in ("write_atomic", "unlink_regular") and request["name"] not in ARCHIVE_FILES:
136
- raise ProtocolError("archive file mutation target is not allowlisted")
137
- if operation in ("write_atomic", "write_rebind_atomic"):
138
- if type(request["replace"]) is not bool:
139
- raise ProtocolError("replace must be a boolean")
140
- if request["temp_name"] == request["name"]:
141
- raise ProtocolError("archive publication temp name must be distinct")
142
- encoded = request["data_base64"]
143
- if not isinstance(encoded, str):
144
- raise ProtocolError("archive publication data must be base64 text")
145
- try:
146
- data = base64.b64decode(encoded, validate=True)
147
- except (ValueError, TypeError) as error:
148
- raise ProtocolError("archive publication data is invalid base64") from error
149
- if len(data) > MAX_DATA_BYTES:
150
- raise ProtocolError("archive publication data exceeds the protocol limit")
151
- request["_data"] = data
152
- if operation == "unlink_regular" and type(request["allow_missing"]) is not bool:
153
- raise ProtocolError("allow_missing must be a boolean")
154
- if operation == "rename_dir":
155
- parent_names = {
156
- request["source_parent_name"],
157
- request["destination_parent_name"],
158
- }
159
- if parent_names != {"active", "archive"}:
160
- raise ProtocolError("archive transition parents must be active and archive")
161
- return operation
162
-
163
-
164
- def require_directory_fd(fd, expected, label):
165
- current = os.fstat(fd)
166
- if not stat.S_ISDIR(current.st_mode):
167
- raise MutationError(f"{label} is not a directory")
168
- require_identity(current, expected, label)
169
-
170
-
171
- def lstat_at(fd, name):
172
- try:
173
- return os.stat(name, dir_fd=fd, follow_symlinks=False)
174
- except FileNotFoundError:
175
- return None
176
-
177
-
178
- def require_canonical_directory(fd, name, expected, label):
179
- current = lstat_at(fd, name)
180
- if current is None or not stat.S_ISDIR(current.st_mode):
181
- raise MutationError(f"{label} is missing or unsafe")
182
- require_identity(current, expected, label)
183
-
184
-
185
- def write_all(fd, data):
186
- offset = 0
187
- while offset < len(data):
188
- written = os.write(fd, data[offset:])
189
- if written <= 0:
190
- raise MutationError("short archive publication write")
191
- offset += written
192
-
193
-
194
- def write_atomic(request):
195
- fd = request["directory_fd"]
196
- expected = request["directory_identity"]
197
- name = request["name"]
198
- temp_name = request["temp_name"]
199
- replace = request["replace"]
200
- require_directory_fd(fd, expected, "archive write directory")
201
- current = lstat_at(fd, name)
202
- if current is not None:
203
- if not replace or not stat.S_ISREG(current.st_mode):
204
- raise MutationError("archive publication target is not safely replaceable")
205
- flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW
206
- temp_fd = None
207
- published = False
208
- try:
209
- temp_fd = os.open(temp_name, flags, 0o600, dir_fd=fd)
210
- write_all(temp_fd, request["_data"])
211
- os.fsync(temp_fd)
212
- os.close(temp_fd)
213
- temp_fd = None
214
- require_directory_fd(fd, expected, "archive write directory")
215
- os.rename(temp_name, name, src_dir_fd=fd, dst_dir_fd=fd)
216
- published = True
217
- require_directory_fd(fd, expected, "archive write directory")
218
- final = lstat_at(fd, name)
219
- if final is None or not stat.S_ISREG(final.st_mode):
220
- raise MutationError("archive publication did not produce a regular file")
221
- finally:
222
- if temp_fd is not None:
223
- os.close(temp_fd)
224
- if not published:
225
- try:
226
- os.unlink(temp_name, dir_fd=fd)
227
- except FileNotFoundError:
228
- pass
229
-
230
-
231
- def unlink_regular(request):
232
- fd = request["directory_fd"]
233
- expected = request["directory_identity"]
234
- require_directory_fd(fd, expected, "archive cleanup directory")
235
- current = lstat_at(fd, request["name"])
236
- if current is None:
237
- if request["allow_missing"]:
238
- return
239
- raise MutationError("archive cleanup target is missing")
240
- if not stat.S_ISREG(current.st_mode):
241
- raise MutationError("archive cleanup target is not a regular file")
242
- os.unlink(request["name"], dir_fd=fd)
243
- require_directory_fd(fd, expected, "archive cleanup directory")
244
- if lstat_at(fd, request["name"]) is not None:
245
- raise MutationError("archive cleanup target still exists")
246
-
247
-
248
- def rename_dir(request):
249
- source_parent_fd = request["source_parent_fd"]
250
- destination_parent_fd = request["destination_parent_fd"]
251
- guard_parent_fd = request["guard_parent_fd"]
252
- require_directory_fd(
253
- source_parent_fd,
254
- request["source_parent_identity"],
255
- "archive source parent",
256
- )
257
- require_directory_fd(
258
- destination_parent_fd,
259
- request["destination_parent_identity"],
260
- "archive destination parent",
261
- )
262
- require_directory_fd(
263
- guard_parent_fd,
264
- request["guard_parent_identity"],
265
- "archive guard parent",
266
- )
267
- require_canonical_directory(
268
- guard_parent_fd,
269
- request["source_parent_name"],
270
- request["source_parent_identity"],
271
- "canonical archive source parent",
272
- )
273
- require_canonical_directory(
274
- guard_parent_fd,
275
- request["destination_parent_name"],
276
- request["destination_parent_identity"],
277
- "canonical archive destination parent",
278
- )
279
- source = lstat_at(source_parent_fd, request["source_name"])
280
- if source is None or not stat.S_ISDIR(source.st_mode):
281
- raise MutationError("archive source directory is missing or unsafe")
282
- require_identity(source, request["source_identity"], "archive source directory")
283
- if lstat_at(destination_parent_fd, request["destination_name"]) is not None:
284
- raise MutationError("archive destination already exists")
285
- os.rename(
286
- request["source_name"],
287
- request["destination_name"],
288
- src_dir_fd=source_parent_fd,
289
- dst_dir_fd=destination_parent_fd,
290
- )
291
- require_directory_fd(
292
- source_parent_fd,
293
- request["source_parent_identity"],
294
- "archive source parent",
295
- )
296
- require_directory_fd(
297
- destination_parent_fd,
298
- request["destination_parent_identity"],
299
- "archive destination parent",
300
- )
301
- if lstat_at(source_parent_fd, request["source_name"]) is not None:
302
- raise MutationError("archive source still exists after transition")
303
- destination = lstat_at(destination_parent_fd, request["destination_name"])
304
- if destination is None or not stat.S_ISDIR(destination.st_mode):
305
- raise MutationError("archive destination is missing after transition")
306
- require_identity(destination, request["source_identity"], "archive destination directory")
307
-
308
-
309
- def mkdir_dir(request):
310
- fd = request["directory_fd"]
311
- expected = request["directory_identity"]
312
- require_directory_fd(fd, expected, "archive mkdir parent")
313
- current = lstat_at(fd, request["name"])
314
- if current is None:
315
- os.mkdir(request["name"], mode=0o700, dir_fd=fd)
316
- current = lstat_at(fd, request["name"])
317
- if current is None or not stat.S_ISDIR(current.st_mode):
318
- raise MutationError("archive directory cannot be created safely")
319
- require_directory_fd(fd, expected, "archive mkdir parent")
320
-
321
-
322
- OPERATIONS = {
323
- "write_atomic": write_atomic,
324
- "write_rebind_atomic": write_atomic,
325
- "unlink_regular": unlink_regular,
326
- "rename_dir": rename_dir,
327
- "mkdir_dir": mkdir_dir,
328
- }
329
-
330
-
331
- def read_request():
332
- raw = sys.stdin.buffer.read(MAX_REQUEST_BYTES + 1)
333
- if len(raw) > MAX_REQUEST_BYTES:
334
- raise ProtocolError("archive mutation request exceeds the protocol limit")
335
- try:
336
- return json.loads(raw.decode("utf-8"))
337
- except (UnicodeDecodeError, json.JSONDecodeError) as error:
338
- raise ProtocolError("archive mutation request is not valid UTF-8 JSON") from error
339
-
340
-
341
- def main():
342
- request = read_request()
343
- operation_name = validate_request(request)
344
- require_platform_support()
345
- OPERATIONS[operation_name](request)
346
- json.dump({"ok": True}, sys.stdout)
347
- sys.stdout.write("\n")
348
-
349
-
350
- if __name__ == "__main__":
351
- try:
352
- main()
353
- except Exception as error: # fail closed at the process boundary
354
- code = error.code if isinstance(error, MutationError) else "ARCHIVE_PATH_UNSAFE"
355
- json.dump(
356
- {
357
- "ok": False,
358
- "code": code,
359
- "error": type(error).__name__,
360
- "message": str(error),
361
- },
362
- sys.stderr,
363
- )
364
- sys.stderr.write("\n")
365
- sys.exit(3 if code == "ARCHIVE_RUNTIME_UNAVAILABLE" else 2)