@abelxiaoxing/cadence 1.7.0 → 1.8.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.
@@ -7,6 +7,7 @@ import { resolveOpenSpecInvocation } from "./openspec-cli.ts";
7
7
  import { resolveStateRoot } from "./state-root.ts";
8
8
  import { requestBounds } from "./transport-budget.ts";
9
9
  import { validateVerificationAdapterCapability } from "./verification-capability.ts";
10
+ import { probeWindowsJob } from "./windows-job-backend.ts";
10
11
 
11
12
  export interface DoctorCheck {
12
13
  name: string;
@@ -121,6 +122,11 @@ export function inspectConsumer(root: string) {
121
122
  "execution",
122
123
  () => {
123
124
  const profile = executionProfile();
125
+ if (profile.mode === "host-trusted") {
126
+ if (!probeWindowsJob(profile.windowsHelperPath ?? ""))
127
+ throw new Error("windows-job-backend-unavailable");
128
+ return "host-trusted: Windows x64 Job probe passed; host files/network accessible";
129
+ }
124
130
  if (process.platform !== "linux")
125
131
  throw new Error("linux-pid-isolation-required");
126
132
 
@@ -139,7 +145,7 @@ export function inspectConsumer(root: string) {
139
145
  if (result.status !== 0) throw new Error("bubblewrap-probe-failed");
140
146
  return `${profile.mode}: Bubblewrap PID namespace probe passed`;
141
147
  },
142
- "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.",
148
+ "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).",
143
149
  );
144
150
  check(
145
151
  "request-budgets",
@@ -15,6 +15,7 @@ import {
15
15
  import path from "node:path";
16
16
  import { DatabaseSync } from "node:sqlite";
17
17
  import { syncDirectory } from "./directory-sync.ts";
18
+ import { assertExecutionsSettled } from "./execution-retention.ts";
18
19
  import { RunStore } from "./run-store.ts";
19
20
  import { prepareStateRoot, type ResolvedStateRoot } from "./state-root.ts";
20
21
 
@@ -160,6 +161,16 @@ export function acquirePackageState(
160
161
  ): { notice?: PackageStateNotice; close(): void } {
161
162
  versionParts(version);
162
163
  const state = prepareStateRoot(unresolved);
164
+ try {
165
+ assertExecutionsSettled(state.rootDir);
166
+ } catch (error) {
167
+ if (
168
+ error instanceof Error &&
169
+ error.message === "isolation-termination-unconfirmed"
170
+ )
171
+ fail(error.message);
172
+ throw error;
173
+ }
163
174
  for (const suffix of ["-wal", "-shm", "-journal"])
164
175
  regular(state.databasePath + suffix);
165
176
  const coordinator = path.join(
@@ -9,6 +9,7 @@ import {
9
9
  verificationSteps,
10
10
  } from "./contracts.ts";
11
11
  import { executionProfile } from "./execution-profile.ts";
12
+ import { retainExecution } from "./execution-retention.ts";
12
13
  import { canonicalJson } from "./implement-graph.ts";
13
14
  import { BubblewrapIsolationBackend } from "./isolation-backend.ts";
14
15
  import { observeSafePath } from "./safe-path.ts";
@@ -19,6 +20,7 @@ import {
19
20
  } from "./verification-capability.ts";
20
21
  import { diagnosticText } from "./verification-diagnostics.ts";
21
22
  import { prepareVerificationEnvironment } from "./verification-environment.ts";
23
+ import { WindowsJobBackend } from "./windows-job-backend.ts";
22
24
 
23
25
  function sha256(value: Uint8Array | string): string {
24
26
  return createHash("sha256").update(value).digest("hex");
@@ -311,6 +313,7 @@ function readReport(root: string, relative: string, maximum: number): string {
311
313
  export async function executePackageVerification(input: {
312
314
  root: string;
313
315
  dependencyOwner: string;
316
+ executionOwnerRoot?: string;
314
317
  verification: StructuredVerificationContract;
315
318
  signal: AbortSignal;
316
319
  maxReportBytes?: number;
@@ -376,6 +379,7 @@ export async function executePackageVerification(input: {
376
379
  "workspace-dependency-missing",
377
380
  "dependency-path-unsafe",
378
381
  "execution-mode-invalid",
382
+ "windows-job-helper-path-invalid",
379
383
  "verification-timeout-invalid",
380
384
  "verification-environment-names-invalid",
381
385
  "verification-environment-requires-trusted-mode",
@@ -384,13 +388,20 @@ export async function executePackageVerification(input: {
384
388
  : "verification-environment-unavailable";
385
389
  return unavailable(code, "environment");
386
390
  }
391
+ let retained = false;
387
392
  try {
388
393
  const profile = executionProfile();
389
- const isolation = new BubblewrapIsolationBackend({
390
- bwrapPath: profile.bwrapPath,
391
- timeoutMs: profile.timeoutMs,
392
- localTrusted: profile.mode === "local-trusted",
393
- });
394
+ const isolation =
395
+ profile.mode === "host-trusted"
396
+ ? new WindowsJobBackend({
397
+ helperPath: profile.windowsHelperPath ?? "",
398
+ timeoutMs: profile.timeoutMs,
399
+ })
400
+ : new BubblewrapIsolationBackend({
401
+ bwrapPath: profile.bwrapPath,
402
+ timeoutMs: profile.timeoutMs,
403
+ localTrusted: profile.mode === "local-trusted",
404
+ });
394
405
  let final: VerificationObservation | undefined;
395
406
  let sequence = 0;
396
407
  for (const step of verificationSteps(input.verification)) {
@@ -401,7 +412,7 @@ export async function executePackageVerification(input: {
401
412
  `${environment.reportRoot}/${relative}`,
402
413
  );
403
414
  if (!invocation) return unavailable("runner-missing");
404
- if (profile.mode === "local-trusted") {
415
+ if (profile.mode !== "isolated") {
405
416
  const translate = (value: string) =>
406
417
  value.startsWith("/workspace/")
407
418
  ? path.join(input.root, value.slice(11))
@@ -409,16 +420,37 @@ export async function executePackageVerification(input: {
409
420
  invocation.executable = translate(invocation.executable);
410
421
  invocation.args = invocation.args.map(translate);
411
422
  }
412
- const executed = await isolation.run({
413
- root: input.root,
414
- ...invocation,
415
- mounts: environment.mounts,
416
- environment: environment.environment,
417
- ...(step.kind !== "vitest" && step.classification === "expected-red"
418
- ? { outputWitness: step.expectedFailure }
419
- : {}),
420
- signal: input.signal,
421
- });
423
+ const lease =
424
+ profile.mode === "host-trusted"
425
+ ? retainExecution(
426
+ input.executionOwnerRoot ?? path.dirname(input.root),
427
+ [input.root, environment.privateRoot],
428
+ )
429
+ : undefined;
430
+ let executed: Awaited<ReturnType<typeof isolation.run>>;
431
+ try {
432
+ executed = await isolation.run({
433
+ root: input.root,
434
+ ...invocation,
435
+ mounts: profile.mode === "host-trusted" ? [] : environment.mounts,
436
+ environment: environment.environment,
437
+ ...(step.kind !== "vitest" && step.classification === "expected-red"
438
+ ? { outputWitness: step.expectedFailure }
439
+ : {}),
440
+ signal: input.signal,
441
+ });
442
+ if (
443
+ !executed.ok &&
444
+ executed.code === "isolation-termination-unconfirmed"
445
+ ) {
446
+ retained = true;
447
+ lease?.uncertain();
448
+ } else lease?.settled();
449
+ } catch (error) {
450
+ retained = !!lease;
451
+ lease?.uncertain();
452
+ throw error;
453
+ }
422
454
  if (!executed.ok) {
423
455
  if (executed.state === "cancelled") return { kind: "cancelled" };
424
456
  return unavailable(
@@ -534,7 +566,7 @@ export async function executePackageVerification(input: {
534
566
  }
535
567
  return final ?? unavailable("verification-contract-unsupported");
536
568
  } finally {
537
- await environment.cleanup();
569
+ if (!retained) await environment.cleanup();
538
570
  }
539
571
  }
540
572
 
@@ -160,6 +160,7 @@ function openPreparedPackageWorkflowService(
160
160
  const workHardLimit = hostLimit("ABEL_WORK_MAX_UNITS", 512);
161
161
  const engine = openDurableWorkflowEngine({
162
162
  workHardLimit,
163
+ awaitPostApplySettlement: true,
163
164
  verificationPolicy: `report-file-v5:${JSON.stringify(executionProfile())}`,
164
165
  verificationEnvironment: coalesceVerificationScans(
165
166
  (plan: import("./implement-plan.ts").ImplementPlan, signal) =>
@@ -210,6 +211,7 @@ function openPreparedPackageWorkflowService(
210
211
  executionWritePaths: input.executionWritePaths,
211
212
  root: input.root,
212
213
  dependencyOwner: consumerRoot,
214
+ executionOwnerRoot: stateRoot.rootDir,
213
215
  verification: input.verification,
214
216
  signal: input.signal,
215
217
  }),
@@ -230,6 +232,7 @@ function openPreparedPackageWorkflowService(
230
232
  ),
231
233
  root: input.root,
232
234
  dependencyOwner: consumerRoot,
235
+ executionOwnerRoot: stateRoot.rootDir,
233
236
  verification,
234
237
  signal: input.signal,
235
238
  });
@@ -1,12 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdtempSync, rmSync } from "node:fs";
2
+ import { mkdtempSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import {
5
5
  inspectCandidateArtifact,
6
6
  sealCandidateArtifact,
7
7
  } from "./candidate-artifact.ts";
8
8
  import { revisionChanges, runGitApply } from "./candidate-workspace.ts";
9
-
10
9
  import type { StructuredVerificationContract } from "./contracts.ts";
11
10
  import type { PlanTaskDraft } from "./delivery-compiler.ts";
12
11
  import type {
@@ -20,6 +19,8 @@ import type {
20
19
  DurableWorkflowEngineOptions,
21
20
  PhaseResources,
22
21
  } from "./durable-contracts.ts";
22
+ import { isExecutionRetained } from "./execution-retention.ts";
23
+ import { removePathSync } from "./remove-path.ts";
23
24
  import type {
24
25
  BeginCandidateInput,
25
26
  TaskLedger,
@@ -308,7 +309,8 @@ export class PhaseExecution {
308
309
  policy: this.#options.verificationPolicy ?? "legacy",
309
310
  });
310
311
  } finally {
311
- rmSync(root, { recursive: true, force: true });
312
+ if (!isExecutionRetained(root))
313
+ removePathSync(root, { recursive: true, force: true });
312
314
  }
313
315
  }
314
316
  return undefined;
@@ -980,7 +982,8 @@ export class PhaseExecution {
980
982
  },
981
983
  };
982
984
  } finally {
983
- rmSync(proposalRoot, { recursive: true, force: true });
985
+ if (!isExecutionRetained(proposalRoot))
986
+ removePathSync(proposalRoot, { recursive: true, force: true });
984
987
  }
985
988
  }
986
989
 
@@ -1165,7 +1168,8 @@ export class PhaseExecution {
1165
1168
  signal: request.signal,
1166
1169
  });
1167
1170
  } finally {
1168
- rmSync(root, { recursive: true, force: true });
1171
+ if (!isExecutionRetained(root))
1172
+ removePathSync(root, { recursive: true, force: true });
1169
1173
  }
1170
1174
  };
1171
1175
  emitWorkflowActivity(request.onActivity, { state: "verifying" });
@@ -2248,7 +2252,8 @@ export class PhaseExecution {
2248
2252
  baselineRevisionId: resources.baselineRevisionId,
2249
2253
  });
2250
2254
  } finally {
2251
- rmSync(proposalRoot, { recursive: true, force: true });
2255
+ if (!isExecutionRetained(proposalRoot))
2256
+ removePathSync(proposalRoot, { recursive: true, force: true });
2252
2257
  }
2253
2258
  }
2254
2259
  }
@@ -0,0 +1,32 @@
1
+ import { lstatSync, readdirSync, rmdirSync, rmSync, unlinkSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ /** Remove owned paths without following directory links. Node 24.13's Windows
5
+ * rmSync uses narrow std::filesystem paths and can silently miss Unicode names.
6
+ * The libuv-backed unlink/rmdir APIs preserve the original UTF-16 path. */
7
+ export function removePathSync(
8
+ target: string,
9
+ options: { recursive?: boolean; force?: boolean } = {},
10
+ ): void {
11
+ if (process.platform !== "win32") {
12
+ rmSync(target, options);
13
+ return;
14
+ }
15
+ let stat: import("node:fs").Stats;
16
+ try {
17
+ stat = lstatSync(target);
18
+ } catch (error) {
19
+ if (options.force && (error as NodeJS.ErrnoException).code === "ENOENT")
20
+ return;
21
+ throw error;
22
+ }
23
+ if (stat.isDirectory() && !stat.isSymbolicLink()) {
24
+ if (!options.recursive)
25
+ throw Object.assign(new Error("Path is a directory"), {
26
+ code: "ERR_FS_EISDIR",
27
+ });
28
+ for (const name of readdirSync(target))
29
+ removePathSync(path.join(target, name), options);
30
+ rmdirSync(target);
31
+ } else unlinkSync(target);
32
+ }
@@ -838,7 +838,7 @@ function validateAtomicCapability(
838
838
  continue;
839
839
  if (
840
840
  lstatSync(path.join(root, relative)).size > 64 * 1024 ||
841
- (executionProfile().mode !== "local-trusted" &&
841
+ (executionProfile().mode === "isolated" &&
842
842
  unsupportedExecutionConfiguration(
843
843
  readFileSync(path.join(root, relative), "utf8"),
844
844
  ))
@@ -4,14 +4,15 @@ import {
4
4
  lstatSync,
5
5
  mkdirSync,
6
6
  readdirSync,
7
+ readlinkSync,
7
8
  realpathSync,
8
- rmSync,
9
9
  symlinkSync,
10
10
  writeFileSync,
11
11
  } from "node:fs";
12
12
  import path from "node:path";
13
13
  import type { ExecutionProfile } from "./execution-profile.ts";
14
14
  import type { IsolationMount } from "./isolation-backend.ts";
15
+ import { removePathSync } from "./remove-path.ts";
15
16
  import type { VerificationRunnerBinding } from "./verification-capability.ts";
16
17
 
17
18
  const CACHES = [".vite", ".vite-temp"];
@@ -34,9 +35,11 @@ export function prepareVerificationEnvironmentIo(
34
35
  checkCancelled: () => void,
35
36
  privateRoot: string,
36
37
  ) {
37
- const cleanup = () => rmSync(privateRoot, { recursive: true, force: true });
38
+ const cleanup = () =>
39
+ removePathSync(privateRoot, { recursive: true, force: true });
38
40
  try {
39
- const local = profile.mode === "local-trusted";
41
+ const local = profile.mode !== "isolated";
42
+ const native = profile.mode === "host-trusted";
40
43
  if (local && realpathSync(root) === realpathSync(dependencyOwner))
41
44
  throw new Error("trusted-candidate-required");
42
45
  const mounts: IsolationMount[] = [
@@ -51,11 +54,19 @@ export function prepareVerificationEnvironmentIo(
51
54
  ]) {
52
55
  mkdirSync(path.join(privateRoot, directory), { mode: 0o700 });
53
56
  }
57
+ if (native) {
58
+ mkdirSync(path.join(privateRoot, "home/AppData/Roaming"), {
59
+ recursive: true,
60
+ });
61
+ mkdirSync(path.join(privateRoot, "home/AppData/Local"), {
62
+ recursive: true,
63
+ });
64
+ }
54
65
  writeFileSync(path.join(privateRoot, "home/user.npmrc"), "");
55
66
  writeFileSync(path.join(privateRoot, "home/global.npmrc"), "");
56
67
  const dependencies = path.join(dependencyOwner, "node_modules");
57
68
  const stat = lstatSync(dependencies, { throwIfNoEntry: false });
58
- if (stat) {
69
+ if (stat && !native) {
59
70
  const target = path.join(root, "node_modules");
60
71
  const targetStat = lstatSync(target, { throwIfNoEntry: false });
61
72
  if (
@@ -133,7 +144,7 @@ export function prepareVerificationEnvironmentIo(
133
144
  });
134
145
  }
135
146
  }
136
- if (local && stat) {
147
+ if (local && stat && !native) {
137
148
  // Private copies keep package caches/writes away from consumer dependencies.
138
149
  const copyEntry = (name: string) => {
139
150
  checkCancelled();
@@ -177,8 +188,77 @@ export function prepareVerificationEnvironmentIo(
177
188
  copyEntry(name);
178
189
  }
179
190
 
191
+ if (native && stat) {
192
+ const target = path.join(root, "node_modules");
193
+ const targetStat = lstatSync(target, { throwIfNoEntry: false });
194
+ if (
195
+ !stat.isDirectory() ||
196
+ stat.isSymbolicLink() ||
197
+ (targetStat &&
198
+ (!targetStat.isDirectory() || targetStat.isSymbolicLink()))
199
+ )
200
+ throw new Error("dependency-path-unsafe");
201
+ const owner = realpathSync(dependencyOwner);
202
+ let count = 0;
203
+ const copy = (source: string, destination: string) => {
204
+ checkCancelled();
205
+ if (++count > 500_000) throw new Error("dependency-view-too-large");
206
+ const entry = lstatSync(source);
207
+ if (entry.isSymbolicLink()) {
208
+ const resolved = realpathSync(source);
209
+ if (!within(owner, resolved))
210
+ throw new Error("dependency-path-unsafe");
211
+ const candidate = path.join(root, path.relative(owner, resolved));
212
+ if (
213
+ !within(realpathSync(dependencies), resolved) &&
214
+ !lstatSync(candidate, { throwIfNoEntry: false })
215
+ )
216
+ throw new Error("workspace-dependency-missing");
217
+ const linked = lstatSync(resolved);
218
+ if (linked.isDirectory()) {
219
+ // Junctions do not require Windows Developer Mode/admin privileges.
220
+ symlinkSync(
221
+ candidate,
222
+ destination,
223
+ process.platform === "win32" ? "junction" : "dir",
224
+ );
225
+ } else if (linked.isFile()) {
226
+ const file = within(realpathSync(dependencies), resolved)
227
+ ? resolved
228
+ : candidate;
229
+ cpSync(file, destination, { mode: constants.COPYFILE_FICLONE });
230
+ } else throw new Error("dependency-path-unsafe");
231
+ } else if (entry.isDirectory()) {
232
+ mkdirSync(destination, { recursive: true });
233
+ for (const name of readdirSync(source)) {
234
+ if (source === dependencies && CACHES.includes(name)) {
235
+ mkdirSync(path.join(destination, name));
236
+ continue;
237
+ }
238
+ copy(path.join(source, name), path.join(destination, name));
239
+ }
240
+ } else if (entry.isFile())
241
+ cpSync(source, destination, { mode: constants.COPYFILE_FICLONE });
242
+ else throw new Error("dependency-path-unsafe");
243
+ };
244
+ removeEntry(target, checkCancelled);
245
+ copy(dependencies, target);
246
+ }
180
247
  const sources = new Map<string, string>();
181
248
  const bindings = runnerBindings.map((binding) => {
249
+ if (native) {
250
+ const translate = (value: string) =>
251
+ path.isAbsolute(value) && within(dependencyOwner, value)
252
+ ? path.join(root, path.relative(dependencyOwner, value))
253
+ : value;
254
+ return {
255
+ ...binding,
256
+ executablePath: translate(binding.executablePath),
257
+ ...(binding.fixedArgs
258
+ ? { fixedArgs: binding.fixedArgs.map(translate) }
259
+ : {}),
260
+ };
261
+ }
182
262
  if (local || !binding.mountSource) return { ...binding };
183
263
  const source = path.resolve(binding.mountSource);
184
264
  const stat = lstatSync(source);
@@ -200,7 +280,15 @@ export function prepareVerificationEnvironmentIo(
200
280
  path.posix.join(target, ...path.relative(source, file).split(path.sep));
201
281
  return {
202
282
  ...binding,
203
- executablePath: translate(executable),
283
+ // Preserve an installation's relative launcher link (e.g. bin/npm).
284
+ // Exposing lib/node_modules/npm/bin on PATH selects npm's Windows
285
+ // installer shell shim during a nested `npm run` on Linux.
286
+ executablePath: translate(
287
+ lstatSync(binding.executablePath).isSymbolicLink() &&
288
+ !path.isAbsolute(readlinkSync(binding.executablePath))
289
+ ? binding.executablePath
290
+ : executable,
291
+ ),
204
292
  ...(binding.fixedArgs
205
293
  ? {
206
294
  fixedArgs: binding.fixedArgs.map((arg) =>
@@ -215,7 +303,7 @@ export function prepareVerificationEnvironmentIo(
215
303
  checkCancelled();
216
304
  return {
217
305
  privateRoot,
218
- mounts,
306
+ mounts: native ? [] : mounts,
219
307
  bindings,
220
308
  reportRoot: local ? privateRoot : "/cadence",
221
309
  environment: {
@@ -228,6 +316,24 @@ export function prepareVerificationEnvironmentIo(
228
316
  ),
229
317
  )
230
318
  : {}),
319
+ ...(native
320
+ ? Object.fromEntries(
321
+ Object.entries(environment).filter(([key]) =>
322
+ ["SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT"].includes(
323
+ key.toUpperCase(),
324
+ ),
325
+ ),
326
+ )
327
+ : {}),
328
+ ...(native
329
+ ? {
330
+ USERPROFILE: path.join(privateRoot, "home"),
331
+ APPDATA: path.join(privateRoot, "home", "AppData", "Roaming"),
332
+ LOCALAPPDATA: path.join(privateRoot, "home", "AppData", "Local"),
333
+ TEMP: path.join(privateRoot, "tmp"),
334
+ TMP: path.join(privateRoot, "tmp"),
335
+ }
336
+ : {}),
231
337
  CI: "1",
232
338
  HOME: local ? path.join(privateRoot, "home") : "/cadence/home",
233
339
  XDG_CACHE_HOME: local
@@ -249,13 +355,13 @@ export function prepareVerificationEnvironmentIo(
249
355
  [
250
356
  ...bindings.filter((binding) => binding.mountSource),
251
357
  ...bindings.filter((binding) => !binding.mountSource),
252
- ].map((binding) => path.posix.dirname(binding.executablePath)),
358
+ ].map((binding) =>
359
+ (local ? path : path.posix).dirname(binding.executablePath),
360
+ ),
253
361
  ),
254
362
  ...(local ? (environment.PATH ?? "").split(path.delimiter) : []),
255
- "/usr/local/bin",
256
- "/usr/bin",
257
- "/bin",
258
- ].join(":"),
363
+ ...(native ? [] : ["/usr/local/bin", "/usr/bin", "/bin"]),
364
+ ].join(local ? path.delimiter : ":"),
259
365
  },
260
366
  };
261
367
  } catch (error) {
@@ -273,8 +379,11 @@ function removeEntry(file: string, checkCancelled: () => void): void {
273
379
  checkCancelled();
274
380
  const stat = lstatSync(file, { throwIfNoEntry: false });
275
381
  if (!stat) return;
276
- if (stat.isDirectory())
382
+ if (stat.isDirectory() && !stat.isSymbolicLink())
277
383
  for (const name of readdirSync(file))
278
384
  removeEntry(path.join(file, name), checkCancelled);
279
- rmSync(file, { recursive: stat.isDirectory(), force: true });
385
+ removePathSync(file, {
386
+ recursive: stat.isDirectory() && !stat.isSymbolicLink(),
387
+ force: true,
388
+ });
280
389
  }
@@ -25,7 +25,18 @@ export async function captureVerificationEnvironmentIdentity(
25
25
  const roots = [path.join(dependencyOwner, "node_modules")];
26
26
  const unavailable: string[] = [];
27
27
  const profile = executionProfile();
28
- roots.push(path.resolve(profile.bwrapPath));
28
+ if (profile.mode === "host-trusted")
29
+ roots.push(
30
+ path.resolve(profile.windowsHelperPath ?? ""),
31
+ path.resolve(`${profile.windowsHelperPath}.json`),
32
+ );
33
+ else roots.push(path.resolve(profile.bwrapPath));
34
+ if (
35
+ profile.mode === "host-trusted" &&
36
+ process.env.ComSpec &&
37
+ path.isAbsolute(process.env.ComSpec)
38
+ )
39
+ roots.push(process.env.ComSpec);
29
40
  const node = resolveVerificationRunner("node");
30
41
  if (node) roots.push(realpathSync(node.executablePath));
31
42
  for (const verification of verifications) {
@@ -72,6 +83,18 @@ export async function captureVerificationEnvironmentIdentity(
72
83
  .update(installed)
73
84
  .update(JSON.stringify(unavailable.sort()))
74
85
  .update(JSON.stringify(profile))
86
+ .update(
87
+ profile.mode === "host-trusted"
88
+ ? JSON.stringify([
89
+ process.platform,
90
+ process.arch,
91
+ process.versions.node,
92
+ ["PATH", "SystemRoot", "WINDIR", "ComSpec", "PATHEXT"].map(
93
+ (name) => [name, process.env[name] ?? null],
94
+ ),
95
+ ])
96
+ : "",
97
+ )
75
98
  .update(
76
99
  JSON.stringify(
77
100
  profile.inheritEnvironment.map((name) => [
@@ -116,7 +139,13 @@ export async function prepareVerificationEnvironment(
116
139
  runnerBindings,
117
140
  profile,
118
141
  Object.fromEntries(
119
- ["PATH", ...profile.inheritEnvironment].flatMap((name) =>
142
+ [
143
+ "PATH",
144
+ ...(profile.mode === "host-trusted"
145
+ ? ["SystemRoot", "WINDIR", "ComSpec", "PATHEXT"]
146
+ : []),
147
+ ...profile.inheritEnvironment,
148
+ ].flatMap((name) =>
120
149
  process.env[name] === undefined ? [] : [[name, process.env[name]]],
121
150
  ),
122
151
  ),