@abelxiaoxing/cadence 1.4.1 → 1.5.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.
- package/README.md +74 -6
- package/config/plan-draft.quick.example.json +78 -0
- package/package.json +6 -2
- package/prompts/abel-design.md +8 -1
- package/prompts/abel-diagnose.md +1 -1
- package/prompts/abel-implement.md +10 -1
- package/prompts/abel-init.md +1 -1
- package/src/change-verification.ts +25 -1
- package/src/child-budget.ts +58 -0
- package/src/child-model.ts +17 -4
- package/src/child-session.ts +20 -24
- package/src/contracts.ts +16 -0
- package/src/delivery-compiler.ts +2 -0
- package/src/durable-contracts.ts +3 -0
- package/src/execution-profile.ts +38 -0
- package/src/isolation-backend.ts +62 -40
- package/src/operator-cli-entry.mjs +24 -0
- package/src/operator-cli.mjs +1421 -0
- package/src/operator-tools.ts +249 -0
- package/src/package-candidate.ts +3 -0
- package/src/package-verification.ts +86 -8
- package/src/package-workflow.ts +73 -35
- package/src/plan-draft-summary.ts +4 -1
- package/src/single-task-draft.ts +142 -0
- package/src/transport-budget.ts +21 -1
- package/src/verification-capability.ts +5 -3
- package/src/verification-diagnostics.ts +76 -0
- package/src/verification-environment-io.ts +280 -0
- package/src/verification-environment.ts +56 -154
- package/src/verification-scan.ts +64 -0
- package/src/worker-broker.ts +2 -0
- package/src/workspace-io-entry.mjs +19 -1
- package/src/workspace-io-worker.mjs +268 -50
- package/src/workspace-io.ts +6 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/build-workspace-io.mjs; edit workspace-io-entry.mjs and its source modules.
|
|
2
2
|
// src/workspace-io-entry.mjs
|
|
3
|
+
import { rmSync as rmSync2 } from "node:fs";
|
|
3
4
|
import { parentPort, workerData } from "node:worker_threads";
|
|
4
5
|
|
|
5
6
|
// src/artifact-store.ts
|
|
@@ -196,19 +197,229 @@ class ArtifactStore {
|
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
// src/verification-environment-io.ts
|
|
201
|
+
import {
|
|
202
|
+
constants as constants2,
|
|
203
|
+
cpSync,
|
|
204
|
+
lstatSync as lstatSync2,
|
|
205
|
+
mkdirSync as mkdirSync2,
|
|
206
|
+
readdirSync,
|
|
207
|
+
realpathSync,
|
|
208
|
+
rmSync,
|
|
209
|
+
symlinkSync,
|
|
210
|
+
writeFileSync as writeFileSync2
|
|
211
|
+
} from "node:fs";
|
|
212
|
+
import path2 from "node:path";
|
|
213
|
+
var CACHES = [".vite", ".vite-temp"];
|
|
214
|
+
function within(root, candidate) {
|
|
215
|
+
const relative = path2.relative(root, candidate);
|
|
216
|
+
return relative !== ".." && !relative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative);
|
|
217
|
+
}
|
|
218
|
+
function prepareVerificationEnvironmentIo(root, dependencyOwner, runnerBindings, profile, environment, checkCancelled, privateRoot) {
|
|
219
|
+
const cleanup = () => rmSync(privateRoot, { recursive: true, force: true });
|
|
220
|
+
try {
|
|
221
|
+
const local = profile.mode === "local-trusted";
|
|
222
|
+
if (local && realpathSync(root) === realpathSync(dependencyOwner))
|
|
223
|
+
throw new Error("trusted-candidate-required");
|
|
224
|
+
const mounts = [
|
|
225
|
+
{ source: privateRoot, target: "/cadence", writable: true }
|
|
226
|
+
];
|
|
227
|
+
for (const directory of [
|
|
228
|
+
"home",
|
|
229
|
+
"cache",
|
|
230
|
+
"tmp",
|
|
231
|
+
"reports",
|
|
232
|
+
"dependencies"
|
|
233
|
+
]) {
|
|
234
|
+
mkdirSync2(path2.join(privateRoot, directory), { mode: 448 });
|
|
235
|
+
}
|
|
236
|
+
writeFileSync2(path2.join(privateRoot, "home/user.npmrc"), "");
|
|
237
|
+
writeFileSync2(path2.join(privateRoot, "home/global.npmrc"), "");
|
|
238
|
+
const dependencies = path2.join(dependencyOwner, "node_modules");
|
|
239
|
+
const stat = lstatSync2(dependencies, { throwIfNoEntry: false });
|
|
240
|
+
if (stat) {
|
|
241
|
+
const target = path2.join(root, "node_modules");
|
|
242
|
+
const targetStat = lstatSync2(target, { throwIfNoEntry: false });
|
|
243
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || targetStat && (!targetStat.isDirectory() || targetStat.isSymbolicLink())) {
|
|
244
|
+
throw new Error("dependency-path-unsafe");
|
|
245
|
+
}
|
|
246
|
+
if (!targetStat)
|
|
247
|
+
mkdirSync2(target, { mode: 448 });
|
|
248
|
+
const view = path2.join(privateRoot, "dependencies");
|
|
249
|
+
mounts.push({ source: view, target: "/workspace/node_modules" });
|
|
250
|
+
const entries = readdirSync(dependencies);
|
|
251
|
+
if (entries.length > 20000)
|
|
252
|
+
throw new Error("dependency-view-too-large");
|
|
253
|
+
const canonical = realpathSync(dependencies);
|
|
254
|
+
let viewEntries = 0;
|
|
255
|
+
const projectEntry = (name) => {
|
|
256
|
+
checkCancelled();
|
|
257
|
+
if (++viewEntries > 20000)
|
|
258
|
+
throw new Error("dependency-view-too-large");
|
|
259
|
+
if (CACHES.includes(name))
|
|
260
|
+
return;
|
|
261
|
+
const source = path2.join(dependencies, name);
|
|
262
|
+
const placeholder = path2.join(view, name);
|
|
263
|
+
const entry = lstatSync2(source);
|
|
264
|
+
if (entry.isSymbolicLink()) {
|
|
265
|
+
const resolved = realpathSync(source);
|
|
266
|
+
if (within(canonical, resolved)) {
|
|
267
|
+
symlinkSync(path2.relative(path2.dirname(source), resolved), placeholder);
|
|
268
|
+
} else if (within(realpathSync(dependencyOwner), resolved)) {
|
|
269
|
+
const relative = path2.relative(realpathSync(dependencyOwner), resolved);
|
|
270
|
+
const candidate = path2.join(root, relative);
|
|
271
|
+
if (!lstatSync2(candidate, { throwIfNoEntry: false }))
|
|
272
|
+
throw new Error("workspace-dependency-missing");
|
|
273
|
+
symlinkSync(local ? candidate : `/workspace/${relative.split(path2.sep).join("/")}`, placeholder);
|
|
274
|
+
} else
|
|
275
|
+
throw new Error("dependency-path-unsafe");
|
|
276
|
+
} else if (entry.isDirectory() && name.startsWith("@") && !name.includes(path2.sep)) {
|
|
277
|
+
mkdirSync2(placeholder);
|
|
278
|
+
for (const child of readdirSync(source))
|
|
279
|
+
projectEntry(path2.join(name, child));
|
|
280
|
+
} else {
|
|
281
|
+
if (entry.isDirectory())
|
|
282
|
+
mkdirSync2(placeholder);
|
|
283
|
+
else if (entry.isFile())
|
|
284
|
+
writeFileSync2(placeholder, "");
|
|
285
|
+
else
|
|
286
|
+
throw new Error("dependency-path-unsafe");
|
|
287
|
+
mounts.push({
|
|
288
|
+
source,
|
|
289
|
+
target: `/workspace/node_modules/${name.split(path2.sep).join("/")}`
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
for (const name of entries)
|
|
294
|
+
projectEntry(name);
|
|
295
|
+
for (const cache of CACHES) {
|
|
296
|
+
mkdirSync2(path2.join(view, cache));
|
|
297
|
+
const source = path2.join(privateRoot, "cache", cache);
|
|
298
|
+
mkdirSync2(source);
|
|
299
|
+
mounts.push({
|
|
300
|
+
source,
|
|
301
|
+
target: `/workspace/node_modules/${cache}`,
|
|
302
|
+
writable: true
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (local && stat) {
|
|
307
|
+
const copyEntry = (name) => {
|
|
308
|
+
checkCancelled();
|
|
309
|
+
const source = path2.join(dependencies, name);
|
|
310
|
+
const destination = path2.join(root, "node_modules", name);
|
|
311
|
+
if (CACHES.includes(name)) {
|
|
312
|
+
mkdirSync2(destination, { recursive: true });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
removeEntry(destination, checkCancelled);
|
|
316
|
+
const stat2 = lstatSync2(source);
|
|
317
|
+
if (stat2.isSymbolicLink()) {
|
|
318
|
+
const resolved = realpathSync(source);
|
|
319
|
+
const translated = within(canonicalRoot(dependencyOwner), resolved) ? path2.join(root, path2.relative(canonicalRoot(dependencyOwner), resolved)) : resolved;
|
|
320
|
+
symlinkSync(translated, destination);
|
|
321
|
+
} else if (stat2.isDirectory() && name.startsWith("@") && !name.includes(path2.sep)) {
|
|
322
|
+
mkdirSync2(destination);
|
|
323
|
+
for (const child of readdirSync(source))
|
|
324
|
+
copyEntry(path2.join(name, child));
|
|
325
|
+
} else
|
|
326
|
+
cpSync(source, destination, {
|
|
327
|
+
recursive: true,
|
|
328
|
+
filter: () => {
|
|
329
|
+
checkCancelled();
|
|
330
|
+
return true;
|
|
331
|
+
},
|
|
332
|
+
mode: constants2.COPYFILE_FICLONE,
|
|
333
|
+
verbatimSymlinks: true
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
for (const name of readdirSync(path2.join(privateRoot, "dependencies")))
|
|
337
|
+
copyEntry(name);
|
|
338
|
+
}
|
|
339
|
+
const sources = new Map;
|
|
340
|
+
const bindings = runnerBindings.map((binding) => {
|
|
341
|
+
if (local || !binding.mountSource)
|
|
342
|
+
return { ...binding };
|
|
343
|
+
const source = path2.resolve(binding.mountSource);
|
|
344
|
+
const stat2 = lstatSync2(source);
|
|
345
|
+
const executable = realpathSync(binding.executablePath);
|
|
346
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink() || !within(source, executable)) {
|
|
347
|
+
throw new Error("runner-mount-unavailable");
|
|
348
|
+
}
|
|
349
|
+
let target = sources.get(source);
|
|
350
|
+
if (!target) {
|
|
351
|
+
target = `/cadence-runners/${sources.size}`;
|
|
352
|
+
sources.set(source, target);
|
|
353
|
+
mounts.push({ source, target });
|
|
354
|
+
}
|
|
355
|
+
const translate = (file) => path2.posix.join(target, ...path2.relative(source, file).split(path2.sep));
|
|
356
|
+
return {
|
|
357
|
+
...binding,
|
|
358
|
+
executablePath: translate(executable),
|
|
359
|
+
...binding.fixedArgs ? {
|
|
360
|
+
fixedArgs: binding.fixedArgs.map((arg) => path2.isAbsolute(arg) && within(source, arg) ? translate(arg) : arg)
|
|
361
|
+
} : {}
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
checkCancelled();
|
|
365
|
+
return {
|
|
366
|
+
privateRoot,
|
|
367
|
+
mounts,
|
|
368
|
+
bindings,
|
|
369
|
+
reportRoot: local ? privateRoot : "/cadence",
|
|
370
|
+
environment: {
|
|
371
|
+
...local ? Object.fromEntries(profile.inheritEnvironment.flatMap((name) => environment[name] === undefined ? [] : [[name, environment[name] ?? ""]])) : {},
|
|
372
|
+
CI: "1",
|
|
373
|
+
HOME: local ? path2.join(privateRoot, "home") : "/cadence/home",
|
|
374
|
+
XDG_CACHE_HOME: local ? path2.join(privateRoot, "cache") : "/cadence/cache",
|
|
375
|
+
TMPDIR: local ? path2.join(privateRoot, "tmp") : "/cadence/tmp",
|
|
376
|
+
npm_config_cache: local ? path2.join(privateRoot, "cache/npm") : "/cadence/cache/npm",
|
|
377
|
+
npm_config_userconfig: local ? path2.join(privateRoot, "home/user.npmrc") : "/cadence/home/user.npmrc",
|
|
378
|
+
npm_config_globalconfig: local ? path2.join(privateRoot, "home/global.npmrc") : "/cadence/home/global.npmrc",
|
|
379
|
+
PATH: [
|
|
380
|
+
...new Set([
|
|
381
|
+
...bindings.filter((binding) => binding.mountSource),
|
|
382
|
+
...bindings.filter((binding) => !binding.mountSource)
|
|
383
|
+
].map((binding) => path2.posix.dirname(binding.executablePath))),
|
|
384
|
+
...local ? (environment.PATH ?? "").split(path2.delimiter) : [],
|
|
385
|
+
"/usr/local/bin",
|
|
386
|
+
"/usr/bin",
|
|
387
|
+
"/bin"
|
|
388
|
+
].join(":")
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
} catch (error) {
|
|
392
|
+
cleanup();
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function canonicalRoot(root) {
|
|
397
|
+
return realpathSync(root);
|
|
398
|
+
}
|
|
399
|
+
function removeEntry(file, checkCancelled) {
|
|
400
|
+
checkCancelled();
|
|
401
|
+
const stat = lstatSync2(file, { throwIfNoEntry: false });
|
|
402
|
+
if (!stat)
|
|
403
|
+
return;
|
|
404
|
+
if (stat.isDirectory())
|
|
405
|
+
for (const name of readdirSync(file))
|
|
406
|
+
removeEntry(path2.join(file, name), checkCancelled);
|
|
407
|
+
rmSync(file, { recursive: stat.isDirectory(), force: true });
|
|
408
|
+
}
|
|
409
|
+
|
|
199
410
|
// src/verification-identity.ts
|
|
200
411
|
import { createHash as createHash2 } from "node:crypto";
|
|
201
412
|
import {
|
|
202
413
|
closeSync as closeSync2,
|
|
203
|
-
constants as
|
|
414
|
+
constants as constants3,
|
|
204
415
|
fstatSync,
|
|
205
|
-
lstatSync as
|
|
416
|
+
lstatSync as lstatSync3,
|
|
206
417
|
openSync as openSync2,
|
|
207
|
-
readdirSync,
|
|
418
|
+
readdirSync as readdirSync2,
|
|
208
419
|
readlinkSync,
|
|
209
420
|
readSync
|
|
210
421
|
} from "node:fs";
|
|
211
|
-
import
|
|
422
|
+
import path3 from "node:path";
|
|
212
423
|
function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
213
424
|
const digest2 = createHash2("sha256");
|
|
214
425
|
let entries = 0;
|
|
@@ -219,7 +430,7 @@ function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
|
219
430
|
checkCancelled();
|
|
220
431
|
if (++entries > 500000 || Date.now() - started > 60000)
|
|
221
432
|
throw new Error("verification-environment-limit");
|
|
222
|
-
const stat =
|
|
433
|
+
const stat = lstatSync3(file, { throwIfNoEntry: false });
|
|
223
434
|
digest2.update(JSON.stringify([relative, stat ? stat.mode & 511 : null]));
|
|
224
435
|
if (!stat) {
|
|
225
436
|
digest2.update("absent");
|
|
@@ -231,16 +442,16 @@ function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
|
231
442
|
}
|
|
232
443
|
if (stat.isDirectory()) {
|
|
233
444
|
digest2.update("directory");
|
|
234
|
-
for (const name of
|
|
445
|
+
for (const name of readdirSync2(file).sort()) {
|
|
235
446
|
if (dependencyRoot && [".vite", ".vite-temp"].includes(name))
|
|
236
447
|
continue;
|
|
237
|
-
visit(
|
|
448
|
+
visit(path3.join(file, name), `${relative}/${name}`, false);
|
|
238
449
|
}
|
|
239
450
|
return;
|
|
240
451
|
}
|
|
241
452
|
if (!stat.isFile())
|
|
242
453
|
throw new Error("verification-environment-unsafe");
|
|
243
|
-
const fd = openSync2(file,
|
|
454
|
+
const fd = openSync2(file, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
244
455
|
try {
|
|
245
456
|
const before = fstatSync(fd);
|
|
246
457
|
if (before.ino !== stat.ino || before.dev !== stat.dev)
|
|
@@ -265,7 +476,7 @@ function verificationEnvironmentDigest(roots, checkCancelled = () => {}) {
|
|
|
265
476
|
}
|
|
266
477
|
};
|
|
267
478
|
for (const root of [...new Set(roots)].sort())
|
|
268
|
-
visit(root, root,
|
|
479
|
+
visit(root, root, path3.basename(root) === "node_modules");
|
|
269
480
|
return digest2.digest("hex");
|
|
270
481
|
}
|
|
271
482
|
|
|
@@ -275,17 +486,17 @@ import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypt
|
|
|
275
486
|
import {
|
|
276
487
|
chmodSync as chmodSync2,
|
|
277
488
|
closeSync as closeSync3,
|
|
278
|
-
constants as
|
|
489
|
+
constants as constants4,
|
|
279
490
|
fsyncSync as fsyncSync2,
|
|
280
|
-
lstatSync as
|
|
281
|
-
mkdirSync as
|
|
491
|
+
lstatSync as lstatSync5,
|
|
492
|
+
mkdirSync as mkdirSync3,
|
|
282
493
|
openSync as openSync3,
|
|
283
494
|
readFileSync as readFileSync2,
|
|
284
495
|
renameSync as renameSync2,
|
|
285
496
|
unlinkSync as unlinkSync2,
|
|
286
|
-
writeFileSync as
|
|
497
|
+
writeFileSync as writeFileSync3
|
|
287
498
|
} from "node:fs";
|
|
288
|
-
import
|
|
499
|
+
import path5 from "node:path";
|
|
289
500
|
import { promisify } from "node:util";
|
|
290
501
|
|
|
291
502
|
// src/canonical.ts
|
|
@@ -404,15 +615,15 @@ function isValidRelativePath(p) {
|
|
|
404
615
|
}
|
|
405
616
|
|
|
406
617
|
// src/safe-path.ts
|
|
407
|
-
import { lstatSync as
|
|
408
|
-
import
|
|
618
|
+
import { lstatSync as lstatSync4 } from "node:fs";
|
|
619
|
+
import path4 from "node:path";
|
|
409
620
|
function observeSafePath(root, relative) {
|
|
410
621
|
if (!isValidRelativePath(relative)) {
|
|
411
622
|
return { kind: "unsafe", reason: "invalid-path" };
|
|
412
623
|
}
|
|
413
624
|
try {
|
|
414
|
-
const resolvedRoot =
|
|
415
|
-
const rootStat =
|
|
625
|
+
const resolvedRoot = path4.resolve(root);
|
|
626
|
+
const rootStat = lstatSync4(resolvedRoot, { throwIfNoEntry: false });
|
|
416
627
|
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
417
628
|
return { kind: "unsafe", reason: "root-unavailable" };
|
|
418
629
|
}
|
|
@@ -421,11 +632,11 @@ function observeSafePath(root, relative) {
|
|
|
421
632
|
let current = resolvedRoot;
|
|
422
633
|
const parts = relative.split("/");
|
|
423
634
|
for (const [index, part] of parts.entries()) {
|
|
424
|
-
current =
|
|
425
|
-
if (!current.startsWith(`${resolvedRoot}${
|
|
635
|
+
current = path4.join(current, part);
|
|
636
|
+
if (!current.startsWith(`${resolvedRoot}${path4.sep}`)) {
|
|
426
637
|
return { kind: "unsafe", reason: "invalid-path" };
|
|
427
638
|
}
|
|
428
|
-
const stat =
|
|
639
|
+
const stat = lstatSync4(current, { throwIfNoEntry: false });
|
|
429
640
|
if (!stat)
|
|
430
641
|
return { kind: "absent" };
|
|
431
642
|
if (stat.isSymbolicLink()) {
|
|
@@ -528,7 +739,7 @@ function revisionId(parentRevisionId, manifest) {
|
|
|
528
739
|
return hash("cadence-workspace-revision", parentRevisionId ?? "", manifest);
|
|
529
740
|
}
|
|
530
741
|
function ensureDirectory2(directory) {
|
|
531
|
-
const stat =
|
|
742
|
+
const stat = lstatSync5(directory, { throwIfNoEntry: false });
|
|
532
743
|
if (stat) {
|
|
533
744
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
534
745
|
throw new Error("unsafe-workspace-path");
|
|
@@ -536,26 +747,26 @@ function ensureDirectory2(directory) {
|
|
|
536
747
|
chmodSync2(directory, 448);
|
|
537
748
|
return;
|
|
538
749
|
}
|
|
539
|
-
let cursor =
|
|
540
|
-
while (cursor !==
|
|
541
|
-
const ancestor =
|
|
750
|
+
let cursor = path5.dirname(directory);
|
|
751
|
+
while (cursor !== path5.dirname(cursor)) {
|
|
752
|
+
const ancestor = lstatSync5(cursor, { throwIfNoEntry: false });
|
|
542
753
|
if (ancestor) {
|
|
543
754
|
if (ancestor.isSymbolicLink() || !ancestor.isDirectory()) {
|
|
544
755
|
throw new Error("unsafe-workspace-path");
|
|
545
756
|
}
|
|
546
757
|
break;
|
|
547
758
|
}
|
|
548
|
-
cursor =
|
|
759
|
+
cursor = path5.dirname(cursor);
|
|
549
760
|
}
|
|
550
|
-
|
|
551
|
-
const created =
|
|
761
|
+
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
762
|
+
const created = lstatSync5(directory);
|
|
552
763
|
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
553
764
|
throw new Error("unsafe-workspace-path");
|
|
554
765
|
}
|
|
555
766
|
chmodSync2(directory, 448);
|
|
556
767
|
}
|
|
557
768
|
function syncDirectory2(directory) {
|
|
558
|
-
const descriptor = openSync3(directory,
|
|
769
|
+
const descriptor = openSync3(directory, constants4.O_RDONLY);
|
|
559
770
|
try {
|
|
560
771
|
fsyncSync2(descriptor);
|
|
561
772
|
} finally {
|
|
@@ -563,12 +774,12 @@ function syncDirectory2(directory) {
|
|
|
563
774
|
}
|
|
564
775
|
}
|
|
565
776
|
function writeAtomic2(target, bytes) {
|
|
566
|
-
const directory =
|
|
777
|
+
const directory = path5.dirname(target);
|
|
567
778
|
ensureDirectory2(directory);
|
|
568
|
-
const temporary =
|
|
569
|
-
const descriptor = openSync3(temporary,
|
|
779
|
+
const temporary = path5.join(directory, `.${path5.basename(target)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
780
|
+
const descriptor = openSync3(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
|
|
570
781
|
try {
|
|
571
|
-
|
|
782
|
+
writeFileSync3(descriptor, bytes);
|
|
572
783
|
fsyncSync2(descriptor);
|
|
573
784
|
} finally {
|
|
574
785
|
closeSync3(descriptor);
|
|
@@ -649,10 +860,10 @@ class WorkspaceStore {
|
|
|
649
860
|
constructor(root, artifacts, options = {}) {
|
|
650
861
|
this.#checkCancelled = options.checkCancelled ?? (() => {});
|
|
651
862
|
this.#onMetrics = options.onMetrics;
|
|
652
|
-
if (!
|
|
863
|
+
if (!path5.isAbsolute(root))
|
|
653
864
|
throw new Error("workspace-store-root-invalid");
|
|
654
|
-
this.root =
|
|
655
|
-
this.#revisions =
|
|
865
|
+
this.root = path5.resolve(root);
|
|
866
|
+
this.#revisions = path5.join(this.root, "revisions");
|
|
656
867
|
this.#artifacts = artifacts;
|
|
657
868
|
ensureDirectory2(this.root);
|
|
658
869
|
ensureDirectory2(this.#revisions);
|
|
@@ -660,7 +871,7 @@ class WorkspaceStore {
|
|
|
660
871
|
#revisionPath(id) {
|
|
661
872
|
if (!SHA2562.test(id))
|
|
662
873
|
throw new Error("workspace-revision-id-invalid");
|
|
663
|
-
return
|
|
874
|
+
return path5.join(this.#revisions, id.slice(0, 2), `${id}.json`);
|
|
664
875
|
}
|
|
665
876
|
#storeRevision(parentRevisionId, rawEntries) {
|
|
666
877
|
const entries = stableEntries(rawEntries);
|
|
@@ -673,7 +884,7 @@ class WorkspaceStore {
|
|
|
673
884
|
entries
|
|
674
885
|
};
|
|
675
886
|
const target = this.#revisionPath(id);
|
|
676
|
-
const existing =
|
|
887
|
+
const existing = lstatSync5(target, { throwIfNoEntry: false });
|
|
677
888
|
if (existing) {
|
|
678
889
|
const stored = this.getRevision(id);
|
|
679
890
|
if (JSON.stringify(stored) !== JSON.stringify(revision)) {
|
|
@@ -705,8 +916,8 @@ class WorkspaceStore {
|
|
|
705
916
|
return false;
|
|
706
917
|
}
|
|
707
918
|
captureBaseline(input) {
|
|
708
|
-
const consumerRoot =
|
|
709
|
-
const rootStat =
|
|
919
|
+
const consumerRoot = path5.resolve(input.consumerRoot);
|
|
920
|
+
const rootStat = lstatSync5(consumerRoot, { throwIfNoEntry: false });
|
|
710
921
|
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
711
922
|
throw new Error("unsafe-workspace-path");
|
|
712
923
|
}
|
|
@@ -719,7 +930,7 @@ class WorkspaceStore {
|
|
|
719
930
|
const timeout = input.gitTimeoutMs ?? 30000;
|
|
720
931
|
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30000)
|
|
721
932
|
throw new Error("workspace-git-timeout-invalid");
|
|
722
|
-
const consumerRoot =
|
|
933
|
+
const consumerRoot = path5.resolve(input.consumerRoot);
|
|
723
934
|
let tracked;
|
|
724
935
|
try {
|
|
725
936
|
tracked = parseNullSeparated(execFileSync("git", ["ls-files", "-z", "--cached"], {
|
|
@@ -753,10 +964,10 @@ class WorkspaceStore {
|
|
|
753
964
|
}
|
|
754
965
|
if (observation.kind !== "file")
|
|
755
966
|
throw new Error("unsafe-workspace-path");
|
|
756
|
-
const absolute =
|
|
967
|
+
const absolute = path5.join(consumerRoot, ...relative.split("/"));
|
|
757
968
|
const bytes = readFileSync2(absolute);
|
|
758
969
|
const artifact = this.#artifacts.put(bytes);
|
|
759
|
-
const stat =
|
|
970
|
+
const stat = lstatSync5(absolute);
|
|
760
971
|
entries[relative] = {
|
|
761
972
|
kind: "file",
|
|
762
973
|
hash: artifact.hash,
|
|
@@ -777,7 +988,7 @@ class WorkspaceStore {
|
|
|
777
988
|
}
|
|
778
989
|
getRevision(id) {
|
|
779
990
|
const target = this.#revisionPath(id);
|
|
780
|
-
const stat =
|
|
991
|
+
const stat = lstatSync5(target, { throwIfNoEntry: false });
|
|
781
992
|
if (!stat?.isFile() || stat.isSymbolicLink()) {
|
|
782
993
|
throw new Error("workspace-revision-unavailable");
|
|
783
994
|
}
|
|
@@ -856,7 +1067,7 @@ class WorkspaceStore {
|
|
|
856
1067
|
}
|
|
857
1068
|
materialize(revisionIdValue, destination) {
|
|
858
1069
|
const revision = this.getRevision(revisionIdValue);
|
|
859
|
-
const root =
|
|
1070
|
+
const root = path5.resolve(destination);
|
|
860
1071
|
ensureDirectory2(root);
|
|
861
1072
|
for (const [relative, entry] of Object.entries(revision.entries)) {
|
|
862
1073
|
this.#checkCancelled();
|
|
@@ -875,10 +1086,10 @@ class WorkspaceStore {
|
|
|
875
1086
|
const segments = relative.split("/");
|
|
876
1087
|
let parent = root;
|
|
877
1088
|
for (const segment of segments.slice(0, -1)) {
|
|
878
|
-
parent =
|
|
1089
|
+
parent = path5.join(parent, segment);
|
|
879
1090
|
ensureDirectory2(parent);
|
|
880
1091
|
}
|
|
881
|
-
const target =
|
|
1092
|
+
const target = path5.join(root, ...segments);
|
|
882
1093
|
if (observation.kind !== "absent") {
|
|
883
1094
|
throw new Error("workspace-materialize-conflict");
|
|
884
1095
|
}
|
|
@@ -890,8 +1101,8 @@ class WorkspaceStore {
|
|
|
890
1101
|
}
|
|
891
1102
|
async captureBaselineWithGit(input, signal) {
|
|
892
1103
|
signal?.throwIfAborted();
|
|
893
|
-
const consumerRoot =
|
|
894
|
-
const stat =
|
|
1104
|
+
const consumerRoot = path5.resolve(input.consumerRoot);
|
|
1105
|
+
const stat = lstatSync5(consumerRoot, { throwIfNoEntry: false });
|
|
895
1106
|
if (!stat?.isDirectory() || stat.isSymbolicLink())
|
|
896
1107
|
throw new Error("unsafe-workspace-path");
|
|
897
1108
|
const excluded = [".git", "node_modules", ...input.excludedPaths ?? []];
|
|
@@ -951,7 +1162,14 @@ var checkCancelled = () => {
|
|
|
951
1162
|
throw new Error("cancelled");
|
|
952
1163
|
};
|
|
953
1164
|
try {
|
|
954
|
-
if (workerData.operation === "
|
|
1165
|
+
if (workerData.operation === "prepareVerification") {
|
|
1166
|
+
const [root, owner, bindings, profile, environment, scratch] = workerData.args;
|
|
1167
|
+
const result = prepareVerificationEnvironmentIo(root, owner, bindings, profile, environment, checkCancelled, scratch);
|
|
1168
|
+
parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
|
|
1169
|
+
} else if (workerData.operation === "cleanupVerification") {
|
|
1170
|
+
rmSync2(workerData.args[0], { recursive: true, force: true });
|
|
1171
|
+
parentPort.postMessage({ ok: true, files: 0, bytes: 0 });
|
|
1172
|
+
} else if (workerData.operation === "verificationIdentity") {
|
|
955
1173
|
const result = verificationEnvironmentDigest(workerData.args[0], checkCancelled);
|
|
956
1174
|
parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
|
|
957
1175
|
} else {
|
package/src/workspace-io.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Worker } from "node:worker_threads";
|
|
2
2
|
|
|
3
3
|
export interface WorkspaceIoMetrics {
|
|
4
|
-
operation:
|
|
4
|
+
operation:
|
|
5
|
+
| "captureBaseline"
|
|
6
|
+
| "materialize"
|
|
7
|
+
| "verificationIdentity"
|
|
8
|
+
| "prepareVerification"
|
|
9
|
+
| "cleanupVerification";
|
|
5
10
|
elapsedMs: number;
|
|
6
11
|
files: number;
|
|
7
12
|
bytes: number;
|