@gfdelarue/testkit 0.3.4 → 0.3.6

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 (124) hide show
  1. package/README.md +11 -73
  2. package/dist/app/doctor/index.js +1 -1
  3. package/dist/app/inspect/artifacts.d.ts +1 -0
  4. package/dist/app/inspect/artifacts.js +1 -1
  5. package/dist/app/inspect/index.js +1 -1
  6. package/dist/app/lint/index.js +1 -1
  7. package/dist/app/lint/native.js +1 -1
  8. package/dist/app/lint/policy.js +1 -1
  9. package/dist/app/lint/rules/data.js +1 -1
  10. package/dist/app/lint/rules/engine-imports.js +1 -1
  11. package/dist/app/lint/rules/syntax.js +1 -1
  12. package/dist/app/lint/rules/unit.js +1 -1
  13. package/dist/app/lint/shared.d.ts +1 -1
  14. package/dist/app/lint/shared.js +1 -1
  15. package/dist/app/run/orchestrator.js +1 -1
  16. package/dist/app/run/request.js +1 -1
  17. package/dist/app/setup/playwright.js +1 -1
  18. package/dist/app/status/index.d.ts +2 -0
  19. package/dist/app/status/index.js +1 -1
  20. package/dist/app/status/runs.d.ts +2 -2
  21. package/dist/app/status/runs.js +1 -1
  22. package/dist/app/stop/index.js +1 -1
  23. package/dist/app/typecheck/index.d.ts +2 -0
  24. package/dist/app/typecheck/index.js +4 -4
  25. package/dist/cli/commands/artifacts.js +2 -2
  26. package/dist/cli/commands/db/schema/refresh.js +1 -1
  27. package/dist/cli/commands/db/schema/verify.js +1 -1
  28. package/dist/cli/commands/doctor.js +1 -1
  29. package/dist/cli/operations/db/schema/refresh/operation.d.ts +1 -0
  30. package/dist/cli/operations/db/schema/refresh/operation.js +1 -1
  31. package/dist/cli/operations/db/schema/verify/operation.d.ts +1 -0
  32. package/dist/cli/operations/db/schema/verify/operation.js +1 -1
  33. package/dist/cli/operations/run/operation.js +1 -1
  34. package/dist/config/loader/discover-tests.d.ts +5 -0
  35. package/dist/config/loader/discover-tests.js +1 -1
  36. package/dist/config/loader/discovery.js +2 -2
  37. package/dist/config/loader/file-metadata.js +1 -1
  38. package/dist/config/loader/index.d.ts +2 -1
  39. package/dist/config/loader/index.js +1 -1
  40. package/dist/config/loader/load.d.ts +2 -0
  41. package/dist/config/loader/load.js +1 -1
  42. package/dist/config/loader/managed-configs.d.ts +2 -0
  43. package/dist/config/loader/managed-configs.js +1 -1
  44. package/dist/config/loader/paths.d.ts +1 -1
  45. package/dist/config/loader/paths.js +1 -1
  46. package/dist/config/loader/skip-config.d.ts +1 -1
  47. package/dist/config/loader/test-selection.d.ts +15 -0
  48. package/dist/config/loader/test-selection.js +1 -0
  49. package/dist/database/index.d.ts +1 -3
  50. package/dist/database/index.js +1 -1
  51. package/dist/database/lifecycle-cleanup.d.ts +7 -0
  52. package/dist/database/lifecycle-cleanup.js +1 -0
  53. package/dist/database/local-postgres.js +1 -1
  54. package/dist/database/schema-source.js +3 -3
  55. package/dist/engines/adapters/evidence-files.js +1 -1
  56. package/dist/engines/catalog.d.ts +3 -8
  57. package/dist/engines/catalog.js +1 -1
  58. package/dist/engines/definitions.d.ts +8 -1
  59. package/dist/engines/definitions.js +1 -1
  60. package/dist/engines/generated-performance.d.ts +1 -0
  61. package/dist/engines/generated-performance.js +12 -0
  62. package/dist/engines/installations/node-vitest.d.ts +8 -22
  63. package/dist/engines/installations/node-vitest.js +1 -1
  64. package/dist/engines/installations/package-requirements.d.ts +28 -0
  65. package/dist/engines/installations/package-requirements.js +1 -0
  66. package/dist/engines/installations/playwright.d.ts +1 -11
  67. package/dist/engines/installations/playwright.js +1 -1
  68. package/dist/engines/k6/adapter/repository-configuration.d.ts +1 -1
  69. package/dist/engines/k6/adapter/repository-configuration.js +1 -1
  70. package/dist/engines/managed-module-resolution.js +111 -21
  71. package/dist/engines/manager.d.ts +1 -0
  72. package/dist/engines/manager.js +1 -1
  73. package/dist/engines/node-vitest/adapter/adapter.d.ts +1 -1
  74. package/dist/engines/node-vitest/adapter/adapter.js +1 -1
  75. package/dist/engines/node-vitest/adapter/consumer-typescript-paths.js +1 -1
  76. package/dist/engines/node-vitest/adapter/resolution.d.ts +1 -1
  77. package/dist/engines/node-vitest/adapter/resolution.js +1 -1
  78. package/dist/engines/node-vitest/adapter/workspace.js +33 -9
  79. package/dist/engines/node-vitest/runtime/runtime-input.d.ts +1 -0
  80. package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
  81. package/dist/engines/runtime-input-file.d.ts +3 -1
  82. package/dist/engines/runtime-input-file.js +1 -1
  83. package/dist/environment/lifecycle-controller.d.ts +1 -2
  84. package/dist/environment/lifecycle-controller.js +1 -1
  85. package/dist/environment/lifecycle.js +1 -1
  86. package/dist/environment/maintenance.d.ts +1 -1
  87. package/dist/environment/maintenance.js +1 -1
  88. package/dist/environment/ready-environment.d.ts +1 -1
  89. package/dist/environment/ready-environment.js +1 -1
  90. package/dist/environment/runtime-contexts.d.ts +1 -1
  91. package/dist/environment/runtime-contexts.js +1 -1
  92. package/dist/environment/runtime-preparation.js +1 -1
  93. package/dist/environment/services.js +1 -1
  94. package/dist/environment/template.d.ts +1 -1
  95. package/dist/environment/template.js +1 -1
  96. package/dist/evidence/bundles.js +1 -1
  97. package/dist/evidence/read-run.js +1 -1
  98. package/dist/evidence/reading.d.ts +1 -1
  99. package/dist/evidence/reading.js +1 -1
  100. package/dist/evidence/storage.js +2 -2
  101. package/dist/kernel/error-serialization.js +1 -1
  102. package/dist/kernel/work-metrics.d.ts +6 -0
  103. package/dist/kernel/work-metrics.js +1 -0
  104. package/dist/process/capture-output.js +1 -1
  105. package/dist/process/execa-bundle.json +1 -1
  106. package/dist/process/files/product-dir.d.ts +1 -0
  107. package/dist/process/files/product-dir.js +1 -0
  108. package/dist/process/locks.js +4 -4
  109. package/dist/process/readiness.js +1 -1
  110. package/dist/process/runs/inspection.d.ts +5 -0
  111. package/dist/process/runs/inspection.js +1 -0
  112. package/dist/process/runs/manifest.d.ts +1 -0
  113. package/dist/process/runs/manifest.js +1 -1
  114. package/dist/process/service-processes.d.ts +1 -1
  115. package/dist/process/service-processes.js +1 -1
  116. package/dist/toolchains/index.js +2 -2
  117. package/docs/cloud-control.md +3 -0
  118. package/docs/engine-architecture.md +4 -1
  119. package/docs/test-types.md +9 -0
  120. package/docs/testkit-cloud-boundary.md +12 -24
  121. package/package.json +2 -1
  122. package/testkit-config-schema.md +2 -0
  123. package/dist/cli/renderers/artifact-preview.d.ts +0 -4
  124. package/dist/cli/renderers/artifact-preview.js +0 -1
@@ -1,9 +1,10 @@
1
- import{inspectManagedNodeModuleResolutionPolicy as o}from"./managed-module-resolution-policy.js";export function createManagedNodeModuleResolution(e){const t=o(e),n={...t.direct};for(const[a,i]of Object.entries(t.packageExports)){const r=i.import??i.default??i.require;r!==null&&(n[a]=r)}return Object.freeze({aliases:Object.freeze(n),bootstrapSource:s(t),dependencyRoots:t.managedImporterRoots})}export function managedNodeModuleResolutionBootstrapSource(e){return createManagedNodeModuleResolution(e).bootstrapSource}function s(e){return`
1
+ import{ownedGeneratedScript as o}from"./generated-performance.js";import{inspectManagedNodeModuleResolutionPolicy as s}from"./managed-module-resolution-policy.js";export function createManagedNodeModuleResolution(e){const t=s(e),n={...t.direct};for(const[a,i]of Object.entries(t.packageExports)){const r=i.import??i.default??i.require;r!==null&&(n[a]=r)}return Object.freeze({aliases:Object.freeze(n),bootstrapSource:c(t),dependencyRoots:t.managedImporterRoots})}export function managedNodeModuleResolutionBootstrapSource(e){return createManagedNodeModuleResolution(e).bootstrapSource}function c(e){return o(`
2
2
  const fs = require("node:fs");
3
3
  const path = require("node:path");
4
- const { registerHooks } = require("node:module");
4
+ const { isBuiltin, registerHooks } = require("node:module");
5
5
  const { fileURLToPath, pathToFileURL } = require("node:url");
6
6
 
7
+ let canonicalPaths = null;
7
8
  const POLICY = Object.freeze(${JSON.stringify(e)});
8
9
  const CONSUMER_ROOTS = Object.freeze(
9
10
  POLICY.consumerRoots.map((root) => verifiedDirectory(root, "consumer root")),
@@ -15,6 +16,9 @@ const DIRECT = new Map(
15
16
  ]),
16
17
  );
17
18
  const PACKAGE_ROOTS = verifyPackageRoots(POLICY.packageRoots, "native");
19
+ const PRIVATE_NATIVE_SCOPES = Object.freeze(
20
+ POLICY.managedImporterRoots.filter((root) => !Object.values(PACKAGE_ROOTS).includes(root)),
21
+ );
18
22
  const DIRECT_BRIDGE_ROOTS = Object.freeze(
19
23
  POLICY.directBridgeRoots.map((root) =>
20
24
  verifiedDirectory(root, "direct bridge root"),
@@ -68,7 +72,30 @@ if (existing !== undefined) {
68
72
  // tools that register asynchronous loaders. Resolution is the complete
69
73
  // policy here, so do not install a load hook that has no policy to enforce.
70
74
  const hooks = registerHooks({
71
- resolve(specifier, context, nextResolve) {
75
+ resolve(specifier, context, nativeResolve) {
76
+ const nextResolve = (specifier, context) => {
77
+ canonicalPaths = new Map();
78
+ try { return __testkitWithOwner("external", "Native module resolution", () => nativeResolve(specifier, context)); }
79
+ finally { canonicalPaths = new Map(); }
80
+ };
81
+ return __testkitWithOwner("testkit", "Testkit managed resolution policy", () => {
82
+ const previousPaths = canonicalPaths;
83
+ canonicalPaths = new Map();
84
+ try {
85
+ // Builtins and physically native-internal file edges need no package graph scan.
86
+ // A downstream resolver may redirect either one, so inspect its result.
87
+ let nativeResult;
88
+ if (isBuiltin(specifier) && !DIRECT.has(specifier)) {
89
+ nativeResult = nextResolve(specifier, context);
90
+ if (isBuiltin(nativeResult.url)) return nativeResult;
91
+ }
92
+ const nativeOrigin = specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("file:") || path.isAbsolute(specifier)
93
+ ? nativeFileOrigin(specifier, context.parentURL) : null;
94
+ if (nativeOrigin !== null) {
95
+ nativeResult = nextResolve(specifier, context);
96
+ if (sameNativePackageEdge(nativeOrigin, context.parentURL, nativeResult.url))
97
+ return nativeResult;
98
+ }
72
99
  const conditions = normalizedConditions(context.conditions);
73
100
  const classified = classifyResolvedSpecifier(specifier, context.parentURL);
74
101
  requireAllowedResolvedSpecifier(classified);
@@ -106,7 +133,7 @@ if (existing !== undefined) {
106
133
  forbiddenPackage,
107
134
  );
108
135
  }
109
- const resolved = nextResolve(specifier, context);
136
+ const resolved = nativeResult ?? nextResolve(specifier, context);
110
137
  const resolvedClassification = classifyResolvedSpecifier(
111
138
  resolved.url,
112
139
  context.parentURL,
@@ -121,6 +148,8 @@ if (existing !== undefined) {
121
148
  if (redirected !== null) return redirected;
122
149
  }
123
150
  return resolved;
151
+ } finally { canonicalPaths = previousPaths; }
152
+ });
124
153
  },
125
154
  });
126
155
  Object.defineProperty(globalThis, STATE, {
@@ -131,6 +160,66 @@ if (existing !== undefined) {
131
160
  });
132
161
  }
133
162
 
163
+ function recordResolutionWork(metric) {
164
+ globalThis[Symbol.for("testkit.work.observer")]?.observer?.(metric, 1);
165
+ }
166
+
167
+ function observedRealpath(file) {
168
+ recordResolutionWork("resolver.realpath");
169
+ // Match policy inspection on Windows: native realpath expands 8.3 names and
170
+ // changes their spelling, mixing physical paths with the inspected roots.
171
+ return process.platform === "win32" ? fs.realpathSync(file) : fs.realpathSync.native(file);
172
+ }
173
+
174
+ function observedRead(file, encoding) {
175
+ recordResolutionWork("resolver.read");
176
+ return fs.readFileSync(file, encoding);
177
+ }
178
+
179
+ function observedExists(file) {
180
+ recordResolutionWork("resolver.exists");
181
+ return fs.existsSync(file);
182
+ }
183
+
184
+ function nativeFileOrigin(specifier, parentURL) {
185
+ const requestedPath = specifier.startsWith("file:") ? fileURLToPath(specifier) : path.isAbsolute(specifier) ? specifier : null;
186
+ // A lexical miss only skips this optimization; all trust uses fresh physical paths.
187
+ if (requestedPath !== null && ![...Object.values(PACKAGE_ROOTS), ...PRIVATE_NATIVE_SCOPES].some(root => isPathWithin(root, requestedPath))) return null;
188
+ if (typeof parentURL !== "string" || !parentURL.startsWith("file:")) return null;
189
+ try {
190
+ const file = observedRealpath(fileURLToPath(parentURL));
191
+ const root = nativePackageRoot(file);
192
+ if (root === undefined) return null;
193
+ if (requestedPath !== null) {
194
+ const requested = observedRealpath(requestedPath);
195
+ if (!isPathWithin(root, requested)) return null;
196
+ }
197
+ return { file, root };
198
+ } catch { return null; }
199
+ }
200
+
201
+ function nativePackageRoot(file) {
202
+ const selected = Object.values(PACKAGE_ROOTS).find((root) => isPathWithin(root, file));
203
+ if (selected !== undefined) return selected;
204
+ for (const scope of PRIVATE_NATIVE_SCOPES) {
205
+ if (!isPathWithin(scope, file)) continue;
206
+ const relative = path.relative(scope, file);
207
+ const separator = relative.indexOf(path.sep);
208
+ if (separator > 0) return path.join(scope, relative.slice(0, separator));
209
+ }
210
+ return undefined;
211
+ }
212
+
213
+ function sameNativePackageEdge(origin, parentURL, targetURL) {
214
+ // Trust never survives another resolver changing the importer or its root.
215
+ if (observedRealpath(fileURLToPath(parentURL)) !== origin.file ||
216
+ observedRealpath(origin.root) !== origin.root)
217
+ throw new Error("Managed " + POLICY.engineLabel + " native importer changed during resolution");
218
+ if (typeof targetURL !== "string" || !targetURL.startsWith("file:")) return false;
219
+ try { return isPathWithin(origin.root, observedRealpath(fileURLToPath(targetURL))); }
220
+ catch { return false; }
221
+ }
222
+
134
223
  function resolveManagedSpecifier(specifier, conditions, parentURL) {
135
224
  const direct = DIRECT.get(specifier);
136
225
  if (direct !== undefined)
@@ -185,9 +274,9 @@ function nearestPackageManifest(importerFile) {
185
274
  let directory = path.dirname(importerFile);
186
275
  while (true) {
187
276
  const manifestFile = path.join(directory, "package.json");
188
- if (fs.existsSync(manifestFile)) {
277
+ if (observedExists(manifestFile)) {
189
278
  try {
190
- const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
279
+ const manifest = JSON.parse(observedRead(manifestFile, "utf8"));
191
280
  if (isObject(manifest)) return manifest;
192
281
  } catch {}
193
282
  return null;
@@ -376,10 +465,7 @@ function unclassified() {
376
465
  function resolvedFileCandidates(file) {
377
466
  const resolved = path.resolve(file);
378
467
  const candidates = [];
379
- try {
380
- const canonical = fs.realpathSync(file);
381
- candidates.push(canonical);
382
- } catch {}
468
+ candidates.push(canonicalPath(file));
383
469
  if (!candidates.includes(resolved)) candidates.push(resolved);
384
470
  return candidates;
385
471
  }
@@ -391,8 +477,8 @@ function classifyPackageBoundaries(file, parentURL) {
391
477
  if (scanBoundary !== null && canonicalPath(directory) === scanBoundary)
392
478
  return null;
393
479
  const manifestFile = path.join(directory, "package.json");
394
- if (fs.existsSync(manifestFile)) {
395
- const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
480
+ if (observedExists(manifestFile)) {
481
+ const manifest = JSON.parse(observedRead(manifestFile, "utf8"));
396
482
  const packageName =
397
483
  typeof manifest.name === "string" ? manifest.name : null;
398
484
  if (packageName !== null && Object.hasOwn(PACKAGE_ROOTS, packageName))
@@ -637,7 +723,7 @@ function verifyPackageRoots(roots, kind) {
637
723
  Object.entries(roots).map(([packageName, root]) => {
638
724
  const canonical = canonicalPath(root);
639
725
  const manifest = JSON.parse(
640
- fs.readFileSync(path.join(canonical, "package.json"), "utf8"),
726
+ observedRead(path.join(canonical, "package.json"), "utf8"),
641
727
  );
642
728
  if (manifest.name !== packageName)
643
729
  throw new Error(
@@ -658,7 +744,7 @@ function verifiedOptionalFile(file, label) {
658
744
  }
659
745
 
660
746
  function verifiedDirectory(directory, label) {
661
- const resolved = fs.realpathSync(directory);
747
+ const resolved = observedRealpath(directory);
662
748
  if (!fs.statSync(resolved).isDirectory())
663
749
  throw new Error(
664
750
  "Managed " + POLICY.engineLabel + " " + label + " is not a directory",
@@ -667,7 +753,7 @@ function verifiedDirectory(directory, label) {
667
753
  }
668
754
 
669
755
  function verifiedFile(file, label) {
670
- const resolved = fs.realpathSync(file);
756
+ const resolved = observedRealpath(file);
671
757
  if (!fs.statSync(resolved).isFile())
672
758
  throw new Error(
673
759
  "Managed " + POLICY.engineLabel + " " + label + " is not a regular file",
@@ -676,11 +762,15 @@ function verifiedFile(file, label) {
676
762
  }
677
763
 
678
764
  function canonicalPath(file) {
679
- try {
680
- return fs.realpathSync(file);
681
- } catch {
682
- return path.resolve(file);
683
- }
765
+ // Share physical checks only within this policy decision. A later import or
766
+ // another resolver can change the filesystem and must see fresh identities.
767
+ const cached = canonicalPaths?.get(file);
768
+ if (cached !== undefined) return cached;
769
+ let canonical;
770
+ try { canonical = observedRealpath(file); }
771
+ catch { canonical = path.resolve(file); }
772
+ canonicalPaths?.set(file, canonical);
773
+ return canonical;
684
774
  }
685
775
 
686
776
  function sameFile(left, right) {
@@ -696,4 +786,4 @@ function isPathWithin(root, candidate) {
696
786
  !path.isAbsolute(relative))
697
787
  );
698
788
  }
699
- `}
789
+ `,"Testkit managed resolution bootstrap")}
@@ -18,6 +18,7 @@ export declare class EngineExecutorLoadError extends Error {
18
18
  export declare class EngineManager {
19
19
  private readonly catalog;
20
20
  private readonly productRoot;
21
+ private readonly installations;
21
22
  constructor(options: Readonly<EngineManagerOptions>);
22
23
  resolve<Id extends EngineId>(engineId: Id): InternalEngineRegistration<Id>;
23
24
  withEngine<Id extends EngineId, Result>(engineId: Id, signal: AbortSignal, operation: (session: ResolvedEngineExecutionSession<Id>) => Result | PromiseLike<Result>): Promise<Result>;
@@ -1 +1 @@
1
- import{ENGINE_PROTOCOL_LIMITS as d,beginEngineExecution as E,createEngineInvocationScope as g}from"./api/index.js";import"../domain/identifiers.js";export class EngineExecutorLoadError extends Error{code="TESTKIT_ENGINE_EXECUTOR_LOAD_FAILED";engineId;constructor(e,o){super(`Resolved ${e} executor could not be loaded`,o),this.name="EngineExecutorLoadError",this.engineId=e}}export class EngineManager{catalog;productRoot;constructor(e){this.catalog=e.catalog,this.productRoot=e.productRoot}resolve(e){return this.catalog.resolve(e)}async withEngine(e,o,r){const a=this.resolve(e);o.throwIfAborted();const c=await a.resolveInstallation({productRoot:this.productRoot,signal:o});return o.throwIfAborted(),r(h(a,c))}}function h(t,e){return Object.freeze({engineId:t.id,async execute(o){u(o.signal);const r=E(o),a=g({timeoutMs:d.cleanupTimeoutMs});let c=null,i,s=!1;try{const l=await w(t);u(o.signal),c=r.acceptResult(await l(r.request,e,a.scope))}catch(l){s=!0,i=l}finally{r.close()}const n=await a.drain();if(s)throw n.length===0?i:new AggregateError([i,...n],`Engine invocation failed and ${n.length.toString()} cleanup handler(s) also failed`,{cause:i});if(n.length>0)throw new AggregateError(n,`${n.length.toString()} engine invocation cleanup handler(s) failed`);if(c===null)throw new Error("Engine invocation completed without a result");return r.commit(),c}})}function u(t){if(t.aborted)throw t.reason instanceof Error?t.reason:new Error("Resolved engine invocation was cancelled",{cause:t.reason})}async function w(t){try{return await t.loadExecutor()}catch(e){throw new EngineExecutorLoadError(t.id,{cause:e})}}
1
+ import{ENGINE_PROTOCOL_LIMITS as d,beginEngineExecution as E,createEngineInvocationScope as h}from"./api/index.js";import"../domain/identifiers.js";export class EngineExecutorLoadError extends Error{code="TESTKIT_ENGINE_EXECUTOR_LOAD_FAILED";engineId;constructor(t,o){super(`Resolved ${t} executor could not be loaded`,o),this.name="EngineExecutorLoadError",this.engineId=t}}export class EngineManager{catalog;productRoot;installations=new WeakMap;constructor(t){this.catalog=t.catalog,this.productRoot=t.productRoot}resolve(t){return this.catalog.resolve(t)}async withEngine(t,o,i){const c=this.resolve(t);o.throwIfAborted();let n=this.installations.get(o);n||(n=new Map,this.installations.set(o,n));let r=n.get(t);if(!r){const a=n;r=c.resolveInstallation({productRoot:this.productRoot,signal:o}).catch(l=>{throw a.delete(t),l}),a.set(t,r)}const s=await r;return o.throwIfAborted(),i(w(c,s))}}function w(e,t){return Object.freeze({engineId:e.id,async execute(o){u(o.signal);const i=E(o),c=h({timeoutMs:d.cleanupTimeoutMs});let n=null,r,s=!1;try{const l=await g(e);u(o.signal),n=i.acceptResult(await l(i.request,t,c.scope))}catch(l){s=!0,r=l}finally{i.close()}const a=await c.drain();if(s)throw a.length===0?r:new AggregateError([r,...a],`Engine invocation failed and ${a.length.toString()} cleanup handler(s) also failed`,{cause:r});if(a.length>0)throw new AggregateError(a,`${a.length.toString()} engine invocation cleanup handler(s) failed`);if(n===null)throw new Error("Engine invocation completed without a result");return i.commit(),n}})}function u(e){if(e.aborted)throw e.reason instanceof Error?e.reason:new Error("Resolved engine invocation was cancelled",{cause:e.reason})}async function g(e){try{return await e.loadExecutor()}catch(t){throw new EngineExecutorLoadError(e.id,{cause:t})}}
@@ -1,7 +1,7 @@
1
1
  import { type ProcessClassification } from "../../adapters/execution.js";
2
2
  import { type EngineExecutionRequest, type EngineExecutionResult, type EngineFailureEvidence, type EngineInvocationScope } from "../../api/index.js";
3
3
  import { type ManagedProcessResult } from "../../../process/index.js";
4
- import type { NodeVitestInstallation } from "../../installations/node-vitest.js";
4
+ import { type NodeVitestInstallation } from "../../installations/node-vitest.js";
5
5
  import { type CollectedNodeVitestEvidence } from "./evidence.js";
6
6
  import { writeOwnedNodeVitestRuntimeInput, type NodeVitestRuntimeInputDocument } from "../runtime/runtime-input.js";
7
7
  import { writeNodeVitestWorkspace } from "./workspace.js";
@@ -1 +1 @@
1
- import{makeTempDirAsync as R}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as W,terminalOutcome as y,processOutputEvidence as z,emptyEvidence as J,failureFromError as f,firstOutputLine as h,processExitFailure as H,toError as x,executeEngineTasks as Y,managedProcessFailure as G}from"../../adapters/execution.js";import{requireRootDirectory as Q,requireAbsoluteWorkspacePath as M,requireConsumerFile as P,safeSegment as A}from"../../adapters/workspace.js";import d from"node:fs/promises";import c from"node:path";import{pathToFileURL as X}from"node:url";import{isEngineTaskDeadlineExceededReason as Z,resolveEngineTaskInputs as q}from"../../api/index.js";import{createInvocationProcessEnvironment as tt,runManagedProcess as et}from"../../../process/index.js";import{collectNodeVitestEvidence as it}from"./evidence.js";import{assertNodeVitestSpawnEnvironmentBudget as rt,NODE_VITEST_RUNTIME_INPUT_FILE_ENV as ot,NODE_VITEST_RUNTIME_INPUT_SHA256_ENV as nt,selectNodeVitestForwardedEnvironment as st,verifyOwnedNodeVitestRuntimeInput as at,writeOwnedNodeVitestRuntimeInput as ct}from"../runtime/runtime-input.js";import{writeNodeVitestWorkspace as ut}from"./workspace.js";import{engineEvidenceIdentity as dt}from"../../adapters/evidence-identity.js";const E={codePrefix:"NODE_VITEST",label:"Node/Vitest"};class L extends Error{constructor(e){const i=x(e);super(i.message,{cause:i}),this.name="NodeVitestConsumerPreparationError"}}export const nodeVitestExecutorDependencies={removeOwnedDirectory:t=>d.rm(t,{force:!0,recursive:!0}),writeRuntimeInput:ct,writeWorkspace:ut};export async function executeNodeVitestEngine(t,e,i,r=nodeVitestExecutorDependencies){const o=await pt(t,e,r,i);return Y(t,o.tasks,E,(n,s)=>ft(t,o,n,s),vt(t,o.installation))}async function pt(t,e,i,r){const o=t.profile.id,n=e.packages.vitest.cli,s=await Q(t.workspace.rootDirectory);M(t.workspace.temporaryDirectory,"temporary directory"),M(t.workspace.artifactDirectory,"artifact directory"),await d.mkdir(t.workspace.temporaryDirectory,{recursive:!0}),await d.mkdir(t.workspace.artifactDirectory,{recursive:!0});const l=await d.realpath(t.workspace.temporaryDirectory),p=await d.realpath(t.workspace.artifactDirectory),I=Object.freeze({...e.authoringEntrypoints,cli:e.packages.vitest.cli,packages:e.packages}),T=await R(`node-vitest-${A(t.invocationId)}-`,{parentDir:l});r.defer(()=>i.removeOwnedDirectory(T));const j=await tt(T),v=[];for(const[U,a]of t.tasks.entries()){const m=q(t,a),S=`${U.toString().padStart(3,"0")}-${A(a.id)}`,u=c.join(T,S),_=await R(`${S}-`,{parentDir:p}),w=c.join(u,"testkit-vitest-report.json"),K=c.join(_,"data","manifest.json"),B=c.join(u,"setup.mjs"),F=c.join(u,"input-bootstrap.mjs"),O=c.join(u,"resolution-bootstrap.cjs"),V=c.join(u,"vitest.config.mjs");try{await d.mkdir(u,{mode:448,recursive:!1});const D=await N(()=>P(s,a.file,"Node/Vitest task file")),k=await N(()=>Tt(s,m)),$=await N(()=>Et(t,o,a,m)),g=await i.writeRuntimeInput(u,$);await i.writeWorkspace({report:{identity:{engineId:"node-vitest",invocationId:t.invocationId,taskId:a.id,sourceFile:a.file},reportFile:w,workspaceDirectory:u},configFile:k.testkitConfig,entrypoints:I,inputBootstrapFile:F,resolutionBootstrapFile:O,rootDirectory:s,runtimeInput:{expectedFile:g.file,expectedInvocationId:t.invocationId,expectedProfileId:o,expectedSha256:g.sha256,expectedTaskId:a.id},setupFile:B,sourceFile:D,timeoutMs:Math.max(1,a.deadlineMs-Date.now()),tsconfigFile:k.tsconfig,vitestConfigFile:V}),v.push({artifactDirectory:_,inputs:m,task:a,preparationFailure:null,prepared:{dataManifestFile:K,inputBootstrapFile:F,reportFile:w,resolutionBootstrapFile:O,runtimeInput:g,vitestConfigFile:V}})}catch(D){v.push({artifactDirectory:_,inputs:m,task:a,preparationFailure:_t(D),prepared:null})}}return Object.freeze({cliFile:n,installation:e,invocationEnvironment:j.environment,profileId:o,rootDirectory:s,tasks:Object.freeze(v)})}async function ft(t,e,i,r){const o=Math.max(0,i.task.deadlineMs-r);try{await at(i.prepared.runtimeInput)}catch(p){return y(i.task.id,"failed",r,J(),{...f(p,"NODE_VITEST_INPUT_INVALID"),classification:"infra"})}const n=await et({args:[e.cliFile,"run","--config",i.prepared.vitestConfigFile,"--configLoader","runner"],cwd:e.rootDirectory,displayLabel:"Node/Vitest",env:mt(t,e,i),executable:process.execPath,signal:t.signal,timeoutMs:o});let s;try{s=await it({identity:{engineId:"node-vitest",invocationId:t.invocationId,taskId:i.task.id,sourceFile:i.task.file},artifactDirectory:i.artifactDirectory,dataManifestFile:i.prepared.dataManifestFile,profileId:e.profileId,reportFile:i.prepared.reportFile,rootDirectory:e.rootDirectory,stderr:n.stderr,stdout:n.stdout})}catch(p){const I=z(n);return y(i.task.id,"failed",r,I,nodeVitestEvidenceCollectionFailure(n,p))}const l=classifyNodeVitestProcessResult(n,s,i.task.timeoutMs,Z(t.signal.reason));return y(i.task.id,l.status,r,s.evidence,l.failure)}export function classifyNodeVitestProcessResult(t,e,i,r){const o=W(t,i,r,E);return o!==null?o:e.reportSummary?.numTotalTests===0?{failure:{code:"NODE_VITEST_NO_TESTS",message:"Vitest did not discover any tests for the task file"},status:"failed"}:lt(e.reportSummary)?{failure:null,status:"skipped"}:t.exitCode!==0?H(t,E,e.reportFailureMessage??h(t.stderr)??h(t.stdout),"Vitest"):e.reportError!==null?{failure:{...f(e.reportError,"NODE_VITEST_REPORT_INVALID"),classification:"infra"},status:"failed"}:e.reportFailureMessage!==null?{failure:{code:"NODE_VITEST_REPORT_FAILED",message:e.reportFailureMessage},status:"failed"}:e.dataEvidenceError!==null?{failure:f(e.dataEvidenceError,"NODE_VITEST_DATA_EVIDENCE_INVALID"),status:"failed"}:e.reportSummary===null||!e.reportSummary.success||e.reportSummary.numFailedTests>0||e.reportSummary.numPassedTests===0?{failure:{code:"NODE_VITEST_REPORT_FAILED",message:"Vitest report did not contain a completed passing test"},status:"failed"}:{failure:null,status:"passed"}}export function nodeVitestEvidenceCollectionFailure(t,e){const i=x(e);return G(t,E,i)??{...f(i,"NODE_VITEST_EVIDENCE_FAILED"),classification:"infra"}}function lt(t){return t!==null&&t.success&&t.numTotalTests>0&&t.numPassedTests===0&&t.numFailedTests===0&&(t.numPendingTests>0||t.numTodoTests>0)}function mt(t,e,i){const r=i.task,o=C(t,r.serviceName),n={...e.invocationEnvironment};return Object.assign(n,{...o===null?{}:{BASE_URL:o},FORCE_COLOR:"0",NODE_OPTIONS:It(i.prepared.resolutionBootstrapFile,i.prepared.inputBootstrapFile),NO_COLOR:"1",TESTKIT_ACTIVE:"1",[ot]:i.prepared.runtimeInput.file,[nt]:i.prepared.runtimeInput.sha256,TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(r.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(r.timeoutMs/1e3))),TESTKIT_INVOCATION_ID:t.invocationId,TESTKIT_LEASE_DIR:r.orchestration.lease.directory,TESTKIT_LEASE_ID:r.orchestration.lease.id,TESTKIT_MODE:r.orchestration.mode,...r.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:r.orchestration.runtimeId},TESTKIT_TEST_FILE:r.file,TESTKIT_TEST_ID:r.id,...e.profileId==="node-vitest-data"?{TESTKIT_DATA_ARTIFACT_DIR:c.dirname(i.prepared.dataManifestFile)}:{}}),rt(n),n}function Et(t,e,i,r){const o=C(t,i.serviceName),n=st(r.environment);return{context:{baseUrl:o,engineId:"node-vitest",invocationId:t.invocationId,profileId:e,serviceName:i.serviceName,task:{file:i.file,id:i.id,suiteName:i.suiteName,testType:i.testType}},engineId:"node-vitest",environment:n,fixtures:r.fixtures,invocationId:t.invocationId,profileId:e,...r.setupData===void 0?{}:{setupData:r.setupData},taskId:i.id,version:1}}function It(t,e){return[`--require=${JSON.stringify(t)}`,`--import=${JSON.stringify(X(e).href)}`].join(" ")}function C(t,e){return Object.hasOwn(t.target.endpoints,e)?t.target.endpoints[e]:null}async function Tt(t,e){return{testkitConfig:await b(t,e.supportModules,"testkitConfig"),tsconfig:await b(t,e.supportModules,"tsconfig")}}async function b(t,e,i){if(!Object.hasOwn(e,i))return null;const r=e[i];if(!c.isAbsolute(r))throw new Error(`supportModules.${i} must be an absolute path`);return P(t,r,`supportModules.${i}`)}function vt(t,e){return{...dt({engineId:"node-vitest",installationKind:"root-direct-packages",profile:t.profile}),directPackages:{esbuild:e.packages.esbuild.version,typescript:e.packages.typescript.version,vite:e.packages.vite.version,vitest:e.packages.vitest.version}}}function _t(t){return{...f(t,"NODE_VITEST_PREPARE_FAILED"),classification:t instanceof L?"product":"infra"}}async function N(t){try{return await t()}catch(e){throw new L(e)}}
1
+ import{makeTempDirAsync as R}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as z,terminalOutcome as I,processOutputEvidence as W,emptyEvidence as J,failureFromError as l,firstOutputLine as h,processExitFailure as H,toError as x,executeEngineTasks as Y,managedProcessFailure as G}from"../../adapters/execution.js";import{requireRootDirectory as Q,requireAbsoluteWorkspacePath as M,requireConsumerFile as P,safeSegment as L}from"../../adapters/workspace.js";import p from"node:fs/promises";import c from"node:path";import{pathToFileURL as X}from"node:url";import{isEngineTaskDeadlineExceededReason as Z,resolveEngineTaskInputs as q}from"../../api/index.js";import{createInvocationProcessEnvironment as tt,runManagedProcess as et}from"../../../process/index.js";import{preflightNodeVitestInstallation as it}from"../../installations/node-vitest.js";import{collectNodeVitestEvidence as rt}from"./evidence.js";import{assertNodeVitestSpawnEnvironmentBudget as ot,NODE_VITEST_RUNTIME_INPUT_FILE_ENV as nt,NODE_VITEST_RUNTIME_INPUT_SHA256_ENV as at,selectNodeVitestForwardedEnvironment as st,verifyOwnedNodeVitestRuntimeInput as ct,writeOwnedNodeVitestRuntimeInput as ut}from"../runtime/runtime-input.js";import{writeNodeVitestWorkspace as dt}from"./workspace.js";import{engineEvidenceIdentity as lt}from"../../adapters/evidence-identity.js";const T={codePrefix:"NODE_VITEST",label:"Node/Vitest"};class A extends Error{constructor(e){const i=x(e);super(i.message,{cause:i}),this.name="NodeVitestConsumerPreparationError"}}export const nodeVitestExecutorDependencies={removeOwnedDirectory:t=>p.rm(t,{force:!0,recursive:!0}),writeRuntimeInput:ut,writeWorkspace:dt};export async function executeNodeVitestEngine(t,e,i,r=nodeVitestExecutorDependencies){const n=await pt(t,e,r,i);return Y(t,n.tasks,T,(o,a)=>ft(t,n,o,a),_t(t,n.installation))}async function pt(t,e,i,r){const n=t.profile.id,o=e.packages.vitest.cli,a=await Q(t.workspace.rootDirectory);M(t.workspace.temporaryDirectory,"temporary directory"),M(t.workspace.artifactDirectory,"artifact directory"),await p.mkdir(t.workspace.temporaryDirectory,{recursive:!0}),await p.mkdir(t.workspace.artifactDirectory,{recursive:!0});const f=await p.realpath(t.workspace.temporaryDirectory),d=await p.realpath(t.workspace.artifactDirectory),v=Object.freeze({...e.authoringEntrypoints,cli:e.packages.vitest.cli,packages:e.packages}),_=await R(`node-vitest-${L(t.invocationId)}-`,{parentDir:f});r.defer(()=>i.removeOwnedDirectory(_));const j=await tt(_),g=[];for(const[K,s]of t.tasks.entries()){const m=q(t,s),S=`${K.toString().padStart(3,"0")}-${L(s.id)}`,u=c.join(_,S),y=await R(`${S}-`,{parentDir:d}),O=c.join(u,"testkit-vitest-report.json"),U=c.join(y,"data","manifest.json"),B=c.join(u,"setup.mjs"),w=c.join(u,"input-bootstrap.mjs"),F=c.join(u,"resolution-bootstrap.cjs"),V=c.join(u,"vitest.config.mjs");try{await p.mkdir(u,{mode:448,recursive:!1});const D=await N(()=>P(a,s.file,"Node/Vitest task file")),k=await N(()=>vt(a,m)),$=await N(()=>It(t,n,s,m)),E=await i.writeRuntimeInput(u,$);await i.writeWorkspace({report:{identity:{engineId:"node-vitest",invocationId:t.invocationId,taskId:s.id,sourceFile:s.file},reportFile:O,workspaceDirectory:u},configFile:k.testkitConfig,entrypoints:v,inputBootstrapFile:w,resolutionBootstrapFile:F,rootDirectory:a,runtimeInput:{expectedFile:E.file,expectedInvocationId:t.invocationId,expectedProfileId:n,expectedSha256:E.sha256,expectedSizeBytes:E.sizeBytes,expectedTaskId:s.id},setupFile:B,sourceFile:D,timeoutMs:Math.max(1,s.deadlineMs-Date.now()),tsconfigFile:k.tsconfig,vitestConfigFile:V}),g.push({artifactDirectory:y,inputs:m,task:s,preparationFailure:null,prepared:{dataManifestFile:U,inputBootstrapFile:w,reportFile:O,resolutionBootstrapFile:F,runtimeInput:E,vitestConfigFile:V}})}catch(D){g.push({artifactDirectory:y,inputs:m,task:s,preparationFailure:gt(D),prepared:null})}}return Object.freeze({cliFile:o,installation:e,invocationEnvironment:j.environment,profileId:n,rootDirectory:a,tasks:Object.freeze(g)})}async function ft(t,e,i,r){const n=Math.max(0,i.task.deadlineMs-r);try{await ct(i.prepared.runtimeInput)}catch(d){return I(i.task.id,"failed",r,J(),{...l(d,"NODE_VITEST_INPUT_INVALID"),classification:"infra"})}const o=await et({args:[e.cliFile,"run","--config",i.prepared.vitestConfigFile,"--configLoader","runner"],cwd:e.rootDirectory,displayLabel:"Node/Vitest",env:Et(t,e,i),executable:process.execPath,signal:t.signal,timeoutMs:n});let a;try{a=await rt({identity:{engineId:"node-vitest",invocationId:t.invocationId,taskId:i.task.id,sourceFile:i.task.file},artifactDirectory:i.artifactDirectory,dataManifestFile:i.prepared.dataManifestFile,profileId:e.profileId,reportFile:i.prepared.reportFile,rootDirectory:e.rootDirectory,stderr:o.stderr,stdout:o.stdout})}catch(d){const v=W(o);return I(i.task.id,"failed",r,v,nodeVitestEvidenceCollectionFailure(o,d))}if(a.reportSummary===null&&o.exitCode!==0&&!t.signal.aborted&&!o.timedOut&&!o.outputLimitExceeded&&o.terminationError===null&&o.error===null)try{await it(e.installation,{signal:t.signal})}catch(d){if(!t.signal.aborted)return I(i.task.id,"failed",r,a.evidence,{...l(d,"NODE_VITEST_INSTALLATION_BROKEN"),classification:"infra"})}const f=classifyNodeVitestProcessResult(o,a,i.task.timeoutMs,Z(t.signal.reason));return I(i.task.id,f.status,r,a.evidence,f.failure)}export function classifyNodeVitestProcessResult(t,e,i,r){const n=z(t,i,r,T);return n!==null?n:e.reportSummary?.numTotalTests===0?{failure:{code:"NODE_VITEST_NO_TESTS",message:"Vitest did not discover any tests for the task file"},status:"failed"}:mt(e.reportSummary)?{failure:null,status:"skipped"}:t.exitCode!==0?H(t,T,e.reportFailureMessage??h(t.stderr)??h(t.stdout),"Vitest"):e.reportError!==null?{failure:{...l(e.reportError,"NODE_VITEST_REPORT_INVALID"),classification:"infra"},status:"failed"}:e.reportFailureMessage!==null?{failure:{code:"NODE_VITEST_REPORT_FAILED",message:e.reportFailureMessage},status:"failed"}:e.dataEvidenceError!==null?{failure:l(e.dataEvidenceError,"NODE_VITEST_DATA_EVIDENCE_INVALID"),status:"failed"}:e.reportSummary===null||!e.reportSummary.success||e.reportSummary.numFailedTests>0||e.reportSummary.numPassedTests===0?{failure:{code:"NODE_VITEST_REPORT_FAILED",message:"Vitest report did not contain a completed passing test"},status:"failed"}:{failure:null,status:"passed"}}export function nodeVitestEvidenceCollectionFailure(t,e){const i=x(e);return G(t,T,i)??{...l(i,"NODE_VITEST_EVIDENCE_FAILED"),classification:"infra"}}function mt(t){return t!==null&&t.success&&t.numTotalTests>0&&t.numPassedTests===0&&t.numFailedTests===0&&(t.numPendingTests>0||t.numTodoTests>0)}function Et(t,e,i){const r=i.task,n=b(t,r.serviceName),o={...e.invocationEnvironment};return Object.assign(o,{...n===null?{}:{BASE_URL:n},FORCE_COLOR:"0",NODE_OPTIONS:Tt(i.prepared.resolutionBootstrapFile,i.prepared.inputBootstrapFile),NO_COLOR:"1",TESTKIT_ACTIVE:"1",[nt]:i.prepared.runtimeInput.file,[at]:i.prepared.runtimeInput.sha256,TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(r.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(r.timeoutMs/1e3))),TESTKIT_INVOCATION_ID:t.invocationId,TESTKIT_LEASE_DIR:r.orchestration.lease.directory,TESTKIT_LEASE_ID:r.orchestration.lease.id,TESTKIT_MODE:r.orchestration.mode,...r.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:r.orchestration.runtimeId},TESTKIT_TEST_FILE:r.file,TESTKIT_TEST_ID:r.id,...e.profileId==="node-vitest-data"?{TESTKIT_DATA_ARTIFACT_DIR:c.dirname(i.prepared.dataManifestFile)}:{}}),ot(o),o}function It(t,e,i,r){const n=b(t,i.serviceName),o=st(r.environment);return{context:{baseUrl:n,engineId:"node-vitest",invocationId:t.invocationId,profileId:e,serviceName:i.serviceName,task:{file:i.file,id:i.id,suiteName:i.suiteName,testType:i.testType}},engineId:"node-vitest",environment:o,fixtures:r.fixtures,invocationId:t.invocationId,profileId:e,...r.setupData===void 0?{}:{setupData:r.setupData},taskId:i.id,version:1}}function Tt(t,e){return[`--require=${JSON.stringify(t)}`,`--import=${JSON.stringify(X(e).href)}`].join(" ")}function b(t,e){return Object.hasOwn(t.target.endpoints,e)?t.target.endpoints[e]:null}async function vt(t,e){return{testkitConfig:await C(t,e.supportModules,"testkitConfig"),tsconfig:await C(t,e.supportModules,"tsconfig")}}async function C(t,e,i){if(!Object.hasOwn(e,i))return null;const r=e[i];if(!c.isAbsolute(r))throw new Error(`supportModules.${i} must be an absolute path`);return P(t,r,`supportModules.${i}`)}function _t(t,e){return{...lt({engineId:"node-vitest",installationKind:"root-direct-packages",profile:t.profile}),directPackages:{esbuild:e.packages.esbuild.version,typescript:e.packages.typescript.version,vite:e.packages.vite.version,vitest:e.packages.vitest.version}}}function gt(t){return{...l(t,"NODE_VITEST_PREPARE_FAILED"),classification:t instanceof A?"product":"infra"}}async function N(t){try{return await t()}catch(e){throw new A(e)}}
@@ -1,3 +1,3 @@
1
- import{isPathWithin as y}from"../../../process/files/paths.js";import u from"node:fs";import{createRequire as h}from"node:module";import i from"node:path";import{fileURLToPath as l}from"node:url";import{NODE_VITEST_PACKAGE_REQUIREMENTS as N}from"../../installations/node-vitest.js";export const managedTypeScriptFile=l(import.meta.resolve("typescript"));const k=/^@gfdelarue\/testkit(?:\/|$)/u;export function createConsumerTypeScriptPathsPlugin(t,e){const r=h(import.meta.url)(managedTypeScriptFile),n=S(t,r),o=Object.keys(n.paths??{});return o.length===0?null:{enforce:"pre",name:"testkit-node-vitest-consumer-typescript-paths",resolveId(a,s){if(s===void 0||isNodeVitestManagedDependencyImporter(s,e.dependencyRoots)||k.test(a)||!o.some(g=>w(a,g)))return null;const c=x(s);if(c===null)return null;const f=r.resolveModuleName(a,c,n,r.sys).resolvedModule?.resolvedFileName;return f===void 0?null:assertNodeVitestConsumerResolution(a,f,s,e)}}}export function assertNodeVitestConsumerResolution(t,e,r,n){if(isNodeVitestManagedDependencyImporter(r,n.dependencyRoots))return e;const o=d(e);if(o===null)return e;const a=v(o);if(a===null)return e;if(a.privatePackage)throw new Error(`Node/Vitest consumers may not resolve ${JSON.stringify(t)} into private Vitest package ${a.packageName}`);const s=n.aliases[t];if(Object.hasOwn(n.aliases,t)&&m(o,s)||Object.values(n.aliases).some(c=>m(o,c)))return e;throw new Error(`Node/Vitest consumers may not resolve ${JSON.stringify(t)} into managed direct package ${a.packageName}; import an exact public ${JSON.stringify(a.packageName)} export instead`)}function v(t){const e=[t];try{const r=u.realpathSync(t);r!==t&&e.push(r)}catch{}for(const r of e){let n=i.dirname(r);for(;;){const o=i.join(n,"package.json");if(u.existsSync(o)){const s=E(o);if(s!==null){if(s.startsWith("@vitest/"))return{packageName:s,privatePackage:!0};if(N.some(({packageName:c})=>c===s))return{packageName:s,privatePackage:!1}}}const a=i.dirname(n);if(a===n)break;n=a}}return null}function E(t){let e;try{e=JSON.parse(u.readFileSync(t,"utf8"))}catch(n){throw new Error(`Cannot inspect package boundary at ${t}`,{cause:n})}if(typeof e!="object"||e===null||Array.isArray(e))return null;const r=Reflect.get(e,"name");return typeof r=="string"?r:null}export function isNodeVitestManagedDependencyImporter(t,e){if(t===void 0)return!1;const r=d(t);return r===null?!1:e.some(n=>y(n,r))}function d(t){const e=t.split(/[?#]/u,1)[0];if(e.startsWith("\0"))return null;if(e.startsWith("file:"))return l(e);const r=e.startsWith("/@fs/")?e.slice(4):e;return i.isAbsolute(r)?i.resolve(r):null}function m(t,e){try{return u.realpathSync(t)===u.realpathSync(e)}catch{return i.resolve(t)===i.resolve(e)}}function S(t,e){const r=i.resolve(t).replaceAll(i.sep,"/"),n=i.posix.dirname(r),o=e.readConfigFile(r,c=>e.sys.readFile(c));if(o.error!==void 0)throw new Error(p(r,[o.error],e));const a=e.parseJsonConfigFileContent(o.config,e.sys,n,{},r),s=a.errors.filter(c=>c.category===e.DiagnosticCategory.Error&&c.code!==18003);if(s.length>0)throw new Error(p(r,s,e));return a.options}function p(t,e,r){const n=r.formatDiagnostics(e,{getCanonicalFileName:o=>o,getCurrentDirectory:()=>i.posix.dirname(t),getNewLine:()=>`
1
+ import{isPathWithin as y}from"../../../process/files/paths.js";import u from"node:fs";import{createRequire as h}from"node:module";import i from"node:path";import{fileURLToPath as l}from"node:url";import{NODE_VITEST_PACKAGE_REQUIREMENTS as N}from"../../installations/package-requirements.js";export const managedTypeScriptFile=l(import.meta.resolve("typescript"));const k=/^@gfdelarue\/testkit(?:\/|$)/u;export function createConsumerTypeScriptPathsPlugin(t,e){const r=h(import.meta.url)(managedTypeScriptFile),n=S(t,r),o=Object.keys(n.paths??{});return o.length===0?null:{enforce:"pre",name:"testkit-node-vitest-consumer-typescript-paths",resolveId(a,s){if(s===void 0||isNodeVitestManagedDependencyImporter(s,e.dependencyRoots)||k.test(a)||!o.some(g=>w(a,g)))return null;const c=x(s);if(c===null)return null;const f=r.resolveModuleName(a,c,n,r.sys).resolvedModule?.resolvedFileName;return f===void 0?null:assertNodeVitestConsumerResolution(a,f,s,e)}}}export function assertNodeVitestConsumerResolution(t,e,r,n){if(isNodeVitestManagedDependencyImporter(r,n.dependencyRoots))return e;const o=d(e);if(o===null)return e;const a=v(o);if(a===null)return e;if(a.privatePackage)throw new Error(`Node/Vitest consumers may not resolve ${JSON.stringify(t)} into private Vitest package ${a.packageName}`);const s=n.aliases[t];if(Object.hasOwn(n.aliases,t)&&m(o,s)||Object.values(n.aliases).some(c=>m(o,c)))return e;throw new Error(`Node/Vitest consumers may not resolve ${JSON.stringify(t)} into managed direct package ${a.packageName}; import an exact public ${JSON.stringify(a.packageName)} export instead`)}function v(t){const e=[t];try{const r=u.realpathSync(t);r!==t&&e.push(r)}catch{}for(const r of e){let n=i.dirname(r);for(;;){const o=i.join(n,"package.json");if(u.existsSync(o)){const s=E(o);if(s!==null){if(s.startsWith("@vitest/"))return{packageName:s,privatePackage:!0};if(N.some(({packageName:c})=>c===s))return{packageName:s,privatePackage:!1}}}const a=i.dirname(n);if(a===n)break;n=a}}return null}function E(t){let e;try{e=JSON.parse(u.readFileSync(t,"utf8"))}catch(n){throw new Error(`Cannot inspect package boundary at ${t}`,{cause:n})}if(typeof e!="object"||e===null||Array.isArray(e))return null;const r=Reflect.get(e,"name");return typeof r=="string"?r:null}export function isNodeVitestManagedDependencyImporter(t,e){if(t===void 0)return!1;const r=d(t);return r===null?!1:e.some(n=>y(n,r))}function d(t){const e=t.split(/[?#]/u,1)[0];if(e.startsWith("\0"))return null;if(e.startsWith("file:"))return l(e);const r=e.startsWith("/@fs/")?e.slice(4):e;return i.isAbsolute(r)?i.resolve(r):null}function m(t,e){try{return u.realpathSync(t)===u.realpathSync(e)}catch{return i.resolve(t)===i.resolve(e)}}function S(t,e){const r=i.resolve(t).replaceAll(i.sep,"/"),n=i.posix.dirname(r),o=e.readConfigFile(r,c=>e.sys.readFile(c));if(o.error!==void 0)throw new Error(p(r,[o.error],e));const a=e.parseJsonConfigFileContent(o.config,e.sys,n,{},r),s=a.errors.filter(c=>c.category===e.DiagnosticCategory.Error&&c.code!==18003);if(s.length>0)throw new Error(p(r,s,e));return a.options}function p(t,e,r){const n=r.formatDiagnostics(e,{getCanonicalFileName:o=>o,getCurrentDirectory:()=>i.posix.dirname(t),getNewLine:()=>`
2
2
  `});return`Cannot load explicit consumer TypeScript paths from ${t}:
3
3
  ${n}`.trim()}function w(t,e){const r=e.indexOf("*");if(r===-1)return t===e;const n=e.slice(0,r),o=e.slice(r+1);return t.startsWith(n)&&t.endsWith(o)}function x(t){const e=t.split(/[?#]/u,1)[0];return e.startsWith("file:")?l(e):i.isAbsolute(e)?e:null}
@@ -1,5 +1,5 @@
1
1
  import { type ManagedNodeModuleResolution } from "../../managed-module-resolution.js";
2
- import { type NodeVitestAuthoringEntrypoints, type NodeVitestInstallation } from "../../installations/node-vitest.js";
2
+ import type { NodeVitestAuthoringEntrypoints, NodeVitestInstallation } from "../../installations/node-vitest.js";
3
3
  export interface NodeVitestResolutionEntrypoints extends NodeVitestAuthoringEntrypoints {
4
4
  readonly cli: string;
5
5
  readonly packages: NodeVitestInstallation["packages"];
@@ -1 +1 @@
1
- import c from"node:path";import{createManagedNodeModuleResolution as d}from"../../managed-module-resolution.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as i,NODE_VITEST_PRIVATE_PACKAGE_PREFIXES as g}from"../../installations/node-vitest.js";export function resolveNodeVitestManagedRuntimeResolution(t,a){const o=i.filter(e=>e.authoringResolution==="exact-entrypoint"),n=Object.fromEntries(i.filter(e=>e.authoringResolution==="package-exports").map(e=>[e.packageName,t.packages[e.packageName].entrypoint])),r=o.map(e=>t.packages[e.packageName].root),s=d({consumerRoots:a.consumerRoots,direct:{"@gfdelarue/testkit":t.root,"@gfdelarue/testkit/config":t.config,"@gfdelarue/testkit/node-vitest/contract":t.contract,"@gfdelarue/testkit/node-vitest/data":t.data,"@gfdelarue/testkit/package.json":t.rootPackageJson,...Object.fromEntries(o.map(e=>[e.packageName,t.packages[e.packageName].entrypoint]))},directBridgeRoots:[c.dirname(t.root),...r],engineLabel:"Node/Vitest",forbiddenConsumerPackagePrefixes:g,packageAnchors:n,reservedPackageNames:["@gfdelarue/testkit",...o.map(e=>e.packageName)],stateSymbol:"@gfdelarue/testkit/node-vitest/resolution-hooks/v1",trustedImporterFiles:a.trustedImporterFiles,trustedInternalEntrypoints:a.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{vitest:["dist/workers/forks.js","suppress-warnings.cjs"]},verifiedProcessEntrypoints:[t.cli]});return Object.freeze({...s,dependencyRoots:Object.freeze([...new Set([...s.dependencyRoots,c.dirname(t.root),...r])])})}
1
+ import c from"node:path";import{createManagedNodeModuleResolution as d}from"../../managed-module-resolution.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as i,NODE_VITEST_PRIVATE_PACKAGE_PREFIXES as g}from"../../installations/package-requirements.js";export function resolveNodeVitestManagedRuntimeResolution(t,a){const o=i.filter(e=>e.authoringResolution==="exact-entrypoint"),n=Object.fromEntries(i.filter(e=>e.authoringResolution==="package-exports").map(e=>[e.packageName,t.packages[e.packageName].entrypoint])),r=o.map(e=>t.packages[e.packageName].root),s=d({consumerRoots:a.consumerRoots,direct:{"@gfdelarue/testkit":t.root,"@gfdelarue/testkit/config":t.config,"@gfdelarue/testkit/node-vitest/contract":t.contract,"@gfdelarue/testkit/node-vitest/data":t.data,"@gfdelarue/testkit/package.json":t.rootPackageJson,...Object.fromEntries(o.map(e=>[e.packageName,t.packages[e.packageName].entrypoint]))},directBridgeRoots:[c.dirname(t.root),...r],engineLabel:"Node/Vitest",forbiddenConsumerPackagePrefixes:g,packageAnchors:n,reservedPackageNames:["@gfdelarue/testkit",...o.map(e=>e.packageName)],stateSymbol:"@gfdelarue/testkit/node-vitest/resolution-hooks/v1",trustedImporterFiles:a.trustedImporterFiles,trustedInternalEntrypoints:a.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{vitest:["dist/workers/forks.js","suppress-warnings.cjs"]},verifiedProcessEntrypoints:[t.cli]});return Object.freeze({...s,dependencyRoots:Object.freeze([...new Set([...s.dependencyRoots,c.dirname(t.root),...r])])})}
@@ -1,11 +1,16 @@
1
- import o from"node:fs/promises";import i from"node:path";import{fileURLToPath as g,pathToFileURL as a}from"node:url";import{NODE_VITEST_RUNTIME_INPUT_SYMBOL as p}from"../runtime/runtime-input.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as y}from"../../installations/node-vitest.js";import{resolveNodeVitestManagedRuntimeResolution as R}from"./resolution.js";import{nativeReporterFile as N}from"../../adapters/native-report.js";const u="elench.testkit.node-vitest.config-runtime.v1",l=g(new URL("../../../../dist/engines/node-vitest/",import.meta.url)),m=i.join(l,"adapter/consumer-typescript-paths.js"),f=i.join(l,"runtime/data/runner.js"),d=i.join(l,"runtime/runtime-input.js");export async function writeNodeVitestWorkspace(e){await o.mkdir(i.dirname(e.setupFile),{recursive:!0});const t=R(e.entrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[e.inputBootstrapFile,e.setupFile,e.vitestConfigFile],trustedInternalEntrypoints:[m,d]});await Promise.all([o.writeFile(e.setupFile,I(e.configFile,e.runtimeInput.expectedProfileId),"utf8"),o.writeFile(e.inputBootstrapFile,S(e.runtimeInput),"utf8"),o.writeFile(e.resolutionBootstrapFile,t.bootstrapSource,{encoding:"utf8",flag:"wx",mode:384}),o.writeFile(e.vitestConfigFile,O(e,t),"utf8")])}function S(e){const t=a(d).href,n=a(m).href;return`
1
+ import o from"node:fs/promises";import i from"node:path";import{fileURLToPath as g,pathToFileURL as a}from"node:url";import{NODE_VITEST_RUNTIME_INPUT_SYMBOL as p}from"../runtime/runtime-input.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as R}from"../../installations/package-requirements.js";import{resolveNodeVitestManagedRuntimeResolution as y}from"./resolution.js";import{nativeReporterFile as S}from"../../adapters/native-report.js";const u="elench.testkit.node-vitest.config-runtime.v1",l=g(new URL("../../../../dist/engines/node-vitest/",import.meta.url)),f=i.join(l,"adapter/consumer-typescript-paths.js"),m=i.join(l,"runtime/data/runner.js"),d=i.join(l,"runtime/runtime-input.js");export async function writeNodeVitestWorkspace(e){await o.mkdir(i.dirname(e.setupFile),{recursive:!0});const t=y(e.entrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[e.inputBootstrapFile,e.setupFile,e.vitestConfigFile],trustedInternalEntrypoints:[f,d]});await Promise.all([o.writeFile(e.setupFile,I(e.configFile,e.runtimeInput.expectedProfileId),"utf8"),o.writeFile(e.inputBootstrapFile,N(e.runtimeInput),"utf8"),o.writeFile(e.resolutionBootstrapFile,t.bootstrapSource,{encoding:"utf8",flag:"wx",mode:384}),o.writeFile(e.vitestConfigFile,O(e,t),"utf8")])}function N(e){const t=a(d).href,n=a(f).href;return`
2
2
  const inputRuntime = await import(${JSON.stringify(t)});
3
- const consumerResolutionRuntime = await import(${JSON.stringify(n)});
4
3
  const { installNodeVitestRuntimeInput } = inputRuntime;
5
4
  await installNodeVitestRuntimeInput(${JSON.stringify(e)});
6
5
  const configRuntimeSymbol = Symbol.for(${JSON.stringify(u)});
7
6
  if (Object.hasOwn(globalThis, configRuntimeSymbol))
8
7
  throw new Error("Managed Node/Vitest config runtime is already installed");
8
+ // Only the Vitest parent requests config policy. Fork workers still validate
9
+ // their own input, but do not load the unused TypeScript alias-policy graph.
10
+ let loadedConfigRuntime;
11
+ const load = Object.freeze(() => {
12
+ loadedConfigRuntime ??= (async () => {
13
+ const consumerResolutionRuntime = await import(${JSON.stringify(n)});
9
14
  const configRuntimeFunctions = [
10
15
  consumerResolutionRuntime.assertNodeVitestConsumerResolution,
11
16
  consumerResolutionRuntime.createConsumerTypeScriptPathsPlugin,
@@ -16,13 +21,17 @@ for (const value of configRuntimeFunctions) {
16
21
  throw new Error("Managed Node/Vitest config runtime is invalid");
17
22
  Object.freeze(value);
18
23
  }
19
- const configRuntime = Object.freeze({
24
+ return Object.freeze({
20
25
  assertNodeVitestConsumerResolution: configRuntimeFunctions[0],
21
26
  createConsumerTypeScriptPathsPlugin: configRuntimeFunctions[1],
22
27
  isNodeVitestManagedDependencyImporter: configRuntimeFunctions[2],
23
28
  managedTypeScriptFile: consumerResolutionRuntime.managedTypeScriptFile,
24
29
  version: 1,
25
30
  });
31
+ })();
32
+ return loadedConfigRuntime;
33
+ });
34
+ const configRuntime = Object.freeze({ load, version: 1 });
26
35
  Object.defineProperty(
27
36
  globalThis,
28
37
  configRuntimeSymbol,
@@ -62,9 +71,20 @@ __testkitRegisterNodeVitestRepositoryConfiguration(
62
71
  if (installedRuntimeInput?.invocationId !== ${JSON.stringify(e.runtimeInput.expectedInvocationId)}
63
72
  || installedRuntimeInput.profileId !== ${JSON.stringify(e.runtimeInput.expectedProfileId)}
64
73
  || installedRuntimeInput.taskId !== ${JSON.stringify(e.runtimeInput.expectedTaskId)})
65
- throw new Error("Managed Node/Vitest input bootstrap was not installed in the Vitest parent");`,s=`const configRuntime = globalThis[
74
+ throw new Error("Managed Node/Vitest input bootstrap was not installed in the Vitest parent");`,s=`const configRuntimeSupplier = globalThis[
66
75
  Symbol.for(${JSON.stringify(u)})
67
76
  ];
77
+ if (
78
+ typeof configRuntimeSupplier !== "object" ||
79
+ configRuntimeSupplier === null ||
80
+ !Object.isFrozen(configRuntimeSupplier) ||
81
+ Reflect.ownKeys(configRuntimeSupplier).length !== 2 ||
82
+ configRuntimeSupplier.version !== 1 ||
83
+ typeof configRuntimeSupplier.load !== "function" ||
84
+ !Object.isFrozen(configRuntimeSupplier.load)
85
+ )
86
+ throw new Error("Managed Node/Vitest config runtime supplier is invalid");
87
+ const configRuntime = await configRuntimeSupplier.load();
68
88
  const configRuntimeKeys = [
69
89
  "assertNodeVitestConsumerResolution",
70
90
  "createConsumerTypeScriptPathsPlugin",
@@ -98,7 +118,7 @@ if (managedTypeScriptFile !== ${JSON.stringify(e.entrypoints.packages.typescript
98
118
  ${n}
99
119
  ${s}
100
120
  const managedAliases = Object.freeze(${JSON.stringify(t.aliases)});
101
- const managedPackageNames = Object.freeze(${JSON.stringify(y.map(({packageName:r})=>r))});
121
+ const managedPackageNames = Object.freeze(${JSON.stringify(R.map(({packageName:r})=>r))});
102
122
  const managedDependencyRoots = Object.freeze(${JSON.stringify(t.dependencyRoots)});
103
123
  const consumerResolutionPolicy = Object.freeze({
104
124
  aliases: managedAliases,
@@ -115,6 +135,7 @@ export default {
115
135
  enforce: "pre",
116
136
  name: "testkit-node-vitest-managed-direct-packages",
117
137
  async resolveId(specifier, importer, options) {
138
+
118
139
  const managedImporter = isNodeVitestManagedDependencyImporter(
119
140
  importer,
120
141
  managedDependencyRoots,
@@ -146,6 +167,7 @@ export default {
146
167
  ${c}
147
168
  ],
148
169
  root: ${JSON.stringify(e.rootDirectory)},
170
+ cacheDir: ${JSON.stringify(i.join(i.dirname(e.vitestConfigFile),"cache"))},
149
171
  resolve: {
150
172
  alias: Object.entries(managedAliases).map(([specifier, replacement]) => ({
151
173
  find: exactSpecifierPattern(specifier),
@@ -153,9 +175,11 @@ export default {
153
175
  })),
154
176
  },
155
177
  test: {
156
- ${e.runtimeInput.expectedProfileId==="node-vitest-data"?`runner: ${JSON.stringify(f)},
157
- server: { deps: { external: ${JSON.stringify([f,e.entrypoints.data].map(r=>r.replaceAll(i.sep,"/")))}.map(exactSpecifierPattern) } },`:""}
158
- reporters: [[${JSON.stringify(N("node-vitest"))}, ${JSON.stringify(e.report)}]],
178
+ ${e.runtimeInput.expectedProfileId==="node-vitest-data"?`runner: ${JSON.stringify(m)},`:""}
179
+ // The installed SDK is compiled JavaScript. Use its native module instance
180
+ // for setup and consumer imports instead of transforming a second instance.
181
+ server: { deps: { external: ${JSON.stringify([e.entrypoints.config,...e.runtimeInput.expectedProfileId==="node-vitest-data"?[m,e.entrypoints.data]:[]].map(r=>r.replaceAll(i.sep,"/")))}.map(exactSpecifierPattern) } },
182
+ reporters: [[${JSON.stringify(S("node-vitest"))}, ${JSON.stringify(e.report)}]],
159
183
  allowOnly: false,
160
184
  disableConsoleIntercept: true,
161
185
  environment: "node",
@@ -174,4 +198,4 @@ export default {
174
198
  watch: false,
175
199
  },
176
200
  };
177
- `}const T=new Set(["!","(",")","*","+","?","@","[","\\","]","{","}"]);export function toVitestIncludePattern(e){return Array.from(e.replaceAll(i.sep,"/"),t=>T.has(t)?`\\${t}`:t).join("")}
201
+ `}const v=new Set(["!","(",")","*","+","?","@","[","\\","]","{","}"]);export function toVitestIncludePattern(e){return Array.from(e.replaceAll(i.sep,"/"),t=>v.has(t)?`\\${t}`:t).join("")}
@@ -49,6 +49,7 @@ export interface InstallNodeVitestRuntimeInputRequest {
49
49
  readonly expectedInvocationId: string;
50
50
  readonly expectedProfileId: NodeVitestProfileId;
51
51
  readonly expectedSha256: string;
52
+ readonly expectedSizeBytes: number;
52
53
  readonly expectedTaskId: string;
53
54
  }
54
55
  export declare function writeOwnedNodeVitestRuntimeInput(taskDirectory: string, document: NodeVitestRuntimeInputDocument): Promise<OwnedNodeVitestRuntimeInput>;
@@ -1 +1 @@
1
- import m from"node:fs/promises";import w from"node:path";import{readVerifiedOwnedFile as f,writeOwnedRuntimeInput as S,RUNTIME_INPUT_BYTE_MEASURE as O}from"../../runtime-input-file.js";import{requireBoundedJsonValue as h,RUNTIME_INPUT_JSON_BOUNDS as V}from"../../contracts/json-bounds.js";import{ENGINE_PROCESS_RESERVED_ENVIRONMENT_KEYS as l}from"../../../process/environment.js";import{installManagedProcessEnvironmentOverlay as R}from"../../../process/environment-overlay.js";export const NODE_VITEST_RUNTIME_INPUT_VERSION=1,NODE_VITEST_RUNTIME_INPUT_FILENAME="runtime-input.json",NODE_VITEST_RUNTIME_INPUT_FILE_ENV="TESTKIT_NODE_VITEST_INPUT_FILE",NODE_VITEST_RUNTIME_INPUT_SHA256_ENV="TESTKIT_NODE_VITEST_INPUT_SHA256",NODE_VITEST_RUNTIME_INPUT_SYMBOL="elench.testkit.node-vitest.runtime-input.v1",NODE_VITEST_DATA_BUDGET_FAILURE_DETAIL_KIND="data-flow-budget",NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_KIND="testkit.failure-details",NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_NAME="data-flow-failure-details";const E={filename:NODE_VITEST_RUNTIME_INPUT_FILENAME,label:"Node/Vitest runtime input",maxSizeBytes:8*1024*1024},N=512,p=4*1024*1024,x=new Set(["BASE_URL","FORCE_COLOR","NODE_OPTIONS","NO_COLOR","UPDATE_SNAPSHOT",...l].map(t=>t.toUpperCase()));export const MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS=24*1024;export async function writeOwnedNodeVitestRuntimeInput(t,e){return S(t,e,E)}export async function verifyOwnedNodeVitestRuntimeInput(t){await f(t,E)}export function selectNodeVitestForwardedEnvironment(t){const e={...t};return _(e),e}export function assertNodeVitestSpawnEnvironmentBudget(t){if(Object.entries(t).reduce((n,[o,r])=>n+o.length+r.length+2,1)>MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS)throw new Error(`Node/Vitest process environment exceeds the ${MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS.toString()} character environment-block budget`)}export async function installNodeVitestRuntimeInput(t){const e=process.env[NODE_VITEST_RUNTIME_INPUT_FILE_ENV],n=process.env[NODE_VITEST_RUNTIME_INPUT_SHA256_ENV];if(e!==t.expectedFile)throw new Error("Node/Vitest runtime input pointer does not match its engine-owned path");if(n!==t.expectedSha256)throw new Error("Node/Vitest runtime input digest does not match its prepared value");const o=await m.lstat(t.expectedFile),r={directory:w.dirname(t.expectedFile),file:t.expectedFile,sha256:t.expectedSha256,sizeBytes:o.size},c=await f(r,E);let i;try{i=JSON.parse(c)}catch(u){throw new Error("Node/Vitest runtime input contains invalid JSON",{cause:u})}const s=a(I(i));if(s.invocationId!==t.expectedInvocationId||s.profileId!==t.expectedProfileId||s.taskId!==t.expectedTaskId)throw new Error("Node/Vitest runtime input identity does not match its task");R({environment:s.environment,label:"Node/Vitest"}),Object.defineProperty(globalThis,Symbol.for(NODE_VITEST_RUNTIME_INPUT_SYMBOL),{configurable:!0,enumerable:!1,value:s,writable:!1}),applyNodeVitestEngineEnvironment()}export function applyNodeVitestEngineEnvironment(){const t=readNodeVitestRuntimeInput();if(t===null)throw new Error("Node/Vitest runtime input is not installed");const e=t.context.baseUrl;typeof e=="string"?process.env.BASE_URL=e:Reflect.deleteProperty(process.env,"BASE_URL")}export function readNodeVitestRuntimeInput(){const t=Reflect.get(globalThis,Symbol.for(NODE_VITEST_RUNTIME_INPUT_SYMBOL));return t===void 0?null:a(I(t,"Installed Node/Vitest runtime input"))}function I(t,e="Node/Vitest runtime input"){if(h(t,e,V,O),!d(t))throw new Error(`${e} must contain an object`);const n=new Set(["context","engineId","environment","fixtures","invocationId","profileId","setupData","taskId","version"]);for(const r of Object.keys(t))if(!n.has(r))throw new Error(`${e} contains unknown field ${r}`);if(t.version!==NODE_VITEST_RUNTIME_INPUT_VERSION)throw new Error(`${e}.version must be 1`);if(t.engineId!=="node-vitest")throw new Error(`${e}.engineId must be "node-vitest"`);if(!A(t.profileId))throw new Error(`${e}.profileId is invalid`);if(!d(t.context)||!d(t.fixtures))throw new Error(`${e} context and fixtures must be objects`);const o=y(t.environment,`${e}.environment`);return _(o),{context:t.context,engineId:"node-vitest",environment:o,fixtures:t.fixtures,invocationId:T(t.invocationId,`${e}.invocationId`),profileId:t.profileId,...Object.hasOwn(t,"setupData")?{setupData:t.setupData}:{},taskId:T(t.taskId,`${e}.taskId`),version:NODE_VITEST_RUNTIME_INPUT_VERSION}}function _(t){const e=Object.entries(t);if(e.length>N)throw new Error(`Node/Vitest forwarded environment exceeds the ${N.toString()} entry limit`);let n=0;const o=new Set;for(const[r,c]of e){D(r,c,"Node/Vitest forwarded environment");const i=r.toUpperCase();if(o.has(i))throw new Error(`Node/Vitest forwarded environment contains case-insensitive duplicate key ${r}`);if(o.add(i),i.startsWith("TESTKIT_")||i.startsWith("VITEST_")||x.has(i))throw new Error(`Node/Vitest forwarded environment contains reserved key ${r}`);n+=Buffer.byteLength(r)+Buffer.byteLength(c)+2}if(n>p)throw new Error(`Node/Vitest forwarded environment exceeds the ${p.toString()} byte limit`)}function y(t,e){if(!d(t))throw new Error(`${e} must be an object`);const n=Object.entries(t);if(!n.every(o=>typeof o[1]=="string"))throw new Error(`${e} values must all be strings`);return Object.fromEntries(n)}function D(t,e,n){if(t.length===0||t.includes("=")||t.includes("\0"))throw new Error(`${n} contains an invalid variable name`);if(e.includes("\0"))throw new Error(`${n}.${t} contains a NUL character`)}function a(t){if(t!==null&&typeof t=="object"&&!Object.isFrozen(t)){Object.freeze(t);for(const e of Object.values(t))a(e)}return t}function T(t,e){if(typeof t!="string"||t.length===0)throw new Error(`${e} must be a non-empty string`);return t}function A(t){return t==="node-vitest-contract"||t==="node-vitest-data"}function d(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}
1
+ import m from"node:path";import{readVerifiedOwnedFile as a,writeOwnedRuntimeInput as w,RUNTIME_INPUT_BYTE_MEASURE as S}from"../../runtime-input-file.js";import{requireBoundedJsonValue as O,RUNTIME_INPUT_JSON_BOUNDS as h}from"../../contracts/json-bounds.js";import{ENGINE_PROCESS_RESERVED_ENVIRONMENT_KEYS as V}from"../../../process/environment.js";import{installManagedProcessEnvironmentOverlay as R}from"../../../process/environment-overlay.js";export const NODE_VITEST_RUNTIME_INPUT_VERSION=1,NODE_VITEST_RUNTIME_INPUT_FILENAME="runtime-input.json",NODE_VITEST_RUNTIME_INPUT_FILE_ENV="TESTKIT_NODE_VITEST_INPUT_FILE",NODE_VITEST_RUNTIME_INPUT_SHA256_ENV="TESTKIT_NODE_VITEST_INPUT_SHA256",NODE_VITEST_RUNTIME_INPUT_SYMBOL="elench.testkit.node-vitest.runtime-input.v1",NODE_VITEST_DATA_BUDGET_FAILURE_DETAIL_KIND="data-flow-budget",NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_KIND="testkit.failure-details",NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_NAME="data-flow-failure-details";const d={filename:NODE_VITEST_RUNTIME_INPUT_FILENAME,label:"Node/Vitest runtime input",maxSizeBytes:8*1024*1024},f=512,p=4*1024*1024,l=new Set(["BASE_URL","FORCE_COLOR","NODE_OPTIONS","NO_COLOR","UPDATE_SNAPSHOT",...V].map(e=>e.toUpperCase()));export const MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS=24*1024;export async function writeOwnedNodeVitestRuntimeInput(e,t){return w(e,t,d)}export async function verifyOwnedNodeVitestRuntimeInput(e){await a(e,d)}export function selectNodeVitestForwardedEnvironment(e){const t={...e};return _(t),t}export function assertNodeVitestSpawnEnvironmentBudget(e){if(Object.entries(e).reduce((n,[i,r])=>n+i.length+r.length+2,1)>MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS)throw new Error(`Node/Vitest process environment exceeds the ${MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS.toString()} character environment-block budget`)}export async function installNodeVitestRuntimeInput(e){const t=process.env[NODE_VITEST_RUNTIME_INPUT_FILE_ENV],n=process.env[NODE_VITEST_RUNTIME_INPUT_SHA256_ENV];if(t!==e.expectedFile)throw new Error("Node/Vitest runtime input pointer does not match its engine-owned path");if(n!==e.expectedSha256)throw new Error("Node/Vitest runtime input digest does not match its prepared value");const i={directory:m.dirname(e.expectedFile),file:e.expectedFile,sha256:e.expectedSha256,sizeBytes:e.expectedSizeBytes},r=await a(i,d);let s;try{s=JSON.parse(r)}catch(u){throw new Error("Node/Vitest runtime input contains invalid JSON",{cause:u})}const o=E(I(s));if(o.invocationId!==e.expectedInvocationId||o.profileId!==e.expectedProfileId||o.taskId!==e.expectedTaskId)throw new Error("Node/Vitest runtime input identity does not match its task");R({environment:o.environment,label:"Node/Vitest"}),Object.defineProperty(globalThis,Symbol.for(NODE_VITEST_RUNTIME_INPUT_SYMBOL),{configurable:!0,enumerable:!1,value:o,writable:!1}),N(o)}export function applyNodeVitestEngineEnvironment(){const e=readNodeVitestRuntimeInput();if(e===null)throw new Error("Node/Vitest runtime input is not installed");N(e)}function N(e){const t=e.context.baseUrl;typeof t=="string"?process.env.BASE_URL=t:Reflect.deleteProperty(process.env,"BASE_URL")}export function readNodeVitestRuntimeInput(){const e=Reflect.get(globalThis,Symbol.for(NODE_VITEST_RUNTIME_INPUT_SYMBOL));return e===void 0?null:E(I(e,"Installed Node/Vitest runtime input"))}function I(e,t="Node/Vitest runtime input"){if(O(e,t,h,S),!c(e))throw new Error(`${t} must contain an object`);const n=new Set(["context","engineId","environment","fixtures","invocationId","profileId","setupData","taskId","version"]);for(const r of Object.keys(e))if(!n.has(r))throw new Error(`${t} contains unknown field ${r}`);if(e.version!==NODE_VITEST_RUNTIME_INPUT_VERSION)throw new Error(`${t}.version must be 1`);if(e.engineId!=="node-vitest")throw new Error(`${t}.engineId must be "node-vitest"`);if(!D(e.profileId))throw new Error(`${t}.profileId is invalid`);if(!c(e.context)||!c(e.fixtures))throw new Error(`${t} context and fixtures must be objects`);const i=x(e.environment,`${t}.environment`);return _(i),{context:e.context,engineId:"node-vitest",environment:i,fixtures:e.fixtures,invocationId:T(e.invocationId,`${t}.invocationId`),profileId:e.profileId,...Object.hasOwn(e,"setupData")?{setupData:e.setupData}:{},taskId:T(e.taskId,`${t}.taskId`),version:NODE_VITEST_RUNTIME_INPUT_VERSION}}function _(e){const t=Object.entries(e);if(t.length>f)throw new Error(`Node/Vitest forwarded environment exceeds the ${f.toString()} entry limit`);let n=0;const i=new Set;for(const[r,s]of t){y(r,s,"Node/Vitest forwarded environment");const o=r.toUpperCase();if(i.has(o))throw new Error(`Node/Vitest forwarded environment contains case-insensitive duplicate key ${r}`);if(i.add(o),o.startsWith("TESTKIT_")||o.startsWith("VITEST_")||l.has(o))throw new Error(`Node/Vitest forwarded environment contains reserved key ${r}`);n+=Buffer.byteLength(r)+Buffer.byteLength(s)+2}if(n>p)throw new Error(`Node/Vitest forwarded environment exceeds the ${p.toString()} byte limit`)}function x(e,t){if(!c(e))throw new Error(`${t} must be an object`);const n=Object.entries(e);if(!n.every(i=>typeof i[1]=="string"))throw new Error(`${t} values must all be strings`);return Object.fromEntries(n)}function y(e,t,n){if(e.length===0||e.includes("=")||e.includes("\0"))throw new Error(`${n} contains an invalid variable name`);if(t.includes("\0"))throw new Error(`${n}.${e} contains a NUL character`)}function E(e){if(e!==null&&typeof e=="object"&&!Object.isFrozen(e)){Object.freeze(e);for(const t of Object.values(e))E(t)}return e}function T(e,t){if(typeof e!="string"||e.length===0)throw new Error(`${t} must be a non-empty string`);return e}function D(e){return e==="node-vitest-contract"||e==="node-vitest-data"}function c(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}
@@ -6,9 +6,11 @@ export declare const RUNTIME_INPUT_BYTE_MEASURE: {
6
6
  /**
7
7
  * Single-writer protocol for the files a host prepares inside an owned task
8
8
  * directory and later hands to a guest process by pointer + digest. Writes are
9
- * exclusive-create (0o600), fsynced, and atomically renamed into place; reads
9
+ * exclusive-create (0o600), closed, and atomically renamed into place; reads
10
10
  * re-verify the file is still an owned regular file with the prepared size,
11
11
  * owner-only permissions, identity realpath, and matching SHA-256 digest.
12
+ * These disposable handoffs require process-lifetime visibility, not persistence
13
+ * across machine power loss; durable evidence uses its separate synced writer.
12
14
  */
13
15
  export interface OwnedFileProtocol {
14
16
  readonly filename: string;
@@ -1 +1 @@
1
- import u from"node:crypto";import a from"node:fs/promises";import o from"node:path";import{serializeBoundedJson as c,RUNTIME_INPUT_JSON_BOUNDS as h}from"./contracts/json-bounds.js";import{readStableEvidenceFile as y,evidenceFileAccess as b}from"./adapters/evidence-files.js";export const ownedRuntimeInputModuleUrl=new URL("../../dist/engines/runtime-input-file.js",import.meta.url).href,RUNTIME_INPUT_BYTE_MEASURE={unitLabel:"bytes",unitsOf:t=>Buffer.byteLength(t,"utf8")};export async function writeOwnedRuntimeInput(t,e,i){const n=c(e,i.label,h,RUNTIME_INPUT_BYTE_MEASURE),s=await m(t,i.label),w=Buffer.byteLength(n);if(w>i.maxSizeBytes)throw new Error(`${i.label} exceeds the ${i.maxSizeBytes.toString()} byte limit`);const f=o.join(s,i.filename),l=o.join(s,`.${i.filename}.${process.pid.toString()}.${u.randomUUID()}.tmp`);let r=null;try{r=await a.open(l,"wx",384),await r.writeFile(n,"utf8"),await r.sync(),await r.close(),r=null,await a.rename(l,f),await a.chmod(f,384)}catch(d){throw await r?.close().catch(()=>{}),await a.rm(l,{force:!0}).catch(()=>{}),d}return{directory:s,file:f,sha256:u.createHash("sha256").update(n,"utf8").digest("hex"),sizeBytes:w}}export async function readVerifiedOwnedFile(t,e){const i=await m(t.directory,e.label);if(o.dirname(t.file)!==i)throw new Error(`${e.label} escaped its owned task directory`);const n=await a.lstat(t.file,{bigint:!0});if(!n.isFile()||n.isSymbolicLink())throw new Error(`${e.label} must be an owned regular file`);if(n.size!==BigInt(t.sizeBytes)||n.size>BigInt(e.maxSizeBytes))throw new Error(`${e.label} size changed after preparation`);if(process.platform!=="win32"&&(n.mode&63n)!==0n)throw new Error(`${e.label} permissions must be owner-only`);const s=await a.realpath(t.file);if(s!==t.file||o.dirname(s)!==i)throw new Error(`${e.label} resolved outside its owned task directory`);const{contents:w,sha256:f}=await y(s,e.maxSizeBytes,e.label,b,n),l=Buffer.from(t.sha256),r=Buffer.from(f);if(l.length!==r.length||!u.timingSafeEqual(r,l))throw new Error(`${e.label} integrity check failed`);return w.toString("utf8")}async function m(t,e){if(!o.isAbsolute(t))throw new Error(`${e} directory must be absolute`);const i=await a.realpath(t);if(!(await a.stat(i)).isDirectory())throw new Error(`${e} directory must be a directory`);return i}
1
+ import u from"node:crypto";import a from"node:fs/promises";import o from"node:path";import{serializeBoundedJson as c,RUNTIME_INPUT_JSON_BOUNDS as h}from"./contracts/json-bounds.js";import{readStableEvidenceFile as y,evidenceFileAccess as b}from"./adapters/evidence-files.js";export const ownedRuntimeInputModuleUrl=new URL("../../dist/engines/runtime-input-file.js",import.meta.url).href,RUNTIME_INPUT_BYTE_MEASURE={unitLabel:"bytes",unitsOf:t=>Buffer.byteLength(t,"utf8")};export async function writeOwnedRuntimeInput(t,e,i){const r=c(e,i.label,h,RUNTIME_INPUT_BYTE_MEASURE),s=await m(t,i.label),w=Buffer.byteLength(r);if(w>i.maxSizeBytes)throw new Error(`${i.label} exceeds the ${i.maxSizeBytes.toString()} byte limit`);const f=o.join(s,i.filename),l=o.join(s,`.${i.filename}.${process.pid.toString()}.${u.randomUUID()}.tmp`);let n=null;try{n=await a.open(l,"wx",384),await n.writeFile(r,"utf8"),await n.close(),n=null,await a.rename(l,f),await a.chmod(f,384)}catch(d){throw await n?.close().catch(()=>{}),await a.rm(l,{force:!0}).catch(()=>{}),d}return{directory:s,file:f,sha256:u.createHash("sha256").update(r,"utf8").digest("hex"),sizeBytes:w}}export async function readVerifiedOwnedFile(t,e){const i=await m(t.directory,e.label);if(o.dirname(t.file)!==i)throw new Error(`${e.label} escaped its owned task directory`);const r=await a.lstat(t.file,{bigint:!0});if(!r.isFile()||r.isSymbolicLink())throw new Error(`${e.label} must be an owned regular file`);if(r.size!==BigInt(t.sizeBytes)||r.size>BigInt(e.maxSizeBytes))throw new Error(`${e.label} size changed after preparation`);if(process.platform!=="win32"&&(r.mode&63n)!==0n)throw new Error(`${e.label} permissions must be owner-only`);const s=await a.realpath(t.file);if(s!==t.file||o.dirname(s)!==i)throw new Error(`${e.label} resolved outside its owned task directory`);const{contents:w,sha256:f}=await y(s,e.maxSizeBytes,e.label,b,r),l=Buffer.from(t.sha256),n=Buffer.from(f);if(l.length!==n.length||!u.timingSafeEqual(n,l))throw new Error(`${e.label} integrity check failed`);return w.toString("utf8")}async function m(t,e){if(!o.isAbsolute(t))throw new Error(`${e} directory must be absolute`);const i=await a.realpath(t);if(!(await a.stat(i)).isDirectory())throw new Error(`${e} directory must be a directory`);return i}
@@ -1,4 +1,5 @@
1
1
  import type { SupervisedProcessHandle, SupervisedProcessReady } from "../process/index.js";
2
+ export { isRunManifestActive } from "../process/runs/inspection.js";
2
3
  import type { RuntimeInstanceContext } from "./runtime-contexts.js";
3
4
  import type { LocalServiceLifecycle } from "./services.js";
4
5
  import { type ProductLifecycleFence } from "../process/lifecycle-fence.js";
@@ -66,6 +67,4 @@ export declare function createUnpublishedRunLifecycle(productDir: string, option
66
67
  export declare function cleanupOwnedRunLifecycleWithinLifecycleFence(fence: Readonly<ProductLifecycleFence>, lifecycle: Readonly<RunLifecycle>): boolean;
67
68
  export declare function readOwnedRunLifecycleWithinLifecycleFence(fence: Readonly<ProductLifecycleFence>, lifecycle: Readonly<RunLifecycle>): OwnedRunLifecycleState;
68
69
  export declare function findPortOwner(productDir: string, { host, port }: Readonly<RunPortQuery>): RunPortOwner | null;
69
- export declare function isRunManifestActive(manifest: Readonly<RunManifest>): boolean;
70
70
  export declare function disposeFailedRunLifecycle(lifecycle: RunLifecycle): void;
71
- export {};
@@ -1 +1 @@
1
- import x from"crypto";import{isTerminalRunStatus as U}from"../process/runs/schema.js";import{observeProcessCreationIdentity as M}from"../process/index.js";import{normalizeOperationReason as _}from"../domain/operation-reason.js";import{finalizeLiveRunArtifact as B}from"../evidence/index.js";import{captureProcessCreationIdentity as Y}from"../process/service-processes.js";import{requireProductLifecycleFenceProductDir as J,withProductLifecycleFence as K}from"../process/lifecycle-fence.js";import{createRunManifest as Q,listRunManifests as $,readRunMaintenanceStopRequest as X,removeRunManifestFile as G,requireSafeRunId as Z,RUN_MANIFEST_SCHEMA_VERSION as ee,runManifestPath as te,writeRunManifest as ne}from"../process/runs/manifest.js";const re=1e3,ie=String,T=new WeakMap;export function createUnpublishedRunLifecycle(n,r={}){const i=r.runId===void 0?le():Z(r.runId,"Testkit run id"),a=te(n,i),s=new AbortController,m=new AbortController,u=r.pid===void 0?process.pid:r.pid;if(!Number.isSafeInteger(u)||u<0)throw new Error("Testkit run pid must be a non-negative safe integer");const b=u>0?Y(u):null;if(u>0&&b===null)throw new Error(`Could not capture the Testkit run process identity for pid ${u.toString()}`);let f={incarnationId:x.randomUUID(),schemaVersion:ee,runId:i,pid:u,processIdentity:typeof b=="string"?b:null,controller:se(r.controller),status:"starting",startedAt:new Date().toISOString(),heartbeatAt:r.heartbeat===!1?null:new Date().toISOString(),interruptReason:null,services:[],runtimeDirs:[]};const O=[];let w,D=null;const y=new Set,S=new Set,P=r.keepAlive===!0?setInterval(()=>{},re):null;let R=null,g=null,v=null,q=!1;function j(e){const t=T.get(I);if(!t)throw new Error("Testkit run lifecycle ownership is unavailable");if(!t.published){Q(n,e),t.published=!0;return}ne(n,e)}function A(){const e=X(n,i);return e===null||e.targetIncarnationId!==f.incarnationId||e.targetPid!==u||e.targetProcessIdentity!==f.processIdentity||e.coordinatorPid===D?.coordinatorPid&&e.coordinatorProcessIdentity===D.coordinatorProcessIdentity?null:M(e.coordinatorPid,e.coordinatorProcessIdentity)==="different"?(D=e,null):e}function h(e){k("mutate run state");const t=oe(f);e(t),t.heartbeatAt!==null&&(t.heartbeatAt=new Date().toISOString()),j(t),f=t}function k(e){if(g!==null)throw new Error(`Cannot ${e} after Testkit run ${i} finalization is committed`)}function W(){R||(R=setInterval(()=>{},1e3))}function N(){R&&(clearInterval(R),R=null)}function z(){for(const e of S)clearInterval(e);S.clear()}function F(e="controller process exited before Testkit run finalized"){if(q||U(f.status))return!1;const t=_(e,"controller process exited before Testkit run finalized"),o=new Date,l=f.status==="interrupting"||s.signal.aborted?"interrupted":"failed",c=l==="interrupted"?f.interruptReason??v??t:t;return h(p=>{p.status=l,p.finishedAt=o.toISOString(),p.interruptReason=c,p.heartbeatAt!==null&&(p.heartbeatAt=o.toISOString())}),B(n,{runId:i,reason:c,status:l,finishedAt:o}),!0}const H=()=>{F()};process.on("exit",H);const I={runId:i,manifestPath:a,maintenanceStopSignal:m.signal,signal:s.signal,commitFinalization(e="finished"){if(g!==null)return g;const t=A(),o=s.signal.aborted||t!==null||f.status==="interrupting",l=o?f.interruptReason??v??t?.reason??"run interrupted":null,c=Object.freeze({interruptReason:l,interrupted:o});return h(p=>{p.status=o?"interrupted":e,p.finishedAt=new Date().toISOString(),p.interruptReason=l}),g=c,z(),c},getInterruptReason(){return f.interruptReason??v},isMaintenanceStopCoordinated(){return A()!==null},isStopRequested(){return s.signal.aborted||g?.interrupted===!0},markRunning(){h(e=>{e.status="running"})},markFinished(e="finished"){h(t=>{t.status=e,t.finishedAt=new Date().toISOString()})},heartbeat(e={}){h(t=>{t.heartbeatAt=new Date().toISOString(),e&&typeof e=="object"&&!Array.isArray(e)&&(t.session={...t.session||{},...e})})},startHeartbeat(e=5e3,t=null){k("start a run heartbeat");const o=Number.isFinite(Number(e))&&Number(e)>0?Math.floor(Number(e)):5e3,l=setInterval(()=>{let c;try{c=typeof t=="function"?t():{}}catch{c={}}I.heartbeat(c)},o);return l.unref(),S.add(l),()=>{clearInterval(l),S.delete(l)}},requestStop(e="interrupted"){if(g!==null||U(f.status))return;const t=A(),o=t?t.reason:_(e,"interrupted"),l=!s.signal.aborted;l&&(v=o),W();let c=null;if(l)try{h(d=>{d.status="interrupting",d.interruptReason=o})}catch(d){c=d instanceof Error?d:new Error("Failed to persist the Testkit stop request",{cause:d})}t!==null&&!m.signal.aborted&&m.abort(new Error(`Testkit run ${i} is being stopped by external maintenance`));const p=[...y].map(({supervisor:d})=>d.stop(t===null?void 0:Date.parse(t.requestedAt)+d.shutdownTimeoutMs));if(Promise.allSettled(p).then(N),s.signal.aborted||s.abort(new Error(`testkit run interrupted (${o})`)),!!l&&c!==null)throw c},trackGraphContext(e){h(t=>{ue(t.runtimeDirs,e.runtimeDir)})},registerService(e,t,o,l){if(k("register application service ownership"),o.pid!==t.pid||(process.platform==="win32"?o.recovery.processGroupId!==null:o.recovery.processGroupId!==o.pid))throw new Error("Application service supervisor returned an invalid ownership handoff");const c={supervisor:t,unsubscribe:()=>{}};y.add(c);const p=ae(e);h(d=>{d.services=d.services.filter(C=>C.pid!==o.pid),d.services.push({serviceName:e.name,runtimeLabel:e.runtimeLabel,command:e.testkit.local.start||null,cwd:l,pid:o.pid,processGroupId:o.recovery.processGroupId,processIdentity:o.recovery.creationIdentity,ports:p,startedAt:new Date().toISOString(),shutdownTimeoutMs:t.shutdownTimeoutMs,shutdownDeadlineAt:t.shutdownDeadlineAt})}),c.unsubscribe=t.onShutdownStarted(d=>{h(C=>{const E=C.services.find(V=>V.pid===o.pid);E&&(E.shutdownDeadlineAt=E.shutdownDeadlineAt==null?d:Math.min(E.shutdownDeadlineAt,d))})})},unregisterService(e){k("unregister application service ownership");for(const t of y)t.supervisor.pid===e&&(t.unsubscribe(),y.delete(t));h(t=>{t.services=t.services.filter(o=>o.pid!==e)})},installSignalHandlers(){if(!(O.length>0)&&!(u!==process.pid||M(u,f.processIdentity)!=="matches")){for(const e of["SIGINT","SIGTERM","SIGHUP"]){const t=I.requestStop.bind(I,e);process.on(e,t),O.push([e,t])}process.platform==="win32"&&(w=setInterval(()=>{const e=A();e!==null&&(clearInterval(w),I.requestStop(e.reason))},100),w.unref(),S.add(w))}},removeSignalHandlers(){w&&(clearInterval(w),S.delete(w),w=void 0);for(const[e,t]of O.splice(0))process.off(e,t)},async removeManifest(){await K(n,e=>cleanupOwnedRunLifecycleWithinLifecycleFence(e,I))},finalizeForControllerExit:F,dispose(){q=!0;for(const e of y)e.unsubscribe();process.off("exit",H),I.removeSignalHandlers(),P&&clearInterval(P),N(),z()}};return T.set(I,{incarnationId:f.incarnationId,pid:u,processIdentity:f.processIdentity,productDir:n,published:!1,runId:i}),I}function oe(n){const r={...n,controller:n.controller===null?null:{...n.controller},runtimeDirs:[...n.runtimeDirs],services:n.services.map(i=>({...i,ports:i.ports.map(a=>({...a}))}))};return n.session!==void 0&&(r.session=n.session===null?null:{...n.session}),r}function se(n){return!n||typeof n!="object"||Array.isArray(n)?null:Object.fromEntries(Object.entries(n).filter(([,r])=>r!==void 0).map(([r,i])=>[r,i===null?null:ie(i)]))}export function cleanupOwnedRunLifecycleWithinLifecycleFence(n,r){const{manifest:i,ownership:a,productDir:s}=readOwnedRunLifecycleWithinLifecycleFence(n,r);if(!i)return!1;if(i.services.length>0)throw new Error(`Cannot clean Testkit run ${r.runId} service ownership while holding the lifecycle fence`);return G(s,r.runId),a.published=!1,!0}export function readOwnedRunLifecycleWithinLifecycleFence(n,r){const i=J(n),a=T.get(r);if(!a||a.productDir!==i)throw new Error("Cannot clean a Testkit run without matching lifecycle ownership");if(a.runId!==r.runId)throw new Error("Testkit run lifecycle identity has been modified");const s=$(i).find(m=>m.runId===r.runId);if(s!==void 0&&(s.incarnationId!==a.incarnationId||s.pid!==a.pid||s.processIdentity!==a.processIdentity))throw new Error(`Testkit run ${r.runId} ownership no longer matches its manifest`);return{manifest:s??null,ownership:a,productDir:i}}export function findPortOwner(n,{host:r,port:i}){const a=$(n);for(const s of a)for(const m of s.services)for(const u of m.ports)if(L(u.host)===L(r)&&u.port===Number(i))return{manifest:s,service:m,active:isRunManifestActive(s)};return null}export function isRunManifestActive(n){return n.pid>0&&M(n.pid,n.processIdentity)!=="different"}function ae(n){const r=new Set,i=[];for(const a of[n.testkit.local.baseUrl,n.testkit.local.readyUrl])if(a)try{const s=new URL(a),m=L(s.hostname),u=Number(s.port||(s.protocol==="https:"?443:80)),b=`${m}:${u.toString()}`;if(r.has(b))continue;r.add(b),i.push({host:m,port:u})}catch{}return i}function ue(n,r){r&&(n.includes(r)||n.push(r))}function le(){return`${Date.now().toString()}-${process.pid.toString()}-${x.randomBytes(4).toString("hex")}`}function L(n){return n?n==="::1"?"127.0.0.1":n:"127.0.0.1"}export function disposeFailedRunLifecycle(n){const r=T.get(n);n.dispose(),n.removeSignalHandlers(),r?.published===!0&&G(r.productDir,n.runId)}
1
+ import H from"crypto";import{isTerminalRunStatus as x}from"../process/runs/schema.js";import{observeProcessCreationIdentity as U}from"../process/process-identity.js";import{isRunManifestActive as B}from"../process/runs/inspection.js";export{isRunManifestActive}from"../process/runs/inspection.js";import{normalizeOperationReason as _}from"../domain/operation-reason.js";import{finalizeLiveRunArtifact as Y}from"../evidence/index.js";import{captureProcessCreationIdentity as J}from"../process/service-processes.js";import{requireProductLifecycleFenceProductDir as K,withProductLifecycleFence as Q}from"../process/lifecycle-fence.js";import{createRunManifest as X,listRunManifests as $,readRunMaintenanceStopRequest as Z,removeRunManifestFile as G,requireSafeRunId as ee,RUN_MANIFEST_SCHEMA_VERSION as te,runManifestPath as ne,writeRunManifest as re}from"../process/runs/manifest.js";const ie=1e3,oe=String,T=new WeakMap;export function createUnpublishedRunLifecycle(n,r={}){const i=r.runId===void 0?ce():ee(r.runId,"Testkit run id"),a=ne(n,i),s=new AbortController,m=new AbortController,u=r.pid===void 0?process.pid:r.pid;if(!Number.isSafeInteger(u)||u<0)throw new Error("Testkit run pid must be a non-negative safe integer");const b=u>0?J(u):null;if(u>0&&b===null)throw new Error(`Could not capture the Testkit run process identity for pid ${u.toString()}`);let f={incarnationId:H.randomUUID(),schemaVersion:te,runId:i,pid:u,processIdentity:typeof b=="string"?b:null,controller:ae(r.controller),status:"starting",startedAt:new Date().toISOString(),heartbeatAt:r.heartbeat===!1?null:new Date().toISOString(),interruptReason:null,services:[],runtimeDirs:[]};const O=[];let w,D=null;const y=new Set,S=new Set,L=r.keepAlive===!0?setInterval(()=>{},ie):null;let R=null,g=null,v=null,P=!1;function j(e){const t=T.get(I);if(!t)throw new Error("Testkit run lifecycle ownership is unavailable");if(!t.published){X(n,e),t.published=!0;return}re(n,e)}function A(){const e=Z(n,i);return e===null||e.targetIncarnationId!==f.incarnationId||e.targetPid!==u||e.targetProcessIdentity!==f.processIdentity||e.coordinatorPid===D?.coordinatorPid&&e.coordinatorProcessIdentity===D.coordinatorProcessIdentity?null:U(e.coordinatorPid,e.coordinatorProcessIdentity)==="different"?(D=e,null):e}function h(e){k("mutate run state");const t=se(f);e(t),t.heartbeatAt!==null&&(t.heartbeatAt=new Date().toISOString()),j(t),f=t}function k(e){if(g!==null)throw new Error(`Cannot ${e} after Testkit run ${i} finalization is committed`)}function W(){R||(R=setInterval(()=>{},1e3))}function q(){R&&(clearInterval(R),R=null)}function N(){for(const e of S)clearInterval(e);S.clear()}function z(e="controller process exited before Testkit run finalized"){if(P||x(f.status))return!1;const t=_(e,"controller process exited before Testkit run finalized"),o=new Date,l=f.status==="interrupting"||s.signal.aborted?"interrupted":"failed",c=l==="interrupted"?f.interruptReason??v??t:t;return h(p=>{p.status=l,p.finishedAt=o.toISOString(),p.interruptReason=c,p.heartbeatAt!==null&&(p.heartbeatAt=o.toISOString())}),Y(n,{runId:i,reason:c,status:l,finishedAt:o}),!0}const F=()=>{z()};process.on("exit",F);const I={runId:i,manifestPath:a,maintenanceStopSignal:m.signal,signal:s.signal,commitFinalization(e="finished"){if(g!==null)return g;const t=A(),o=s.signal.aborted||t!==null||f.status==="interrupting",l=o?f.interruptReason??v??t?.reason??"run interrupted":null,c=Object.freeze({interruptReason:l,interrupted:o});return h(p=>{p.status=o?"interrupted":e,p.finishedAt=new Date().toISOString(),p.interruptReason=l}),g=c,N(),c},getInterruptReason(){return f.interruptReason??v},isMaintenanceStopCoordinated(){return A()!==null},isStopRequested(){return s.signal.aborted||g?.interrupted===!0},markRunning(){h(e=>{e.status="running"})},markFinished(e="finished"){h(t=>{t.status=e,t.finishedAt=new Date().toISOString()})},heartbeat(e={}){h(t=>{t.heartbeatAt=new Date().toISOString(),e&&typeof e=="object"&&!Array.isArray(e)&&(t.session={...t.session||{},...e})})},startHeartbeat(e=5e3,t=null){k("start a run heartbeat");const o=Number.isFinite(Number(e))&&Number(e)>0?Math.floor(Number(e)):5e3,l=setInterval(()=>{let c;try{c=typeof t=="function"?t():{}}catch{c={}}I.heartbeat(c)},o);return l.unref(),S.add(l),()=>{clearInterval(l),S.delete(l)}},requestStop(e="interrupted"){if(g!==null||x(f.status))return;const t=A(),o=t?t.reason:_(e,"interrupted"),l=!s.signal.aborted;l&&(v=o),W();let c=null;if(l)try{h(d=>{d.status="interrupting",d.interruptReason=o})}catch(d){c=d instanceof Error?d:new Error("Failed to persist the Testkit stop request",{cause:d})}t!==null&&!m.signal.aborted&&m.abort(new Error(`Testkit run ${i} is being stopped by external maintenance`));const p=[...y].map(({supervisor:d})=>d.stop(t===null?void 0:Date.parse(t.requestedAt)+d.shutdownTimeoutMs));if(Promise.allSettled(p).then(q),s.signal.aborted||s.abort(new Error(`testkit run interrupted (${o})`)),!!l&&c!==null)throw c},trackGraphContext(e){h(t=>{le(t.runtimeDirs,e.runtimeDir)})},registerService(e,t,o,l){if(k("register application service ownership"),o.pid!==t.pid||(process.platform==="win32"?o.recovery.processGroupId!==null:o.recovery.processGroupId!==o.pid))throw new Error("Application service supervisor returned an invalid ownership handoff");const c={supervisor:t,unsubscribe:()=>{}};y.add(c);const p=ue(e);h(d=>{d.services=d.services.filter(M=>M.pid!==o.pid),d.services.push({serviceName:e.name,runtimeLabel:e.runtimeLabel,command:e.testkit.local.start||null,cwd:l,pid:o.pid,processGroupId:o.recovery.processGroupId,processIdentity:o.recovery.creationIdentity,ports:p,startedAt:new Date().toISOString(),shutdownTimeoutMs:t.shutdownTimeoutMs,shutdownDeadlineAt:t.shutdownDeadlineAt})}),c.unsubscribe=t.onShutdownStarted(d=>{h(M=>{const E=M.services.find(V=>V.pid===o.pid);E&&(E.shutdownDeadlineAt=E.shutdownDeadlineAt==null?d:Math.min(E.shutdownDeadlineAt,d))})})},unregisterService(e){k("unregister application service ownership");for(const t of y)t.supervisor.pid===e&&(t.unsubscribe(),y.delete(t));h(t=>{t.services=t.services.filter(o=>o.pid!==e)})},installSignalHandlers(){if(!(O.length>0)&&!(u!==process.pid||U(u,f.processIdentity)!=="matches")){for(const e of["SIGINT","SIGTERM","SIGHUP"]){const t=I.requestStop.bind(I,e);process.on(e,t),O.push([e,t])}process.platform==="win32"&&(w=setInterval(()=>{const e=A();e!==null&&(clearInterval(w),I.requestStop(e.reason))},100),w.unref(),S.add(w))}},removeSignalHandlers(){w&&(clearInterval(w),S.delete(w),w=void 0);for(const[e,t]of O.splice(0))process.off(e,t)},async removeManifest(){await Q(n,e=>cleanupOwnedRunLifecycleWithinLifecycleFence(e,I))},finalizeForControllerExit:z,dispose(){P=!0;for(const e of y)e.unsubscribe();process.off("exit",F),I.removeSignalHandlers(),L&&clearInterval(L),q(),N()}};return T.set(I,{incarnationId:f.incarnationId,pid:u,processIdentity:f.processIdentity,productDir:n,published:!1,runId:i}),I}function se(n){const r={...n,controller:n.controller===null?null:{...n.controller},runtimeDirs:[...n.runtimeDirs],services:n.services.map(i=>({...i,ports:i.ports.map(a=>({...a}))}))};return n.session!==void 0&&(r.session=n.session===null?null:{...n.session}),r}function ae(n){return!n||typeof n!="object"||Array.isArray(n)?null:Object.fromEntries(Object.entries(n).filter(([,r])=>r!==void 0).map(([r,i])=>[r,i===null?null:oe(i)]))}export function cleanupOwnedRunLifecycleWithinLifecycleFence(n,r){const{manifest:i,ownership:a,productDir:s}=readOwnedRunLifecycleWithinLifecycleFence(n,r);if(!i)return!1;if(i.services.length>0)throw new Error(`Cannot clean Testkit run ${r.runId} service ownership while holding the lifecycle fence`);return G(s,r.runId),a.published=!1,!0}export function readOwnedRunLifecycleWithinLifecycleFence(n,r){const i=K(n),a=T.get(r);if(!a||a.productDir!==i)throw new Error("Cannot clean a Testkit run without matching lifecycle ownership");if(a.runId!==r.runId)throw new Error("Testkit run lifecycle identity has been modified");const s=$(i).find(m=>m.runId===r.runId);if(s!==void 0&&(s.incarnationId!==a.incarnationId||s.pid!==a.pid||s.processIdentity!==a.processIdentity))throw new Error(`Testkit run ${r.runId} ownership no longer matches its manifest`);return{manifest:s??null,ownership:a,productDir:i}}export function findPortOwner(n,{host:r,port:i}){const a=$(n);for(const s of a)for(const m of s.services)for(const u of m.ports)if(C(u.host)===C(r)&&u.port===Number(i))return{manifest:s,service:m,active:B(s)};return null}function ue(n){const r=new Set,i=[];for(const a of[n.testkit.local.baseUrl,n.testkit.local.readyUrl])if(a)try{const s=new URL(a),m=C(s.hostname),u=Number(s.port||(s.protocol==="https:"?443:80)),b=`${m}:${u.toString()}`;if(r.has(b))continue;r.add(b),i.push({host:m,port:u})}catch{}return i}function le(n,r){r&&(n.includes(r)||n.push(r))}function ce(){return`${Date.now().toString()}-${process.pid.toString()}-${H.randomBytes(4).toString("hex")}`}function C(n){return n?n==="::1"?"127.0.0.1":n:"127.0.0.1"}export function disposeFailedRunLifecycle(n){const r=T.get(n);n.dispose(),n.removeSignalHandlers(),r?.published===!0&&G(r.productDir,n.runId)}