@e2edev/e2e 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/README.md +41 -15
  2. package/dist/agent/act.d.ts +1 -1
  3. package/dist/agent/act.js +12 -36
  4. package/dist/agent/act.js.map +1 -1
  5. package/dist/agent/actions.d.ts +2 -2
  6. package/dist/agent/actions.d.ts.map +1 -1
  7. package/dist/agent/actions.js +1 -1
  8. package/dist/agent/default-agent.d.ts +3 -29
  9. package/dist/agent/default-agent.d.ts.map +1 -1
  10. package/dist/agent/default-agent.js +4 -11
  11. package/dist/agent/default-agent.js.map +1 -1
  12. package/dist/agent/error.d.ts +0 -2
  13. package/dist/agent/error.d.ts.map +1 -1
  14. package/dist/agent/error.js +1 -3
  15. package/dist/agent/error.js.map +1 -1
  16. package/dist/agent/executor.d.ts +15 -49
  17. package/dist/agent/executor.d.ts.map +1 -1
  18. package/dist/agent/executor.js +0 -1
  19. package/dist/agent/executor.js.map +1 -1
  20. package/dist/agent/index.js +2 -2
  21. package/dist/agent/invocation.d.ts +6 -4
  22. package/dist/agent/invocation.d.ts.map +1 -1
  23. package/dist/agent/invocation.js +2 -2
  24. package/dist/agent/invocation.js.map +1 -1
  25. package/dist/agent/ledger.d.ts +16 -11
  26. package/dist/agent/ledger.d.ts.map +1 -1
  27. package/dist/agent/ledger.js +5 -8
  28. package/dist/agent/ledger.js.map +1 -1
  29. package/dist/agent/observation.d.ts +6 -6
  30. package/dist/agent/observation.d.ts.map +1 -1
  31. package/dist/agent/observation.js +5 -5
  32. package/dist/agent/observation.js.map +1 -1
  33. package/dist/agent/phases.d.ts +3 -3
  34. package/dist/agent/phases.d.ts.map +1 -1
  35. package/dist/agent/phases.js +4 -4
  36. package/dist/agent/phases.js.map +1 -1
  37. package/dist/agent/primitives.d.ts +4 -25
  38. package/dist/agent/primitives.d.ts.map +1 -1
  39. package/dist/agent/primitives.js +3 -36
  40. package/dist/agent/primitives.js.map +1 -1
  41. package/dist/agent/protocol.d.ts +1 -1
  42. package/dist/agent/protocol.js +1 -1
  43. package/dist/agent/public.d.ts +7 -9
  44. package/dist/agent/public.d.ts.map +1 -1
  45. package/dist/agent/public.js +6 -7
  46. package/dist/agent/public.js.map +1 -1
  47. package/dist/agent/replay.d.ts +1 -1
  48. package/dist/agent/replay.d.ts.map +1 -1
  49. package/dist/agent/secrets.d.ts +2 -2
  50. package/dist/agent/secrets.d.ts.map +1 -1
  51. package/dist/agent/secrets.js +3 -3
  52. package/dist/agent/secrets.js.map +1 -1
  53. package/dist/agent/tool-loop.d.ts +2 -30
  54. package/dist/agent/tool-loop.d.ts.map +1 -1
  55. package/dist/agent/tool-loop.js +9 -33
  56. package/dist/agent/tool-loop.js.map +1 -1
  57. package/dist/agent/tool.d.ts +4 -5
  58. package/dist/agent/tool.d.ts.map +1 -1
  59. package/dist/agent/tool.js +2 -7
  60. package/dist/agent/tool.js.map +1 -1
  61. package/dist/cache/anchors.d.ts +1 -1
  62. package/dist/cache/anchors.d.ts.map +1 -1
  63. package/dist/cache/identity.d.ts +8 -8
  64. package/dist/cache/identity.d.ts.map +1 -1
  65. package/dist/cache/identity.js +5 -5
  66. package/dist/cache/identity.js.map +1 -1
  67. package/dist/cache/relocate.d.ts +1 -1
  68. package/dist/cache/relocate.d.ts.map +1 -1
  69. package/dist/cli/index.js +3 -3
  70. package/dist/cli/index.js.map +1 -1
  71. package/dist/cli/init/{backends.d.ts → engines.d.ts} +12 -13
  72. package/dist/cli/init/engines.d.ts.map +1 -0
  73. package/dist/cli/init/{backends.js → engines.js} +13 -14
  74. package/dist/cli/init/engines.js.map +1 -0
  75. package/dist/cli/init/scaffold.d.ts +3 -3
  76. package/dist/cli/init/scaffold.d.ts.map +1 -1
  77. package/dist/cli/init/scaffold.js +9 -9
  78. package/dist/cli/init/scaffold.js.map +1 -1
  79. package/dist/cli/init.d.ts.map +1 -1
  80. package/dist/cli/init.js +11 -10
  81. package/dist/cli/init.js.map +1 -1
  82. package/dist/cli/signals.d.ts +1 -1
  83. package/dist/cli/signals.d.ts.map +1 -1
  84. package/dist/cli/signals.js +1 -1
  85. package/dist/cli/signals.js.map +1 -1
  86. package/dist/collect/collect.d.ts +32 -3
  87. package/dist/collect/collect.d.ts.map +1 -1
  88. package/dist/collect/collect.js +95 -17
  89. package/dist/collect/collect.js.map +1 -1
  90. package/dist/collect/registry.js +1 -1
  91. package/dist/collect/registry.js.map +1 -1
  92. package/dist/collect/select.d.ts.map +1 -1
  93. package/dist/collect/select.js +9 -5
  94. package/dist/collect/select.js.map +1 -1
  95. package/dist/config/app.d.ts +76 -19
  96. package/dist/config/app.d.ts.map +1 -1
  97. package/dist/config/app.js +157 -102
  98. package/dist/config/app.js.map +1 -1
  99. package/dist/config/resolve.d.ts +6 -5
  100. package/dist/config/resolve.d.ts.map +1 -1
  101. package/dist/config/resolve.js +35 -32
  102. package/dist/config/resolve.js.map +1 -1
  103. package/dist/{backend → engine}/contract.d.ts +26 -18
  104. package/dist/engine/contract.d.ts.map +1 -0
  105. package/dist/{backend → engine}/contract.js +18 -18
  106. package/dist/engine/contract.js.map +1 -0
  107. package/dist/{backend → engine}/index.d.ts +141 -79
  108. package/dist/engine/index.d.ts.map +1 -0
  109. package/dist/{backend → engine}/index.js +60 -35
  110. package/dist/engine/index.js.map +1 -0
  111. package/dist/engine/session.d.ts +23 -0
  112. package/dist/engine/session.d.ts.map +1 -0
  113. package/dist/{backend → engine}/session.js +55 -51
  114. package/dist/engine/session.js.map +1 -0
  115. package/dist/{backend → engine}/surface.d.ts +11 -11
  116. package/dist/engine/surface.d.ts.map +1 -0
  117. package/dist/{backend → engine}/surface.js +2 -2
  118. package/dist/engine/surface.js.map +1 -0
  119. package/dist/engine/timing.d.ts.map +1 -0
  120. package/dist/engine/timing.js +8 -0
  121. package/dist/engine/timing.js.map +1 -0
  122. package/dist/expect/index.js +1 -1
  123. package/dist/index.d.ts +3 -2
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +1 -1
  126. package/dist/index.js.map +1 -1
  127. package/dist/internal/brands.d.ts +1 -1
  128. package/dist/internal/brands.d.ts.map +1 -1
  129. package/dist/internal/brands.js +1 -1
  130. package/dist/internal/brands.js.map +1 -1
  131. package/dist/internal/errors.d.ts +16 -16
  132. package/dist/internal/errors.d.ts.map +1 -1
  133. package/dist/internal/errors.js +39 -39
  134. package/dist/internal/errors.js.map +1 -1
  135. package/dist/internal/objects.d.ts +21 -0
  136. package/dist/internal/objects.d.ts.map +1 -0
  137. package/dist/internal/objects.js +14 -0
  138. package/dist/internal/objects.js.map +1 -0
  139. package/dist/internal/urls.d.ts +1 -1
  140. package/dist/internal/urls.d.ts.map +1 -1
  141. package/dist/internal/urls.js +6 -1
  142. package/dist/internal/urls.js.map +1 -1
  143. package/dist/locator/engine.d.ts +6 -6
  144. package/dist/locator/engine.d.ts.map +1 -1
  145. package/dist/locator/engine.js +13 -13
  146. package/dist/locator/engine.js.map +1 -1
  147. package/dist/locator/expression.d.ts +4 -2
  148. package/dist/locator/expression.d.ts.map +1 -1
  149. package/dist/locator/expression.js +24 -4
  150. package/dist/locator/expression.js.map +1 -1
  151. package/dist/locator/screen.d.ts +1 -1
  152. package/dist/locator/screen.d.ts.map +1 -1
  153. package/dist/locator/screen.js +2 -2
  154. package/dist/locator/screen.js.map +1 -1
  155. package/dist/report/build.d.ts +9 -8
  156. package/dist/report/build.d.ts.map +1 -1
  157. package/dist/report/build.js +21 -18
  158. package/dist/report/build.js.map +1 -1
  159. package/dist/report/code-frame.d.ts +8 -4
  160. package/dist/report/code-frame.d.ts.map +1 -1
  161. package/dist/report/code-frame.js +23 -18
  162. package/dist/report/code-frame.js.map +1 -1
  163. package/dist/report/debug-steps.js +1 -1
  164. package/dist/report/debug-steps.js.map +1 -1
  165. package/dist/report/format.d.ts +73 -0
  166. package/dist/report/format.d.ts.map +1 -0
  167. package/dist/report/format.js +147 -0
  168. package/dist/report/format.js.map +1 -0
  169. package/dist/report/list.d.ts +96 -47
  170. package/dist/report/list.d.ts.map +1 -1
  171. package/dist/report/list.js +485 -244
  172. package/dist/report/list.js.map +1 -1
  173. package/dist/report/live-window.d.ts +28 -0
  174. package/dist/report/live-window.d.ts.map +1 -0
  175. package/dist/report/live-window.js +102 -0
  176. package/dist/report/live-window.js.map +1 -0
  177. package/dist/run/declared-processes.d.ts +30 -0
  178. package/dist/run/declared-processes.d.ts.map +1 -0
  179. package/dist/run/declared-processes.js +61 -0
  180. package/dist/run/declared-processes.js.map +1 -0
  181. package/dist/run/events.d.ts +30 -2
  182. package/dist/run/events.d.ts.map +1 -1
  183. package/dist/run/events.js.map +1 -1
  184. package/dist/run/execute.d.ts +17 -13
  185. package/dist/run/execute.d.ts.map +1 -1
  186. package/dist/run/execute.js +61 -62
  187. package/dist/run/execute.js.map +1 -1
  188. package/dist/run/fixture-recording.d.ts +10 -4
  189. package/dist/run/fixture-recording.d.ts.map +1 -1
  190. package/dist/run/fixture-recording.js +19 -6
  191. package/dist/run/fixture-recording.js.map +1 -1
  192. package/dist/run/fixtures.d.ts +1 -1
  193. package/dist/run/fixtures.d.ts.map +1 -1
  194. package/dist/run/fixtures.js +33 -41
  195. package/dist/run/fixtures.js.map +1 -1
  196. package/dist/run/in-process.d.ts +1 -1
  197. package/dist/run/in-process.js +1 -1
  198. package/dist/run/managed-process.d.ts +39 -9
  199. package/dist/run/managed-process.d.ts.map +1 -1
  200. package/dist/run/managed-process.js +102 -34
  201. package/dist/run/managed-process.js.map +1 -1
  202. package/dist/run/runner.d.ts +1 -1
  203. package/dist/run/runner.d.ts.map +1 -1
  204. package/dist/run/runner.js +67 -31
  205. package/dist/run/runner.js.map +1 -1
  206. package/dist/run/scheduler.d.ts +4 -3
  207. package/dist/run/scheduler.d.ts.map +1 -1
  208. package/dist/run/scheduler.js +5 -4
  209. package/dist/run/scheduler.js.map +1 -1
  210. package/dist/run/serial.d.ts +9 -1
  211. package/dist/run/serial.d.ts.map +1 -1
  212. package/dist/run/serial.js +2 -0
  213. package/dist/run/serial.js.map +1 -1
  214. package/dist/run/sessions.d.ts +9 -9
  215. package/dist/run/sessions.d.ts.map +1 -1
  216. package/dist/run/sessions.js +10 -10
  217. package/dist/run/sessions.js.map +1 -1
  218. package/dist/run/steps.d.ts +1 -1
  219. package/dist/run/steps.d.ts.map +1 -1
  220. package/dist/run/worker/entry.d.ts +1 -1
  221. package/dist/run/worker/entry.js +2 -2
  222. package/dist/run/worker/entry.js.map +1 -1
  223. package/dist/run/worker/protocol.d.ts +18 -6
  224. package/dist/run/worker/protocol.d.ts.map +1 -1
  225. package/dist/run/worker/protocol.js +1 -1
  226. package/dist/run/worker/protocol.js.map +1 -1
  227. package/dist/run/worker/session.d.ts +2 -2
  228. package/dist/run/worker/session.d.ts.map +1 -1
  229. package/dist/run/worker/session.js +5 -3
  230. package/dist/run/worker/session.js.map +1 -1
  231. package/dist/types.d.ts +36 -37
  232. package/dist/types.d.ts.map +1 -1
  233. package/package.json +5 -5
  234. package/dist/backend/contract.d.ts.map +0 -1
  235. package/dist/backend/contract.js.map +0 -1
  236. package/dist/backend/index.d.ts.map +0 -1
  237. package/dist/backend/index.js.map +0 -1
  238. package/dist/backend/session.d.ts +0 -23
  239. package/dist/backend/session.d.ts.map +0 -1
  240. package/dist/backend/session.js.map +0 -1
  241. package/dist/backend/surface.d.ts.map +0 -1
  242. package/dist/backend/surface.js.map +0 -1
  243. package/dist/backend/timing.d.ts.map +0 -1
  244. package/dist/backend/timing.js +0 -8
  245. package/dist/backend/timing.js.map +0 -1
  246. package/dist/cli/init/backends.d.ts.map +0 -1
  247. package/dist/cli/init/backends.js.map +0 -1
  248. package/dist/report/live-status.d.ts +0 -51
  249. package/dist/report/live-status.d.ts.map +0 -1
  250. package/dist/report/live-status.js +0 -163
  251. package/dist/report/live-status.js.map +0 -1
  252. package/dist/run/fixture-legacy.d.ts +0 -36
  253. package/dist/run/fixture-legacy.d.ts.map +0 -1
  254. package/dist/run/fixture-legacy.js +0 -143
  255. package/dist/run/fixture-legacy.js.map +0 -1
  256. /package/dist/{backend → engine}/timing.d.ts +0 -0
@@ -1,98 +1,74 @@
1
- /** App, command, and service resolution (spec 05-config.md). */
1
+ /**
2
+ * Per-target app resolution (spec 05-config.md): what an engine declares
3
+ * about the app it drives, validated where an error can name the target.
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
2
7
  import { ConfigurationError } from '../internal/errors.js';
8
+ import { obj } from '../internal/objects.js';
3
9
  import { isImplicitTestHost, normalizeBaseUrl } from '../internal/urls.js';
4
10
  import { httpUrl, positiveInt } from './validate.js';
5
- const APP_KEYS = new Set([
6
- 'url',
7
- 'command',
8
- 'readyUrl',
9
- 'services',
10
- 'allowedOrigins',
11
- 'environment',
12
- 'identity',
13
- ]);
14
- /** App keys that make sense without an app URL; every other key needs one. */
15
- const URL_FREE_APP_KEYS = new Set(['url', 'command', 'services']);
16
- export function resolveApp(raw, env) {
17
- if (raw.app !== undefined) {
18
- for (const key of Object.keys(raw.app)) {
19
- if (!APP_KEYS.has(key)) {
20
- throw new ConfigurationError('INVALID_CONFIG', `unknown app config key "${key}"`);
21
- }
22
- }
23
- }
24
- const services = resolveServices(raw.app?.services);
25
- const rawUrl = raw.app?.url ?? env['APP_URL'];
26
- if (rawUrl === undefined || rawUrl === '') {
27
- // Not every surface has an app URL to point at (a device, a desktop
28
- // shell): a synthetic loopback base keeps navigation resolution and
29
- // identity digests defined, and `app.open()` fails loud with
30
- // APP_URL_REQUIRED the moment a test actually needs one. A configured
31
- // app command without a URL is still a mistake worth failing early on.
32
- const orphaned = Object.keys(raw.app ?? {}).filter((key) => !URL_FREE_APP_KEYS.has(key));
33
- if (orphaned.length > 0) {
34
- throw new ConfigurationError('INVALID_CONFIG', `app.${orphaned[0]} requires app.url: set app.url in e2e.config.ts or the APP_URL environment variable`);
35
- }
36
- if (raw.app?.command === undefined) {
37
- return {
38
- configured: false,
39
- base: normalizeBaseUrl('http://127.0.0.1:1'),
40
- readyUrl: 'http://127.0.0.1:1/',
41
- allowedOrigins: [],
42
- environment: 'test',
43
- command: undefined,
44
- services,
45
- identity: undefined,
46
- };
47
- }
48
- throw new ConfigurationError('APP_URL_REQUIRED', 'an app URL is required alongside app.command: set app.url in e2e.config.ts or the APP_URL environment variable');
11
+ const ENVIRONMENTS = new Set(['test', 'staging', 'production']);
12
+ /**
13
+ * Resolves one target's app from its engine's `app` declaration. Every fact
14
+ * is optional: a URL normalizes like any base URL, origins must be serialized
15
+ * origins, a command needs something to poll, services need one readiness
16
+ * contract each, and the identity defaults to where the app is served when
17
+ * the engine gives none. `projectRoot` anchors every command's `log` path.
18
+ */
19
+ export function resolveTargetApp(targetName, engine, projectRoot) {
20
+ const declared = engine?.app ?? {};
21
+ const where = `target "${targetName}" engine ${engine?.name ?? 'none'}`;
22
+ const base = declared.url === undefined ? undefined : normalizeBaseUrl(declared.url);
23
+ const environment = declared.environment ??
24
+ (base !== undefined && !isImplicitTestHost(new URL(base.href).hostname) ? 'production' : 'test');
25
+ if (!ENVIRONMENTS.has(environment)) {
26
+ throw new ConfigurationError('INVALID_CONFIG', `${where} declares invalid app.environment "${String(environment)}"`);
49
27
  }
50
- const base = normalizeBaseUrl(rawUrl);
51
- const baseHost = new URL(base.href).hostname;
52
- // The environment is a label for the report and the cache/session identity
53
- // digest, not a gate: a loopback, `.localhost`, or `.test` host is `test`,
54
- // any other host is `production` unless the config says otherwise.
55
- const environment = raw.app?.environment ?? (isImplicitTestHost(baseHost) ? 'test' : 'production');
56
- if (!['test', 'staging', 'production'].includes(environment)) {
57
- throw new ConfigurationError('INVALID_CONFIG', `invalid app.environment "${environment}"`);
28
+ const allowedOrigins = declared.allowedOrigins ?? (base === undefined ? [] : [base.origin]);
29
+ if (!Array.isArray(allowedOrigins)) {
30
+ throw new ConfigurationError('INVALID_CONFIG', `${where} app.allowedOrigins must be an array`);
58
31
  }
59
- const allowedOrigins = raw.app?.allowedOrigins ?? [base.origin];
60
32
  for (const origin of allowedOrigins) {
61
33
  let parsed;
62
34
  try {
63
35
  parsed = new URL(origin);
64
36
  }
65
37
  catch {
66
- throw new ConfigurationError('INVALID_CONFIG', `invalid allowed origin: ${origin}`);
38
+ throw new ConfigurationError('INVALID_CONFIG', `${where} declares an invalid allowed origin: ${origin}`);
67
39
  }
68
40
  if (parsed.origin !== origin) {
69
- throw new ConfigurationError('INVALID_CONFIG', `allowed origin must be a serialized origin, got ${origin} (expected ${parsed.origin})`);
41
+ throw new ConfigurationError('INVALID_CONFIG', `${where} allowed origin must be a serialized origin, got ${origin} (expected ${parsed.origin})`);
70
42
  }
71
43
  }
72
- const command = raw.app?.command;
73
- if (command !== undefined)
74
- validateCommand(command, 'app.command');
75
- const identity = raw.app?.identity;
44
+ const identity = declared.identity;
76
45
  if (identity !== undefined && (typeof identity !== 'string' || identity.trim() === '')) {
77
- throw new ConfigurationError('INVALID_CONFIG', 'app.identity must be a non-empty string');
46
+ throw new ConfigurationError('INVALID_CONFIG', `${where} app.identity must be a non-empty string`);
47
+ }
48
+ const command = declared.command;
49
+ if (command !== undefined)
50
+ validateCommand(command, `${where} app.command`, projectRoot);
51
+ const readyUrl = httpUrl(declared.readyUrl, `${where} app.readyUrl`) ?? base?.href;
52
+ if (command !== undefined && readyUrl === undefined) {
53
+ throw new ConfigurationError('APP_URL_REQUIRED', `${where} declares app.command without a URL to poll: declare url or readyUrl beside it`);
78
54
  }
79
55
  return {
80
- configured: true,
81
56
  base,
82
- readyUrl: httpUrl(raw.app?.readyUrl, 'app.readyUrl') ?? base.href,
83
57
  allowedOrigins,
84
58
  environment,
59
+ identity: identity ?? (base === undefined ? undefined : `${base.origin}${base.basePath}`),
85
60
  command,
86
- services,
87
- identity,
61
+ readyUrl: command === undefined ? undefined : readyUrl,
62
+ services: resolveServices(declared.services, projectRoot, `${where} app.services`),
88
63
  };
89
64
  }
90
65
  /**
91
- * The shape every spawned command shares: a non-empty executable and, when
92
- * set, positive integer timeouts. A NaN or infinite budget would otherwise
93
- * make the readiness loop spin without a deadline.
66
+ * The shape every spawned command shares: a non-empty executable, when set
67
+ * positive integer timeouts, and when set a `log` path inside the project
68
+ * root. A NaN or infinite budget would otherwise make the readiness loop spin
69
+ * without a deadline; a log outside the root would let config write anywhere.
94
70
  */
95
- function validateCommand(command, label) {
71
+ function validateCommand(command, label, projectRoot) {
96
72
  if (typeof command !== 'object' || command === null) {
97
73
  throw new ConfigurationError('INVALID_CONFIG', `${label} must be an object`);
98
74
  }
@@ -101,42 +77,119 @@ function validateCommand(command, label) {
101
77
  }
102
78
  positiveInt(command.startupTimeout, `${label}.startupTimeout`);
103
79
  positiveInt(command.shutdownTimeout, `${label}.shutdownTimeout`);
80
+ if (command.log !== undefined) {
81
+ if (typeof command.log !== 'string' || command.log.trim() === '') {
82
+ throw new ConfigurationError('INVALID_CONFIG', `${label}.log must be a non-empty path`);
83
+ }
84
+ if (!insideProjectRoot(projectRoot, path.resolve(projectRoot, command.log))) {
85
+ throw new ConfigurationError('INVALID_CONFIG', `${label}.log must be a file inside the project root, got ${JSON.stringify(command.log)}`);
86
+ }
87
+ }
88
+ if (command.reuseExisting !== undefined && typeof command.reuseExisting !== 'boolean') {
89
+ throw new ConfigurationError('INVALID_CONFIG', `${label}.reuseExisting must be a boolean`);
90
+ }
91
+ }
92
+ /** Only a command with a URL to probe can find something already answering there. */
93
+ function rejectReuse(command, label, reason) {
94
+ if (command.reuseExisting !== undefined) {
95
+ throw new ConfigurationError('INVALID_CONFIG', `${label}.reuseExisting needs readyUrl: ${reason}`);
96
+ }
97
+ }
98
+ /**
99
+ * `target` with the symlinks of its deepest existing ancestor resolved and the
100
+ * missing tail appended as written. A log file usually does not exist yet,
101
+ * but the directory a symlink points at does, so this is what the runner
102
+ * would actually open.
103
+ */
104
+ function realpathOfExisting(target) {
105
+ const tail = [];
106
+ let current = target;
107
+ for (;;) {
108
+ try {
109
+ return path.join(fs.realpathSync(current), ...tail);
110
+ }
111
+ catch {
112
+ const parent = path.dirname(current);
113
+ if (parent === current)
114
+ return target;
115
+ tail.unshift(path.basename(current));
116
+ current = parent;
117
+ }
118
+ }
104
119
  }
105
- /** Services carry no name, so messages name one by its position and command line. */
106
- function commandLine(command) {
107
- return [command.executable, ...(command.args ?? [])].join(' ');
120
+ /**
121
+ * Whether `target` is a path strictly below the project root once both are
122
+ * resolved through the filesystem: an in-project symlink pointing outside is
123
+ * rejected, a symlinked project root still counts as the root, and a name
124
+ * that merely starts with `..` (`..logs/out.log`) is an ordinary entry.
125
+ */
126
+ function insideProjectRoot(projectRoot, target) {
127
+ const relative = path.relative(realpathOfExisting(projectRoot), realpathOfExisting(target));
128
+ return (relative !== '' &&
129
+ relative !== '..' &&
130
+ !relative.startsWith(`..${path.sep}`) &&
131
+ !path.isAbsolute(relative));
132
+ }
133
+ /** The longest `name` a service may carry; a label, not a description. */
134
+ const SERVICE_NAME_MAX_LENGTH = 64;
135
+ /**
136
+ * The name a service goes by in errors, reporter output, and the report: the
137
+ * explicit `name`, trimmed, or the executable's base name (`docker`, `pnpm`,
138
+ * and for a shell wrapper just `sh`, which is when an explicit name earns its
139
+ * keep). Explicit names must be unique so two failures never read alike.
140
+ */
141
+ function serviceName(service, position, taken) {
142
+ if (service.name === undefined)
143
+ return path.basename(service.executable);
144
+ const name = typeof service.name === 'string' ? service.name.trim() : '';
145
+ if (name.length === 0 || name.length > SERVICE_NAME_MAX_LENGTH) {
146
+ throw new ConfigurationError('INVALID_CONFIG', `${position}.name must be a non-empty string of at most ${SERVICE_NAME_MAX_LENGTH} characters`);
147
+ }
148
+ if (taken.has(name)) {
149
+ throw new ConfigurationError('INVALID_CONFIG', `${position}.name "${name}" is already used by another service; service names must be unique`);
150
+ }
151
+ taken.add(name);
152
+ return name;
108
153
  }
109
154
  /**
110
- * Resolves `app.services`: every service is a command with exactly one
111
- * readiness contract (`readyUrl` or `waitForExit`), and a `teardown` is a
155
+ * Resolves a declaration's `services`: every service is a command with exactly
156
+ * one readiness contract (`readyUrl` or `waitForExit`), and a `teardown` is a
112
157
  * command of its own. The service fields that only steer the runner
113
- * (`readyUrl`, `waitForExit`, `teardown`) are lifted out of the command.
158
+ * (`name`, `readyUrl`, `waitForExit`, `teardown`) are lifted out of the
159
+ * command; `reuseExisting` stays on it, and only a `readyUrl` service may set it.
160
+ * `projectRoot` anchors each `log` path; `prefix` names the declaring target
161
+ * in errors, so a failing service is traceable to the engine that declared it.
114
162
  */
115
- export function resolveServices(raw) {
163
+ export function resolveServices(raw, projectRoot, prefix = 'app.services') {
116
164
  if (raw === undefined)
117
165
  return [];
118
166
  if (!Array.isArray(raw)) {
119
- throw new ConfigurationError('INVALID_CONFIG', 'app.services must be an array');
167
+ throw new ConfigurationError('INVALID_CONFIG', `${prefix} must be an array`);
120
168
  }
169
+ const names = new Set();
121
170
  return raw.map((service, index) => {
122
- const position = `app.services[${index}]`;
123
- validateCommand(service, position);
124
- const { readyUrl: rawReadyUrl, waitForExit, teardown, ...command } = service;
171
+ const position = `${prefix}[${index}]`;
172
+ validateCommand(service, position, projectRoot);
173
+ const { name: _name, readyUrl: rawReadyUrl, waitForExit, teardown, ...command } = service;
125
174
  const readyUrl = httpUrl(rawReadyUrl, `${position}.readyUrl`);
126
175
  if ((readyUrl !== undefined) === (waitForExit === true)) {
127
176
  throw new ConfigurationError('INVALID_CONFIG', `${position} needs exactly one readiness contract: set readyUrl or waitForExit: true`);
128
177
  }
129
178
  const readiness = readyUrl === undefined ? { waitForExit: true } : { readyUrl };
130
- const label = `${position} (${commandLine(command)})`;
131
- if (teardown !== undefined)
132
- validateCommand(teardown, `${position}.teardown`);
179
+ if (readyUrl === undefined)
180
+ rejectReuse(command, position, 'a waitForExit service has nothing to reuse');
181
+ const name = serviceName(service, position, names);
182
+ const label = `service "${name}"`;
183
+ if (teardown !== undefined) {
184
+ validateCommand(teardown, `${position}.teardown`, projectRoot);
185
+ rejectReuse(teardown, `${position}.teardown`, 'a teardown command has nothing to reuse');
186
+ }
133
187
  return {
134
188
  label,
189
+ name: service.name === undefined ? undefined : name,
135
190
  command,
136
191
  readiness,
137
- teardown: teardown === undefined
138
- ? undefined
139
- : { label: `${label} teardown (${commandLine(teardown)})`, command: teardown },
192
+ teardown: teardown === undefined ? undefined : { label: `${label} teardown`, command: teardown },
140
193
  };
141
194
  });
142
195
  }
@@ -147,22 +200,24 @@ function digestCommand(command) {
147
200
  return { ...rest, env: Object.fromEntries(Object.keys(env).map((key) => [key, { envName: key }])) };
148
201
  }
149
202
  /**
150
- * The digest view of `app`: `app.command.env`, every service's env, and every
151
- * service teardown's env become names, so no environment value contributes to
152
- * the digest. Live values never appear under `app`, so nothing else changes.
203
+ * The declarative part of an engine's `app` manifest as it enters the config
204
+ * digest: hooks stripped, and every `command.env`, service env, and service
205
+ * teardown env value replaced by `{ envName: key }`, so no environment value
206
+ * contributes to the digest.
153
207
  */
154
- export function digestApp(app) {
155
- return {
156
- ...app,
157
- ...(app.command === undefined ? {} : { command: digestCommand(app.command) }),
158
- ...(app.services === undefined
159
- ? {}
160
- : {
161
- services: app.services.map(({ teardown, ...service }) => ({
162
- ...digestCommand(service),
163
- ...(teardown === undefined ? {} : { teardown: digestCommand(teardown) }),
164
- })),
165
- }),
166
- };
208
+ export function digestAppDeclaration(app) {
209
+ const { url, allowedOrigins, environment, identity, command, readyUrl, services } = app;
210
+ return obj({
211
+ url,
212
+ allowedOrigins,
213
+ environment,
214
+ identity,
215
+ readyUrl,
216
+ command: command === undefined ? undefined : digestCommand(command),
217
+ services: services?.map(({ teardown, ...service }) => obj({
218
+ ...digestCommand(service),
219
+ teardown: teardown === undefined ? undefined : digestCommand(teardown),
220
+ })),
221
+ });
167
222
  }
168
223
  //# sourceMappingURL=app.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/config/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/config/app.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  /** Config validation, defaults, and resolution (spec 05-config.md). */
2
2
  import type { ArtifactStore, CacheMode, E2EConfig, Platform, SecretProvider, TraceCacheStore } from '../types.ts';
3
- import { type BackendHandle } from '../backend/index.ts';
3
+ import { type EngineHandle } from '../engine/index.ts';
4
4
  import { type ResolvedAgentConfig, type ResolvedLimits } from './agent.ts';
5
5
  import { type ResolvedApp } from './app.ts';
6
6
  export type { ResolvedAgentConfig, ResolvedLimits } from './agent.ts';
@@ -9,8 +9,10 @@ export interface ResolvedTarget {
9
9
  readonly name: string;
10
10
  readonly index: number;
11
11
  readonly platform: Platform;
12
- /** Validated backend; undefined for an agent-tools-only target. */
13
- readonly backend: BackendHandle | undefined;
12
+ /** Validated engine; undefined for an agent-tools-only target. */
13
+ readonly engine: EngineHandle | undefined;
14
+ /** The app under test, resolved from the engine's declaration. */
15
+ readonly app: ResolvedApp;
14
16
  }
15
17
  export interface ResolvedCredential {
16
18
  readonly name: string;
@@ -25,7 +27,6 @@ export interface ResolvedConfig {
25
27
  readonly projectRoot: string;
26
28
  readonly configPath: string | undefined;
27
29
  readonly ci: boolean;
28
- readonly app: ResolvedApp;
29
30
  readonly targets: readonly ResolvedTarget[];
30
31
  readonly tests: readonly string[];
31
32
  readonly timeout: number;
@@ -36,7 +37,7 @@ export interface ResolvedConfig {
36
37
  readonly retries: number;
37
38
  readonly workers: number;
38
39
  readonly artifacts: readonly ('trace' | 'screenshot')[];
39
- /** True when artifact kinds were set in config, so a backend that cannot produce one is an error. */
40
+ /** True when artifact kinds were set in config, so an engine that cannot produce one is an error. */
40
41
  readonly artifactsExplicit: boolean;
41
42
  /** Host store every produced artifact is handed to; undefined keeps files local only. */
42
43
  readonly artifactStore: ArtifactStore | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/config/resolve.ts"],"names":[],"mappings":"AAAA,uEAAuE;AASvE,OAAO,KAAK,EAEV,aAAa,EACb,SAAS,EACT,SAAS,EAET,QAAQ,EACR,cAAc,EACd,eAAe,EAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAyB,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAEnE,YAAY,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAAC;IAC3C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CACxD;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,OAAO,GAAG,YAAY,CAAC,EAAE,CAAC;IACxD,qGAAqG;IACrG,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,yFAAyF;IACzF,QAAQ,CAAC,aAAa,EAAE,aAAa,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IAC3D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC9D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IAC5C,qCAAqC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IACnD,+DAA+D;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AA+BD,oDAAoD;AACpD,wBAAgB,QAAQ,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAKtE;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAC3B,GAAG,EAAE,SAAS,EACd,OAAO,EAAE;IACP,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB,GACA,cAAc,CAgGhB"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/config/resolve.ts"],"names":[],"mappings":"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"}
@@ -6,15 +6,14 @@ import { ConfigurationError } from '../internal/errors.js';
6
6
  import { canonicalDigest, sha256Hex } from '../internal/ids.js';
7
7
  import { isStepExecutor } from '../agent/executor.js';
8
8
  import { boundedInt, positiveInt } from './validate.js';
9
- import { isBackendHandle } from '../backend/index.js';
9
+ import { isEngineHandle } from '../engine/index.js';
10
10
  import { isModelInstance, resolveAgentConfig, resolveLimits, } from './agent.js';
11
- import { digestApp, resolveApp } from './app.js';
11
+ import { digestAppDeclaration, resolveTargetApp } from './app.js';
12
12
  const TARGET_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
13
- const TARGET_KEYS = new Set(['name', 'platform', 'backend']);
13
+ const TARGET_KEYS = new Set(['name', 'platform', 'engine']);
14
14
  const TOP_LEVEL_KEYS = new Set([
15
15
  'specVersion',
16
16
  'projectId',
17
- 'app',
18
17
  'targets',
19
18
  'tests',
20
19
  'timeout',
@@ -58,8 +57,7 @@ export function resolveConfig(raw, options) {
58
57
  if (raw.specVersion !== undefined && raw.specVersion !== '0.1') {
59
58
  throw new ConfigurationError('INVALID_CONFIG', `unsupported specVersion ${JSON.stringify(raw.specVersion)}; this runner implements 0.1`);
60
59
  }
61
- const targets = resolveTargets(raw);
62
- const app = resolveApp(raw, env);
60
+ const targets = resolveTargets(raw, options.projectRoot);
63
61
  const tests = normalizeTests(raw.tests);
64
62
  const timeout = positiveInt(raw.timeout, 'timeout') ?? 120_000;
65
63
  const launchTimeout = positiveInt(raw.launchTimeout, 'launchTimeout') ?? 60_000;
@@ -102,7 +100,6 @@ export function resolveConfig(raw, options) {
102
100
  projectRoot: options.projectRoot,
103
101
  configPath: options.configPath,
104
102
  ci,
105
- app,
106
103
  targets,
107
104
  tests,
108
105
  timeout,
@@ -129,19 +126,23 @@ export function resolveConfig(raw, options) {
129
126
  * Resolves the `cache` key. The cache is opt-out: an unset key means
130
127
  * `read-write`, so a project earns replay speed without asking for it, and
131
128
  * `cache: 'off'` or `--no-cache` (which wins over the config) turns it off.
132
- * CI forces the default file store from `read-write` down to `read-only`:
133
- * committed caches are untrusted input, and a CI run never publishes what it
134
- * learned (spec 10-determinism.md). A host-supplied `cache.store` is exempt —
135
- * it is not a committed file cache, and the host states its own trust through
136
- * the store's `writable` flag.
129
+ * CI demotes the *defaulted* mode from `read-write` to `read-only`: a
130
+ * committed cache is untrusted input, and a CI run never publishes what it
131
+ * learned unless the project says so (spec 10-determinism.md). An explicit
132
+ * `read-write` in the config is that statement of trust and is honored as
133
+ * written, as is a host-supplied `cache.store`, which states its own trust
134
+ * through the store's `writable` flag.
137
135
  */
138
136
  function resolveCacheConfig(raw, ci, projectRoot, cliMode) {
139
137
  const value = raw.cache;
140
138
  let mode = 'read-write';
139
+ /** Whether the config named a mode; only a defaulted mode is demoted in CI. */
140
+ let explicit = false;
141
141
  let store;
142
142
  let dir;
143
143
  if (typeof value === 'string') {
144
144
  mode = value;
145
+ explicit = true;
145
146
  }
146
147
  else if (value !== undefined) {
147
148
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
@@ -153,6 +154,7 @@ function resolveCacheConfig(raw, ci, projectRoot, cliMode) {
153
154
  }
154
155
  }
155
156
  mode = value.mode ?? 'read-write';
157
+ explicit = value.mode !== undefined;
156
158
  store = value.store;
157
159
  if (store !== undefined && !isTraceCacheStore(store)) {
158
160
  throw new ConfigurationError('INVALID_CONFIG', 'cache.store must implement TraceCacheStore: { writable, read(keyHash), write(keyHash, payload) }');
@@ -169,7 +171,7 @@ function resolveCacheConfig(raw, ci, projectRoot, cliMode) {
169
171
  }
170
172
  if (cliMode !== undefined)
171
173
  mode = cliMode;
172
- if (ci && mode === 'read-write' && store === undefined)
174
+ if (ci && mode === 'read-write' && !explicit && store === undefined)
173
175
  mode = 'read-only';
174
176
  return {
175
177
  mode,
@@ -238,10 +240,10 @@ function isTraceCacheStore(value) {
238
240
  typeof candidate['read'] === 'function' &&
239
241
  typeof candidate['write'] === 'function');
240
242
  }
241
- function resolveTargets(raw) {
243
+ function resolveTargets(raw, projectRoot) {
242
244
  if (raw.targets === undefined) {
243
- throw new ConfigurationError('INVALID_CONFIG', 'targets is required: declare at least one target and the backend that drives it, ' +
244
- 'e.g. targets: [{ name, platform, backend }]');
245
+ throw new ConfigurationError('INVALID_CONFIG', 'targets is required: declare at least one target and the engine that drives it, ' +
246
+ 'e.g. targets: [{ name, platform, engine }]');
245
247
  }
246
248
  if (!Array.isArray(raw.targets) || raw.targets.length === 0) {
247
249
  throw new ConfigurationError('INVALID_CONFIG', 'targets must be a non-empty array');
@@ -257,20 +259,21 @@ function resolveTargets(raw) {
257
259
  seen.add(target.name);
258
260
  for (const key of Object.keys(target)) {
259
261
  if (!TARGET_KEYS.has(key)) {
260
- throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" has unknown key "${key}"; a target is { name, platform, backend? }`);
262
+ throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" has unknown key "${key}"; a target is { name, platform, engine? }`);
261
263
  }
262
264
  }
263
265
  if (typeof target.platform !== 'string' || target.platform.trim() === '') {
264
266
  throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" requires a non-empty platform`);
265
267
  }
266
- if (target.backend !== undefined && !isBackendHandle(target.backend)) {
267
- throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" backend must be a defineBackend(...) handle`);
268
+ if (target.engine !== undefined && !isEngineHandle(target.engine)) {
269
+ throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" engine must be a defineEngine(...) handle`);
268
270
  }
269
271
  return {
270
272
  name: target.name,
271
273
  index,
272
274
  platform: target.platform,
273
- backend: target.backend,
275
+ engine: target.engine,
276
+ app: resolveTargetApp(target.name, target.engine, projectRoot),
274
277
  };
275
278
  });
276
279
  }
@@ -324,7 +327,7 @@ function resolveCredentials(raw, env) {
324
327
  /**
325
328
  * SHA-256/JCS digest of resolved config after replacing credential material
326
329
  * with `{ secretName }` and env values with `{ envName }` (13-reporting.md).
327
- * Live objects (backend handles, model instances) are replaced by their stable
330
+ * Live objects (engine handles, model instances) are replaced by their stable
328
331
  * identity before the JSON clone, so they never enter the digest and cannot
329
332
  * make it nondeterministic across processes.
330
333
  */
@@ -341,11 +344,9 @@ function computeConfigDigest(raw, projectId) {
341
344
  // which is exactly what survives the function-stripping JSON clone below.
342
345
  // Every model slot is reduced to its identity: a live instance carries
343
346
  // provider settings (and possibly credentials) that must never be digested.
344
- // `app` is digested with every command env reduced to names.
345
347
  const rawAgent = raw.agent;
346
348
  const forClone = {
347
349
  ...raw,
348
- ...(raw.app === undefined ? {} : { app: digestApp(raw.app) }),
349
350
  ...(rawAgent === undefined || isStepExecutor(rawAgent)
350
351
  ? {}
351
352
  : {
@@ -382,17 +383,19 @@ function computeConfigDigest(raw, projectId) {
382
383
  }
383
384
  if (raw.targets !== undefined) {
384
385
  sanitized['targets'] = raw.targets.map((target) => {
385
- // A backend handle holds live functions; its digest identity is the
386
- // declaration - name, version, contract version, and capability set.
387
- if (isBackendHandle(target.backend)) {
388
- const { backend, ...rest } = target;
386
+ // An engine handle holds live functions; its digest identity is the
387
+ // declaration - name, version, contract version, capability set, and
388
+ // what it declares about the app under test.
389
+ if (isEngineHandle(target.engine)) {
390
+ const { engine, ...rest } = target;
389
391
  return {
390
392
  ...rest,
391
- backend: {
392
- name: backend.name,
393
- ...(backend.version === undefined ? {} : { version: backend.version }),
394
- spiVersion: backend.spiVersion,
395
- capabilities: [...backend.capabilities].toSorted(),
393
+ engine: {
394
+ name: engine.name,
395
+ ...(engine.version === undefined ? {} : { version: engine.version }),
396
+ spiVersion: engine.spiVersion,
397
+ capabilities: [...engine.capabilities].toSorted(),
398
+ app: digestAppDeclaration(engine.app ?? {}),
396
399
  },
397
400
  };
398
401
  }