@abelxiaoxing/cadence 1.8.3 → 2.0.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 (208) hide show
  1. package/README.md +324 -111
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -26
  4. package/agents/diagnosis-worker.md +14 -25
  5. package/agents/implementation-worker.md +16 -35
  6. package/config/.env.example +2 -5
  7. package/config/workflow-task.example.json +7 -0
  8. package/docs/remote-access.md +60 -0
  9. package/docs/reuse-and-distribution.md +44 -0
  10. package/docs/service-management.md +108 -0
  11. package/licenses/bundled-web.txt +6245 -0
  12. package/licenses/pi-web-ui-MIT.txt +21 -0
  13. package/licenses/pi-web-ui-reuse.md +22 -0
  14. package/package.json +94 -21
  15. package/src/index.ts +22 -1807
  16. package/src/native-build.ts +6 -0
  17. package/src/native-client.ts +103 -0
  18. package/src/native-export.ts +58 -0
  19. package/src/native-history.ts +359 -0
  20. package/src/operator-cli.mjs +453 -1937
  21. package/src/plan-proposal.ts +87 -0
  22. package/src/sdk-worker.mjs +549 -0
  23. package/src/service/contracts/active.ts +82 -0
  24. package/src/service/contracts/budget.ts +148 -0
  25. package/src/service/contracts/build.ts +67 -0
  26. package/src/service/contracts/capture.ts +48 -0
  27. package/src/service/contracts/core.ts +33 -0
  28. package/src/service/contracts/current.ts +261 -0
  29. package/src/service/contracts/domain.ts +68 -0
  30. package/src/service/contracts/history.ts +2245 -0
  31. package/src/service/contracts/models.ts +139 -0
  32. package/src/service/coordination.ts +862 -0
  33. package/src/service/private-files.ts +204 -0
  34. package/src/service-cli.mjs +5592 -0
  35. package/src/service-client.ts +747 -0
  36. package/src/service-contract.ts +245 -0
  37. package/src/service-host.mjs +22382 -0
  38. package/src/service-http-client.ts +425 -0
  39. package/src/service-wire.ts +372 -0
  40. package/src/task-shell.ts +1045 -0
  41. package/src/task-store.ts +479 -0
  42. package/src/task-types.ts +184 -0
  43. package/src/verification-diagnostics.ts +29 -72
  44. package/src/worker-capture.mjs +1197 -0
  45. package/src/worker-public.mjs +1753 -0
  46. package/src/worker-redaction.mjs +608 -0
  47. package/src/worker-tools.mjs +202 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  50. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  56. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  62. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  65. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  68. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  71. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  74. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  77. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  80. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  89. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  92. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  95. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  98. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  101. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  104. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  107. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  108. package/web/dist/assets/cadence.css +2 -0
  109. package/web/dist/assets/cadence.js +7 -0
  110. package/web/dist/assets/markdown.js +296 -0
  111. package/web/dist/assets/react.js +9 -0
  112. package/web/dist/assets/rolldown-runtime.js +1 -0
  113. package/web/dist/assets/timeline-client.js +41 -0
  114. package/web/dist/assets/vis-timeline.css +1274 -0
  115. package/web/dist/assets/vis-timeline.js +32 -0
  116. package/web/dist/index.html +18 -0
  117. package/web/dist/manifest.json +505 -0
  118. package/config/plan-draft.example.json +0 -125
  119. package/config/plan-draft.multiple-tasks.example.json +0 -375
  120. package/config/plan-draft.quick.example.json +0 -78
  121. package/config/routes.example.json +0 -39
  122. package/prompts/abel-design.md +0 -382
  123. package/prompts/abel-diagnose.md +0 -71
  124. package/prompts/abel-implement.md +0 -284
  125. package/prompts/abel-init.md +0 -52
  126. package/src/activation.ts +0 -44
  127. package/src/activity-contracts.ts +0 -82
  128. package/src/agent-registry.ts +0 -53
  129. package/src/apply-transaction.ts +0 -1437
  130. package/src/artifact-store.ts +0 -213
  131. package/src/build-windows-job.mjs +0 -95
  132. package/src/candidate-artifact.ts +0 -143
  133. package/src/candidate-patch.ts +0 -271
  134. package/src/candidate-workspace.ts +0 -80
  135. package/src/canonical.ts +0 -16
  136. package/src/change-contract-schema.ts +0 -191
  137. package/src/change-contract.ts +0 -324
  138. package/src/change-verification.ts +0 -1617
  139. package/src/child-budget.ts +0 -58
  140. package/src/child-model.ts +0 -142
  141. package/src/child-session.ts +0 -640
  142. package/src/contracts.ts +0 -2476
  143. package/src/control-contracts.ts +0 -382
  144. package/src/delivery-compiler.ts +0 -1805
  145. package/src/delivery-revision.ts +0 -42
  146. package/src/design-control.ts +0 -1299
  147. package/src/design-diagnostics.ts +0 -181
  148. package/src/design-journal.ts +0 -1773
  149. package/src/directory-sync.ts +0 -36
  150. package/src/durable-contracts.ts +0 -231
  151. package/src/durable-workflow.ts +0 -978
  152. package/src/evidence-draft.ts +0 -122
  153. package/src/execution-profile.ts +0 -64
  154. package/src/execution-retention.ts +0 -77
  155. package/src/file-snapshot.ts +0 -163
  156. package/src/implement-continuation.ts +0 -559
  157. package/src/implement-graph.ts +0 -690
  158. package/src/implement-plan.ts +0 -72
  159. package/src/install-config.d.mts +0 -6
  160. package/src/isolation-backend.ts +0 -353
  161. package/src/model-source.ts +0 -22
  162. package/src/openspec-cli.ts +0 -494
  163. package/src/operator-cli-entry.mjs +0 -24
  164. package/src/operator-tools.ts +0 -309
  165. package/src/package-candidate.ts +0 -227
  166. package/src/package-delivery.ts +0 -486
  167. package/src/package-state.ts +0 -320
  168. package/src/package-verification.ts +0 -635
  169. package/src/package-workflow.ts +0 -342
  170. package/src/packet-runtime.ts +0 -570
  171. package/src/parent-provider.ts +0 -375
  172. package/src/phase-execution.ts +0 -2259
  173. package/src/pi-adapter.ts +0 -19
  174. package/src/plan-draft-summary.ts +0 -527
  175. package/src/plan-draft.ts +0 -502
  176. package/src/remove-path.ts +0 -32
  177. package/src/route-policy.ts +0 -545
  178. package/src/run-state.ts +0 -420
  179. package/src/run-store.ts +0 -1179
  180. package/src/safe-path.ts +0 -77
  181. package/src/scoped-grep-worker.mjs +0 -23
  182. package/src/scoped-tools.ts +0 -775
  183. package/src/single-task-draft.ts +0 -142
  184. package/src/sqlite-schema.ts +0 -171
  185. package/src/state-root.ts +0 -215
  186. package/src/storage-schema.ts +0 -360
  187. package/src/subagent-activity.ts +0 -1417
  188. package/src/submit-tool.ts +0 -950
  189. package/src/task-ledger.ts +0 -1338
  190. package/src/transport-budget.ts +0 -52
  191. package/src/verification-capability.ts +0 -1191
  192. package/src/verification-environment-io.ts +0 -389
  193. package/src/verification-environment.ts +0 -162
  194. package/src/verification-identity.ts +0 -77
  195. package/src/verification-scan.ts +0 -64
  196. package/src/windows-job-backend.ts +0 -419
  197. package/src/windows-job.c +0 -149
  198. package/src/worker-broker.ts +0 -806
  199. package/src/workflow-engine.ts +0 -28
  200. package/src/workflow-policy.ts +0 -1679
  201. package/src/workflow-recovery-policy.ts +0 -97
  202. package/src/workflow-scheduling.ts +0 -105
  203. package/src/workflow-state-machine.ts +0 -4355
  204. package/src/workflow-status.ts +0 -576
  205. package/src/workspace-io-entry.mjs +0 -76
  206. package/src/workspace-io-worker.mjs +0 -1312
  207. package/src/workspace-io.ts +0 -89
  208. package/src/workspace-store.ts +0 -634
@@ -1,1312 +0,0 @@
1
- // Generated by scripts/build-workspace-io.mjs; edit workspace-io-entry.mjs and its source modules.
2
- // src/workspace-io-entry.mjs
3
- import { parentPort, workerData } from "node:worker_threads";
4
-
5
- // src/artifact-store.ts
6
- import { createHash, randomUUID } from "node:crypto";
7
- import {
8
- chmodSync,
9
- closeSync as closeSync2,
10
- constants as constants2,
11
- copyFileSync,
12
- fsyncSync as fsyncSync2,
13
- lstatSync,
14
- mkdirSync,
15
- openSync as openSync2,
16
- readFileSync,
17
- renameSync,
18
- unlinkSync,
19
- writeFileSync
20
- } from "node:fs";
21
- import path from "node:path";
22
-
23
- // src/directory-sync.ts
24
- import { closeSync, constants, fstatSync, fsyncSync, openSync } from "node:fs";
25
- function syncDirectory(directory) {
26
- const descriptor = openSync(directory, constants.O_RDONLY);
27
- try {
28
- try {
29
- fsyncSync(descriptor);
30
- return "synced";
31
- } catch (error) {
32
- const failure = error;
33
- if (process.platform === "win32" && failure?.code === "EPERM" && failure.syscall === "fsync" && fstatSync(descriptor).isDirectory()) {
34
- return "unsupported-directory-barrier";
35
- }
36
- throw error;
37
- }
38
- } finally {
39
- closeSync(descriptor);
40
- }
41
- }
42
-
43
- // src/artifact-store.ts
44
- var SHA256 = /^[a-f0-9]{64}$/u;
45
- function digest(bytes) {
46
- return createHash("sha256").update(bytes).digest("hex");
47
- }
48
- function requireHash(hash) {
49
- if (!SHA256.test(hash))
50
- throw new Error("artifact-hash-invalid");
51
- }
52
- function ensureDirectory(directory) {
53
- const stat = lstatSync(directory, { throwIfNoEntry: false });
54
- if (stat) {
55
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
56
- throw new Error("artifact-store-path-unsafe");
57
- }
58
- chmodSync(directory, 448);
59
- return;
60
- }
61
- let cursor = path.dirname(directory);
62
- while (cursor !== path.dirname(cursor)) {
63
- const ancestor = lstatSync(cursor, { throwIfNoEntry: false });
64
- if (ancestor) {
65
- if (ancestor.isSymbolicLink() || !ancestor.isDirectory()) {
66
- throw new Error("artifact-store-path-unsafe");
67
- }
68
- break;
69
- }
70
- cursor = path.dirname(cursor);
71
- }
72
- mkdirSync(directory, { recursive: true, mode: 448 });
73
- const created = lstatSync(directory);
74
- if (created.isSymbolicLink() || !created.isDirectory()) {
75
- throw new Error("artifact-store-path-unsafe");
76
- }
77
- chmodSync(directory, 448);
78
- }
79
- function writeAtomic(target, bytes) {
80
- const directory = path.dirname(target);
81
- ensureDirectory(directory);
82
- const temporary = path.join(directory, `.${path.basename(target)}.${process.pid}.${randomUUID()}.tmp`);
83
- const descriptor = openSync2(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
84
- try {
85
- writeFileSync(descriptor, bytes);
86
- fsyncSync2(descriptor);
87
- } finally {
88
- closeSync2(descriptor);
89
- }
90
- try {
91
- renameSync(temporary, target);
92
- chmodSync(target, 384);
93
- syncDirectory(directory);
94
- } catch (error) {
95
- try {
96
- unlinkSync(temporary);
97
- } catch {}
98
- throw error;
99
- }
100
- }
101
-
102
- class ArtifactStore {
103
- root;
104
- #blobs;
105
- #references;
106
- constructor(root) {
107
- if (!path.isAbsolute(root))
108
- throw new Error("artifact-store-root-invalid");
109
- this.root = path.resolve(root);
110
- this.#blobs = path.join(this.root, "blobs");
111
- this.#references = path.join(this.root, "references");
112
- ensureDirectory(this.root);
113
- ensureDirectory(this.#blobs);
114
- ensureDirectory(this.#references);
115
- }
116
- #blobPath(hash) {
117
- requireHash(hash);
118
- return path.join(this.#blobs, hash.slice(0, 2), hash);
119
- }
120
- #referencePath(hash) {
121
- requireHash(hash);
122
- return path.join(this.#references, hash.slice(0, 2), `${hash}.ref`);
123
- }
124
- put(input) {
125
- if (!(input instanceof Uint8Array))
126
- throw new Error("artifact-bytes-invalid");
127
- const bytes = Buffer.from(input);
128
- const hash = digest(bytes);
129
- const target = this.#blobPath(hash);
130
- const existing = lstatSync(target, { throwIfNoEntry: false });
131
- if (existing) {
132
- if (existing.isSymbolicLink() || !existing.isFile()) {
133
- throw new Error("artifact-store-path-unsafe");
134
- }
135
- const current = readFileSync(target);
136
- if (current.length !== bytes.length || digest(current) !== hash) {
137
- throw new Error("artifact-integrity-invalid");
138
- }
139
- } else {
140
- writeAtomic(target, bytes);
141
- const stored = readFileSync(target);
142
- if (stored.length !== bytes.length || digest(stored) !== hash) {
143
- throw new Error("artifact-integrity-invalid");
144
- }
145
- }
146
- return Object.freeze({ hash, bytes: bytes.length });
147
- }
148
- read(hash) {
149
- const target = this.#blobPath(hash);
150
- const stat = lstatSync(target, { throwIfNoEntry: false });
151
- if (!stat?.isFile() || stat.isSymbolicLink()) {
152
- throw new Error("artifact-unavailable");
153
- }
154
- const bytes = readFileSync(target);
155
- if (digest(bytes) !== hash)
156
- throw new Error("artifact-integrity-invalid");
157
- return Buffer.from(bytes);
158
- }
159
- copyVerified(hash, destination) {
160
- const source = this.#blobPath(hash);
161
- const stat = lstatSync(source, { throwIfNoEntry: false });
162
- if (!stat?.isFile() || stat.isSymbolicLink())
163
- throw new Error("artifact-unavailable");
164
- copyFileSync(source, destination, constants2.COPYFILE_EXCL | constants2.COPYFILE_FICLONE);
165
- const bytes = readFileSync(destination);
166
- if (digest(bytes) !== hash)
167
- throw new Error("artifact-integrity-invalid");
168
- return bytes.byteLength;
169
- }
170
- referenceCount(hash) {
171
- requireHash(hash);
172
- const referencePath = this.#referencePath(hash);
173
- const stat = lstatSync(referencePath, { throwIfNoEntry: false });
174
- if (!stat)
175
- return 0;
176
- if (!stat.isFile() || stat.isSymbolicLink()) {
177
- throw new Error("artifact-reference-invalid");
178
- }
179
- const raw = readFileSync(referencePath, "utf8");
180
- if (!/^(?:0|[1-9][0-9]*)\n$/u.test(raw)) {
181
- throw new Error("artifact-reference-invalid");
182
- }
183
- const count = Number(raw.trim());
184
- if (!Number.isSafeInteger(count))
185
- throw new Error("artifact-reference-invalid");
186
- return count;
187
- }
188
- #setReferenceCount(hash, count) {
189
- if (!Number.isSafeInteger(count) || count < 0) {
190
- throw new Error("artifact-reference-invalid");
191
- }
192
- writeAtomic(this.#referencePath(hash), Buffer.from(`${count}
193
- `));
194
- return count;
195
- }
196
- retain(hash) {
197
- this.read(hash);
198
- const current = this.referenceCount(hash);
199
- if (current === Number.MAX_SAFE_INTEGER) {
200
- throw new Error("artifact-reference-overflow");
201
- }
202
- return this.#setReferenceCount(hash, current + 1);
203
- }
204
- release(hash) {
205
- this.read(hash);
206
- const current = this.referenceCount(hash);
207
- if (current === 0)
208
- throw new Error("artifact-reference-underflow");
209
- return this.#setReferenceCount(hash, current - 1);
210
- }
211
- }
212
-
213
- // src/remove-path.ts
214
- import { lstatSync as lstatSync2, readdirSync, rmdirSync, rmSync, unlinkSync as unlinkSync2 } from "node:fs";
215
- import path2 from "node:path";
216
- function removePathSync(target, options = {}) {
217
- if (process.platform !== "win32") {
218
- rmSync(target, options);
219
- return;
220
- }
221
- let stat;
222
- try {
223
- stat = lstatSync2(target);
224
- } catch (error) {
225
- if (options.force && error.code === "ENOENT")
226
- return;
227
- throw error;
228
- }
229
- if (stat.isDirectory() && !stat.isSymbolicLink()) {
230
- if (!options.recursive)
231
- throw Object.assign(new Error("Path is a directory"), {
232
- code: "ERR_FS_EISDIR"
233
- });
234
- for (const name of readdirSync(target))
235
- removePathSync(path2.join(target, name), options);
236
- rmdirSync(target);
237
- } else
238
- unlinkSync2(target);
239
- }
240
-
241
- // src/verification-environment-io.ts
242
- import {
243
- constants as constants3,
244
- cpSync,
245
- lstatSync as lstatSync3,
246
- mkdirSync as mkdirSync2,
247
- readdirSync as readdirSync2,
248
- readlinkSync,
249
- realpathSync,
250
- symlinkSync,
251
- writeFileSync as writeFileSync2
252
- } from "node:fs";
253
- import path3 from "node:path";
254
- var CACHES = [".vite", ".vite-temp"];
255
- function within(root, candidate) {
256
- const relative = path3.relative(root, candidate);
257
- return relative !== ".." && !relative.startsWith(`..${path3.sep}`) && !path3.isAbsolute(relative);
258
- }
259
- function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings, profile, environment, checkCancelled, privateRoot) {
260
- const cleanup = () => removePathSync(privateRoot, { recursive: true, force: true });
261
- try {
262
- const local = profile.mode !== "isolated";
263
- const native = profile.mode === "host-trusted";
264
- if (local && realpathSync(root) === realpathSync(dependencyOwner))
265
- throw new Error("trusted-candidate-required");
266
- const mounts = [
267
- { source: privateRoot, target: "/cadence", writable: true }
268
- ];
269
- for (const directory of [
270
- "home",
271
- "cache",
272
- "tmp",
273
- "reports",
274
- "dependencies"
275
- ]) {
276
- mkdirSync2(path3.join(privateRoot, directory), { mode: 448 });
277
- }
278
- if (native) {
279
- mkdirSync2(path3.join(privateRoot, "home/AppData/Roaming"), {
280
- recursive: true
281
- });
282
- mkdirSync2(path3.join(privateRoot, "home/AppData/Local"), {
283
- recursive: true
284
- });
285
- }
286
- writeFileSync2(path3.join(privateRoot, "home/user.npmrc"), "");
287
- writeFileSync2(path3.join(privateRoot, "home/global.npmrc"), "");
288
- const dependencies = path3.join(dependencyOwner, "node_modules");
289
- const stat = lstatSync3(dependencies, { throwIfNoEntry: false });
290
- if (stat && !native) {
291
- const target = path3.join(root, "node_modules");
292
- const targetStat = lstatSync3(target, { throwIfNoEntry: false });
293
- if (!stat.isDirectory() || stat.isSymbolicLink() || targetStat && (!targetStat.isDirectory() || targetStat.isSymbolicLink())) {
294
- throw new Error("dependency-path-unsafe");
295
- }
296
- if (!targetStat)
297
- mkdirSync2(target, { mode: 448 });
298
- const view = path3.join(privateRoot, "dependencies");
299
- mounts.push({ source: view, target: "/workspace/node_modules" });
300
- const entries = readdirSync2(dependencies);
301
- if (entries.length > 20000)
302
- throw new Error("dependency-view-too-large");
303
- const canonical = realpathSync(dependencies);
304
- let viewEntries = 0;
305
- const projectEntry = (name) => {
306
- checkCancelled();
307
- if (++viewEntries > 20000)
308
- throw new Error("dependency-view-too-large");
309
- if (CACHES.includes(name))
310
- return;
311
- const source = path3.join(dependencies, name);
312
- const placeholder = path3.join(view, name);
313
- const entry = lstatSync3(source);
314
- if (entry.isSymbolicLink()) {
315
- const resolved = realpathSync(source);
316
- if (within(canonical, resolved)) {
317
- symlinkSync(path3.relative(path3.dirname(source), resolved), placeholder);
318
- } else if (within(realpathSync(dependencyOwner), resolved)) {
319
- const relative = path3.relative(realpathSync(dependencyOwner), resolved);
320
- const candidate = path3.join(root, relative);
321
- if (!lstatSync3(candidate, { throwIfNoEntry: false }))
322
- throw new Error("workspace-dependency-missing");
323
- symlinkSync(local ? candidate : `/workspace/${relative.split(path3.sep).join("/")}`, placeholder);
324
- } else
325
- throw new Error("dependency-path-unsafe");
326
- } else if (entry.isDirectory() && name.startsWith("@") && !name.includes(path3.sep)) {
327
- mkdirSync2(placeholder);
328
- for (const child of readdirSync2(source))
329
- projectEntry(path3.join(name, child));
330
- } else {
331
- if (entry.isDirectory())
332
- mkdirSync2(placeholder);
333
- else if (entry.isFile())
334
- writeFileSync2(placeholder, "");
335
- else
336
- throw new Error("dependency-path-unsafe");
337
- mounts.push({
338
- source,
339
- target: `/workspace/node_modules/${name.split(path3.sep).join("/")}`
340
- });
341
- }
342
- };
343
- for (const name of entries)
344
- projectEntry(name);
345
- for (const cache of CACHES) {
346
- mkdirSync2(path3.join(view, cache));
347
- const source = path3.join(privateRoot, "cache", cache);
348
- mkdirSync2(source);
349
- mounts.push({
350
- source,
351
- target: `/workspace/node_modules/${cache}`,
352
- writable: true
353
- });
354
- }
355
- }
356
- if (local && stat && !native) {
357
- const copyEntry = (name) => {
358
- checkCancelled();
359
- const source = path3.join(dependencies, name);
360
- const destination = path3.join(root, "node_modules", name);
361
- if (CACHES.includes(name)) {
362
- mkdirSync2(destination, { recursive: true });
363
- return;
364
- }
365
- removeEntry(destination, checkCancelled);
366
- const stat2 = lstatSync3(source);
367
- if (stat2.isSymbolicLink()) {
368
- const resolved = realpathSync(source);
369
- const translated = within(canonicalRoot(dependencyOwner), resolved) ? path3.join(root, path3.relative(canonicalRoot(dependencyOwner), resolved)) : resolved;
370
- symlinkSync(translated, destination);
371
- } else if (stat2.isDirectory() && name.startsWith("@") && !name.includes(path3.sep)) {
372
- mkdirSync2(destination);
373
- for (const child of readdirSync2(source))
374
- copyEntry(path3.join(name, child));
375
- } else
376
- cpSync(source, destination, {
377
- recursive: true,
378
- filter: () => {
379
- checkCancelled();
380
- return true;
381
- },
382
- mode: constants3.COPYFILE_FICLONE,
383
- verbatimSymlinks: true
384
- });
385
- };
386
- for (const name of readdirSync2(path3.join(privateRoot, "dependencies")))
387
- copyEntry(name);
388
- }
389
- if (native && stat) {
390
- const target = path3.join(root, "node_modules");
391
- const targetStat = lstatSync3(target, { throwIfNoEntry: false });
392
- if (!stat.isDirectory() || stat.isSymbolicLink() || targetStat && (!targetStat.isDirectory() || targetStat.isSymbolicLink()))
393
- throw new Error("dependency-path-unsafe");
394
- const owner = realpathSync(dependencyOwner);
395
- let count = 0;
396
- const copy = (source, destination) => {
397
- checkCancelled();
398
- if (++count > 500000)
399
- throw new Error("dependency-view-too-large");
400
- const entry = lstatSync3(source);
401
- if (entry.isSymbolicLink()) {
402
- const resolved = realpathSync(source);
403
- if (!within(owner, resolved))
404
- throw new Error("dependency-path-unsafe");
405
- const candidate = path3.join(root, path3.relative(owner, resolved));
406
- if (!within(realpathSync(dependencies), resolved) && !lstatSync3(candidate, { throwIfNoEntry: false }))
407
- throw new Error("workspace-dependency-missing");
408
- const linked = lstatSync3(resolved);
409
- if (linked.isDirectory()) {
410
- symlinkSync(candidate, destination, process.platform === "win32" ? "junction" : "dir");
411
- } else if (linked.isFile()) {
412
- const file = within(realpathSync(dependencies), resolved) ? resolved : candidate;
413
- cpSync(file, destination, { mode: constants3.COPYFILE_FICLONE });
414
- } else
415
- throw new Error("dependency-path-unsafe");
416
- } else if (entry.isDirectory()) {
417
- mkdirSync2(destination, { recursive: true });
418
- for (const name of readdirSync2(source)) {
419
- if (source === dependencies && CACHES.includes(name)) {
420
- mkdirSync2(path3.join(destination, name));
421
- continue;
422
- }
423
- copy(path3.join(source, name), path3.join(destination, name));
424
- }
425
- } else if (entry.isFile())
426
- cpSync(source, destination, { mode: constants3.COPYFILE_FICLONE });
427
- else
428
- throw new Error("dependency-path-unsafe");
429
- };
430
- removeEntry(target, checkCancelled);
431
- copy(dependencies, target);
432
- }
433
- const sources = new Map;
434
- const bindings = runnerBindings.map((binding) => {
435
- if (native) {
436
- const translate2 = (value) => path3.isAbsolute(value) && within(dependencyOwner, value) ? path3.join(root, path3.relative(dependencyOwner, value)) : value;
437
- return {
438
- ...binding,
439
- executablePath: translate2(binding.executablePath),
440
- ...binding.fixedArgs ? { fixedArgs: binding.fixedArgs.map(translate2) } : {}
441
- };
442
- }
443
- if (local || !binding.mountSource)
444
- return { ...binding };
445
- const source = path3.resolve(binding.mountSource);
446
- const stat2 = lstatSync3(source);
447
- const executable = realpathSync(binding.executablePath);
448
- if (!stat2.isDirectory() || stat2.isSymbolicLink() || !within(source, executable)) {
449
- throw new Error("runner-mount-unavailable");
450
- }
451
- let target = sources.get(source);
452
- if (!target) {
453
- target = `/cadence-runners/${sources.size}`;
454
- sources.set(source, target);
455
- mounts.push({ source, target });
456
- }
457
- const translate = (file) => path3.posix.join(target, ...path3.relative(source, file).split(path3.sep));
458
- return {
459
- ...binding,
460
- executablePath: translate(lstatSync3(binding.executablePath).isSymbolicLink() && !path3.isAbsolute(readlinkSync(binding.executablePath)) ? binding.executablePath : executable),
461
- ...binding.fixedArgs ? {
462
- fixedArgs: binding.fixedArgs.map((arg) => path3.isAbsolute(arg) && within(source, arg) ? translate(arg) : arg)
463
- } : {}
464
- };
465
- });
466
- checkCancelled();
467
- return {
468
- privateRoot,
469
- mounts: native ? [] : mounts,
470
- bindings,
471
- reportRoot: local ? privateRoot : "/cadence",
472
- environment: {
473
- ...local ? Object.fromEntries(profile.inheritEnvironment.flatMap((name) => environment[name] === undefined ? [] : [[name, environment[name] ?? ""]])) : {},
474
- ...native ? Object.fromEntries(Object.entries(environment).filter(([key]) => ["SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT"].includes(key.toUpperCase()))) : {},
475
- ...native ? {
476
- USERPROFILE: path3.join(privateRoot, "home"),
477
- APPDATA: path3.join(privateRoot, "home", "AppData", "Roaming"),
478
- LOCALAPPDATA: path3.join(privateRoot, "home", "AppData", "Local"),
479
- TEMP: path3.join(privateRoot, "tmp"),
480
- TMP: path3.join(privateRoot, "tmp")
481
- } : {},
482
- CI: "1",
483
- HOME: local ? path3.join(privateRoot, "home") : "/cadence/home",
484
- XDG_CACHE_HOME: local ? path3.join(privateRoot, "cache") : "/cadence/cache",
485
- TMPDIR: local ? path3.join(privateRoot, "tmp") : "/cadence/tmp",
486
- npm_config_cache: local ? path3.join(privateRoot, "cache/npm") : "/cadence/cache/npm",
487
- npm_config_userconfig: local ? path3.join(privateRoot, "home/user.npmrc") : "/cadence/home/user.npmrc",
488
- npm_config_globalconfig: local ? path3.join(privateRoot, "home/global.npmrc") : "/cadence/home/global.npmrc",
489
- PATH: [
490
- ...new Set([
491
- ...bindings.filter((binding) => binding.mountSource),
492
- ...bindings.filter((binding) => !binding.mountSource)
493
- ].map((binding) => (local ? path3 : path3.posix).dirname(binding.executablePath))),
494
- ...local ? (environment.PATH ?? "").split(path3.delimiter) : [],
495
- ...native ? [] : ["/usr/local/bin", "/usr/bin", "/bin"]
496
- ].join(local ? path3.delimiter : ":")
497
- }
498
- };
499
- } catch (error) {
500
- cleanup();
501
- throw error;
502
- }
503
- }
504
- function canonicalRoot(root) {
505
- return realpathSync(root);
506
- }
507
- function removeEntry(file, checkCancelled) {
508
- checkCancelled();
509
- const stat = lstatSync3(file, { throwIfNoEntry: false });
510
- if (!stat)
511
- return;
512
- if (stat.isDirectory() && !stat.isSymbolicLink())
513
- for (const name of readdirSync2(file))
514
- removeEntry(path3.join(file, name), checkCancelled);
515
- removePathSync(file, {
516
- recursive: stat.isDirectory() && !stat.isSymbolicLink(),
517
- force: true
518
- });
519
- }
520
-
521
- // src/verification-identity.ts
522
- import { createHash as createHash2 } from "node:crypto";
523
- import {
524
- closeSync as closeSync3,
525
- constants as constants4,
526
- fstatSync as fstatSync2,
527
- lstatSync as lstatSync4,
528
- openSync as openSync3,
529
- readdirSync as readdirSync3,
530
- readlinkSync as readlinkSync2,
531
- readSync
532
- } from "node:fs";
533
- import path4 from "node:path";
534
- function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
535
- const digest2 = createHash2("sha256");
536
- let entries = 0;
537
- let bytes = 0;
538
- const started = Date.now();
539
- const buffer = Buffer.alloc(256 * 1024);
540
- const visit = (file, relative, dependencyRoot) => {
541
- checkCancelled();
542
- if (++entries > 500000 || Date.now() - started > 60000)
543
- throw new Error("verification-environment-limit");
544
- const stat = lstatSync4(file, { throwIfNoEntry: false });
545
- digest2.update(JSON.stringify([relative, stat ? stat.mode & 511 : null]));
546
- if (!stat) {
547
- digest2.update("absent");
548
- return;
549
- }
550
- if (stat.isSymbolicLink()) {
551
- digest2.update(JSON.stringify(["link", readlinkSync2(file)]));
552
- return;
553
- }
554
- if (stat.isDirectory()) {
555
- digest2.update("directory");
556
- for (const name of readdirSync3(file).sort()) {
557
- if (dependencyRoot && [".vite", ".vite-temp"].includes(name))
558
- continue;
559
- visit(path4.join(file, name), `${relative}/${name}`, false);
560
- }
561
- return;
562
- }
563
- if (!stat.isFile())
564
- throw new Error("verification-environment-unsafe");
565
- const fd = openSync3(file, constants4.O_RDONLY | constants4.O_NOFOLLOW);
566
- try {
567
- const before = fstatSync2(fd);
568
- if (before.ino !== stat.ino || before.dev !== stat.dev)
569
- throw new Error("verification-environment-changed");
570
- const content = createHash2("sha256");
571
- for (;; ) {
572
- const count = readSync(fd, buffer, 0, buffer.length, null);
573
- if (count === 0)
574
- break;
575
- checkCancelled();
576
- bytes += count;
577
- if (bytes > 16 * 1024 ** 3 || Date.now() - started > 60000)
578
- throw new Error("verification-environment-limit");
579
- content.update(buffer.subarray(0, count));
580
- }
581
- const after = fstatSync2(fd);
582
- if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs)
583
- throw new Error("verification-environment-changed");
584
- digest2.update(JSON.stringify(["file", content.digest("hex")]));
585
- } finally {
586
- closeSync3(fd);
587
- }
588
- };
589
- for (const root of [...new Set(roots)].sort())
590
- visit(root, root, path4.basename(root) === "node_modules");
591
- return digest2.digest("hex");
592
- }
593
-
594
- // src/workspace-store.ts
595
- import { execFile, execFileSync } from "node:child_process";
596
- import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
597
- import {
598
- chmodSync as chmodSync2,
599
- closeSync as closeSync4,
600
- constants as constants5,
601
- fsyncSync as fsyncSync3,
602
- lstatSync as lstatSync6,
603
- mkdirSync as mkdirSync3,
604
- openSync as openSync4,
605
- readFileSync as readFileSync2,
606
- renameSync as renameSync2,
607
- unlinkSync as unlinkSync3,
608
- writeFileSync as writeFileSync3
609
- } from "node:fs";
610
- import path6 from "node:path";
611
- import { promisify } from "node:util";
612
-
613
- // src/canonical.ts
614
- function compareCanonicalStrings(left, right) {
615
- return left < right ? -1 : left > right ? 1 : 0;
616
- }
617
-
618
- // src/control-contracts.ts
619
- var CONTROL_SCHEMA_PROPERTIES = {
620
- command: {
621
- type: "string",
622
- enum: ["start", "status", "resume", "rebind", "cancel", "discard"]
623
- },
624
- stage: { type: "string", enum: ["abel-implement"] },
625
- change: { type: "string" },
626
- operationId: { type: "string" },
627
- deliveryRevision: { type: "integer", minimum: 1 },
628
- receiptHash: { type: "string" },
629
- routeId: { type: "string" },
630
- recovery: {
631
- type: "object",
632
- additionalProperties: false,
633
- properties: {
634
- incidentKey: { type: "string", pattern: "^[a-f0-9]{64}$" },
635
- failureSequence: { type: "integer", minimum: 1 },
636
- reason: {
637
- type: "string",
638
- enum: ["parent-directed-retry", "route-changed", "context-extended"]
639
- }
640
- },
641
- required: ["incidentKey", "failureSequence", "reason"]
642
- }
643
- };
644
- var commandSchema = (command, fields) => ({
645
- type: "object",
646
- properties: {
647
- command: { type: "string", enum: [command] },
648
- ...Object.fromEntries(fields.map((field) => [field, CONTROL_SCHEMA_PROPERTIES[field]]))
649
- },
650
- required: ["command", ...fields],
651
- additionalProperties: false
652
- });
653
- var CONTROL_COMMAND_PARAMETERS = {
654
- type: "object",
655
- properties: CONTROL_SCHEMA_PROPERTIES,
656
- required: ["command", "stage", "change"],
657
- anyOf: [
658
- commandSchema("start", ["stage", "change", "operationId"]),
659
- commandSchema("status", ["stage", "change"]),
660
- commandSchema("resume", ["stage", "change", "operationId"]),
661
- commandSchema("resume", ["stage", "change", "operationId", "recovery"]),
662
- commandSchema("resume", [
663
- "stage",
664
- "change",
665
- "operationId",
666
- "deliveryRevision",
667
- "receiptHash",
668
- "recovery"
669
- ]),
670
- commandSchema("resume", [
671
- "stage",
672
- "change",
673
- "operationId",
674
- "deliveryRevision",
675
- "receiptHash"
676
- ]),
677
- commandSchema("rebind", ["stage", "change", "operationId", "routeId"]),
678
- commandSchema("cancel", ["stage", "change", "operationId"]),
679
- commandSchema("discard", ["stage", "change", "operationId"])
680
- ],
681
- additionalProperties: false
682
- };
683
- var CONTROL_TOOL_FIELDS = new Set([
684
- "command",
685
- "stage",
686
- "change",
687
- "operationId",
688
- "deliveryRevision",
689
- "receiptHash",
690
- "routeId",
691
- "recovery"
692
- ]);
693
- var COMMAND_FIELDS = Object.freeze({
694
- start: new Set(["command", "stage", "change", "operationId"]),
695
- status: new Set(["command", "stage", "change"]),
696
- resume: new Set([
697
- "command",
698
- "stage",
699
- "change",
700
- "operationId",
701
- "deliveryRevision",
702
- "receiptHash",
703
- "recovery"
704
- ]),
705
- rebind: new Set(["command", "stage", "change", "operationId", "routeId"]),
706
- cancel: new Set(["command", "stage", "change", "operationId"]),
707
- discard: new Set(["command", "stage", "change", "operationId"])
708
- });
709
- // src/contracts.ts
710
- var LIMITS = {
711
- maxActiveChildSessions: 4,
712
- maxEnvelopeBytes: 64 * 1024,
713
- phaseTimeoutMs: 20 * 60 * 1000,
714
- maxCompleteResultBytes: 64 * 1024
715
- };
716
- var VERIFICATION_LIMITS = Object.freeze({
717
- minSteps: 1,
718
- maxSteps: 8,
719
- minTestFiles: 1,
720
- maxTestFiles: 64
721
- });
722
- var NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
723
- var RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
724
- function isValidRelativePath(p) {
725
- return typeof p === "string" && p.length > 0 && p.length <= 512 && !NONCANONICAL.test(p) && !p.startsWith("/") && !/^[a-z]:\//iu.test(p) && (p === "." || !p.startsWith("./") && !p.endsWith("/") && !p.split("/").includes(".")) && !p.includes("..") && !p.includes("\\") && !p.includes("\x00");
726
- }
727
-
728
- // src/safe-path.ts
729
- import { lstatSync as lstatSync5 } from "node:fs";
730
- import path5 from "node:path";
731
- function observeSafePath(root, relative) {
732
- if (!isValidRelativePath(relative)) {
733
- return { kind: "unsafe", reason: "invalid-path" };
734
- }
735
- try {
736
- const resolvedRoot = path5.resolve(root);
737
- const rootStat = lstatSync5(resolvedRoot, { throwIfNoEntry: false });
738
- if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
739
- return { kind: "unsafe", reason: "root-unavailable" };
740
- }
741
- if (relative === ".")
742
- return { kind: "directory" };
743
- let current = resolvedRoot;
744
- const parts = relative.split("/");
745
- for (const [index, part] of parts.entries()) {
746
- current = path5.join(current, part);
747
- if (!current.startsWith(`${resolvedRoot}${path5.sep}`)) {
748
- return { kind: "unsafe", reason: "invalid-path" };
749
- }
750
- const stat = lstatSync5(current, { throwIfNoEntry: false });
751
- if (!stat)
752
- return { kind: "absent" };
753
- if (stat.isSymbolicLink()) {
754
- return { kind: "unsafe", reason: "symlink" };
755
- }
756
- if (index < parts.length - 1) {
757
- if (!stat.isDirectory()) {
758
- return { kind: "unsafe", reason: "parent-not-directory" };
759
- }
760
- continue;
761
- }
762
- if (stat.isFile())
763
- return { kind: "file" };
764
- if (stat.isDirectory())
765
- return { kind: "directory" };
766
- return { kind: "unsafe", reason: "special-file" };
767
- }
768
- } catch {
769
- return { kind: "unsafe", reason: "path-unavailable" };
770
- }
771
- return { kind: "unsafe", reason: "invalid-path" };
772
- }
773
-
774
- // src/workspace-io.ts
775
- import { Worker } from "node:worker_threads";
776
- async function runWorkspaceIo(input) {
777
- input.signal?.throwIfAborted();
778
- const cancelled = new SharedArrayBuffer(4);
779
- const flag = new Int32Array(cancelled);
780
- const started = performance.now();
781
- const worker = new Worker(new URL("./workspace-io-worker.mjs", import.meta.url), {
782
- workerData: {
783
- root: input.root,
784
- artifactRoot: input.artifactRoot,
785
- operation: input.operation,
786
- args: input.args,
787
- cancelled
788
- },
789
- execArgv: []
790
- });
791
- const abort = () => {
792
- Atomics.store(flag, 0, 1);
793
- worker.postMessage("cancel");
794
- };
795
- input.signal?.addEventListener("abort", abort, { once: true });
796
- if (input.signal?.aborted)
797
- abort();
798
- try {
799
- return await new Promise((resolve, reject) => {
800
- let message;
801
- let failure;
802
- worker.once("message", (value) => {
803
- message = value;
804
- });
805
- worker.once("error", (error) => {
806
- failure = error;
807
- });
808
- worker.once("exit", (code) => {
809
- try {
810
- input.onMetrics?.({
811
- operation: input.operation,
812
- elapsedMs: performance.now() - started,
813
- files: message?.files ?? 0,
814
- bytes: message?.bytes ?? 0
815
- });
816
- } catch {}
817
- if (input.signal?.aborted)
818
- reject(input.signal.reason);
819
- else if (failure || code !== 0 || !message)
820
- reject(new Error("workspace-io-unavailable"));
821
- else if (!message.ok)
822
- reject(new Error(message.error ?? "workspace-io-failed"));
823
- else
824
- resolve(message.result);
825
- });
826
- });
827
- } finally {
828
- input.signal?.removeEventListener("abort", abort);
829
- }
830
- }
831
-
832
- // src/workspace-store.ts
833
- var SHA2562 = /^[a-f0-9]{64}$/u;
834
- function hash(...values) {
835
- const digest2 = createHash3("sha256");
836
- for (const value of values) {
837
- const bytes = typeof value === "string" ? Buffer.from(value) : value;
838
- digest2.update(`${bytes.byteLength}:`);
839
- digest2.update(bytes);
840
- }
841
- return digest2.digest("hex");
842
- }
843
- function stableEntries(entries) {
844
- return Object.fromEntries(Object.entries(entries).sort(([left], [right]) => compareCanonicalStrings(left, right)).map(([relative, entry]) => [relative, { ...entry }]));
845
- }
846
- function manifestHash(entries) {
847
- return hash("cadence-workspace-manifest", JSON.stringify(stableEntries(entries)));
848
- }
849
- function revisionId(parentRevisionId, manifest) {
850
- return hash("cadence-workspace-revision", parentRevisionId ?? "", manifest);
851
- }
852
- function ensureDirectory2(directory) {
853
- const stat = lstatSync6(directory, { throwIfNoEntry: false });
854
- if (stat) {
855
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
856
- throw new Error("unsafe-workspace-path");
857
- }
858
- chmodSync2(directory, 448);
859
- return;
860
- }
861
- let cursor = path6.dirname(directory);
862
- while (cursor !== path6.dirname(cursor)) {
863
- const ancestor = lstatSync6(cursor, { throwIfNoEntry: false });
864
- if (ancestor) {
865
- if (ancestor.isSymbolicLink() || !ancestor.isDirectory()) {
866
- throw new Error("unsafe-workspace-path");
867
- }
868
- break;
869
- }
870
- cursor = path6.dirname(cursor);
871
- }
872
- mkdirSync3(directory, { recursive: true, mode: 448 });
873
- const created = lstatSync6(directory);
874
- if (created.isSymbolicLink() || !created.isDirectory()) {
875
- throw new Error("unsafe-workspace-path");
876
- }
877
- chmodSync2(directory, 448);
878
- }
879
- function writeAtomic2(target, bytes) {
880
- const directory = path6.dirname(target);
881
- ensureDirectory2(directory);
882
- const temporary = path6.join(directory, `.${path6.basename(target)}.${process.pid}.${randomUUID2()}.tmp`);
883
- const descriptor = openSync4(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
884
- try {
885
- writeFileSync3(descriptor, bytes);
886
- fsyncSync3(descriptor);
887
- } finally {
888
- closeSync4(descriptor);
889
- }
890
- try {
891
- renameSync2(temporary, target);
892
- chmodSync2(target, 384);
893
- syncDirectory(directory);
894
- } catch (error) {
895
- try {
896
- unlinkSync3(temporary);
897
- } catch {}
898
- throw error;
899
- }
900
- }
901
- function requireSafeRelative(relative) {
902
- if (!isValidRelativePath(relative) || relative === ".") {
903
- throw new Error("unsafe-workspace-path");
904
- }
905
- }
906
- function isExcluded(relative, excluded) {
907
- return excluded.some((entry) => relative === entry || relative.startsWith(`${entry}/`));
908
- }
909
- function parseNullSeparated(bytes) {
910
- const decoded = bytes.toString("utf8");
911
- if (!Buffer.from(decoded, "utf8").equals(bytes)) {
912
- throw new Error("unsafe-workspace-path");
913
- }
914
- return decoded.split("\x00").filter((entry) => entry.length > 0);
915
- }
916
- function sameEntry(left, right) {
917
- if (!left || !right)
918
- return left === right;
919
- if (left.kind !== right.kind)
920
- return false;
921
- return left.kind === "absent" || right.kind === "file" && left.hash === right.hash && left.bytes === right.bytes && left.mode === right.mode;
922
- }
923
- function requireMode(mode) {
924
- if (!Number.isInteger(mode) || mode < 0 || mode > 511) {
925
- throw new Error("workspace-file-mode-invalid");
926
- }
927
- return mode;
928
- }
929
- function requireRevisionShape(value) {
930
- if (!value || typeof value !== "object" || Array.isArray(value)) {
931
- throw new Error("workspace-revision-invalid");
932
- }
933
- const candidate = value;
934
- if (typeof candidate.revisionId !== "string" || !SHA2562.test(candidate.revisionId) || candidate.parentRevisionId !== null && (typeof candidate.parentRevisionId !== "string" || !SHA2562.test(candidate.parentRevisionId)) || typeof candidate.manifestHash !== "string" || !SHA2562.test(candidate.manifestHash) || !candidate.entries || typeof candidate.entries !== "object" || Array.isArray(candidate.entries)) {
935
- throw new Error("workspace-revision-invalid");
936
- }
937
- for (const [relative, entry] of Object.entries(candidate.entries)) {
938
- requireSafeRelative(relative);
939
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
940
- throw new Error("workspace-revision-invalid");
941
- }
942
- if (entry.kind === "absent") {
943
- if (Object.keys(entry).length !== 1) {
944
- throw new Error("workspace-revision-invalid");
945
- }
946
- continue;
947
- }
948
- const file = entry;
949
- if (file.kind !== "file" || !SHA2562.test(file.hash) || !Number.isSafeInteger(file.bytes) || file.bytes < 0 || Object.keys(file).length !== 4) {
950
- throw new Error("workspace-revision-invalid");
951
- }
952
- requireMode(file.mode);
953
- }
954
- return candidate;
955
- }
956
-
957
- class WorkspaceStore {
958
- root;
959
- #revisions;
960
- #artifacts;
961
- #checkCancelled;
962
- #onMetrics;
963
- constructor(root, artifacts, options = {}) {
964
- this.#checkCancelled = options.checkCancelled ?? (() => {});
965
- this.#onMetrics = options.onMetrics;
966
- if (!path6.isAbsolute(root))
967
- throw new Error("workspace-store-root-invalid");
968
- this.root = path6.resolve(root);
969
- this.#revisions = path6.join(this.root, "revisions");
970
- this.#artifacts = artifacts;
971
- ensureDirectory2(this.root);
972
- ensureDirectory2(this.#revisions);
973
- }
974
- #revisionPath(id) {
975
- if (!SHA2562.test(id))
976
- throw new Error("workspace-revision-id-invalid");
977
- return path6.join(this.#revisions, id.slice(0, 2), `${id}.json`);
978
- }
979
- #storeRevision(parentRevisionId, rawEntries) {
980
- const entries = stableEntries(rawEntries);
981
- const manifest = manifestHash(entries);
982
- const id = revisionId(parentRevisionId, manifest);
983
- const revision = {
984
- revisionId: id,
985
- parentRevisionId,
986
- manifestHash: manifest,
987
- entries
988
- };
989
- const target = this.#revisionPath(id);
990
- const existing = lstatSync6(target, { throwIfNoEntry: false });
991
- if (existing) {
992
- const stored = this.getRevision(id);
993
- if (JSON.stringify(stored) !== JSON.stringify(revision)) {
994
- throw new Error("workspace-revision-conflict");
995
- }
996
- return stored;
997
- }
998
- writeAtomic2(target, Buffer.from(`${JSON.stringify(revision)}
999
- `));
1000
- const inherited = parentRevisionId ? this.getRevision(parentRevisionId).entries : {};
1001
- for (const [relative, entry] of Object.entries(entries)) {
1002
- if (entry.kind === "file" && !sameEntry(entry, inherited[relative]))
1003
- this.#artifacts.retain(entry.hash);
1004
- }
1005
- return structuredClone(revision);
1006
- }
1007
- #isAncestor(ancestorId, revision) {
1008
- let cursor = revision;
1009
- const visited = new Set;
1010
- while (cursor) {
1011
- if (cursor.revisionId === ancestorId)
1012
- return true;
1013
- if (visited.has(cursor.revisionId)) {
1014
- throw new Error("workspace-revision-cycle");
1015
- }
1016
- visited.add(cursor.revisionId);
1017
- cursor = cursor.parentRevisionId ? this.getRevision(cursor.parentRevisionId) : null;
1018
- }
1019
- return false;
1020
- }
1021
- captureBaseline(input) {
1022
- const consumerRoot = path6.resolve(input.consumerRoot);
1023
- const rootStat = lstatSync6(consumerRoot, { throwIfNoEntry: false });
1024
- if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
1025
- throw new Error("unsafe-workspace-path");
1026
- }
1027
- const excluded = [".git", "node_modules", ...input.excludedPaths ?? []];
1028
- for (const relative of excluded)
1029
- requireSafeRelative(relative);
1030
- return this.#captureBaselineFiles(input, this.#trackedFiles(input), consumerRoot, excluded);
1031
- }
1032
- #trackedFiles(input) {
1033
- const timeout = input.gitTimeoutMs ?? 30000;
1034
- if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30000)
1035
- throw new Error("workspace-git-timeout-invalid");
1036
- const consumerRoot = path6.resolve(input.consumerRoot);
1037
- let tracked;
1038
- try {
1039
- tracked = parseNullSeparated(execFileSync("git", ["ls-files", "-z", "--cached"], {
1040
- cwd: consumerRoot,
1041
- maxBuffer: 32 * 1024 * 1024,
1042
- timeout,
1043
- killSignal: "SIGKILL"
1044
- }));
1045
- } catch (error) {
1046
- if (error instanceof Error && error.message === "unsafe-workspace-path") {
1047
- throw error;
1048
- }
1049
- throw new Error("workspace-git-baseline-unavailable");
1050
- }
1051
- return tracked;
1052
- }
1053
- #captureBaselineFiles(input, tracked, consumerRoot, excluded) {
1054
- const approved = input.approvedUntracked ?? [];
1055
- const absent = input.absent ?? [];
1056
- const selected = [...new Set([...tracked, ...approved])].sort();
1057
- const entries = {};
1058
- for (const relative of selected) {
1059
- this.#checkCancelled();
1060
- requireSafeRelative(relative);
1061
- if (isExcluded(relative, excluded))
1062
- continue;
1063
- const observation = observeSafePath(consumerRoot, relative);
1064
- if (observation.kind === "absent") {
1065
- entries[relative] = { kind: "absent" };
1066
- continue;
1067
- }
1068
- if (observation.kind !== "file")
1069
- throw new Error("unsafe-workspace-path");
1070
- const absolute = path6.join(consumerRoot, ...relative.split("/"));
1071
- const bytes = readFileSync2(absolute);
1072
- const artifact = this.#artifacts.put(bytes);
1073
- const stat = lstatSync6(absolute);
1074
- entries[relative] = {
1075
- kind: "file",
1076
- hash: artifact.hash,
1077
- bytes: artifact.bytes,
1078
- mode: requireMode(stat.mode & 511)
1079
- };
1080
- }
1081
- for (const relative of [...new Set(absent)].sort()) {
1082
- requireSafeRelative(relative);
1083
- if (isExcluded(relative, excluded))
1084
- throw new Error("unsafe-workspace-path");
1085
- if (observeSafePath(consumerRoot, relative).kind !== "absent") {
1086
- throw new Error("workspace-path-not-absent");
1087
- }
1088
- entries[relative] = { kind: "absent" };
1089
- }
1090
- return this.#storeRevision(null, entries);
1091
- }
1092
- getRevision(id) {
1093
- const target = this.#revisionPath(id);
1094
- const stat = lstatSync6(target, { throwIfNoEntry: false });
1095
- if (!stat?.isFile() || stat.isSymbolicLink()) {
1096
- throw new Error("workspace-revision-unavailable");
1097
- }
1098
- let parsed;
1099
- try {
1100
- parsed = JSON.parse(readFileSync2(target, "utf8"));
1101
- } catch {
1102
- throw new Error("workspace-revision-invalid");
1103
- }
1104
- const revision = requireRevisionShape(parsed);
1105
- if (revision.revisionId !== id || manifestHash(revision.entries) !== revision.manifestHash && hash("cadence-workspace-manifest", JSON.stringify(revision.entries)) !== revision.manifestHash || revisionId(revision.parentRevisionId, revision.manifestHash) !== id) {
1106
- throw new Error("workspace-revision-integrity-invalid");
1107
- }
1108
- return structuredClone(revision);
1109
- }
1110
- createRevision(input) {
1111
- const parent = this.getRevision(input.parentRevisionId);
1112
- const entries = stableEntries(parent.entries);
1113
- for (const [relative, change] of Object.entries(input.changes).sort(([left], [right]) => compareCanonicalStrings(left, right))) {
1114
- requireSafeRelative(relative);
1115
- if (change === null || typeof change === "object" && "kind" in change && change.kind === "absent") {
1116
- entries[relative] = { kind: "absent" };
1117
- continue;
1118
- }
1119
- const descriptor = change instanceof Uint8Array ? {
1120
- kind: "file",
1121
- bytes: change,
1122
- mode: entries[relative]?.kind === "file" ? entries[relative].mode : 420
1123
- } : change;
1124
- if (descriptor?.kind !== "file" || !(descriptor.bytes instanceof Uint8Array)) {
1125
- throw new Error("workspace-change-invalid");
1126
- }
1127
- const artifact = this.#artifacts.put(descriptor.bytes);
1128
- entries[relative] = {
1129
- kind: "file",
1130
- hash: artifact.hash,
1131
- bytes: artifact.bytes,
1132
- mode: requireMode(descriptor.mode ?? 420)
1133
- };
1134
- }
1135
- return this.#storeRevision(parent.revisionId, entries);
1136
- }
1137
- mergeRevision(input) {
1138
- const base = this.getRevision(input.baseRevisionId);
1139
- const current = this.getRevision(input.currentRevisionId);
1140
- const candidate = this.getRevision(input.candidateRevisionId);
1141
- if (candidate.parentRevisionId !== base.revisionId || !this.#isAncestor(base.revisionId, current)) {
1142
- throw new Error("workspace-revision-base-invalid");
1143
- }
1144
- const boundPaths = [...new Set(input.boundPaths)];
1145
- for (const relative of boundPaths) {
1146
- requireSafeRelative(relative);
1147
- if (!sameEntry(base.entries[relative], current.entries[relative])) {
1148
- throw new Error("workspace-revision-stale");
1149
- }
1150
- }
1151
- const allPaths = new Set([
1152
- ...Object.keys(base.entries),
1153
- ...Object.keys(candidate.entries)
1154
- ]);
1155
- const delta = [...allPaths].filter((relative) => !sameEntry(base.entries[relative], candidate.entries[relative]));
1156
- for (const relative of delta) {
1157
- if (!sameEntry(base.entries[relative], current.entries[relative])) {
1158
- throw new Error("workspace-revision-stale");
1159
- }
1160
- }
1161
- const merged = stableEntries(current.entries);
1162
- for (const relative of delta.sort()) {
1163
- const entry = candidate.entries[relative];
1164
- if (entry)
1165
- merged[relative] = { ...entry };
1166
- else
1167
- delete merged[relative];
1168
- }
1169
- return this.#storeRevision(current.revisionId, merged);
1170
- }
1171
- materialize(revisionIdValue, destination) {
1172
- const revision = this.getRevision(revisionIdValue);
1173
- const root = path6.resolve(destination);
1174
- ensureDirectory2(root);
1175
- for (const [relative, entry] of Object.entries(revision.entries)) {
1176
- this.#checkCancelled();
1177
- requireSafeRelative(relative);
1178
- const observation = observeSafePath(root, relative);
1179
- if (entry.kind === "absent") {
1180
- const segments2 = relative.split("/");
1181
- const supersededByFileAncestor = segments2.slice(0, -1).some((_segment, index) => revision.entries[segments2.slice(0, index + 1).join("/")]?.kind === "file");
1182
- if (supersededByFileAncestor)
1183
- continue;
1184
- if (observation.kind !== "absent") {
1185
- throw new Error("workspace-materialize-conflict");
1186
- }
1187
- continue;
1188
- }
1189
- const segments = relative.split("/");
1190
- let parent = root;
1191
- for (const segment of segments.slice(0, -1)) {
1192
- parent = path6.join(parent, segment);
1193
- ensureDirectory2(parent);
1194
- }
1195
- const target = path6.join(root, ...segments);
1196
- if (observation.kind !== "absent") {
1197
- throw new Error("workspace-materialize-conflict");
1198
- }
1199
- const bytes = this.#artifacts.copyVerified(entry.hash, target);
1200
- if (bytes !== entry.bytes)
1201
- throw new Error("workspace-revision-integrity-invalid");
1202
- chmodSync2(target, entry.mode);
1203
- }
1204
- }
1205
- async captureBaselineWithGit(input, signal) {
1206
- signal?.throwIfAborted();
1207
- const consumerRoot = path6.resolve(input.consumerRoot);
1208
- const stat = lstatSync6(consumerRoot, { throwIfNoEntry: false });
1209
- if (!stat?.isDirectory() || stat.isSymbolicLink())
1210
- throw new Error("unsafe-workspace-path");
1211
- const excluded = [".git", "node_modules", ...input.excludedPaths ?? []];
1212
- for (const relative of excluded)
1213
- requireSafeRelative(relative);
1214
- const timeout = input.gitTimeoutMs ?? 30000;
1215
- if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30000)
1216
- throw new Error("workspace-git-timeout-invalid");
1217
- let tracked;
1218
- try {
1219
- const { stdout } = await promisify(execFile)("git", ["ls-files", "-z", "--cached"], {
1220
- cwd: consumerRoot,
1221
- encoding: "buffer",
1222
- maxBuffer: 32 * 1024 * 1024,
1223
- timeout,
1224
- killSignal: "SIGKILL",
1225
- signal
1226
- });
1227
- tracked = parseNullSeparated(stdout);
1228
- } catch (error) {
1229
- signal?.throwIfAborted();
1230
- if (error instanceof Error && error.message === "unsafe-workspace-path")
1231
- throw error;
1232
- throw new Error("workspace-git-baseline-unavailable");
1233
- }
1234
- this.#checkCancelled();
1235
- return this.#captureBaselineFiles(input, tracked, consumerRoot, excluded);
1236
- }
1237
- captureBaselineAsync(input, signal) {
1238
- return runWorkspaceIo({
1239
- root: this.root,
1240
- artifactRoot: this.#artifacts.root,
1241
- operation: "captureBaseline",
1242
- args: [input],
1243
- signal,
1244
- onMetrics: this.#onMetrics
1245
- });
1246
- }
1247
- materializeAsync(revisionId2, destination, signal) {
1248
- return runWorkspaceIo({
1249
- root: this.root,
1250
- artifactRoot: this.#artifacts.root,
1251
- operation: "materialize",
1252
- args: [revisionId2, destination],
1253
- signal,
1254
- onMetrics: this.#onMetrics
1255
- });
1256
- }
1257
- }
1258
-
1259
- // src/workspace-io-entry.mjs
1260
- var controller = new AbortController;
1261
- var flag = new Int32Array(workerData.cancelled);
1262
- parentPort.on("message", () => controller.abort(new Error("cancelled")));
1263
- var checkCancelled = () => {
1264
- if (Atomics.load(flag, 0))
1265
- throw new Error("cancelled");
1266
- };
1267
- try {
1268
- if (workerData.operation === "prepareVerification") {
1269
- const [root, owner, bindings, profile, environment, scratch] = workerData.args;
1270
- const result = prepareVerificationEnvironmentIo(root, owner, bindings, profile, environment, checkCancelled, scratch);
1271
- parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
1272
- } else if (workerData.operation === "cleanupVerification") {
1273
- removePathSync(workerData.args[0], { recursive: true, force: true });
1274
- parentPort.postMessage({ ok: true, files: 0, bytes: 0 });
1275
- } else if (workerData.operation === "verificationIdentity") {
1276
- const result = verificationEnvironmentDigest(workerData.args[0], checkCancelled);
1277
- parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
1278
- } else {
1279
- const artifacts = new ArtifactStore(workerData.artifactRoot);
1280
- const store = new WorkspaceStore(workerData.root, artifacts, {
1281
- checkCancelled
1282
- });
1283
- let result;
1284
- let revision;
1285
- checkCancelled();
1286
- if (workerData.operation === "captureBaseline") {
1287
- revision = await store.captureBaselineWithGit(workerData.args[0], controller.signal);
1288
- result = revision;
1289
- } else if (workerData.operation === "materialize") {
1290
- revision = store.getRevision(workerData.args[0]);
1291
- store.materialize(workerData.args[0], workerData.args[1]);
1292
- } else {
1293
- throw new Error("workspace-io-operation-invalid");
1294
- }
1295
- const files = Object.values(revision.entries).filter((entry) => entry.kind === "file");
1296
- parentPort.postMessage({
1297
- ok: true,
1298
- result,
1299
- files: files.length,
1300
- bytes: files.reduce((sum, entry) => sum + entry.bytes, 0)
1301
- });
1302
- }
1303
- } catch (error) {
1304
- parentPort.postMessage({
1305
- ok: false,
1306
- error: error instanceof Error ? error.message : "workspace-io-failed",
1307
- files: 0,
1308
- bytes: 0
1309
- });
1310
- } finally {
1311
- parentPort.close();
1312
- }