agent-scenario-loop 0.1.3 → 0.1.5

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 (52) hide show
  1. package/app/profile-session.ts +263 -17
  2. package/dist/core/artifact-contract.d.ts +6 -4
  3. package/dist/core/artifact-contract.js +164 -15
  4. package/dist/core/artifact-layout.d.ts +2 -0
  5. package/dist/core/artifact-layout.js +2 -0
  6. package/dist/core/planner.js +4 -3
  7. package/dist/core/schema-validator.d.ts +1 -0
  8. package/dist/core/schema-validator.js +1 -0
  9. package/dist/runner/android-adb-driver.d.ts +7 -2
  10. package/dist/runner/android-adb-driver.js +7 -1
  11. package/dist/runner/android-adb.d.ts +40 -5
  12. package/dist/runner/android-adb.js +1046 -664
  13. package/dist/runner/ios-simctl.d.ts +1 -0
  14. package/dist/runner/ios-simctl.js +1 -0
  15. package/dist/runner/profile-android.d.ts +11 -1
  16. package/dist/runner/profile-android.js +266 -25
  17. package/dist/runner/profile-ios.d.ts +3 -2
  18. package/dist/runner/profile-ios.js +252 -22
  19. package/dist/runner/profile-mobile.d.ts +63 -4
  20. package/dist/runner/profile-mobile.js +1002 -20
  21. package/dist/runner/validate-project.js +3 -0
  22. package/dist/scripts/consumer-rehearsal.d.ts +127 -0
  23. package/dist/scripts/consumer-rehearsal.js +774 -0
  24. package/dist/scripts/downstream-local-package-gate.d.ts +2 -0
  25. package/dist/scripts/downstream-local-package-gate.js +264 -0
  26. package/dist/scripts/package-smoke.d.ts +104 -0
  27. package/dist/scripts/package-smoke.js +2304 -0
  28. package/dist/scripts/release-check.d.ts +47 -0
  29. package/dist/scripts/release-check.js +117 -0
  30. package/dist/scripts/release-readiness.d.ts +2 -0
  31. package/dist/scripts/release-readiness.js +539 -0
  32. package/docs/adapters.md +3 -1
  33. package/docs/api.md +2 -2
  34. package/docs/authoring.md +34 -2
  35. package/docs/consumer-rehearsal.md +33 -1
  36. package/docs/contracts.md +16 -2
  37. package/docs/live-proofs.md +12 -4
  38. package/examples/mobile-app/runner-manifests/evidence-provider.json +3 -3
  39. package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +25 -0
  40. package/examples/runners/README.md +3 -3
  41. package/examples/runners/axe-accessibility-provider.json +2 -2
  42. package/examples/runners/script-accessibility-provider.json +2 -2
  43. package/examples/runners/script-memory-provider.json +2 -2
  44. package/examples/runners/script-network-provider.json +2 -2
  45. package/examples/runners/script-profiler-provider.json +2 -2
  46. package/package.json +12 -4
  47. package/schemas/manifest.schema.json +73 -3
  48. package/schemas/profiler.schema.json +243 -0
  49. package/schemas/runner-capabilities.schema.json +8 -2
  50. package/schemas/scenario.schema.json +18 -2
  51. package/templates/evidence-provider.json +3 -3
  52. package/templates/scripts/asl-capture-profiler-provider.mjs +20 -0
@@ -18,7 +18,7 @@ const fs = require('node:fs');
18
18
  const path = require('node:path');
19
19
  const { hasHelpFlag } = require('./cli');
20
20
  const { buildScenarioExecutionPlan } = require('../core/execution-plan');
21
- const { buildProfileHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, readScalarArg, runProfileCli, runProfileMobile, usage, } = require('./profile-mobile');
21
+ const { buildProfileHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, readScalarArg, resolveArtifactRoot, resolveProfileScenarioName, runProfileCompatibilityPreflight, runProfileCli, runProfileMobile, usage, } = require('./profile-mobile');
22
22
  exports.buildProfileHealth = buildProfileHealth;
23
23
  exports.buildProfileVerdict = buildProfileVerdict;
24
24
  exports.buildVerdictBudgetChecks = buildVerdictBudgetChecks;
@@ -28,7 +28,10 @@ const { runIosSimctlCapture } = require('./ios-simctl');
28
28
  const { runAgentDeviceCapture } = require('./agent-device');
29
29
  const { loadAslLocalEnv, readStringArgOrEnv } = require('./local-env');
30
30
  const PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS = 1000;
31
- const PROFILE_SESSION_CAPTURE_MAX_MS = 30000;
31
+ const PROFILE_SESSION_CAPTURE_COMMAND_OVERHEAD_MS = 250;
32
+ const PROFILE_SESSION_CAPTURE_BUFFER_MIN_MS = 2000;
33
+ const PROFILE_SESSION_CAPTURE_BUFFER_RATIO = 0.2;
34
+ const PROFILE_SESSION_CAPTURE_MAX_MS = 10 * 60 * 1000;
32
35
  const DEFAULT_IOS_PROFILE_SESSION_STORAGE_KEY = 'agent-scenario-loop.profile-session.1';
33
36
  const DEFAULT_IOS_PROFILE_COMMAND_STORAGE_KEY = 'agent-scenario-loop.profile-commands.1';
34
37
  const DEFAULT_IOS_PROFILE_EVENT_STORAGE_KEY = 'agent-scenario-loop.profile-events.1';
@@ -49,6 +52,17 @@ const MANIFEST_LIFECYCLE_PHASES = new Set([
49
52
  'reboot',
50
53
  'relaunch',
51
54
  ]);
55
+ const IOS_PROFILE_RUNNER_CAPABILITIES = {
56
+ schemaVersion: '1.0.0',
57
+ runnerId: 'ios-simctl-profile-runner',
58
+ kind: 'primary',
59
+ platforms: ['ios'],
60
+ capabilities: ['launch', 'sessionControl', 'command', 'logCapture', 'artifactWrite'],
61
+ driverActions: ['tap', 'scroll', 'assertVisible', 'inspectTree', 'screenshot', 'readLogs'],
62
+ artifactOutputs: ['logs', 'signals', 'screenshot', 'uiTree'],
63
+ uiContexts: ['app'],
64
+ lifecycle: ['prepare', 'launch', 'startSession', 'executeStep', 'waitForTruthEvent', 'captureEvidence', 'stopSession', 'finalize'],
65
+ };
52
66
  /**
53
67
  * Reads and parses a JSON object from disk.
54
68
  *
@@ -171,7 +185,7 @@ function resolveIosConflictingBundleIds(config) {
171
185
  * @param {{config: Record<string, unknown>, action: 'start' | 'command', scenario: string, runId: string, command?: string}} options
172
186
  * @returns {string}
173
187
  */
174
- function buildProfileSessionUrl({ action, command, commandId, config, queueId, runId, scenario, sequence, waitForMilestone, waitTimeoutMs, }) {
188
+ function buildProfileSessionUrl({ action, command, commandId, config, queueId, runId, scenario, sequence, waitForMilestone, waitMs, waitTimeoutMs, }) {
175
189
  const scheme = typeof config.app?.profileSessionScheme === 'string'
176
190
  ? config.app.profileSessionScheme
177
191
  : typeof config.app?.scheme === 'string'
@@ -192,6 +206,9 @@ function buildProfileSessionUrl({ action, command, commandId, config, queueId, r
192
206
  if (waitForMilestone) {
193
207
  params.set('waitForMilestone', waitForMilestone);
194
208
  }
209
+ if (typeof waitMs === 'number') {
210
+ params.set('waitMs', String(waitMs));
211
+ }
195
212
  if (typeof waitTimeoutMs === 'number') {
196
213
  params.set('waitTimeoutMs', String(waitTimeoutMs));
197
214
  }
@@ -215,24 +232,49 @@ function readStepWaitMs(step) {
215
232
  return readPositiveInteger(step.timeoutMs, 0);
216
233
  }
217
234
  /**
218
- * Derives a storage-backed profile capture window from scenario waits and cycles.
235
+ * Reads wait time from a profile-session command.
236
+ *
237
+ * @param {IosSimctlProfileCommand} command
238
+ * @returns {number}
239
+ */
240
+ function readProfileCommandWindowMs(command) {
241
+ return readPositiveInteger(command.waitMs, 0) +
242
+ readPositiveInteger(command.waitTimeoutMs, 0) +
243
+ PROFILE_SESSION_CAPTURE_COMMAND_OVERHEAD_MS;
244
+ }
245
+ /**
246
+ * Reads execution-plan waits that are not already attached to a profile-session command.
219
247
  *
220
248
  * @param {Record<string, unknown>} scenario
221
249
  * @returns {number}
222
250
  */
223
- function deriveProfileSessionCaptureWaitMs(scenario) {
251
+ function readUnattachedExecutionWaitMs(scenario) {
224
252
  const executionPlan = buildScenarioExecutionPlan(scenario);
225
253
  const iterations = readScenarioIterationCount(scenario);
226
- const perIterationWaitMs = executionPlan.steps.reduce((total, step) => {
227
- if (step.kind === 'command') {
228
- return total + readStepWaitMs(step);
254
+ const perIterationWaitMs = executionPlan.steps.reduce((total, step, index) => {
255
+ if (step.portMethod !== 'waitForTruthEvent') {
256
+ return total;
229
257
  }
230
- if (step.portMethod === 'waitForTruthEvent') {
231
- return total + readPositiveInteger(step.timeoutMs, 0);
258
+ const previousStep = executionPlan.steps[index - 1];
259
+ if (previousStep?.portMethod === 'executeStep') {
260
+ return total;
232
261
  }
233
- return total;
262
+ return total + readPositiveInteger(step.timeoutMs, 0);
234
263
  }, 0);
235
- const derivedWaitMs = PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS + (perIterationWaitMs * iterations);
264
+ return perIterationWaitMs * iterations;
265
+ }
266
+ /**
267
+ * Derives a storage-backed profile capture window from scenario waits, command gates, and cycles.
268
+ *
269
+ * @param {Record<string, unknown>} scenario
270
+ * @returns {number}
271
+ */
272
+ function deriveProfileSessionCaptureWaitMs(scenario) {
273
+ const commands = resolveIosSimctlProfileCommands(scenario);
274
+ const commandWindowMs = commands.reduce((total, command) => total + readProfileCommandWindowMs(command), 0);
275
+ const executionWindowMs = commandWindowMs + readUnattachedExecutionWaitMs(scenario);
276
+ const bufferMs = Math.max(PROFILE_SESSION_CAPTURE_BUFFER_MIN_MS, Math.ceil(executionWindowMs * PROFILE_SESSION_CAPTURE_BUFFER_RATIO));
277
+ const derivedWaitMs = PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS + executionWindowMs + bufferMs;
236
278
  return Math.min(Math.max(derivedWaitMs, PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS), PROFILE_SESSION_CAPTURE_MAX_MS);
237
279
  }
238
280
  /**
@@ -271,16 +313,182 @@ function resolveExecutionPlanProfileCommands(scenario) {
271
313
  waitMs: readStepWaitMs(step),
272
314
  ...(nextStep?.portMethod === 'waitForTruthEvent' && typeof nextStep.milestone === 'string'
273
315
  ? {
274
- waitForMilestone: nextStep.milestone,
316
+ waitForMilestone: resolveMilestoneEventName(scenario, nextStep.milestone),
275
317
  waitTimeoutMs: readPositiveInteger(nextStep.timeoutMs, 0),
276
318
  }
277
319
  : {}),
278
320
  });
279
321
  }
280
- return Array.from({ length: repeat }).flatMap((_, iteration) => commands.map((command, commandIndex) => ({
322
+ return expandProfileCommandCycles(scenario, commands, repeat);
323
+ }
324
+ /**
325
+ * Returns true when a command is part of the setup prefix that establishes app readiness before repeated cycle work.
326
+ *
327
+ * @param {Record<string, unknown>} scenario
328
+ * @param {IosSimctlProfileCommand} command
329
+ * @returns {boolean}
330
+ */
331
+ function isReadinessSetupProfileCommand(scenario, command) {
332
+ if (typeof command.waitForMilestone !== 'string') {
333
+ return false;
334
+ }
335
+ const readyEvent = resolveScenarioReadinessEvent(scenario);
336
+ return typeof readyEvent === 'string' && command.waitForMilestone === readyEvent;
337
+ }
338
+ /**
339
+ * Reads a string id list from scenario cycles metadata.
340
+ *
341
+ * @param {unknown} value
342
+ * @returns {Set<string>}
343
+ */
344
+ function readCycleStepIdSet(value) {
345
+ return new Set(Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : []);
346
+ }
347
+ /**
348
+ * Resolves the milestone ids that represent measured cycle boundaries.
349
+ *
350
+ * @param {Record<string, unknown>} scenario
351
+ * @returns {Set<string>}
352
+ */
353
+ function resolveMeasuredCycleMilestoneEvents(scenario) {
354
+ const milestones = new Set();
355
+ for (const budget of Array.isArray(scenario.budgets) ? scenario.budgets : []) {
356
+ if (!budget || typeof budget !== 'object' || budget.source !== 'milestone') {
357
+ continue;
358
+ }
359
+ if (typeof budget.fromMilestone === 'string') {
360
+ milestones.add(resolveMilestoneEventName(scenario, budget.fromMilestone));
361
+ }
362
+ if (typeof budget.toMilestone === 'string') {
363
+ milestones.add(resolveMilestoneEventName(scenario, budget.toMilestone));
364
+ }
365
+ }
366
+ return milestones;
367
+ }
368
+ /**
369
+ * Resolves how many leading commands are setup-only before repeated cycle work.
370
+ *
371
+ * @param {Record<string, unknown>} scenario
372
+ * @param {IosSimctlProfileCommand[]} commands
373
+ * @returns {number}
374
+ */
375
+ function resolveSetupCommandCount(scenario, commands) {
376
+ const explicitSetupStepIds = readCycleStepIdSet(scenario.cycles?.setupStepIds);
377
+ if (explicitSetupStepIds.size > 0) {
378
+ let count = 0;
379
+ for (const command of commands) {
380
+ if (!command.commandId || !explicitSetupStepIds.has(command.commandId)) {
381
+ break;
382
+ }
383
+ count += 1;
384
+ }
385
+ return count;
386
+ }
387
+ const explicitBodyStepIds = readCycleStepIdSet(scenario.cycles?.bodyStepIds);
388
+ if (explicitBodyStepIds.size > 0) {
389
+ const firstBodyIndex = commands.findIndex((command) => (typeof command.commandId === 'string' && explicitBodyStepIds.has(command.commandId)));
390
+ return firstBodyIndex > 0 ? firstBodyIndex : 0;
391
+ }
392
+ let readinessSetupCommandCount = 0;
393
+ for (const command of commands) {
394
+ if (!isReadinessSetupProfileCommand(scenario, command)) {
395
+ break;
396
+ }
397
+ readinessSetupCommandCount += 1;
398
+ }
399
+ if (readinessSetupCommandCount > 0) {
400
+ return readinessSetupCommandCount;
401
+ }
402
+ const measuredMilestones = resolveMeasuredCycleMilestoneEvents(scenario);
403
+ if (measuredMilestones.size === 0) {
404
+ return 0;
405
+ }
406
+ const firstMeasuredCommandIndex = commands.findIndex((command) => (typeof command.waitForMilestone === 'string' && measuredMilestones.has(command.waitForMilestone)));
407
+ return firstMeasuredCommandIndex > 0 ? firstMeasuredCommandIndex : 0;
408
+ }
409
+ /**
410
+ * Expands commands so setup/readiness commands execute once while cycle-body commands repeat.
411
+ *
412
+ * @param {Record<string, unknown>} scenario
413
+ * @param {IosSimctlProfileCommand[]} commands
414
+ * @param {number} repeat
415
+ * @returns {IosSimctlProfileCommand[]}
416
+ */
417
+ function expandProfileCommandCycles(scenario, commands, repeat) {
418
+ const setupCommandCount = resolveSetupCommandCount(scenario, commands);
419
+ const setupCommands = commands.slice(0, setupCommandCount);
420
+ const cycleCommands = commands.slice(setupCommandCount);
421
+ const expandedCommands = cycleCommands.length === 0
422
+ ? setupCommands
423
+ : [
424
+ ...setupCommands,
425
+ ...Array.from({ length: repeat }).flatMap(() => cycleCommands),
426
+ ];
427
+ return expandedCommands.map((command, index) => ({
281
428
  ...command,
282
- sequence: (iteration * commands.length) + commandIndex + 1,
283
- })));
429
+ sequence: index + 1,
430
+ }));
431
+ }
432
+ /**
433
+ * Resolves a portable milestone id to the app truth event that releases command sequencing.
434
+ *
435
+ * @param {Record<string, unknown>} scenario
436
+ * @param {string} milestone
437
+ * @returns {string}
438
+ */
439
+ function resolveMilestoneEventName(scenario, milestone) {
440
+ const milestoneEntry = Array.isArray(scenario.milestones)
441
+ ? scenario.milestones.find((entry) => entry?.id === milestone)
442
+ : undefined;
443
+ if (typeof milestoneEntry?.event === 'string' && milestoneEntry.event.length > 0) {
444
+ return milestoneEntry.event;
445
+ }
446
+ const metricEvent = scenario.metricEvents?.[milestone];
447
+ return typeof metricEvent === 'string' && metricEvent.length > 0 ? metricEvent : milestone;
448
+ }
449
+ /**
450
+ * Resolves the scenario truth event that represents initial app readiness.
451
+ *
452
+ * @param {Record<string, unknown>} scenario
453
+ * @returns {string | null}
454
+ */
455
+ function resolveScenarioReadinessEvent(scenario) {
456
+ const explicitReadyEvent = scenario.truthEvents?.ready?.event;
457
+ if (typeof explicitReadyEvent === 'string' && explicitReadyEvent.length > 0) {
458
+ return explicitReadyEvent;
459
+ }
460
+ const milestoneEntry = Array.isArray(scenario.milestones)
461
+ ? scenario.milestones.find((entry) => (String(entry?.event ?? '').includes('ready')))
462
+ : undefined;
463
+ return typeof milestoneEntry?.event === 'string' && milestoneEntry.event.length > 0
464
+ ? milestoneEntry.event
465
+ : null;
466
+ }
467
+ /**
468
+ * Applies wait gates from the normalized execution plan to platform-declared commands.
469
+ *
470
+ * @param {Record<string, unknown>} scenario
471
+ * @param {IosSimctlProfileCommand[]} commands
472
+ * @returns {IosSimctlProfileCommand[]}
473
+ */
474
+ function applyExecutionPlanCommandGates(scenario, commands) {
475
+ const planCommands = resolveExecutionPlanProfileCommands(scenario);
476
+ if (planCommands.length === 0) {
477
+ return commands;
478
+ }
479
+ return commands.map((command, index) => {
480
+ const planCommand = planCommands[index];
481
+ if (!planCommand || typeof planCommand.waitForMilestone !== 'string' || typeof command.waitForMilestone === 'string') {
482
+ return command;
483
+ }
484
+ return {
485
+ ...command,
486
+ waitForMilestone: planCommand.waitForMilestone,
487
+ ...(typeof command.waitTimeoutMs === 'number'
488
+ ? {}
489
+ : { waitTimeoutMs: readPositiveInteger(planCommand.waitTimeoutMs, 0) }),
490
+ };
491
+ });
284
492
  }
285
493
  /**
286
494
  * Expands scenario-declared iOS commands for a simctl capture profile session.
@@ -316,7 +524,7 @@ function resolveIosSimctlProfileCommands(scenario) {
316
524
  });
317
525
  }
318
526
  }
319
- return commands;
527
+ return applyExecutionPlanCommandGates(scenario, commands);
320
528
  }
321
529
  /**
322
530
  * Returns true when the scenario asks iOS simctl capture to preserve a screenshot.
@@ -411,6 +619,23 @@ async function runProfileIos(args, options = {}) {
411
619
  const config = readJson(path.resolve(args.config));
412
620
  const scenario = readJson(path.resolve(args.scenario));
413
621
  const runId = typeof args['run-id'] === 'string' ? args['run-id'] : createRunId();
622
+ const scenarioName = resolveProfileScenarioName({ scenario, scenarioPath: path.resolve(args.scenario) });
623
+ const artifactRoot = resolveArtifactRoot({
624
+ args,
625
+ config,
626
+ configPath: path.resolve(args.config),
627
+ platform: 'ios',
628
+ });
629
+ await runProfileCompatibilityPreflight({
630
+ args,
631
+ artifactRoot,
632
+ platform: 'ios',
633
+ primaryRunner: IOS_PROFILE_RUNNER_CAPABILITIES,
634
+ runDir: path.join(artifactRoot, scenarioName, runId),
635
+ runId,
636
+ scenario,
637
+ scenarioName,
638
+ });
414
639
  const profileSessionEnabled = isEnabled(args['profile-session']);
415
640
  const profileSessionStorageEnabled = isEnabled(args['profile-session-storage']);
416
641
  const profileSessionStorageKey = readStringArgOrEnv(args['ios-profile-session-storage-key'], [
@@ -441,7 +666,6 @@ async function runProfileIos(args, options = {}) {
441
666
  'ASL_IOS_DEV_CLIENT_WAIT_MS',
442
667
  'ASL_EXAMPLE_IOS_DEV_CLIENT_WAIT_MS',
443
668
  ]), 1000);
444
- const scenarioName = typeof scenario.name === 'string' ? scenario.name : path.basename(args.scenario, '.json');
445
669
  const profileSessionCommands = profileSessionEnabled ? resolveIosSimctlProfileCommands(scenario) : [];
446
670
  const iosDevClientDeepLinks = iosDevClientUrl
447
671
  ? [
@@ -476,6 +700,7 @@ async function runProfileIos(args, options = {}) {
476
700
  ...(typeof profileCommand.queueId === 'string' ? { queueId: profileCommand.queueId } : {}),
477
701
  ...(typeof profileCommand.sequence === 'number' ? { sequence: profileCommand.sequence } : {}),
478
702
  ...(typeof profileCommand.waitForMilestone === 'string' ? { waitForMilestone: profileCommand.waitForMilestone } : {}),
703
+ ...(typeof profileCommand.waitMs === 'number' ? { waitMs: profileCommand.waitMs } : {}),
479
704
  ...(typeof profileCommand.waitTimeoutMs === 'number' ? { waitTimeoutMs: profileCommand.waitTimeoutMs } : {}),
480
705
  }),
481
706
  waitMs: profileCommand.waitMs,
@@ -514,6 +739,7 @@ async function runProfileIos(args, options = {}) {
514
739
  ...(typeof profileCommand.queueId === 'string' ? { queueId: profileCommand.queueId } : {}),
515
740
  ...(typeof profileCommand.sequence === 'number' ? { sequence: profileCommand.sequence } : {}),
516
741
  ...(typeof profileCommand.waitForMilestone === 'string' ? { waitForMilestone: profileCommand.waitForMilestone } : {}),
742
+ ...(typeof profileCommand.waitMs === 'number' ? { waitMs: profileCommand.waitMs } : {}),
517
743
  ...(typeof profileCommand.waitTimeoutMs === 'number' ? { waitTimeoutMs: profileCommand.waitTimeoutMs } : {}),
518
744
  })),
519
745
  runId,
@@ -580,7 +806,11 @@ async function runProfileIos(args, options = {}) {
580
806
  : baseProfileArgs;
581
807
  const lifecyclePhase = resolveManifestLifecyclePhase(args);
582
808
  const environmentSource = agentDeviceCapture ? 'agent-device' : 'simctl';
583
- const lifecycleArtifact = simctlCapture ? 'raw/ios-simctl-log.txt' : 'raw/interaction.log';
809
+ const copiedSimctlLogArtifact = simctlCapture &&
810
+ !fs.existsSync(path.join(simctlCapture.runDir, 'raw', 'ios-profile-events.log')) &&
811
+ fs.existsSync(path.join(simctlCapture.runDir, 'raw', 'ios-simctl-log.txt'))
812
+ ? 'raw/ios-simctl-log.txt'
813
+ : undefined;
584
814
  return runProfileMobile(profileArgs, {
585
815
  commandTransport: agentDeviceCapture
586
816
  ? 'agent-device'
@@ -596,13 +826,13 @@ async function runProfileIos(args, options = {}) {
596
826
  value: 'foreground',
597
827
  evidence: 'asserted',
598
828
  source: environmentSource,
599
- artifact: lifecycleArtifact,
829
+ ...(copiedSimctlLogArtifact ? { artifact: copiedSimctlLogArtifact } : {}),
600
830
  },
601
831
  lifecyclePhase: {
602
832
  value: 'foreground',
603
833
  evidence: 'asserted',
604
834
  source: environmentSource,
605
- artifact: lifecycleArtifact,
835
+ ...(copiedSimctlLogArtifact ? { artifact: copiedSimctlLogArtifact } : {}),
606
836
  },
607
837
  },
608
838
  environmentPreconditions: {
@@ -615,7 +845,7 @@ async function runProfileIos(args, options = {}) {
615
845
  value: lifecyclePhase,
616
846
  evidence: 'asserted',
617
847
  source: environmentSource,
618
- artifact: lifecycleArtifact,
848
+ ...(copiedSimctlLogArtifact ? { artifact: copiedSimctlLogArtifact } : {}),
619
849
  },
620
850
  },
621
851
  interactionDriver: agentDeviceCapture ? 'agent-device' : 'ios-simctl',
@@ -10,6 +10,7 @@ type CliArgs = {
10
10
  events?: string | boolean;
11
11
  out?: string | boolean;
12
12
  provider?: CliArgValue;
13
+ 'profile-session-entries'?: string | boolean;
13
14
  'run-id'?: string | boolean;
14
15
  signal?: CliArgValue;
15
16
  [key: string]: CliArgValue | undefined;
@@ -19,6 +20,16 @@ type ProfileRunResult = {
19
20
  health: Record<string, unknown>;
20
21
  verdict: Record<string, unknown>;
21
22
  };
23
+ type CompatibilityPreflightOptions = {
24
+ args: CliArgs;
25
+ artifactRoot: string;
26
+ platform: ProfilePlatform;
27
+ primaryRunner: Record<string, unknown>;
28
+ runDir: string;
29
+ runId: string;
30
+ scenario: Record<string, unknown>;
31
+ scenarioName: string;
32
+ };
22
33
  type ProfilePlatform = 'android' | 'ios';
23
34
  type ProfileMobileOptions = {
24
35
  commandTransport?: string;
@@ -35,6 +46,25 @@ type ProviderEvidenceKind = 'accessibility' | 'logs' | 'profiler';
35
46
  type SignalEvidenceKind = 'js' | 'memory' | 'network';
36
47
  type EvidenceChannel = 'capture' | 'provider' | 'signal';
37
48
  type EvidenceKind = CaptureEvidenceKind | ProviderEvidenceKind | SignalEvidenceKind;
49
+ type DiagnosticStatus = 'captured' | 'not_requested' | 'not_supported' | 'unavailable' | 'failed' | 'skipped' | 'missing';
50
+ type DiagnosticKind = EvidenceKind | 'logs';
51
+ type DiagnosticInventoryEntry = {
52
+ kind: DiagnosticKind;
53
+ status: DiagnosticStatus;
54
+ required: boolean;
55
+ name?: string;
56
+ provider?: string;
57
+ runnerId?: string;
58
+ path?: string;
59
+ reason?: string;
60
+ nextAction?: string;
61
+ sidecarRoot?: string;
62
+ evidenceDependency?: {
63
+ kind: string;
64
+ root?: 'run' | 'sidecar';
65
+ path: string;
66
+ };
67
+ };
38
68
  type EvidenceAttachment = {
39
69
  channel: EvidenceChannel;
40
70
  completenessStatus: 'complete';
@@ -43,6 +73,7 @@ type EvidenceAttachment = {
43
73
  kind: EvidenceKind;
44
74
  manifestPath: string;
45
75
  redactionStatus: 'not-redacted';
76
+ required: boolean;
46
77
  sha256: string;
47
78
  sourcePath: string;
48
79
  sourceFileName: string;
@@ -54,6 +85,8 @@ type EvidenceAttachmentInput = {
54
85
  destinationPath: string;
55
86
  kind: EvidenceKind;
56
87
  manifestPath: string;
88
+ providerId?: string;
89
+ required?: boolean;
57
90
  sourcePath: string;
58
91
  };
59
92
  type AttachedEvidence = {
@@ -70,6 +103,7 @@ type ProviderCommandOutput = {
70
103
  channel: EvidenceChannel;
71
104
  kind: EvidenceKind;
72
105
  path: string;
106
+ required?: boolean;
73
107
  };
74
108
  type ProviderCommand = {
75
109
  args?: string[];
@@ -78,7 +112,7 @@ type ProviderCommand = {
78
112
  env?: Record<string, string>;
79
113
  id: string;
80
114
  outputs: ProviderCommandOutput[];
81
- phase: 'prepare' | 'startWindow' | 'capture' | 'stopWindow' | 'finalize';
115
+ phase: 'prepare' | 'startWindow' | 'capture' | 'stopWindow' | 'afterCapture' | 'postRun' | 'finalize';
82
116
  };
83
117
  type ProviderCommandFailure = {
84
118
  commandId: string;
@@ -139,13 +173,18 @@ declare function resolveAttachedEvidence({ args, layout, providerInputs, }: {
139
173
  /**
140
174
  * Builds scenario health from profile metrics.
141
175
  *
142
- * @param {{scenario: Record<string, unknown>, runId: string, metrics: Record<string, unknown>}} options
176
+ * @param {{scenario: Record<string, unknown>, runId: string, metrics: Record<string, unknown>, diagnostics?: DiagnosticInventoryEntry[], profileEventCount?: number, profileSessionEntryCount?: number, commandTransport?: string, sessionEntries?: Record<string, unknown>[]}} options
143
177
  * @returns {Record<string, unknown>}
144
178
  */
145
- declare function buildProfileHealth({ scenario, runId, metrics, }: {
179
+ declare function buildProfileHealth({ scenario, runId, metrics, diagnostics, profileEventCount, profileSessionEntryCount, commandTransport, sessionEntries, }: {
146
180
  scenario: Record<string, any>;
147
181
  runId: string;
148
182
  metrics: Record<string, any>;
183
+ diagnostics?: DiagnosticInventoryEntry[];
184
+ profileEventCount?: number;
185
+ profileSessionEntryCount?: number;
186
+ commandTransport?: string;
187
+ sessionEntries?: Record<string, any>[];
149
188
  }): Record<string, unknown>;
150
189
  /**
151
190
  * Builds failed scenario health from evidence-provider command failures.
@@ -231,6 +270,26 @@ declare function resolveEventLogPath({ args, platform }: {
231
270
  args: CliArgs;
232
271
  platform: ProfilePlatform;
233
272
  }): string | null;
273
+ /**
274
+ * Resolves the profile scenario name from modern or legacy scenario identity fields.
275
+ *
276
+ * @param {{scenario: Record<string, unknown>, scenarioPath: string}} options
277
+ * @returns {string}
278
+ */
279
+ declare function resolveProfileScenarioName({ scenario, scenarioPath, }: {
280
+ scenario: Record<string, unknown>;
281
+ scenarioPath: string;
282
+ }): string;
283
+ /**
284
+ * Runs planner compatibility before a live profile capture starts.
285
+ *
286
+ * Failed compatibility writes classified artifacts in the profile run folder so
287
+ * agents can stop before adb, simctl, or provider work consumes runtime time.
288
+ *
289
+ * @param {CompatibilityPreflightOptions} options
290
+ * @returns {Promise<void>}
291
+ */
292
+ declare function runProfileCompatibilityPreflight({ args, artifactRoot, platform, primaryRunner, runDir, runId, scenario, scenarioName, }: CompatibilityPreflightOptions): Promise<void>;
234
293
  /**
235
294
  * Creates a stable fingerprint for the scenario contract used by one run.
236
295
  *
@@ -258,5 +317,5 @@ declare function runProfileCli({ argv, binaryName, defaultDriver, platform, }: {
258
317
  defaultDriver: string;
259
318
  platform: ProfilePlatform;
260
319
  }): Promise<void>;
261
- export { buildProfileHealth, buildProviderCommandFailureHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, buildEvidenceAttachmentManifest, readScalarArg, resolveAppId, resolveArtifactRoot, resolveAttachedEvidence, resolveComparisonLane, resolveEventLogPath, resolveInteractionDriver, runProfileCli, runProfileMobile, hashScenarioContract, usage, };
320
+ export { buildProfileHealth, buildProviderCommandFailureHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, buildEvidenceAttachmentManifest, readScalarArg, resolveAppId, resolveArtifactRoot, resolveAttachedEvidence, resolveComparisonLane, resolveEventLogPath, resolveInteractionDriver, resolveProfileScenarioName, runProfileCompatibilityPreflight, runProfileCli, runProfileMobile, hashScenarioContract, usage, };
262
321
  export type { CliArgs, ProfileMobileOptions, ProfilePlatform, ProfileRunResult, };