@abelxiaoxing/cadence 1.6.0 → 1.8.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 (45) hide show
  1. package/README.md +25 -4
  2. package/config/.env.example +6 -0
  3. package/config/plan-draft.example.json +3 -0
  4. package/config/plan-draft.multiple-tasks.example.json +30 -0
  5. package/package.json +1 -1
  6. package/prompts/abel-design.md +33 -3
  7. package/prompts/abel-implement.md +36 -4
  8. package/src/apply-transaction.ts +28 -8
  9. package/src/build-windows-job.mjs +95 -0
  10. package/src/candidate-workspace.ts +6 -0
  11. package/src/change-verification.ts +764 -67
  12. package/src/contracts.ts +8 -0
  13. package/src/delivery-compiler.ts +452 -9
  14. package/src/design-control.ts +2 -2
  15. package/src/design-diagnostics.ts +6 -4
  16. package/src/durable-contracts.ts +21 -3
  17. package/src/durable-workflow.ts +55 -40
  18. package/src/execution-profile.ts +28 -2
  19. package/src/execution-retention.ts +77 -0
  20. package/src/implement-continuation.ts +559 -0
  21. package/src/implement-graph.ts +114 -0
  22. package/src/implement-plan.ts +2 -0
  23. package/src/index.ts +151 -1
  24. package/src/isolation-backend.ts +2 -1
  25. package/src/operator-cli.mjs +664 -104
  26. package/src/operator-tools.ts +7 -1
  27. package/src/package-state.ts +11 -0
  28. package/src/package-verification.ts +66 -18
  29. package/src/package-workflow.ts +6 -0
  30. package/src/phase-execution.ts +18 -7
  31. package/src/plan-draft-summary.ts +385 -7
  32. package/src/plan-draft.ts +9 -0
  33. package/src/remove-path.ts +32 -0
  34. package/src/subagent-activity.ts +3 -1
  35. package/src/verification-capability.ts +18 -6
  36. package/src/verification-environment-io.ts +123 -14
  37. package/src/verification-environment.ts +50 -3
  38. package/src/windows-job-backend.ts +419 -0
  39. package/src/windows-job.c +149 -0
  40. package/src/workflow-policy.ts +296 -5
  41. package/src/workflow-recovery-policy.ts +2 -0
  42. package/src/workflow-state-machine.ts +314 -80
  43. package/src/workflow-status.ts +251 -59
  44. package/src/workspace-io-entry.mjs +2 -2
  45. package/src/workspace-io-worker.mjs +207 -110
@@ -1,17 +1,19 @@
1
1
  // Generated by scripts/build-workspace-io.mjs; edit operator-cli-entry.mjs and its source modules.
2
2
  // src/operator-cli-entry.mjs
3
- import path6 from "node:path";
3
+ import path9 from "node:path";
4
4
 
5
5
  // src/operator-tools.ts
6
- import { spawnSync } from "node:child_process";
7
- import { lstatSync as lstatSync6, readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync4 } from "node:fs";
8
- import path5 from "node:path";
6
+ import { spawnSync as spawnSync2 } from "node:child_process";
7
+ import { lstatSync as lstatSync8, readdirSync as readdirSync4, readFileSync as readFileSync5, realpathSync as realpathSync4 } from "node:fs";
8
+ import path8 from "node:path";
9
9
  import { DatabaseSync } from "node:sqlite";
10
10
 
11
11
  // src/execution-profile.ts
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
12
14
  function executionProfile(env = process.env) {
13
15
  const mode = env.ABEL_EXECUTION_MODE ?? "isolated";
14
- if (mode !== "isolated" && mode !== "local-trusted")
16
+ if (mode !== "isolated" && mode !== "local-trusted" && mode !== "host-trusted")
15
17
  throw new Error("execution-mode-invalid");
16
18
  const timeoutMs = Number(env.ABEL_VERIFICATION_TIMEOUT_MS ?? 600000);
17
19
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86400000)
@@ -21,17 +23,22 @@ function executionProfile(env = process.env) {
21
23
  throw new Error("verification-environment-names-invalid");
22
24
  if (mode === "isolated" && inheritEnvironment.length)
23
25
  throw new Error("verification-environment-requires-trusted-mode");
26
+ if (mode === "host-trusted" && env.ABEL_WINDOWS_JOB_HELPER !== undefined && (!env.ABEL_WINDOWS_JOB_HELPER || env.ABEL_WINDOWS_JOB_HELPER.includes("\x00") || !(path.isAbsolute(env.ABEL_WINDOWS_JOB_HELPER) || path.win32.isAbsolute(env.ABEL_WINDOWS_JOB_HELPER))))
27
+ throw new Error("windows-job-helper-path-invalid");
24
28
  return {
25
29
  mode,
26
30
  timeoutMs,
27
31
  bwrapPath: env.ABEL_BWRAP_PATH ?? "/usr/bin/bwrap",
28
- inheritEnvironment
32
+ inheritEnvironment,
33
+ ...mode === "host-trusted" ? {
34
+ windowsHelperPath: env.ABEL_WINDOWS_JOB_HELPER ?? fileURLToPath(new URL("./windows-job.exe", import.meta.url))
35
+ } : {}
29
36
  };
30
37
  }
31
38
 
32
39
  // src/openspec-cli.ts
33
40
  import { lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
34
- import path from "node:path";
41
+ import path2 from "node:path";
35
42
  var PACKAGE_NAME = "@fission-ai/openspec";
36
43
  var OPENSPEC_LIMITS = Object.freeze({
37
44
  timeoutMs: 30000,
@@ -57,8 +64,8 @@ function record(value) {
57
64
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
58
65
  }
59
66
  function inside(root, target) {
60
- const relative = path.relative(root, target);
61
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
67
+ const relative = path2.relative(root, target);
68
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative);
62
69
  }
63
70
  function envValue(environment, name) {
64
71
  const key = Object.keys(environment).sort().find((candidate) => process.platform === "win32" ? candidate.toUpperCase() === name.toUpperCase() : candidate === name);
@@ -66,15 +73,15 @@ function envValue(environment, name) {
66
73
  }
67
74
  function packageEntry(root) {
68
75
  const canonicalRoot = realpathSync(root);
69
- const manifestPath = realpathSync(path.join(canonicalRoot, "package.json"));
76
+ const manifestPath = realpathSync(path2.join(canonicalRoot, "package.json"));
70
77
  const manifestStat = lstatSync(manifestPath);
71
78
  if (!inside(canonicalRoot, manifestPath) || !manifestStat.isFile() || manifestStat.size > 1024 * 1024)
72
79
  throw new Error("manifest-limit");
73
80
  const manifest = record(JSON.parse(readFileSync(manifestPath, "utf8")));
74
81
  const bin = typeof manifest?.bin === "string" ? manifest.bin : record(manifest?.bin)?.openspec;
75
- if (manifest?.name !== PACKAGE_NAME || typeof bin !== "string" || path.isAbsolute(bin))
82
+ if (manifest?.name !== PACKAGE_NAME || typeof bin !== "string" || path2.isAbsolute(bin))
76
83
  throw new Error("manifest-invalid");
77
- const entry = realpathSync(path.resolve(canonicalRoot, bin));
84
+ const entry = realpathSync(path2.resolve(canonicalRoot, bin));
78
85
  if (!inside(canonicalRoot, entry) || !/\.(?:cjs|mjs|js)$/iu.test(entry) || !lstatSync(entry).isFile())
79
86
  throw new Error("entry-invalid");
80
87
  return entry;
@@ -99,7 +106,7 @@ function commandIn(directory) {
99
106
  if (matches.length > 1)
100
107
  throw new Error("ambiguous-command");
101
108
  if (matches.length === 1) {
102
- const candidate = path.join(directory, matches[0]);
109
+ const candidate = path2.join(directory, matches[0]);
103
110
  if (lstatSync(candidate, { throwIfNoEntry: false }))
104
111
  return candidate;
105
112
  }
@@ -112,7 +119,7 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
112
119
  const node = configuredNode ?? (process.release.name === "node" && !process.versions.bun ? process.execPath : undefined);
113
120
  let executable;
114
121
  try {
115
- if (!node || !path.isAbsolute(node) || /\.(?:cmd|bat|ps1)$/iu.test(node)) {
122
+ if (!node || !path2.isAbsolute(node) || /\.(?:cmd|bat|ps1)$/iu.test(node)) {
116
123
  throw new Error("runtime-invalid");
117
124
  }
118
125
  executable = realpathSync(node);
@@ -123,7 +130,7 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
123
130
  }
124
131
  if (configuredRoot !== undefined) {
125
132
  try {
126
- if (!path.isAbsolute(configuredRoot))
133
+ if (!path2.isAbsolute(configuredRoot))
127
134
  throw new Error("root-invalid");
128
135
  return { executable, entry: packageEntry(configuredRoot) };
129
136
  } catch {
@@ -131,9 +138,9 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
131
138
  }
132
139
  }
133
140
  const consumer = canonicalConsumerRoot(consumerRoot);
134
- const directories = (envValue(environment, "PATH") ?? "").split(path.delimiter);
141
+ const directories = (envValue(environment, "PATH") ?? "").split(path2.delimiter);
135
142
  for (const directory of directories) {
136
- if (!path.isAbsolute(directory))
143
+ if (!path2.isAbsolute(directory))
137
144
  continue;
138
145
  let command;
139
146
  let canonicalDirectory;
@@ -153,14 +160,14 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
153
160
  throw new OpenSpecCliError("resolve", "resolution", "unsafe-installation");
154
161
  }
155
162
  const layoutRoots = [
156
- path.join(canonicalDirectory, "node_modules", PACKAGE_NAME),
157
- path.resolve(canonicalDirectory, "../lib/node_modules", PACKAGE_NAME)
163
+ path2.join(canonicalDirectory, "node_modules", PACKAGE_NAME),
164
+ path2.resolve(canonicalDirectory, "../lib/node_modules", PACKAGE_NAME)
158
165
  ];
159
166
  const roots = [];
160
- let ancestor = path.dirname(target);
167
+ let ancestor = path2.dirname(target);
161
168
  for (let depth = 0;depth < 8; depth++) {
162
169
  roots.push(ancestor);
163
- const parent = path.dirname(ancestor);
170
+ const parent = path2.dirname(ancestor);
164
171
  if (parent === ancestor)
165
172
  break;
166
173
  ancestor = parent;
@@ -194,7 +201,7 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
194
201
  import { createHash } from "node:crypto";
195
202
  import { chmodSync, lstatSync as lstatSync2, mkdirSync, realpathSync as realpathSync2 } from "node:fs";
196
203
  import os from "node:os";
197
- import path2 from "node:path";
204
+ import path3 from "node:path";
198
205
  class StateRootError extends Error {
199
206
  code;
200
207
  constructor(code) {
@@ -204,15 +211,15 @@ class StateRootError extends Error {
204
211
  }
205
212
  }
206
213
  function isContainedBy(parent, candidate) {
207
- const relative = path2.relative(parent, candidate);
208
- return relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative);
214
+ const relative = path3.relative(parent, candidate);
215
+ return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
209
216
  }
210
217
  function inspectExistingComponents(absolutePath) {
211
- const parsed = path2.parse(absolutePath);
212
- const relativeParts = absolutePath.slice(parsed.root.length).split(path2.sep).filter(Boolean);
218
+ const parsed = path3.parse(absolutePath);
219
+ const relativeParts = absolutePath.slice(parsed.root.length).split(path3.sep).filter(Boolean);
213
220
  let current = parsed.root;
214
221
  for (const part of relativeParts) {
215
- current = path2.join(current, part);
222
+ current = path3.join(current, part);
216
223
  let stat;
217
224
  try {
218
225
  stat = lstatSync2(current);
@@ -230,37 +237,37 @@ function inspectExistingComponents(absolutePath) {
230
237
  }
231
238
  }
232
239
  function canonicalFuturePath(input) {
233
- const absolute = path2.resolve(input);
240
+ const absolute = path3.resolve(input);
234
241
  inspectExistingComponents(absolute);
235
242
  let cursor = absolute;
236
243
  const missing = [];
237
244
  for (;; ) {
238
245
  try {
239
- return path2.join(realpathSync2.native(cursor), ...missing.reverse());
246
+ return path3.join(realpathSync2.native(cursor), ...missing.reverse());
240
247
  } catch (error) {
241
248
  if (error.code !== "ENOENT")
242
249
  throw error;
243
- const parent = path2.dirname(cursor);
250
+ const parent = path3.dirname(cursor);
244
251
  if (parent === cursor)
245
252
  throw error;
246
- missing.push(path2.basename(cursor));
253
+ missing.push(path3.basename(cursor));
247
254
  cursor = parent;
248
255
  }
249
256
  }
250
257
  }
251
258
  function requireAbsoluteDirectoryBase(xdgStateHome, homeDir) {
252
- if (xdgStateHome && path2.isAbsolute(xdgStateHome)) {
259
+ if (xdgStateHome && path3.isAbsolute(xdgStateHome)) {
253
260
  return canonicalFuturePath(xdgStateHome);
254
261
  }
255
- if (!path2.isAbsolute(homeDir)) {
262
+ if (!path3.isAbsolute(homeDir)) {
256
263
  throw new StateRootError("invalid-state-base");
257
264
  }
258
- return canonicalFuturePath(path2.join(homeDir, ".local", "state"));
265
+ return canonicalFuturePath(path3.join(homeDir, ".local", "state"));
259
266
  }
260
267
  function resolveStateRoot(options) {
261
268
  let consumerRoot;
262
269
  try {
263
- consumerRoot = realpathSync2.native(path2.resolve(options.consumerRoot));
270
+ consumerRoot = realpathSync2.native(path3.resolve(options.consumerRoot));
264
271
  if (!lstatSync2(consumerRoot).isDirectory()) {
265
272
  throw new StateRootError("consumer-root-unavailable");
266
273
  }
@@ -271,9 +278,9 @@ function resolveStateRoot(options) {
271
278
  }
272
279
  const stateBase = requireAbsoluteDirectoryBase(options.xdgStateHome, options.homeDir ?? os.homedir());
273
280
  const consumerRootHash = createHash("sha256").update(consumerRoot).digest("hex");
274
- const productRoot = canonicalFuturePath(path2.join(stateBase, "abel-cadence"));
275
- const rootsDir = canonicalFuturePath(path2.join(productRoot, "roots"));
276
- const rootDir = canonicalFuturePath(path2.join(rootsDir, consumerRootHash));
281
+ const productRoot = canonicalFuturePath(path3.join(stateBase, "abel-cadence"));
282
+ const rootsDir = canonicalFuturePath(path3.join(productRoot, "roots"));
283
+ const rootDir = canonicalFuturePath(path3.join(rootsDir, consumerRootHash));
277
284
  if (isContainedBy(consumerRoot, rootDir)) {
278
285
  throw new StateRootError("state-root-inside-consumer");
279
286
  }
@@ -284,10 +291,10 @@ function resolveStateRoot(options) {
284
291
  productRoot,
285
292
  rootsDir,
286
293
  rootDir,
287
- databasePath: path2.join(rootDir, "control.sqlite3"),
288
- artifactsDir: path2.join(rootDir, "artifacts"),
289
- workspacesDir: path2.join(rootDir, "workspaces"),
290
- transactionsDir: path2.join(rootDir, "transactions")
294
+ databasePath: path3.join(rootDir, "control.sqlite3"),
295
+ artifactsDir: path3.join(rootDir, "artifacts"),
296
+ workspacesDir: path3.join(rootDir, "workspaces"),
297
+ transactionsDir: path3.join(rootDir, "transactions")
291
298
  };
292
299
  }
293
300
 
@@ -318,7 +325,7 @@ import {
318
325
  readFileSync as readFileSync3,
319
326
  realpathSync as realpathSync3
320
327
  } from "node:fs";
321
- import path4 from "node:path";
328
+ import path5 from "node:path";
322
329
 
323
330
  // src/control-contracts.ts
324
331
  var CONTROL_SCHEMA_PROPERTIES = {
@@ -609,13 +616,13 @@ import { join, resolve } from "node:path";
609
616
 
610
617
  // src/safe-path.ts
611
618
  import { lstatSync as lstatSync3 } from "node:fs";
612
- import path3 from "node:path";
619
+ import path4 from "node:path";
613
620
  function observeSafePath(root, relative) {
614
621
  if (!isValidRelativePath(relative)) {
615
622
  return { kind: "unsafe", reason: "invalid-path" };
616
623
  }
617
624
  try {
618
- const resolvedRoot = path3.resolve(root);
625
+ const resolvedRoot = path4.resolve(root);
619
626
  const rootStat = lstatSync3(resolvedRoot, { throwIfNoEntry: false });
620
627
  if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
621
628
  return { kind: "unsafe", reason: "root-unavailable" };
@@ -625,8 +632,8 @@ function observeSafePath(root, relative) {
625
632
  let current = resolvedRoot;
626
633
  const parts = relative.split("/");
627
634
  for (const [index, part] of parts.entries()) {
628
- current = path3.join(current, part);
629
- if (!current.startsWith(`${resolvedRoot}${path3.sep}`)) {
635
+ current = path4.join(current, part);
636
+ if (!current.startsWith(`${resolvedRoot}${path4.sep}`)) {
630
637
  return { kind: "unsafe", reason: "invalid-path" };
631
638
  }
632
639
  const stat = lstatSync3(current, { throwIfNoEntry: false });
@@ -652,9 +659,6 @@ function observeSafePath(root, relative) {
652
659
  }
653
660
  return { kind: "unsafe", reason: "invalid-path" };
654
661
  }
655
- function isSafeRegularFile(root, relative) {
656
- return observeSafePath(root, relative).kind === "file";
657
- }
658
662
 
659
663
  // src/file-snapshot.ts
660
664
  function sha256(bytes) {
@@ -697,10 +701,16 @@ function capabilitySuccess(verificationId, runnerBindings = []) {
697
701
  }))
698
702
  };
699
703
  }
700
- function adapterFailure(verificationId, code, message) {
704
+ function adapterFailure(verificationId, code, message, inputObservation) {
701
705
  return {
702
706
  ok: false,
703
- diagnostic: { kind: "verification-adapter", code, verificationId, message }
707
+ diagnostic: {
708
+ kind: "verification-adapter",
709
+ code,
710
+ verificationId,
711
+ message,
712
+ ...inputObservation ? { inputObservation } : {}
713
+ }
704
714
  };
705
715
  }
706
716
  function fileStatus(root, relative) {
@@ -713,11 +723,11 @@ function regularFile(root, relative) {
713
723
  return fileStatus(root, relative) === "regular";
714
724
  }
715
725
  function localExecutable(root, executable, dependencyOwner = root, environment = {}) {
716
- const dependencyRoot = path4.resolve(dependencyOwner, "node_modules");
726
+ const dependencyRoot = path5.resolve(dependencyOwner, "node_modules");
717
727
  try {
718
728
  const canonicalDependencyRoot = realpathSync3(dependencyRoot);
719
729
  for (const name of localExecutableNames(executable, environment)) {
720
- const unresolved = resolveCandidateName(path4.resolve(dependencyRoot, ".bin"), name, environment.platform ?? process.platform);
730
+ const unresolved = resolveCandidateName(path5.resolve(dependencyRoot, ".bin"), name, environment.platform ?? process.platform);
721
731
  if (!unresolved)
722
732
  continue;
723
733
  const canonical = realpathSync3(unresolved);
@@ -733,11 +743,11 @@ function localExecutable(root, executable, dependencyOwner = root, environment =
733
743
  }
734
744
  function pathApi(platform, ...values) {
735
745
  if (platform !== "win32")
736
- return path4.posix;
746
+ return path5.posix;
737
747
  if (process.platform === "win32" || values.some((value) => /^[a-z]:[\\/]/iu.test(value) || value.startsWith("\\\\"))) {
738
- return path4.win32;
748
+ return path5.win32;
739
749
  }
740
- return path4.posix;
750
+ return path5.posix;
741
751
  }
742
752
  function pathWithin(root, candidate, platform) {
743
753
  const platformPath = pathApi(platform, root, candidate);
@@ -767,7 +777,7 @@ function runnerNames(command, environment) {
767
777
  if ((environment.platform ?? process.platform) !== "win32") {
768
778
  return [command];
769
779
  }
770
- const extension = path4.extname(command).toLowerCase();
780
+ const extension = path5.extname(command).toLowerCase();
771
781
  if (extension) {
772
782
  return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [command] : [];
773
783
  }
@@ -779,7 +789,7 @@ function localExecutableNames(executable, environment) {
779
789
  if ((environment.platform ?? process.platform) !== "win32") {
780
790
  return [executable];
781
791
  }
782
- const extension = path4.extname(executable).toLowerCase();
792
+ const extension = path5.extname(executable).toLowerCase();
783
793
  if (extension) {
784
794
  return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [executable] : [];
785
795
  }
@@ -789,18 +799,18 @@ function localExecutableNames(executable, environment) {
789
799
  ];
790
800
  }
791
801
  function resolveCandidateName(directory, name, platform) {
792
- const exact = path4.join(directory, name);
802
+ const exact = path5.join(directory, name);
793
803
  if (platform !== "win32")
794
804
  return exact;
795
805
  try {
796
806
  const matches = readdirSync3(directory).filter((entry) => entry.toLowerCase() === name.toLowerCase());
797
- return matches.length === 1 ? path4.join(directory, matches[0]) : null;
807
+ return matches.length === 1 ? path5.join(directory, matches[0]) : null;
798
808
  } catch {
799
809
  return null;
800
810
  }
801
811
  }
802
812
  function within(root, candidate) {
803
- return candidate === root || candidate.startsWith(`${root}${path4.sep}`);
813
+ return candidate === root || candidate.startsWith(`${root}${path5.sep}`);
804
814
  }
805
815
  function commonDirectory(left, right, platform = process.platform) {
806
816
  const platformPath = pathApi(platform, left, right);
@@ -827,15 +837,15 @@ function commonDirectory(left, right, platform = process.platform) {
827
837
  function unsafeMountSource(source) {
828
838
  if (BROAD_MOUNT_ROOTS.has(source))
829
839
  return true;
830
- if (path4.resolve(source) === path4.parse(path4.resolve(source)).root)
840
+ if (path5.resolve(source) === path5.parse(path5.resolve(source)).root)
831
841
  return true;
832
- const parts = path4.relative(path4.parse(path4.resolve(source)).root, path4.resolve(source)).split(path4.sep).filter(Boolean).map((part) => part.toLowerCase());
842
+ const parts = path5.relative(path5.parse(path5.resolve(source)).root, path5.resolve(source)).split(path5.sep).filter(Boolean).map((part) => part.toLowerCase());
833
843
  return parts[0] === "home" && parts.length <= 2 || parts[0] === "users" && parts.length <= 2 || parts[0] === "root" && parts.length <= 1;
834
844
  }
835
845
  function safeRunnerSource(paths, platform) {
836
- let source = path4.dirname(paths[0]);
846
+ let source = path5.dirname(paths[0]);
837
847
  for (const candidate of paths.slice(1)) {
838
- source = commonDirectory(source, path4.dirname(candidate), platform);
848
+ source = commonDirectory(source, path5.dirname(candidate), platform);
839
849
  }
840
850
  if (unsafeMountSource(source))
841
851
  return null;
@@ -853,18 +863,18 @@ function resolvedRunnerEnvironment(environment) {
853
863
  platform: environment.platform ?? process.platform,
854
864
  path: environment.path ?? process.env.PATH ?? "",
855
865
  pathExt: environment.pathExt ?? process.env.PATHEXT ?? DEFAULT_WINDOWS_PATH_EXT,
856
- pathDelimiter: environment.pathDelimiter ?? path4.delimiter
866
+ pathDelimiter: environment.pathDelimiter ?? path5.delimiter
857
867
  };
858
868
  }
859
869
  function windowsShimBinding(command, launcherPath, environment) {
860
- const name = path4.basename(command, path4.extname(command)).toLowerCase();
870
+ const name = path5.basename(command, path5.extname(command)).toLowerCase();
861
871
  if (name !== "npm" && name !== "npx")
862
872
  return null;
863
873
  const node = resolveVerificationRunner("node", environment);
864
- if (!node || node.fixedArgs || path4.extname(node.executablePath).toLowerCase() !== ".exe") {
874
+ if (!node || node.fixedArgs || path5.extname(node.executablePath).toLowerCase() !== ".exe") {
865
875
  return null;
866
876
  }
867
- const cli = path4.join(path4.dirname(launcherPath), "node_modules", "npm", "bin", `${name}-cli.js`);
877
+ const cli = path5.join(path5.dirname(launcherPath), "node_modules", "npm", "bin", `${name}-cli.js`);
868
878
  try {
869
879
  const canonicalLauncher = realpathSync3(launcherPath);
870
880
  const canonicalCli = realpathSync3(cli);
@@ -889,14 +899,14 @@ function localPackageDirectories(dependencyRoot) {
889
899
  for (const entry of readdirSync3(dependencyRoot, { withFileTypes: true })) {
890
900
  if (!entry.isDirectory() || entry.name.startsWith("."))
891
901
  continue;
892
- const candidate = path4.join(dependencyRoot, entry.name);
902
+ const candidate = path5.join(dependencyRoot, entry.name);
893
903
  if (!entry.name.startsWith("@")) {
894
904
  packages.push(candidate);
895
905
  continue;
896
906
  }
897
907
  for (const scoped of readdirSync3(candidate, { withFileTypes: true })) {
898
908
  if (scoped.isDirectory() && !scoped.name.startsWith(".")) {
899
- packages.push(path4.join(candidate, scoped.name));
909
+ packages.push(path5.join(candidate, scoped.name));
900
910
  }
901
911
  }
902
912
  }
@@ -906,7 +916,7 @@ function localPackageDirectories(dependencyRoot) {
906
916
  return packages;
907
917
  }
908
918
  function localPackageBinTargets(dependencyOwner, executable, platform) {
909
- const dependencyRoot = path4.resolve(dependencyOwner, "node_modules");
919
+ const dependencyRoot = path5.resolve(dependencyOwner, "node_modules");
910
920
  let canonicalDependencyRoot;
911
921
  try {
912
922
  canonicalDependencyRoot = realpathSync3(dependencyRoot);
@@ -924,7 +934,7 @@ function localPackageBinTargets(dependencyOwner, executable, platform) {
924
934
  if (!pathWithin(canonicalDependencyRoot, canonicalPackage, platform) || canonicalPackage === canonicalDependencyRoot) {
925
935
  continue;
926
936
  }
927
- const manifestPath = path4.join(canonicalPackage, "package.json");
937
+ const manifestPath = path5.join(canonicalPackage, "package.json");
928
938
  const manifestStat = lstatSync5(manifestPath);
929
939
  if (!manifestStat.isFile() || manifestStat.isSymbolicLink())
930
940
  continue;
@@ -946,7 +956,7 @@ function localPackageBinTargets(dependencyOwner, executable, platform) {
946
956
  if (typeof relative !== "string" || relative.length === 0 || relative.includes("\x00")) {
947
957
  continue;
948
958
  }
949
- const unresolved = path4.resolve(canonicalPackage, relative);
959
+ const unresolved = path5.resolve(canonicalPackage, relative);
950
960
  if (!pathWithin(canonicalPackage, unresolved, platform))
951
961
  continue;
952
962
  const canonical = realpathSync3(unresolved);
@@ -966,7 +976,7 @@ function resolveLocalVerificationRunner(dependencyOwner, executable, runnerEnvir
966
976
  if (targets.length !== 1)
967
977
  return null;
968
978
  const target = targets[0];
969
- const extension = path4.extname(target).toLowerCase();
979
+ const extension = path5.extname(target).toLowerCase();
970
980
  if (extension === ".exe") {
971
981
  return { command: executable, executablePath: target };
972
982
  }
@@ -988,7 +998,7 @@ function resolveVerificationRunner(command, runnerEnvironment = {}) {
988
998
  const candidates = [
989
999
  ...command === "bun" && process.versions.bun && environment.platform === process.platform ? [process.execPath] : [],
990
1000
  ...directories.flatMap((directory) => {
991
- const resolvedDirectory = path4.resolve(directory);
1001
+ const resolvedDirectory = path5.resolve(directory);
992
1002
  const key = environment.platform === "win32" ? resolvedDirectory.toLowerCase() : resolvedDirectory;
993
1003
  if (seenDirectories.has(key))
994
1004
  return [];
@@ -1002,7 +1012,7 @@ function resolveVerificationRunner(command, runnerEnvironment = {}) {
1002
1012
  const seenCandidates = new Set;
1003
1013
  for (const candidate of candidates) {
1004
1014
  try {
1005
- const executablePath = path4.resolve(candidate);
1015
+ const executablePath = path5.resolve(candidate);
1006
1016
  const candidateKey = environment.platform === "win32" ? executablePath.toLowerCase() : executablePath;
1007
1017
  if (seenCandidates.has(candidateKey))
1008
1018
  continue;
@@ -1012,7 +1022,7 @@ function resolveVerificationRunner(command, runnerEnvironment = {}) {
1012
1022
  if (!regularExecutableFile(stat, environment.platform))
1013
1023
  continue;
1014
1024
  if (environment.platform === "win32") {
1015
- const extension = path4.extname(executablePath).toLowerCase();
1025
+ const extension = path5.extname(executablePath).toLowerCase();
1016
1026
  if (extension === ".cmd") {
1017
1027
  const shim = windowsShimBinding(command, executablePath, environment);
1018
1028
  if (shim)
@@ -1029,7 +1039,7 @@ function resolveVerificationRunner(command, runnerEnvironment = {}) {
1029
1039
  const systemVisible = SYSTEM_MOUNT_ROOTS.some((root) => within(root, executablePath) && within(root, canonical));
1030
1040
  if (systemVisible)
1031
1041
  return { command, executablePath };
1032
- const mountSource = commonDirectory(path4.dirname(executablePath), path4.dirname(canonical), environment.platform);
1042
+ const mountSource = commonDirectory(path5.dirname(executablePath), path5.dirname(canonical), environment.platform);
1033
1043
  if (unsafeMountSource(mountSource))
1034
1044
  continue;
1035
1045
  const sourceStat = lstatSync5(mountSource);
@@ -1044,7 +1054,7 @@ function packageManifest(root) {
1044
1054
  if (!regularFile(root, "package.json"))
1045
1055
  return null;
1046
1056
  try {
1047
- const value = JSON.parse(readFileSync3(path4.join(root, "package.json"), "utf8"));
1057
+ const value = JSON.parse(readFileSync3(path5.join(root, "package.json"), "utf8"));
1048
1058
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1049
1059
  } catch {
1050
1060
  return null;
@@ -1166,7 +1176,7 @@ function validateAtomicCapability(root, step, dependencyOwner, runnerEnvironment
1166
1176
  "pnpm-workspace.yaml"
1167
1177
  ].includes(relative))
1168
1178
  continue;
1169
- if (lstatSync5(path4.join(root, relative)).size > 64 * 1024 || executionProfile().mode !== "local-trusted" && unsupportedExecutionConfiguration(readFileSync3(path4.join(root, relative), "utf8"))) {
1179
+ if (lstatSync5(path5.join(root, relative)).size > 64 * 1024 || executionProfile().mode === "isolated" && unsupportedExecutionConfiguration(readFileSync3(path5.join(root, relative), "utf8"))) {
1170
1180
  return adapterFailure(step.id, "verification-config-unsafe", "Execution configuration requires unsupported credentials or host configuration");
1171
1181
  }
1172
1182
  }
@@ -1223,8 +1233,10 @@ function validateCapability(root, value, options, requireInputAvailability) {
1223
1233
  function validateAcceptedVerificationCapability(root, verification, dependencyOwner, runnerEnvironment, requireInputAvailability, executionWritePaths) {
1224
1234
  if (requireInputAvailability) {
1225
1235
  for (const input of verificationInputPaths(verification)) {
1226
- if (!isSafeRegularFile(root, input)) {
1227
- return adapterFailure(verification.id, "input-missing", `verification input ${input} is missing or unsafe`);
1236
+ const status = fileStatus(root, input);
1237
+ if (status !== "regular") {
1238
+ const inputObservation = { path: input, kind: status };
1239
+ return adapterFailure(verification.id, status === "absent" ? "input-missing" : "input-unsafe", `verification input ${input} is ${status}`, inputObservation);
1228
1240
  }
1229
1241
  }
1230
1242
  }
@@ -1238,6 +1250,549 @@ function validateAcceptedVerificationCapability(root, verification, dependencyOw
1238
1250
  return capabilitySuccess(verification.id, runnerBindings);
1239
1251
  }
1240
1252
 
1253
+ // src/windows-job-backend.ts
1254
+ import { spawn as spawn2, spawnSync } from "node:child_process";
1255
+ import { createHash as createHash3 } from "node:crypto";
1256
+ import { lstatSync as lstatSync7, readFileSync as readFileSync4 } from "node:fs";
1257
+ import path7 from "node:path";
1258
+
1259
+ // src/isolation-backend.ts
1260
+ import { spawn } from "node:child_process";
1261
+ import { accessSync, constants, lstatSync as lstatSync6 } from "node:fs";
1262
+ import path6 from "node:path";
1263
+ var ISOLATION_RUN_LIMITS = Object.freeze({
1264
+ timeoutMs: 10 * 60000,
1265
+ maxOutputBytes: 256 * 1024,
1266
+ terminateGraceMs: 1000
1267
+ });
1268
+ function defaultProbe(executable) {
1269
+ try {
1270
+ accessSync(executable, constants.X_OK);
1271
+ return true;
1272
+ } catch {
1273
+ return false;
1274
+ }
1275
+ }
1276
+ function validEnvironmentName(name) {
1277
+ return /^[A-Za-z_][A-Za-z0-9_]*$/u.test(name);
1278
+ }
1279
+
1280
+ class BoundedLog {
1281
+ bytes = 0;
1282
+ #head = Buffer.alloc(0);
1283
+ #tail = Buffer.alloc(0);
1284
+ limit;
1285
+ #witness;
1286
+ #witnessTail = Buffer.alloc(0);
1287
+ witnessMatched = false;
1288
+ constructor(limit, witness) {
1289
+ this.limit = limit;
1290
+ if (witness)
1291
+ this.#witness = Buffer.from(witness);
1292
+ }
1293
+ append(chunk) {
1294
+ const input = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1295
+ this.bytes += input.byteLength;
1296
+ if (this.#witness && !this.witnessMatched) {
1297
+ const searchable = Buffer.concat([this.#witnessTail, input]);
1298
+ this.witnessMatched = searchable.includes(this.#witness);
1299
+ this.#witnessTail = this.witnessMatched ? Buffer.alloc(0) : Buffer.from(searchable.subarray(Math.max(0, searchable.length - this.#witness.length + 1)));
1300
+ }
1301
+ const headLimit = Math.ceil(this.limit / 2);
1302
+ const take = Math.min(input.length, headLimit - this.#head.length);
1303
+ if (take > 0)
1304
+ this.#head = Buffer.concat([this.#head, input.subarray(0, take)]);
1305
+ const rest = input.subarray(take);
1306
+ const tailLimit = this.limit - headLimit;
1307
+ if (rest.length && tailLimit) {
1308
+ this.#tail = rest.length >= tailLimit ? Buffer.from(rest.subarray(-tailLimit)) : Buffer.concat([
1309
+ this.#tail.subarray(Math.max(0, this.#tail.length + rest.length - tailLimit)),
1310
+ rest
1311
+ ]);
1312
+ }
1313
+ }
1314
+ text() {
1315
+ return Buffer.concat([this.#head, this.#tail]).toString("utf8");
1316
+ }
1317
+ metadata() {
1318
+ return { bytes: this.bytes, truncated: this.bytes > this.limit };
1319
+ }
1320
+ }
1321
+
1322
+ class BubblewrapIsolationBackend {
1323
+ #localTrusted;
1324
+ #bwrapPath;
1325
+ #probe;
1326
+ #spawn;
1327
+ #timeoutMs;
1328
+ #maxOutputBytes;
1329
+ #terminateGraceMs;
1330
+ constructor(options = {}) {
1331
+ this.#localTrusted = options.localTrusted === true;
1332
+ this.#bwrapPath = options.bwrapPath ?? "/usr/bin/bwrap";
1333
+ this.#probe = options.probe ?? defaultProbe;
1334
+ this.#spawn = options.spawnProcess ?? spawn;
1335
+ this.#timeoutMs = options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs;
1336
+ this.#maxOutputBytes = options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes;
1337
+ this.#terminateGraceMs = options.terminateGraceMs ?? ISOLATION_RUN_LIMITS.terminateGraceMs;
1338
+ if (!Number.isSafeInteger(this.#timeoutMs) || this.#timeoutMs < 1 || !Number.isSafeInteger(this.#maxOutputBytes) || this.#maxOutputBytes < 1 || !Number.isSafeInteger(this.#terminateGraceMs) || this.#terminateGraceMs < 1) {
1339
+ throw new Error("isolation-run-limits-invalid");
1340
+ }
1341
+ }
1342
+ async available() {
1343
+ try {
1344
+ if (this.#localTrusted && process.platform !== "linux")
1345
+ return false;
1346
+ return Boolean(await this.#probe(this.#bwrapPath));
1347
+ } catch {
1348
+ return false;
1349
+ }
1350
+ }
1351
+ async run(input) {
1352
+ if (input.outputWitness !== undefined && (typeof input.outputWitness !== "string" || input.outputWitness.length < 1 || input.outputWitness.length > 512))
1353
+ throw new Error("isolation-output-witness-invalid");
1354
+ if (!await this.available()) {
1355
+ return {
1356
+ ok: false,
1357
+ state: "paused",
1358
+ code: "isolation-backend-unavailable"
1359
+ };
1360
+ }
1361
+ if (input.signal?.aborted) {
1362
+ return { ok: false, state: "cancelled", code: "cancelled" };
1363
+ }
1364
+ const root = path6.resolve(input.root);
1365
+ const rootStat = lstatSync6(root, { throwIfNoEntry: false });
1366
+ if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
1367
+ throw new Error("isolation-root-invalid");
1368
+ }
1369
+ const argv = this.#localTrusted ? [
1370
+ "--die-with-parent",
1371
+ "--new-session",
1372
+ "--unshare-pid",
1373
+ "--bind",
1374
+ "/",
1375
+ "/",
1376
+ "--proc",
1377
+ "/proc",
1378
+ "--dev",
1379
+ "/dev",
1380
+ "--chdir",
1381
+ root,
1382
+ "--clearenv"
1383
+ ] : [
1384
+ "--die-with-parent",
1385
+ "--new-session",
1386
+ "--unshare-all",
1387
+ "--proc",
1388
+ "/proc",
1389
+ "--dev",
1390
+ "/dev",
1391
+ "--tmpfs",
1392
+ "/tmp",
1393
+ "--bind",
1394
+ root,
1395
+ "/workspace",
1396
+ "--chdir",
1397
+ "/workspace",
1398
+ "--clearenv",
1399
+ "--setenv",
1400
+ "PATH",
1401
+ "/usr/local/bin:/usr/bin:/bin"
1402
+ ];
1403
+ if (!this.#localTrusted) {
1404
+ for (const systemRoot of ["/usr", "/bin", "/lib", "/lib64"]) {
1405
+ if (lstatSync6(systemRoot, { throwIfNoEntry: false }))
1406
+ argv.push("--ro-bind", systemRoot, systemRoot);
1407
+ }
1408
+ for (const mount of input.mounts ?? []) {
1409
+ if (!path6.isAbsolute(mount.source) || !path6.isAbsolute(mount.target))
1410
+ throw new Error("isolation-mount-invalid");
1411
+ argv.push(mount.writable ? "--bind" : "--ro-bind", mount.source, mount.target);
1412
+ }
1413
+ }
1414
+ for (const [name, value] of Object.entries(input.environment ?? {}).sort()) {
1415
+ if (!validEnvironmentName(name) || value.includes("\x00"))
1416
+ throw new Error("isolation-environment-invalid");
1417
+ argv.push("--setenv", name, value);
1418
+ }
1419
+ argv.push("--", input.executable, ...input.args ?? []);
1420
+ return new Promise((resolve2) => {
1421
+ let settled = false;
1422
+ const stdout = new BoundedLog(this.#maxOutputBytes, input.outputWitness);
1423
+ const stderr = new BoundedLog(this.#maxOutputBytes, input.outputWitness);
1424
+ let child;
1425
+ let executionTimer;
1426
+ let killTimer;
1427
+ let terminationResult;
1428
+ const settle = (result) => {
1429
+ if (settled)
1430
+ return;
1431
+ settled = true;
1432
+ if (executionTimer)
1433
+ clearTimeout(executionTimer);
1434
+ input.signal?.removeEventListener("abort", abort);
1435
+ resolve2(result);
1436
+ };
1437
+ const kill = (signal) => {
1438
+ child?.kill(signal);
1439
+ };
1440
+ const terminate = (result) => {
1441
+ if (settled || terminationResult)
1442
+ return;
1443
+ terminationResult = result;
1444
+ if (executionTimer)
1445
+ clearTimeout(executionTimer);
1446
+ killTimer = setTimeout(() => kill("SIGKILL"), this.#terminateGraceMs);
1447
+ killTimer.unref?.();
1448
+ kill("SIGTERM");
1449
+ };
1450
+ const abort = () => {
1451
+ terminate({ ok: false, state: "cancelled", code: "cancelled" });
1452
+ };
1453
+ try {
1454
+ child = this.#spawn(this.#bwrapPath, argv, {
1455
+ cwd: root,
1456
+ shell: false,
1457
+ stdio: ["ignore", "pipe", "pipe"]
1458
+ });
1459
+ } catch {
1460
+ settle({
1461
+ ok: false,
1462
+ state: "paused",
1463
+ code: "isolation-backend-launch-failed"
1464
+ });
1465
+ return;
1466
+ }
1467
+ const capture = (destination, chunk) => {
1468
+ if (!settled && !terminationResult)
1469
+ destination.append(chunk);
1470
+ };
1471
+ child.stdout?.on("data", (chunk) => {
1472
+ capture(stdout, chunk);
1473
+ });
1474
+ child.stderr?.on("data", (chunk) => {
1475
+ capture(stderr, chunk);
1476
+ });
1477
+ child.once("error", () => {
1478
+ if (terminationResult)
1479
+ return;
1480
+ settle({
1481
+ ok: false,
1482
+ state: "paused",
1483
+ code: "isolation-backend-launch-failed"
1484
+ });
1485
+ });
1486
+ child.once("close", (code) => {
1487
+ if (killTimer)
1488
+ clearTimeout(killTimer);
1489
+ settle(terminationResult ?? {
1490
+ ok: true,
1491
+ state: "completed",
1492
+ exitCode: code ?? 1,
1493
+ stdout: stdout.text(),
1494
+ stderr: stderr.text(),
1495
+ logs: { stdout: stdout.metadata(), stderr: stderr.metadata() },
1496
+ ...input.outputWitness ? {
1497
+ outputWitnessMatched: stdout.witnessMatched || stderr.witnessMatched
1498
+ } : {}
1499
+ });
1500
+ });
1501
+ input.signal?.addEventListener("abort", abort, { once: true });
1502
+ if (input.signal?.aborted) {
1503
+ abort();
1504
+ return;
1505
+ }
1506
+ executionTimer = setTimeout(() => terminate({
1507
+ ok: false,
1508
+ state: "paused",
1509
+ code: "isolation-execution-timeout"
1510
+ }), this.#timeoutMs);
1511
+ executionTimer.unref?.();
1512
+ });
1513
+ }
1514
+ }
1515
+
1516
+ // src/windows-job-backend.ts
1517
+ var SHUTDOWN_MS = 5000;
1518
+ var invalid = () => {
1519
+ throw new Error("windows-job-request-invalid");
1520
+ };
1521
+ var text = (value) => typeof value === "string" && value.length <= 16384 && !value.includes("\x00") && !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value);
1522
+ var absolute = (value) => text(value) && /^[A-Za-z]:\\/u.test(value) && path7.win32.isAbsolute(value);
1523
+ function encodeWindowsJobRequest(input, timeoutMs) {
1524
+ if (!absolute(input.root) || !absolute(input.executable) || !input.executable.toLowerCase().endsWith(".exe") || input.mounts?.length || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86400000)
1525
+ invalid();
1526
+ const args = input.args ?? [];
1527
+ if (args.length > 128 || !args.every(text))
1528
+ invalid();
1529
+ const seen = new Set;
1530
+ const env = Object.entries(input.environment ?? {});
1531
+ for (const [name, value] of env) {
1532
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || !text(value) || seen.has(name.toUpperCase()))
1533
+ invalid();
1534
+ seen.add(name.toUpperCase());
1535
+ }
1536
+ const quote = (value) => `"${value.replace(/(\\*)"/gu, "$1$1\\\"").replace(/(\\+)$/gu, "$1$1")}"`;
1537
+ const command = [input.executable, ...args].map(quote).join(" ");
1538
+ if (command.length >= 32767)
1539
+ invalid();
1540
+ const environment = `${env.sort(([a], [b]) => a.toUpperCase() < b.toUpperCase() ? -1 : 1).map(([k, v]) => `${k}=${v}`).join("\x00")}\x00\x00`;
1541
+ const parts = [
1542
+ `${input.executable}\x00`,
1543
+ `${command}\x00`,
1544
+ `${input.root}\x00`,
1545
+ environment
1546
+ ].map((s) => Buffer.from(s, "utf16le"));
1547
+ const header = Buffer.alloc(36);
1548
+ [2, timeoutMs, SHUTDOWN_MS, 0, ...parts.map((p) => p.length), 0].forEach((n, i) => {
1549
+ header.writeUInt32LE(n, i * 4);
1550
+ });
1551
+ const wire = Buffer.concat([header, ...parts]);
1552
+ if (wire.length > 65536 || parts.some((p) => p.length > 60000))
1553
+ invalid();
1554
+ return wire;
1555
+ }
1556
+ function verifyWindowsJobHelper(helper) {
1557
+ try {
1558
+ if (!path7.isAbsolute(helper))
1559
+ return false;
1560
+ const files = [helper, `${helper}.json`];
1561
+ if (files.some((file) => {
1562
+ const s = lstatSync7(file);
1563
+ return !s.isFile() || s.isSymbolicLink() || s.size > 8 * 1024 * 1024;
1564
+ }))
1565
+ return false;
1566
+ const bytes = readFileSync4(helper);
1567
+ const pe = bytes.length >= 64 ? bytes.readUInt32LE(60) : -1;
1568
+ if (bytes.toString("ascii", 0, 2) !== "MZ" || pe < 64 || pe + 6 > bytes.length || bytes.readUInt32LE(pe) !== 17744 || bytes.readUInt16LE(pe + 4) !== 34404)
1569
+ return false;
1570
+ const hash = (data) => createHash3("sha256").update(data).digest("hex");
1571
+ const manifest = JSON.parse(readFileSync4(`${helper}.json`, "utf8"));
1572
+ return manifest.version === 2 && manifest.arch === "x64" && manifest.helperSha256 === hash(bytes) && manifest.sourceSha256 === hash(readFileSync4(new URL("./windows-job.c", import.meta.url)));
1573
+ } catch {
1574
+ return false;
1575
+ }
1576
+ }
1577
+ function observation(value) {
1578
+ if (!value || typeof value !== "object")
1579
+ return;
1580
+ const v = value;
1581
+ if (Object.keys(v).sort().join(",") !== "exitCode,managedSettled,outcome,reason,rootExited,version" || v.version !== 2 || !["complete", "failed", "uncertain"].includes(v.outcome) || ![
1582
+ "exit",
1583
+ "timeout",
1584
+ "cancelled",
1585
+ "launch-failed",
1586
+ "termination-unconfirmed"
1587
+ ].includes(v.reason) || typeof v.rootExited !== "boolean" || typeof v.managedSettled !== "boolean" || !(v.exitCode === null || Number.isInteger(v.exitCode) && v.exitCode >= 0 && v.exitCode <= 4294967295))
1588
+ return;
1589
+ if (v.outcome === "complete" && (v.reason !== "exit" || !v.rootExited || !v.managedSettled || v.exitCode === null))
1590
+ return;
1591
+ if (v.outcome === "failed" && (!v.managedSettled || !["timeout", "cancelled", "launch-failed"].includes(v.reason)))
1592
+ return;
1593
+ return v;
1594
+ }
1595
+ var uncertain = () => ({
1596
+ ok: false,
1597
+ state: "paused",
1598
+ code: "isolation-termination-unconfirmed"
1599
+ });
1600
+ function classify(v, helperExit, output, cancelled) {
1601
+ if (!v?.managedSettled || v.outcome === "uncertain" || helperExit !== 0)
1602
+ return uncertain();
1603
+ if (cancelled || v.reason === "cancelled")
1604
+ return { ok: false, state: "cancelled", code: "cancelled" };
1605
+ if (v.reason === "timeout")
1606
+ return { ok: false, state: "paused", code: "isolation-execution-timeout" };
1607
+ if (v.reason === "launch-failed")
1608
+ return {
1609
+ ok: false,
1610
+ state: "paused",
1611
+ code: "isolation-backend-launch-failed"
1612
+ };
1613
+ if (v.outcome !== "complete" || v.exitCode === null)
1614
+ return uncertain();
1615
+ return {
1616
+ ok: true,
1617
+ state: "completed",
1618
+ exitCode: v.exitCode,
1619
+ stdout: output.text(),
1620
+ stderr: "",
1621
+ outputWitnessMatched: output.witnessMatched,
1622
+ logs: { stdout: output.metadata(), stderr: { bytes: 0, truncated: false } }
1623
+ };
1624
+ }
1625
+
1626
+ class WindowsJobBackend {
1627
+ #options;
1628
+ constructor(options) {
1629
+ for (const [value, maximum] of [
1630
+ [options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs, 86400000],
1631
+ [
1632
+ options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes,
1633
+ 1024 * 1024
1634
+ ]
1635
+ ])
1636
+ if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
1637
+ throw new Error("isolation-run-limits-invalid");
1638
+ this.#options = options;
1639
+ }
1640
+ available() {
1641
+ return (this.#options.platform ?? process.platform) === "win32" && (this.#options.arch ?? process.arch) === "x64" && (this.#options.probe ?? verifyWindowsJobHelper)(this.#options.helperPath);
1642
+ }
1643
+ async run(input) {
1644
+ if (!this.available())
1645
+ return {
1646
+ ok: false,
1647
+ state: "paused",
1648
+ code: "isolation-backend-unavailable"
1649
+ };
1650
+ if (input.signal?.aborted)
1651
+ return { ok: false, state: "cancelled", code: "cancelled" };
1652
+ if (input.outputWitness !== undefined && (!text(input.outputWitness) || input.outputWitness.length < 1 || input.outputWitness.length > 512))
1653
+ return {
1654
+ ok: false,
1655
+ state: "paused",
1656
+ code: "isolation-backend-launch-failed"
1657
+ };
1658
+ const timeoutMs = this.#options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs;
1659
+ let wire;
1660
+ try {
1661
+ wire = encodeWindowsJobRequest(input, timeoutMs);
1662
+ } catch {
1663
+ return {
1664
+ ok: false,
1665
+ state: "paused",
1666
+ code: "isolation-backend-launch-failed"
1667
+ };
1668
+ }
1669
+ const output = new BoundedLog(this.#options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes, input.outputWitness);
1670
+ return new Promise((resolve2) => {
1671
+ let child;
1672
+ let line = "", final, bad = false, closed = false, launchFailed = false;
1673
+ let timer;
1674
+ let stopping = false;
1675
+ const armDeadline = (duration) => {
1676
+ clearTimeout(timer);
1677
+ timer = setTimeout(() => {
1678
+ bad = true;
1679
+ child.kill();
1680
+ finish(uncertain());
1681
+ }, duration);
1682
+ timer.unref?.();
1683
+ };
1684
+ const cancel = () => {
1685
+ if (stopping || closed)
1686
+ return;
1687
+ stopping = true;
1688
+ child.stdin?.end();
1689
+ armDeadline(SHUTDOWN_MS + 5000);
1690
+ };
1691
+ const finish = (result) => {
1692
+ if (closed)
1693
+ return;
1694
+ closed = true;
1695
+ clearTimeout(timer);
1696
+ input.signal?.removeEventListener("abort", cancel);
1697
+ resolve2(result);
1698
+ };
1699
+ try {
1700
+ child = (this.#options.spawnProcess ?? spawn2)(this.#options.helperPath, [], {
1701
+ cwd: input.root,
1702
+ shell: false,
1703
+ windowsHide: true,
1704
+ stdio: ["pipe", "pipe", "pipe"]
1705
+ });
1706
+ } catch {
1707
+ finish({
1708
+ ok: false,
1709
+ state: "paused",
1710
+ code: "isolation-backend-launch-failed"
1711
+ });
1712
+ return;
1713
+ }
1714
+ const malformed = () => {
1715
+ bad = true;
1716
+ cancel();
1717
+ };
1718
+ child.stdout?.on("data", (chunk) => {
1719
+ if (closed || bad)
1720
+ return;
1721
+ line += chunk.toString("utf8");
1722
+ while (line.includes(`
1723
+ `)) {
1724
+ const end = line.indexOf(`
1725
+ `);
1726
+ const row = line.slice(0, end);
1727
+ line = line.slice(end + 1);
1728
+ if (row.length > 4096 || final) {
1729
+ malformed();
1730
+ return;
1731
+ }
1732
+ try {
1733
+ const value = JSON.parse(row);
1734
+ if (Object.keys(value).length === 1 && typeof value.outputHex === "string" && /^(?:[a-f0-9]{2}){1,1024}$/u.test(value.outputHex))
1735
+ output.append(Buffer.from(value.outputHex, "hex"));
1736
+ else {
1737
+ final = observation(value);
1738
+ if (!final) {
1739
+ malformed();
1740
+ return;
1741
+ }
1742
+ }
1743
+ } catch {
1744
+ malformed();
1745
+ return;
1746
+ }
1747
+ }
1748
+ if (line.length > 4096)
1749
+ malformed();
1750
+ });
1751
+ child.stderr?.on("data", () => {});
1752
+ child.stdin?.on("error", () => {
1753
+ bad = true;
1754
+ });
1755
+ child.once("error", () => {
1756
+ bad = true;
1757
+ launchFailed = child.pid === undefined;
1758
+ });
1759
+ child.once("close", (code) => finish(launchFailed ? {
1760
+ ok: false,
1761
+ state: "paused",
1762
+ code: "isolation-backend-launch-failed"
1763
+ } : bad || line.length ? uncertain() : classify(final, code, output, input.signal?.aborted === true)));
1764
+ armDeadline(timeoutMs + SHUTDOWN_MS + 1e4);
1765
+ input.signal?.addEventListener("abort", cancel, { once: true });
1766
+ child.stdin?.write(wire);
1767
+ if (input.signal?.aborted)
1768
+ cancel();
1769
+ });
1770
+ }
1771
+ }
1772
+ function probeWindowsJob(helper) {
1773
+ if (process.platform !== "win32" || process.arch !== "x64" || !verifyWindowsJobHelper(helper))
1774
+ return false;
1775
+ const wire = encodeWindowsJobRequest({
1776
+ root: process.cwd(),
1777
+ executable: process.execPath,
1778
+ args: ["-e", "process.exit(0)"],
1779
+ environment: { SystemRoot: process.env.SystemRoot ?? "C:\\Windows" }
1780
+ }, 5000);
1781
+ const result = spawnSync(helper, ["--probe"], {
1782
+ input: wire,
1783
+ shell: false,
1784
+ encoding: "utf8",
1785
+ timeout: 15000,
1786
+ maxBuffer: 65536
1787
+ });
1788
+ try {
1789
+ const v = observation(JSON.parse(result.stdout.trim()));
1790
+ return !result.error && result.status === 0 && v?.outcome === "complete" && v.exitCode === 0;
1791
+ } catch {
1792
+ return false;
1793
+ }
1794
+ }
1795
+
1241
1796
  // src/operator-tools.ts
1242
1797
  var PACKAGE_LOCKS = {
1243
1798
  bun: ["bun.lock", "bun.lockb"],
@@ -1247,7 +1802,7 @@ var PACKAGE_LOCKS = {
1247
1802
  };
1248
1803
  function packageManagerSelection(root, declaration) {
1249
1804
  const managers = Object.keys(PACKAGE_LOCKS);
1250
- const lockfiles = managers.flatMap((manager) => PACKAGE_LOCKS[manager].filter((file) => lstatSync6(path5.join(root, file), { throwIfNoEntry: false })?.isFile()));
1805
+ const lockfiles = managers.flatMap((manager) => PACKAGE_LOCKS[manager].filter((file) => lstatSync8(path8.join(root, file), { throwIfNoEntry: false })?.isFile()));
1251
1806
  const inferred = managers.filter((manager) => PACKAGE_LOCKS[manager].some((file) => lockfiles.includes(file)));
1252
1807
  const declared = typeof declaration === "string" ? managers.find((manager) => new RegExp(`^${manager}@[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][a-z0-9._+-]+)?$`, "iu").test(declaration)) : undefined;
1253
1808
  const selectedPackageManager = declaration !== undefined ? declared ?? null : inferred.length === 1 ? inferred[0] ?? null : inferred.length > 1 ? null : "npm";
@@ -1280,7 +1835,7 @@ function inspectConsumer(root) {
1280
1835
  return process.versions.node;
1281
1836
  }, "Install Node >=22.13.");
1282
1837
  check("git", () => {
1283
- const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
1838
+ const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
1284
1839
  cwd: root,
1285
1840
  encoding: "utf8",
1286
1841
  timeout: 5000,
@@ -1296,9 +1851,14 @@ function inspectConsumer(root) {
1296
1851
  }, "Install @fission-ai/openspec@1.5.0, or configure ABEL_OPENSPEC_PACKAGE_ROOT / ABEL_OPENSPEC_NODE before launching the host.");
1297
1852
  check("execution", () => {
1298
1853
  const profile = executionProfile();
1854
+ if (profile.mode === "host-trusted") {
1855
+ if (!probeWindowsJob(profile.windowsHelperPath ?? ""))
1856
+ throw new Error("windows-job-backend-unavailable");
1857
+ return "host-trusted: Windows x64 Job probe passed; host files/network accessible";
1858
+ }
1299
1859
  if (process.platform !== "linux")
1300
1860
  throw new Error("linux-pid-isolation-required");
1301
- const result = spawnSync(profile.bwrapPath, [
1861
+ const result = spawnSync2(profile.bwrapPath, [
1302
1862
  "--ro-bind",
1303
1863
  "/",
1304
1864
  "/",
@@ -1309,45 +1869,45 @@ function inspectConsumer(root) {
1309
1869
  if (result.status !== 0)
1310
1870
  throw new Error("bubblewrap-probe-failed");
1311
1871
  return `${profile.mode}: Bubblewrap PID namespace probe passed`;
1312
- }, "On Linux install/enable Bubblewrap; set ABEL_BWRAP_PATH for a custom install. Trusted Linux projects may explicitly set ABEL_EXECUTION_MODE=local-trusted; Windows should use WSL.");
1872
+ }, "On Linux install/enable Bubblewrap; set ABEL_BWRAP_PATH for a custom install. Trusted Linux projects may explicitly set ABEL_EXECUTION_MODE=local-trusted; Windows x64 trusted projects may explicitly select ABEL_EXECUTION_MODE=host-trusted and install the matching native Job helper (ABEL_WINDOWS_JOB_HELPER).");
1313
1873
  check("request-budgets", () => JSON.stringify(requestBounds()), "Set ABEL_FIRST_PROGRESS_MS and ABEL_STREAM_IDLE_MS to positive milliseconds <=1200000.");
1314
1874
  check("state-root", () => resolveStateRoot({
1315
1875
  consumerRoot: root,
1316
1876
  xdgStateHome: process.env.XDG_STATE_HOME
1317
1877
  }).rootDir, "Use an absolute repository-external XDG_STATE_HOME.");
1318
1878
  check("workspace-dependencies", () => {
1319
- const dependencies = path5.join(root, "node_modules");
1320
- if (!lstatSync6(dependencies, { throwIfNoEntry: false }))
1879
+ const dependencies = path8.join(root, "node_modules");
1880
+ if (!lstatSync8(dependencies, { throwIfNoEntry: false }))
1321
1881
  return "no node_modules; prepare dependencies before verification";
1322
- const names = readdirSync4(dependencies).flatMap((name) => name.startsWith("@") && lstatSync6(path5.join(dependencies, name)).isDirectory() ? readdirSync4(path5.join(dependencies, name)).map((child) => `${name}/${child}`) : [name]);
1882
+ const names = readdirSync4(dependencies).flatMap((name) => name.startsWith("@") && lstatSync8(path8.join(dependencies, name)).isDirectory() ? readdirSync4(path8.join(dependencies, name)).map((child) => `${name}/${child}`) : [name]);
1323
1883
  if (names.length > 20000)
1324
1884
  throw new Error("dependency-view-too-large");
1325
1885
  let links = 0;
1326
1886
  for (const name of names) {
1327
- const file = path5.join(dependencies, name);
1328
- if (!lstatSync6(file).isSymbolicLink())
1887
+ const file = path8.join(dependencies, name);
1888
+ if (!lstatSync8(file).isSymbolicLink())
1329
1889
  continue;
1330
1890
  const resolved = realpathSync4(file);
1331
- const relative = path5.relative(root, resolved);
1332
- if (relative === ".." || relative.startsWith(`..${path5.sep}`) || path5.isAbsolute(relative))
1891
+ const relative = path8.relative(root, resolved);
1892
+ if (relative === ".." || relative.startsWith(`..${path8.sep}`) || path8.isAbsolute(relative))
1333
1893
  throw new Error("dependency-link-outside-consumer");
1334
- if (!resolved.startsWith(`${dependencies}${path5.sep}`))
1894
+ if (!resolved.startsWith(`${dependencies}${path8.sep}`))
1335
1895
  links++;
1336
1896
  }
1337
1897
  return `${links} repository workspace link(s); their targets must be included in the candidate baseline`;
1338
1898
  }, "Repair dangling/external workspace links and ensure local package sources are tracked or admitted in the candidate.");
1339
- const manifest = path5.join(root, "package.json");
1899
+ const manifest = path8.join(root, "package.json");
1340
1900
  let selection = packageManagerSelection(root, undefined);
1341
- if (lstatSync6(manifest, { throwIfNoEntry: false })?.isFile()) {
1901
+ if (lstatSync8(manifest, { throwIfNoEntry: false })?.isFile()) {
1342
1902
  check("manifest", () => {
1343
- const parsed2 = JSON.parse(readFileSync4(manifest, "utf8"));
1903
+ const parsed2 = JSON.parse(readFileSync5(manifest, "utf8"));
1344
1904
  if (!parsed2 || typeof parsed2 !== "object")
1345
1905
  throw new Error("manifest-invalid");
1346
1906
  return Object.keys(parsed2.scripts ?? {}).join(", ") || "no package scripts";
1347
1907
  }, "Repair package.json.");
1348
1908
  const parsed = (() => {
1349
1909
  try {
1350
- const value = JSON.parse(readFileSync4(manifest, "utf8"));
1910
+ const value = JSON.parse(readFileSync5(manifest, "utf8"));
1351
1911
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
1352
1912
  } catch {
1353
1913
  return {};
@@ -1390,7 +1950,7 @@ function storageUsage(root, maximum = 1e5) {
1390
1950
  truncated = true;
1391
1951
  return;
1392
1952
  }
1393
- const stat = lstatSync6(file, { throwIfNoEntry: false });
1953
+ const stat = lstatSync8(file, { throwIfNoEntry: false });
1394
1954
  if (!stat)
1395
1955
  return;
1396
1956
  entries++;
@@ -1398,7 +1958,7 @@ function storageUsage(root, maximum = 1e5) {
1398
1958
  bytes += stat.size;
1399
1959
  else if (stat.isDirectory())
1400
1960
  for (const name of readdirSync4(file)) {
1401
- visit(path5.join(file, name));
1961
+ visit(path8.join(file, name));
1402
1962
  if (truncated)
1403
1963
  break;
1404
1964
  }
@@ -1412,7 +1972,7 @@ function inspectRuns(root) {
1412
1972
  xdgStateHome: process.env.XDG_STATE_HOME
1413
1973
  });
1414
1974
  const usage = storageUsage(state.rootDir);
1415
- if (!lstatSync6(state.databasePath, { throwIfNoEntry: false }))
1975
+ if (!lstatSync8(state.databasePath, { throwIfNoEntry: false }))
1416
1976
  return { stateRoot: state.rootDir, usage, runs: [], truncated: false };
1417
1977
  const db = new DatabaseSync(state.databasePath, { readOnly: true });
1418
1978
  try {
@@ -1433,7 +1993,7 @@ var [command = "doctor", root = process.cwd()] = process.argv.slice(2);
1433
1993
  try {
1434
1994
  if (!["doctor", "runs"].includes(command))
1435
1995
  throw new Error("Usage: node --experimental-strip-types <cadence>/src/operator-cli.mjs doctor|runs [consumer-root]");
1436
- const result = command === "doctor" ? inspectConsumer(path6.resolve(root)) : inspectRuns(path6.resolve(root));
1996
+ const result = command === "doctor" ? inspectConsumer(path9.resolve(root)) : inspectRuns(path9.resolve(root));
1437
1997
  console.log(JSON.stringify(result, null, 2));
1438
1998
  if (command === "doctor" && !result.ok)
1439
1999
  process.exitCode = 1;