@abelxiaoxing/cadence 1.8.4 → 2.0.1

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 (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. 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
- }