agent-scenario-loop 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/app/profile-session.ts +812 -0
  4. package/core/config-template.json +41 -0
  5. package/dist/core/agent-summary.d.ts +15 -0
  6. package/dist/core/agent-summary.js +177 -0
  7. package/dist/core/artifact-contract.d.ts +151 -0
  8. package/dist/core/artifact-contract.js +897 -0
  9. package/dist/core/artifact-layout.d.ts +56 -0
  10. package/dist/core/artifact-layout.js +61 -0
  11. package/dist/core/artifact-writer.d.ts +44 -0
  12. package/dist/core/artifact-writer.js +55 -0
  13. package/dist/core/comparison.d.ts +133 -0
  14. package/dist/core/comparison.js +294 -0
  15. package/dist/core/evidence-interpreter.d.ts +28 -0
  16. package/dist/core/evidence-interpreter.js +69 -0
  17. package/dist/core/execution-plan.d.ts +44 -0
  18. package/dist/core/execution-plan.js +95 -0
  19. package/dist/core/planner.d.ts +132 -0
  20. package/dist/core/planner.js +812 -0
  21. package/dist/core/ports.d.ts +198 -0
  22. package/dist/core/ports.js +146 -0
  23. package/dist/core/run-index.d.ts +62 -0
  24. package/dist/core/run-index.js +143 -0
  25. package/dist/core/schema-validator.d.ts +86 -0
  26. package/dist/core/schema-validator.js +407 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.js +27 -0
  29. package/dist/runner/agent-device-driver.d.ts +126 -0
  30. package/dist/runner/agent-device-driver.js +168 -0
  31. package/dist/runner/agent-device.d.ts +295 -0
  32. package/dist/runner/agent-device.js +1271 -0
  33. package/dist/runner/android-adb-driver.d.ts +175 -0
  34. package/dist/runner/android-adb-driver.js +399 -0
  35. package/dist/runner/android-adb.d.ts +254 -0
  36. package/dist/runner/android-adb.js +1618 -0
  37. package/dist/runner/argent-driver.d.ts +183 -0
  38. package/dist/runner/argent-driver.js +297 -0
  39. package/dist/runner/argent.d.ts +349 -0
  40. package/dist/runner/argent.js +1211 -0
  41. package/dist/runner/check-plan.d.ts +45 -0
  42. package/dist/runner/check-plan.js +210 -0
  43. package/dist/runner/cli.d.ts +20 -0
  44. package/dist/runner/cli.js +23 -0
  45. package/dist/runner/compare-latest.d.ts +99 -0
  46. package/dist/runner/compare-latest.js +233 -0
  47. package/dist/runner/compare.d.ts +58 -0
  48. package/dist/runner/compare.js +157 -0
  49. package/dist/runner/demo-loop.d.ts +45 -0
  50. package/dist/runner/demo-loop.js +170 -0
  51. package/dist/runner/example-android-live.d.ts +137 -0
  52. package/dist/runner/example-android-live.js +454 -0
  53. package/dist/runner/example-ios-live.d.ts +137 -0
  54. package/dist/runner/example-ios-live.js +471 -0
  55. package/dist/runner/host-doctor.d.ts +131 -0
  56. package/dist/runner/host-doctor.js +628 -0
  57. package/dist/runner/init-project.d.ts +88 -0
  58. package/dist/runner/init-project.js +263 -0
  59. package/dist/runner/ios-simctl-driver.d.ts +69 -0
  60. package/dist/runner/ios-simctl-driver.js +97 -0
  61. package/dist/runner/ios-simctl.d.ts +254 -0
  62. package/dist/runner/ios-simctl.js +1415 -0
  63. package/dist/runner/live-android.d.ts +137 -0
  64. package/dist/runner/live-android.js +539 -0
  65. package/dist/runner/live-comparison.d.ts +67 -0
  66. package/dist/runner/live-comparison.js +147 -0
  67. package/dist/runner/live-ios.d.ts +137 -0
  68. package/dist/runner/live-ios.js +460 -0
  69. package/dist/runner/live-proof-summary.d.ts +263 -0
  70. package/dist/runner/live-proof-summary.js +465 -0
  71. package/dist/runner/live-proof.d.ts +467 -0
  72. package/dist/runner/live-proof.js +920 -0
  73. package/dist/runner/local-env.d.ts +64 -0
  74. package/dist/runner/local-env.js +155 -0
  75. package/dist/runner/profile-android.d.ts +82 -0
  76. package/dist/runner/profile-android.js +671 -0
  77. package/dist/runner/profile-ios.d.ts +108 -0
  78. package/dist/runner/profile-ios.js +532 -0
  79. package/dist/runner/profile-mobile.d.ts +254 -0
  80. package/dist/runner/profile-mobile.js +1307 -0
  81. package/dist/runner/validate-project.d.ts +273 -0
  82. package/dist/runner/validate-project.js +1501 -0
  83. package/docs/adapters.md +145 -0
  84. package/docs/api.md +94 -0
  85. package/docs/authoring.md +196 -0
  86. package/docs/concepts.md +136 -0
  87. package/docs/consumer-rehearsal.md +115 -0
  88. package/docs/contracts.md +267 -0
  89. package/docs/live-proofs.md +270 -0
  90. package/docs/principles.md +46 -0
  91. package/examples/event-logs/app-startup-baseline.log +4 -0
  92. package/examples/event-logs/app-startup-current.log +4 -0
  93. package/examples/minimal-app/README.md +70 -0
  94. package/examples/mobile-app/README.md +302 -0
  95. package/examples/mobile-app/app.json +22 -0
  96. package/examples/mobile-app/asl/package-scripts.json +32 -0
  97. package/examples/mobile-app/asl.config.json +37 -0
  98. package/examples/mobile-app/event-logs/android-app-startup.log +4 -0
  99. package/examples/mobile-app/event-logs/android-open-close-cycle.log +12 -0
  100. package/examples/mobile-app/event-logs/android-scroll-settle.log +12 -0
  101. package/examples/mobile-app/event-logs/app-startup.log +4 -0
  102. package/examples/mobile-app/event-logs/open-close-cycle.log +12 -0
  103. package/examples/mobile-app/event-logs/scroll-settle.log +12 -0
  104. package/examples/mobile-app/index.ts +20 -0
  105. package/examples/mobile-app/metro.config.js +20 -0
  106. package/examples/mobile-app/package.json +62 -0
  107. package/examples/mobile-app/patches/expo-modules-jsi@56.0.10.patch +19 -0
  108. package/examples/mobile-app/plugins/with-ios-build-compat.js +271 -0
  109. package/examples/mobile-app/pnpm-lock.yaml +4440 -0
  110. package/examples/mobile-app/runner-manifests/evidence-provider.json +79 -0
  111. package/examples/mobile-app/runner-manifests/primary-runner.json +19 -0
  112. package/examples/mobile-app/scenarios/android/app-startup-video.json +73 -0
  113. package/examples/mobile-app/scenarios/android/app-startup.json +44 -0
  114. package/examples/mobile-app/scenarios/android/open-close-cycle.json +54 -0
  115. package/examples/mobile-app/scenarios/android/scroll-settle.json +49 -0
  116. package/examples/mobile-app/scenarios/ios/app-startup.json +44 -0
  117. package/examples/mobile-app/scenarios/ios/open-close-cycle.json +54 -0
  118. package/examples/mobile-app/scenarios/ios/scroll-settle.json +49 -0
  119. package/examples/mobile-app/scenarios/mobile/app-startup.json +91 -0
  120. package/examples/mobile-app/scenarios/mobile/open-close-cycle.json +160 -0
  121. package/examples/mobile-app/scenarios/mobile/scroll-settle.json +148 -0
  122. package/examples/mobile-app/scripts/asl-capture-accessibility-provider.mjs +112 -0
  123. package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +127 -0
  124. package/examples/mobile-app/src/devtools/profile-session.ts +7 -0
  125. package/examples/mobile-app/src/example-screen.tsx +322 -0
  126. package/examples/mobile-app/tsconfig.json +16 -0
  127. package/examples/mobile-app/tsconfig.typecheck.json +13 -0
  128. package/examples/runners/README.md +44 -0
  129. package/examples/runners/adb-android.json +25 -0
  130. package/examples/runners/agent-device-android.json +27 -0
  131. package/examples/runners/agent-device-ios.json +27 -0
  132. package/examples/runners/argent-android.json +32 -0
  133. package/examples/runners/argent-ios.json +32 -0
  134. package/examples/runners/argent-react-profiler-provider.json +15 -0
  135. package/examples/runners/axe-accessibility-provider.json +24 -0
  136. package/examples/runners/manual-log-ingest.json +9 -0
  137. package/examples/runners/rozenite-profiler-provider.json +9 -0
  138. package/examples/runners/script-accessibility-provider.json +24 -0
  139. package/examples/runners/script-memory-provider.json +24 -0
  140. package/examples/runners/script-network-provider.json +24 -0
  141. package/examples/runners/script-profiler-provider.json +30 -0
  142. package/examples/runners/xcodebuildmcp-ios.json +29 -0
  143. package/examples/scenarios/ios/app-startup.json +28 -0
  144. package/examples/scenarios/ios/open-close-cycle.json +35 -0
  145. package/examples/scenarios/mobile/app-startup.json +72 -0
  146. package/examples/scenarios/mobile/media-open-close.json +141 -0
  147. package/examples/scenarios/mobile/open-close-cycle.json +135 -0
  148. package/examples/scenarios/mobile/scroll-settle.json +106 -0
  149. package/package.json +240 -0
  150. package/schemas/budget-verdict.schema.json +115 -0
  151. package/schemas/causal-run.schema.json +279 -0
  152. package/schemas/comparison.schema.json +196 -0
  153. package/schemas/health.schema.json +108 -0
  154. package/schemas/live-proof-set.schema.json +195 -0
  155. package/schemas/live-proof.schema.json +413 -0
  156. package/schemas/manifest.schema.json +204 -0
  157. package/schemas/metrics.schema.json +137 -0
  158. package/schemas/project-validation.schema.json +343 -0
  159. package/schemas/runner-capabilities.schema.json +217 -0
  160. package/schemas/scenario.schema.json +400 -0
  161. package/schemas/verdict.schema.json +88 -0
  162. package/templates/evidence-provider.json +83 -0
  163. package/templates/gitignore-snippet +9 -0
  164. package/templates/integration-readme.md +125 -0
  165. package/templates/mobile-scenario.json +133 -0
  166. package/templates/package-scripts.json +32 -0
  167. package/templates/primary-runner.json +19 -0
  168. package/templates/project.config.json +37 -0
  169. package/templates/scripts/asl-capture-accessibility-provider.mjs +112 -0
  170. package/templates/scripts/asl-capture-profiler-provider.mjs +127 -0
@@ -0,0 +1,460 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.assertNoRegressedComparisons = assertNoRegressedComparisons;
5
+ exports.assertAggregatePassed = assertAggregatePassed;
6
+ exports.assertPassedRun = assertPassedRun;
7
+ exports.buildRunId = buildRunId;
8
+ exports.buildSkippedInteractionProofs = buildSkippedInteractionProofs;
9
+ exports.isTrustedProfileRun = isTrustedProfileRun;
10
+ exports.main = main;
11
+ exports.normalizeRunSuffix = normalizeRunSuffix;
12
+ exports.readJson = readJson;
13
+ exports.resolveIosBundleId = resolveIosBundleId;
14
+ exports.resolveScenarioId = resolveScenarioId;
15
+ exports.runIosLiveProof = runIosLiveProof;
16
+ exports.usage = usage;
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+ const { hasHelpFlag, writeUsage } = require('./cli');
20
+ const { parseArgs, parsePositiveInteger, runIosSimctlCapture } = require('./ios-simctl');
21
+ const { compareLiveProfilesToLatest, isEnabledFlag } = require('./live-comparison');
22
+ const { writeLiveProofSummary } = require('./live-proof-summary');
23
+ const { runAgentDeviceCapture } = require('./agent-device');
24
+ const { parseBaseArgs: parseArgentBaseArgs, runArgentCapture } = require('./argent');
25
+ const { loadAslLocalEnv, readStringArgOrEnv } = require('./local-env');
26
+ const { runProfileIos } = require('./profile-ios');
27
+ /**
28
+ * Prints CLI usage.
29
+ *
30
+ * @param {{write: (message: string) => unknown}} [output]
31
+ * @returns {void}
32
+ */
33
+ function usage(output = process.stderr) {
34
+ writeUsage([
35
+ 'Usage: asl-live-ios --config <path> --scenario <path> [--out <dir>] [--bundle <id>] [--device <udid|booted>] [--ios-dev-client-url <url>] [--ios-profile-session-transport storage|deeplink] [--run-id <id>] [--run-suffix <label>] [--compare-latest] [--fail-on-regression] [--agent-device-proof] [--argent-proof]',
36
+ '',
37
+ 'Runs one generic iOS live proof: simctl preflight, profile-session simctl capture, optional sidecars, optional latest-trusted comparison, and aggregate live-proof artifacts.',
38
+ 'Set ASL_IOS_DEV_CLIENT_URL when an Expo dev-client shell must open a specific Metro URL before profile evidence is collected.',
39
+ 'Use --ios-profile-session-transport deeplink when profile-session control should use app URLs instead of simulator storage seeding; storage is the default deterministic startup-control transport.',
40
+ 'Use --agent-device-proof to attach scenario-declared portable driver actions through agent-device; pass --agent-device-session-mode bind when a named session should still receive the configured UDID.',
41
+ 'Use --argent-proof to attach scenario-declared Argent-compatible driver actions; set ASL_ARGENT_BIN and ASL_ARGENT_BASE_ARGS for non-global installs. iOS Argent screenshots fall back to simctl when Argent screenshot is unavailable.',
42
+ ], output);
43
+ }
44
+ /**
45
+ * Reads a JSON object from disk.
46
+ *
47
+ * @param {string} filePath
48
+ * @returns {Record<string, unknown>}
49
+ */
50
+ function readJson(filePath) {
51
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
52
+ }
53
+ /**
54
+ * Reads a nested object property from unknown JSON.
55
+ *
56
+ * @param {Record<string, unknown>} value
57
+ * @param {string} key
58
+ * @returns {Record<string, unknown> | null}
59
+ */
60
+ function readObjectProperty(value, key) {
61
+ const property = value[key];
62
+ return property && typeof property === 'object' && !Array.isArray(property)
63
+ ? property
64
+ : null;
65
+ }
66
+ /**
67
+ * Resolves a stable scenario id from a scenario file.
68
+ *
69
+ * @param {{scenario: Record<string, unknown>, scenarioPath: string}} options
70
+ * @returns {string}
71
+ */
72
+ function resolveScenarioId({ scenario, scenarioPath, }) {
73
+ return typeof scenario.id === 'string' && scenario.id.length > 0
74
+ ? scenario.id
75
+ : path.basename(scenarioPath, '.json');
76
+ }
77
+ /**
78
+ * Resolves the iOS bundle id from explicit input or ASL config.
79
+ *
80
+ * @param {{args: CliArgs, config: Record<string, unknown>}} options
81
+ * @returns {string | null}
82
+ */
83
+ function resolveIosBundleId({ args, config, }) {
84
+ if (typeof args.bundle === 'string') {
85
+ return args.bundle;
86
+ }
87
+ const envBundleId = readStringArgOrEnv(undefined, ['ASL_IOS_APP_ID', 'ASL_EXAMPLE_IOS_APP_ID']);
88
+ if (envBundleId) {
89
+ return envBundleId;
90
+ }
91
+ const app = readObjectProperty(config, 'app');
92
+ return typeof app?.iosBundleId === 'string' ? app.iosBundleId : null;
93
+ }
94
+ /**
95
+ * Resolves optional sibling iOS bundle ids that make simulator targeting ambiguous.
96
+ *
97
+ * @param {Record<string, unknown>} config
98
+ * @returns {string[]}
99
+ */
100
+ function resolveIosConflictingBundleIds(config) {
101
+ const app = readObjectProperty(config, 'app');
102
+ const configured = app?.iosConflictingBundleIds;
103
+ return Array.isArray(configured)
104
+ ? configured.filter((value) => typeof value === 'string' && value.trim().length > 0)
105
+ : [];
106
+ }
107
+ /**
108
+ * Converts an optional run suffix into a path-safe segment.
109
+ *
110
+ * @param {unknown} value
111
+ * @returns {string | null}
112
+ */
113
+ function normalizeRunSuffix(value) {
114
+ if (typeof value !== 'string') {
115
+ return null;
116
+ }
117
+ const normalized = value
118
+ .trim()
119
+ .toLowerCase()
120
+ .replace(/[^a-z0-9._-]+/gu, '-')
121
+ .replace(/^-+|-+$/gu, '')
122
+ .slice(0, 64);
123
+ return normalized.length > 0 ? normalized : null;
124
+ }
125
+ /**
126
+ * Applies an optional suffix to deterministic run ids.
127
+ *
128
+ * @param {string} baseRunId
129
+ * @param {string | null} suffix
130
+ * @returns {string}
131
+ */
132
+ function buildRunId(baseRunId, suffix) {
133
+ return suffix ? `${baseRunId}-${suffix}` : baseRunId;
134
+ }
135
+ /**
136
+ * Resolves how iOS live proof should deliver app-owned profile-session control.
137
+ *
138
+ * @param {CliArgs} args
139
+ * @returns {'storage' | 'deeplink'}
140
+ */
141
+ function resolveIosProfileSessionTransport(args) {
142
+ const value = readStringArgOrEnv(args['ios-profile-session-transport'], [
143
+ 'ASL_IOS_PROFILE_SESSION_TRANSPORT',
144
+ 'ASL_EXAMPLE_IOS_PROFILE_SESSION_TRANSPORT',
145
+ ]);
146
+ if (!value) {
147
+ return 'storage';
148
+ }
149
+ const normalized = value.trim().toLowerCase();
150
+ if (normalized === 'storage' || normalized === 'deeplink') {
151
+ return normalized;
152
+ }
153
+ throw new Error(`Unsupported iOS profile-session transport "${value}". Use storage or deeplink.`);
154
+ }
155
+ /**
156
+ * Throws if a profile or interaction proof failed before aggregate writing.
157
+ *
158
+ * @param {{kind: string, runDir: string, health: Record<string, unknown>, verdict?: Record<string, unknown>}} options
159
+ * @returns {void}
160
+ */
161
+ function assertPassedRun({ health, kind, runDir, verdict, }) {
162
+ if (health.healthStatus === 'passed' && (!verdict || verdict.verdictStatus === 'passed')) {
163
+ return;
164
+ }
165
+ throw new Error([
166
+ `iOS live proof failed for ${kind}.`,
167
+ `Health: ${health.healthStatus ?? 'unknown'}.`,
168
+ ...(verdict ? [`Verdict: ${verdict.verdictStatus ?? 'unknown'}.`] : []),
169
+ `Inspect ${runDir}/agent-summary.md.`,
170
+ ].join(' '));
171
+ }
172
+ /**
173
+ * Reports whether profile evidence is trusted enough to run sidecar interaction proofs.
174
+ *
175
+ * @param {{health: Record<string, unknown>, verdict: Record<string, unknown>}} run
176
+ * @returns {boolean}
177
+ */
178
+ function isTrustedProfileRun({ health, verdict, }) {
179
+ return health.healthStatus === 'passed' && verdict.verdictStatus === 'passed';
180
+ }
181
+ /**
182
+ * Builds skipped sidecar pointers for requested runners when the profile gate failed.
183
+ *
184
+ * @param {{requestedRunners: string[], runIdsByRunner: Record<string, string>, scenarioId: string, profileHealthStatus: unknown, profileVerdictStatus: unknown}} options
185
+ * @returns {SkippedInteractionProof[]}
186
+ */
187
+ function buildSkippedInteractionProofs({ profileHealthStatus, profileVerdictStatus, requestedRunners, runIdsByRunner, scenarioId, }) {
188
+ const reason = `Profile gate failed with health=${String(profileHealthStatus ?? 'unknown')} verdict=${String(profileVerdictStatus ?? 'unknown')}; sidecar interaction proof was skipped because timing and runner evidence would not be trustworthy.`;
189
+ return requestedRunners.map((runnerId) => ({
190
+ label: `interaction-${runnerId}`,
191
+ nextAction: {
192
+ code: 'fix_profile_gate',
193
+ summary: 'Inspect the profile health and verdict before rerunning sidecar interaction proofs.',
194
+ },
195
+ reason,
196
+ runId: runIdsByRunner[runnerId] ?? `${scenarioId}-ios-${runnerId}`,
197
+ runnerId,
198
+ scenarioId,
199
+ }));
200
+ }
201
+ /**
202
+ * Throws after aggregate writing when the live proof itself failed.
203
+ *
204
+ * @param {IosGenericLiveResult} result
205
+ * @returns {void}
206
+ */
207
+ function assertAggregatePassed(result) {
208
+ const proof = readJson(result.aggregateSummary.liveProofPath);
209
+ if (proof.status === 'passed') {
210
+ return;
211
+ }
212
+ throw new Error(`iOS live proof failed. Inspect ${result.aggregateSummary.summaryPath}.`);
213
+ }
214
+ /**
215
+ * Throws after aggregate proof writing when fail-on-regression should gate the run.
216
+ *
217
+ * @param {{result: IosGenericLiveResult, comparisons: Array<{label: string, status: string}>}} options
218
+ * @returns {void}
219
+ */
220
+ function assertNoRegressedComparisons({ comparisons, result, }) {
221
+ const regressed = comparisons.filter((comparison) => comparison.status === 'worse');
222
+ if (regressed.length === 0) {
223
+ return;
224
+ }
225
+ throw new Error(`iOS live proof found regressed comparison(s): ${regressed.map((comparison) => comparison.label).join(', ')}. Inspect ${result.aggregateSummary.summaryPath}.`);
226
+ }
227
+ /**
228
+ * Runs a generic one-scenario iOS live proof.
229
+ *
230
+ * @param {CliArgs} args
231
+ * @param {IosGenericLiveOptions} [options]
232
+ * @returns {Promise<IosGenericLiveResult>}
233
+ */
234
+ async function runIosLiveProof(args, options = {}) {
235
+ if (typeof args.config !== 'string' || typeof args.scenario !== 'string') {
236
+ throw new Error('Both --config and --scenario are required.');
237
+ }
238
+ const configPath = path.resolve(args.config);
239
+ const scenarioPath = path.resolve(args.scenario);
240
+ const config = readJson(configPath);
241
+ const scenario = readJson(scenarioPath);
242
+ const scenarioId = resolveScenarioId({ scenario, scenarioPath });
243
+ const bundleId = resolveIosBundleId({ args, config });
244
+ const deviceId = readStringArgOrEnv(args.device, ['ASL_IOS_UDID', 'ASL_EXAMPLE_IOS_UDID']);
245
+ const xcrunPath = readStringArgOrEnv(args.xcrun, ['ASL_XCRUN_PATH', 'ASL_IOS_XCRUN_BIN']);
246
+ const agentDeviceSession = readStringArgOrEnv(args['agent-device-session'], [
247
+ 'ASL_IOS_AGENT_DEVICE_SESSION',
248
+ 'ASL_EXAMPLE_IOS_AGENT_DEVICE_SESSION',
249
+ ]);
250
+ const agentDeviceSessionMode = readStringArgOrEnv(args['agent-device-session-mode'], [
251
+ 'ASL_IOS_AGENT_DEVICE_SESSION_MODE',
252
+ 'ASL_EXAMPLE_IOS_AGENT_DEVICE_SESSION_MODE',
253
+ ]);
254
+ const iosDevClientUrl = readStringArgOrEnv(args['ios-dev-client-url'], [
255
+ 'ASL_IOS_DEV_CLIENT_URL',
256
+ 'ASL_EXAMPLE_IOS_DEV_CLIENT_URL',
257
+ ]);
258
+ const iosDevClientWaitMs = readStringArgOrEnv(args['ios-dev-client-wait-ms'], [
259
+ 'ASL_IOS_DEV_CLIENT_WAIT_MS',
260
+ 'ASL_EXAMPLE_IOS_DEV_CLIENT_WAIT_MS',
261
+ ]);
262
+ const iosProfileSessionTransport = resolveIosProfileSessionTransport(args);
263
+ const iosProfileSessionStorageKey = readStringArgOrEnv(args['ios-profile-session-storage-key'], [
264
+ 'ASL_IOS_PROFILE_SESSION_STORAGE_KEY',
265
+ 'ASL_EXAMPLE_IOS_PROFILE_SESSION_STORAGE_KEY',
266
+ ]);
267
+ const iosProfileCommandStorageKey = readStringArgOrEnv(args['ios-profile-command-storage-key'], [
268
+ 'ASL_IOS_PROFILE_COMMAND_STORAGE_KEY',
269
+ 'ASL_EXAMPLE_IOS_PROFILE_COMMAND_STORAGE_KEY',
270
+ ]);
271
+ const iosProfileEventStorageKey = readStringArgOrEnv(args['ios-profile-event-storage-key'], [
272
+ 'ASL_IOS_PROFILE_EVENT_STORAGE_KEY',
273
+ 'ASL_EXAMPLE_IOS_PROFILE_EVENT_STORAGE_KEY',
274
+ ]);
275
+ const iosProfileSignalStorageKey = readStringArgOrEnv(args['ios-profile-signal-storage-key'], [
276
+ 'ASL_IOS_PROFILE_SIGNAL_STORAGE_KEY',
277
+ 'ASL_EXAMPLE_IOS_PROFILE_SIGNAL_STORAGE_KEY',
278
+ ]);
279
+ const iosProfileSessionEntriesStorageKey = readStringArgOrEnv(args['ios-profile-session-entries-storage-key'], [
280
+ 'ASL_IOS_PROFILE_SESSION_ENTRIES_STORAGE_KEY',
281
+ 'ASL_EXAMPLE_IOS_PROFILE_SESSION_ENTRIES_STORAGE_KEY',
282
+ ]);
283
+ const outputDir = typeof args.out === 'string' ? path.resolve(args.out) : path.resolve('artifacts/asl/ios-live');
284
+ const runSuffix = normalizeRunSuffix(args['run-suffix']);
285
+ const aggregateRunId = buildRunId(typeof args['run-id'] === 'string' ? args['run-id'] : 'ios-live-proof', runSuffix);
286
+ const preflightRunId = buildRunId(`${scenarioId}-ios-preflight`, runSuffix);
287
+ const profileRunId = buildRunId(`${scenarioId}-ios-live`, runSuffix);
288
+ const agentDeviceRunId = buildRunId(`${scenarioId}-ios-agent-device`, runSuffix);
289
+ const argentRunId = buildRunId(`${scenarioId}-ios-argent`, runSuffix);
290
+ const enabledInteractionRunners = [
291
+ ...(isEnabledFlag(args['agent-device-proof']) ? ['agent-device'] : []),
292
+ ...(isEnabledFlag(args['argent-proof']) ? ['argent'] : []),
293
+ ];
294
+ const runIdsByRunner = {
295
+ 'agent-device': agentDeviceRunId,
296
+ argent: argentRunId,
297
+ };
298
+ const comparisonLane = enabledInteractionRunners.length > 0
299
+ ? `${scenarioId}-ios-live+${enabledInteractionRunners.join('+')}`
300
+ : `${scenarioId}-ios-live`;
301
+ const preflightDir = path.join(outputDir, '_preflight', preflightRunId);
302
+ const preflight = await runIosSimctlCapture({
303
+ bundleId,
304
+ conflictingBundleIds: resolveIosConflictingBundleIds(config),
305
+ ...(deviceId ? { device: deviceId } : {}),
306
+ ...(options.executor ? { executor: options.executor } : {}),
307
+ outputDir: preflightDir,
308
+ runId: preflightRunId,
309
+ ...(xcrunPath ? { xcrunPath } : {}),
310
+ });
311
+ if (preflight.health.healthStatus !== 'passed') {
312
+ throw new Error(`iOS live proof preflight failed; inspect ${preflight.runDir}/agent-summary.md.`);
313
+ }
314
+ const interactionProofs = [];
315
+ const profile = await runProfileIos({
316
+ config: configPath,
317
+ ...(deviceId ? { device: deviceId } : {}),
318
+ launch: true,
319
+ out: outputDir,
320
+ ...(iosDevClientUrl ? { 'ios-dev-client-url': iosDevClientUrl } : {}),
321
+ ...(iosDevClientWaitMs ? { 'ios-dev-client-wait-ms': iosDevClientWaitMs } : {}),
322
+ 'profile-session': true,
323
+ ...(iosProfileSessionTransport === 'storage' ? { 'profile-session-storage': true } : {}),
324
+ ...(iosProfileSessionStorageKey ? { 'ios-profile-session-storage-key': iosProfileSessionStorageKey } : {}),
325
+ ...(iosProfileCommandStorageKey ? { 'ios-profile-command-storage-key': iosProfileCommandStorageKey } : {}),
326
+ ...(iosProfileEventStorageKey ? { 'ios-profile-event-storage-key': iosProfileEventStorageKey } : {}),
327
+ ...(iosProfileSignalStorageKey ? { 'ios-profile-signal-storage-key': iosProfileSignalStorageKey } : {}),
328
+ ...(iosProfileSessionEntriesStorageKey ? { 'ios-profile-session-entries-storage-key': iosProfileSessionEntriesStorageKey } : {}),
329
+ 'run-id': profileRunId,
330
+ scenario: scenarioPath,
331
+ 'simctl-capture': true,
332
+ 'simctl-out': path.join(outputDir, '_ios-simctl-captures', profileRunId),
333
+ ...(typeof args['wait-ms'] === 'string' ? { 'wait-ms': args['wait-ms'] } : {}),
334
+ ...(bundleId ? { bundle: bundleId } : {}),
335
+ ...(xcrunPath ? { xcrun: xcrunPath } : {}),
336
+ }, {
337
+ comparisonLane,
338
+ ...(options.delay ? { delay: options.delay } : {}),
339
+ ...(options.executor ? { executor: options.executor } : {}),
340
+ });
341
+ const profileTrusted = isTrustedProfileRun({ health: profile.health, verdict: profile.verdict });
342
+ let skippedInteractionProofs = [];
343
+ if (!profileTrusted) {
344
+ skippedInteractionProofs = buildSkippedInteractionProofs({
345
+ profileHealthStatus: profile.health.healthStatus,
346
+ profileVerdictStatus: profile.verdict.verdictStatus,
347
+ requestedRunners: enabledInteractionRunners,
348
+ runIdsByRunner,
349
+ scenarioId,
350
+ });
351
+ }
352
+ if (profileTrusted && isEnabledFlag(args['agent-device-proof'])) {
353
+ const capture = await runAgentDeviceCapture({
354
+ ...(typeof args['agent-device'] === 'string' ? { agentDevicePath: args['agent-device'] } : {}),
355
+ app: bundleId,
356
+ ...(options.agentDeviceExecutor ? { executor: options.agentDeviceExecutor } : {}),
357
+ open: true,
358
+ outputDir: path.join(outputDir, '_agent-device-captures', agentDeviceRunId),
359
+ platform: 'ios',
360
+ runId: agentDeviceRunId,
361
+ scenario,
362
+ ...(deviceId ? { udid: deviceId } : {}),
363
+ ...(agentDeviceSession ? { session: agentDeviceSession } : {}),
364
+ ...(agentDeviceSessionMode
365
+ ? { sessionMode: agentDeviceSessionMode }
366
+ : {}),
367
+ waitMs: parsePositiveInteger(args['agent-device-wait-ms'], 1000),
368
+ });
369
+ interactionProofs.push({
370
+ label: 'interaction-agent-device',
371
+ runDir: capture.runDir,
372
+ runId: agentDeviceRunId,
373
+ runnerId: 'agent-device',
374
+ scenarioId,
375
+ });
376
+ }
377
+ if (profileTrusted && isEnabledFlag(args['argent-proof'])) {
378
+ const argentBaseArgs = parseArgentBaseArgs(process.env.ASL_ARGENT_BASE_ARGS);
379
+ const capture = await runArgentCapture({
380
+ app: bundleId,
381
+ argentCommand: process.env.ASL_ARGENT_BIN || 'argent',
382
+ ...(argentBaseArgs ? { baseArgs: argentBaseArgs } : {}),
383
+ commandTimeoutMs: parsePositiveInteger(process.env.ASL_ARGENT_COMMAND_TIMEOUT_MS, 60_000),
384
+ deviceId: deviceId ?? 'booted',
385
+ ...(options.delay ? { delay: options.delay } : {}),
386
+ ...(options.argentExecutor ? { executor: options.argentExecutor } : {}),
387
+ iosSimctlScreenshotFallback: true,
388
+ ...(options.executor ? { iosSimctlExecutor: options.executor } : {}),
389
+ outputDir: path.join(outputDir, '_argent-captures', argentRunId),
390
+ platform: 'ios',
391
+ runId: argentRunId,
392
+ scenario,
393
+ });
394
+ interactionProofs.push({
395
+ label: 'interaction-argent',
396
+ runDir: capture.runDir,
397
+ runId: argentRunId,
398
+ runnerId: 'argent',
399
+ scenarioId,
400
+ });
401
+ }
402
+ const profiles = [{
403
+ label: scenarioId,
404
+ runDir: profile.runDir,
405
+ runId: profileRunId,
406
+ scenarioId,
407
+ }];
408
+ const comparisons = profileTrusted && isEnabledFlag(args['compare-latest'])
409
+ ? await compareLiveProfilesToLatest({ outputDir, profiles })
410
+ : [];
411
+ const aggregateSummary = await writeLiveProofSummary({
412
+ comparisons,
413
+ interactionProofs,
414
+ outputDir,
415
+ platform: 'ios',
416
+ preflightDir: preflight.runDir,
417
+ preflightRunId,
418
+ profiles,
419
+ runId: aggregateRunId,
420
+ skippedInteractionProofs,
421
+ });
422
+ const result = {
423
+ aggregateSummary,
424
+ outputDir,
425
+ preflightDir: preflight.runDir,
426
+ profileDir: profile.runDir,
427
+ };
428
+ if (isEnabledFlag(args['fail-on-regression'])) {
429
+ assertNoRegressedComparisons({ comparisons, result });
430
+ }
431
+ assertAggregatePassed(result);
432
+ return result;
433
+ }
434
+ /**
435
+ * Runs the generic iOS live CLI.
436
+ *
437
+ * @returns {Promise<void>}
438
+ */
439
+ async function main() {
440
+ const argv = process.argv.slice(2);
441
+ if (hasHelpFlag(argv)) {
442
+ usage(process.stdout);
443
+ return;
444
+ }
445
+ loadAslLocalEnv();
446
+ const args = parseArgs(argv);
447
+ if (typeof args.config !== 'string' || typeof args.scenario !== 'string') {
448
+ usage();
449
+ process.exitCode = 1;
450
+ return;
451
+ }
452
+ const result = await runIosLiveProof(args);
453
+ process.stdout.write(`${result.aggregateSummary.summaryPath}\n`);
454
+ }
455
+ if (require.main === module) {
456
+ main().catch((error) => {
457
+ console.error(error instanceof Error ? error.message : String(error));
458
+ process.exitCode = 1;
459
+ });
460
+ }