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,454 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.assertAggregatePassed = assertAggregatePassed;
5
+ exports.formatResult = formatResult;
6
+ exports.assertNoRegressedComparisons = assertNoRegressedComparisons;
7
+ exports.buildLiveRunId = buildLiveRunId;
8
+ exports.buildSkippedInteractionProofs = buildSkippedInteractionProofs;
9
+ exports.buildInteractionComparisonLane = buildInteractionComparisonLane;
10
+ exports.isTrustedProfileRun = isTrustedProfileRun;
11
+ exports.main = main;
12
+ exports.normalizeRunSuffix = normalizeRunSuffix;
13
+ exports.resolveAndroidSerial = resolveAndroidSerial;
14
+ exports.runExampleAndroidLiveProof = runExampleAndroidLiveProof;
15
+ exports.usage = usage;
16
+ const fs = require('node:fs');
17
+ const path = require('node:path');
18
+ const { hasHelpFlag, writeUsage } = require('./cli');
19
+ const { parsePositiveInteger, parseArgs, runAndroidAdbPreflight, } = require('./android-adb');
20
+ const { compareLiveProfilesToLatest, isEnabledFlag, } = require('./live-comparison');
21
+ const { writeLiveProofSummary } = require('./live-proof-summary');
22
+ const { runAgentDeviceCapture } = require('./agent-device');
23
+ const { parseBaseArgs: parseArgentBaseArgs, runArgentCapture } = require('./argent');
24
+ const { loadAslLocalEnv, readStringArgOrEnv } = require('./local-env');
25
+ const { runProfileAndroid } = require('./profile-android');
26
+ const EXAMPLE_PROFILES = [
27
+ {
28
+ label: 'startup',
29
+ runId: 'android-live-startup',
30
+ scenario: 'app-startup.json',
31
+ scenarioId: 'app-startup',
32
+ },
33
+ {
34
+ label: 'open-close',
35
+ runId: 'android-live-open-close',
36
+ scenario: 'open-close-cycle.json',
37
+ scenarioId: 'open-close-cycle',
38
+ },
39
+ {
40
+ label: 'scroll',
41
+ runId: 'android-live-scroll',
42
+ scenario: 'scroll-settle.json',
43
+ scenarioId: 'scroll-settle',
44
+ },
45
+ ];
46
+ const DEFAULT_REACT_NATIVE_DEBUG_HOST = 'localhost:8097';
47
+ /**
48
+ * Prints CLI usage.
49
+ *
50
+ * @param {{write: (message: string) => unknown}} [output]
51
+ * @returns {void}
52
+ */
53
+ function usage(output = process.stderr) {
54
+ writeUsage([
55
+ 'Usage: asl-example-android-live [--config <path>] [--out <dir>] [--package <name>] [--serial <device>] [--react-native-debug-host <host:port>] [--run-suffix <label>] [--compare-latest] [--fail-on-regression] [--agent-device-proof] [--argent-proof]',
56
+ '',
57
+ 'Runs the packaged example Android live proof: adb preflight, startup, open-close, and scroll-settle.',
58
+ 'The example app must already be installed and reachable on an online Android emulator or device.',
59
+ `By default, the runner sets the app React Native debug host to ${DEFAULT_REACT_NATIVE_DEBUG_HOST} for the isolated Metro server.`,
60
+ 'Use --run-suffix to preserve multiple live proof artifact sets without changing deterministic default run ids.',
61
+ 'Use --compare-latest to compare each passed scenario against the latest trusted prior run under the artifact root.',
62
+ 'Use --fail-on-regression with --compare-latest to exit nonzero after writing evidence when any comparison regressed.',
63
+ 'Use --agent-device-proof to attach the shared startup UI assertion through agent-device; pass --agent-device-session-mode bind when a named session should still receive the configured serial.',
64
+ 'Use --argent-proof to attach the shared startup UI assertion through Argent; set ASL_ARGENT_BIN and ASL_ARGENT_BASE_ARGS for non-global installs.',
65
+ ], output);
66
+ }
67
+ /**
68
+ * Resolves the package root for source and built CLI execution.
69
+ *
70
+ * @returns {string}
71
+ */
72
+ function defaultPackageRoot() {
73
+ return path.resolve(__dirname, '..', '..');
74
+ }
75
+ /**
76
+ * Reads a JSON object from disk.
77
+ *
78
+ * @param {string} filePath
79
+ * @returns {Record<string, unknown>}
80
+ */
81
+ function readJson(filePath) {
82
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
83
+ }
84
+ /**
85
+ * Reads a nested object property from unknown JSON.
86
+ *
87
+ * @param {Record<string, unknown>} value
88
+ * @param {string} key
89
+ * @returns {Record<string, unknown> | null}
90
+ */
91
+ function readObjectProperty(value, key) {
92
+ const property = value[key];
93
+ return property && typeof property === 'object' && !Array.isArray(property)
94
+ ? property
95
+ : null;
96
+ }
97
+ /**
98
+ * Resolves the Android package name from explicit input or ASL config.
99
+ *
100
+ * @param {{args: CliArgs, config: Record<string, unknown>}} options
101
+ * @returns {string | null}
102
+ */
103
+ function resolveAndroidPackageName({ args, config, }) {
104
+ if (typeof args.package === 'string') {
105
+ return args.package;
106
+ }
107
+ const app = readObjectProperty(config, 'app');
108
+ return typeof app?.androidPackage === 'string' ? app.androidPackage : null;
109
+ }
110
+ /**
111
+ * Resolves the Android target used by every example proof lane.
112
+ *
113
+ * @param {CliArgs} args
114
+ * @returns {string}
115
+ */
116
+ function resolveAndroidSerial(args) {
117
+ return readStringArgOrEnv(args.serial, ['ASL_ANDROID_SERIAL', 'ASL_EXAMPLE_ANDROID_SERIAL']) ?? 'emulator-5554';
118
+ }
119
+ /**
120
+ * Converts a caller-provided run suffix into a path-safe run-id segment.
121
+ *
122
+ * @param {unknown} value
123
+ * @returns {string | null}
124
+ */
125
+ function normalizeRunSuffix(value) {
126
+ if (typeof value !== 'string') {
127
+ return null;
128
+ }
129
+ const normalized = value
130
+ .trim()
131
+ .toLowerCase()
132
+ .replace(/[^a-z0-9._-]+/gu, '-')
133
+ .replace(/^-+|-+$/gu, '')
134
+ .slice(0, 64);
135
+ return normalized.length > 0 ? normalized : null;
136
+ }
137
+ /**
138
+ * Applies the optional run suffix while preserving deterministic defaults.
139
+ *
140
+ * @param {string} baseRunId
141
+ * @param {string | null} suffix
142
+ * @returns {string}
143
+ */
144
+ function buildLiveRunId(baseRunId, suffix) {
145
+ return suffix ? `${baseRunId}-${suffix}` : baseRunId;
146
+ }
147
+ /**
148
+ * Builds the comparison lane suffix for enabled interaction proofs.
149
+ *
150
+ * @param {string[]} runnerIds
151
+ * @returns {string}
152
+ */
153
+ function buildInteractionComparisonLane(runnerIds) {
154
+ return runnerIds.length > 0
155
+ ? `example-android-live+${runnerIds.join('+')}`
156
+ : 'example-android-live';
157
+ }
158
+ /**
159
+ * Reports whether profile evidence is healthy enough to trust sidecar proofs and comparisons.
160
+ *
161
+ * @param {{health: Record<string, unknown>, verdict: Record<string, unknown>}} run
162
+ * @returns {boolean}
163
+ */
164
+ function isTrustedProfileRun({ health, verdict, }) {
165
+ return health.healthStatus === 'passed' && verdict.verdictStatus === 'passed';
166
+ }
167
+ /**
168
+ * Builds skipped sidecar pointers for requested example runners when the profile batch failed.
169
+ *
170
+ * @param {{failedProfiles: AndroidLiveProfile[], requestedRunners: string[], runIdsByRunner: Record<string, string>}} options
171
+ * @returns {AndroidSkippedInteractionProof[]}
172
+ */
173
+ function buildSkippedInteractionProofs({ failedProfiles, requestedRunners, runIdsByRunner, }) {
174
+ if (failedProfiles.length === 0) {
175
+ return [];
176
+ }
177
+ const failedSummary = failedProfiles
178
+ .map((profile) => `${profile.label}=health:${profile.healthStatus ?? 'unknown'}/verdict:${profile.verdictStatus ?? 'unknown'}`)
179
+ .join(', ');
180
+ const reason = `Profile batch failed (${failedSummary}); sidecar interaction proof was skipped because aggregate runner evidence would not be trustworthy.`;
181
+ const labelsByRunner = {
182
+ 'agent-device': 'startup-ui',
183
+ argent: 'startup-ui-argent',
184
+ };
185
+ return requestedRunners.map((runnerId) => ({
186
+ label: labelsByRunner[runnerId] ?? `startup-ui-${runnerId}`,
187
+ nextAction: {
188
+ code: 'fix_profile_gate',
189
+ summary: 'Inspect the failed profile health and verdict before rerunning sidecar interaction proofs.',
190
+ },
191
+ reason,
192
+ runId: runIdsByRunner[runnerId] ?? `android-${runnerId}-startup`,
193
+ runnerId,
194
+ scenarioId: 'app-startup',
195
+ }));
196
+ }
197
+ /**
198
+ * Throws after aggregate proof writing when the live proof itself failed.
199
+ *
200
+ * @param {AndroidLiveProofResult} result
201
+ * @returns {void}
202
+ */
203
+ function assertAggregatePassed(result) {
204
+ const proof = readJson(result.aggregateSummary.liveProofPath);
205
+ if (proof.status === 'passed') {
206
+ return;
207
+ }
208
+ throw new Error(`Android example live proof failed. Inspect ${result.aggregateSummary.summaryPath}.`);
209
+ }
210
+ /**
211
+ * Throws after aggregate proof writing when fail-on-regression should gate the run.
212
+ *
213
+ * @param {RegressionGateOptions} options
214
+ * @returns {void}
215
+ */
216
+ function assertNoRegressedComparisons({ platformLabel, result, }) {
217
+ const regressed = result.comparisons.filter((comparison) => comparison.status === 'worse');
218
+ if (regressed.length === 0) {
219
+ return;
220
+ }
221
+ const labels = regressed.map((comparison) => comparison.label).join(', ');
222
+ throw new Error(`${platformLabel} example live proof found regressed comparison(s): ${labels}. Inspect ${result.aggregateSummary.summaryPath}.`);
223
+ }
224
+ /**
225
+ * Runs adb preflight plus all canonical Android example live profiles.
226
+ *
227
+ * @param {CliArgs} args
228
+ * @param {AndroidLiveProofOptions} [options]
229
+ * @returns {Promise<AndroidLiveProofResult>}
230
+ */
231
+ async function runExampleAndroidLiveProof(args, options = {}) {
232
+ const packageRoot = options.packageRoot ?? defaultPackageRoot();
233
+ const exampleRoot = path.join(packageRoot, 'examples', 'mobile-app');
234
+ const configPath = typeof args.config === 'string'
235
+ ? path.resolve(args.config)
236
+ : path.join(exampleRoot, 'asl.config.json');
237
+ const outputDir = typeof args.out === 'string'
238
+ ? path.resolve(args.out)
239
+ : path.resolve('artifacts/example-mobile-app/android');
240
+ const config = readJson(configPath);
241
+ const packageName = resolveAndroidPackageName({ args, config });
242
+ const serial = resolveAndroidSerial(args);
243
+ const agentDeviceSession = readStringArgOrEnv(args['agent-device-session'], [
244
+ 'ASL_ANDROID_AGENT_DEVICE_SESSION',
245
+ 'ASL_EXAMPLE_ANDROID_AGENT_DEVICE_SESSION',
246
+ ]);
247
+ const agentDeviceSessionMode = readStringArgOrEnv(args['agent-device-session-mode'], [
248
+ 'ASL_ANDROID_AGENT_DEVICE_SESSION_MODE',
249
+ 'ASL_EXAMPLE_ANDROID_AGENT_DEVICE_SESSION_MODE',
250
+ ]);
251
+ const runSuffix = normalizeRunSuffix(args['run-suffix']);
252
+ const aggregateRunId = buildLiveRunId('android-live-proof', runSuffix);
253
+ const preflightRunId = buildLiveRunId('android-live-preflight', runSuffix);
254
+ const agentDeviceRunId = buildLiveRunId('android-agent-device-startup', runSuffix);
255
+ const argentRunId = buildLiveRunId('android-argent-startup', runSuffix);
256
+ const enabledInteractionRunners = [
257
+ ...(isEnabledFlag(args['agent-device-proof']) ? ['agent-device'] : []),
258
+ ...(isEnabledFlag(args['argent-proof']) ? ['argent'] : []),
259
+ ];
260
+ const comparisonLane = buildInteractionComparisonLane(enabledInteractionRunners);
261
+ const runIdsByRunner = {
262
+ 'agent-device': agentDeviceRunId,
263
+ argent: argentRunId,
264
+ };
265
+ const preflightDir = path.join(outputDir, '_preflight', preflightRunId);
266
+ const reactNativeDebugHost = typeof args['react-native-debug-host'] === 'string'
267
+ ? args['react-native-debug-host']
268
+ : DEFAULT_REACT_NATIVE_DEBUG_HOST;
269
+ const preflight = await runAndroidAdbPreflight({
270
+ ...(typeof args.adb === 'string' ? { adbPath: args.adb } : {}),
271
+ ...(options.delay ? { delay: options.delay } : {}),
272
+ ...(options.executor ? { executor: options.executor } : {}),
273
+ outputDir: preflightDir,
274
+ packageName,
275
+ reactNativeDebugHost,
276
+ runId: preflightRunId,
277
+ serial,
278
+ });
279
+ if (preflight.health.healthStatus !== 'passed') {
280
+ throw new Error(`Android live proof preflight failed; inspect ${preflight.runDir}/agent-summary.md.`);
281
+ }
282
+ const interactionProofs = [];
283
+ const profiles = [];
284
+ const failedProfiles = [];
285
+ for (const profile of EXAMPLE_PROFILES) {
286
+ const profileRunId = buildLiveRunId(profile.runId, runSuffix);
287
+ const result = await runProfileAndroid({
288
+ ...(typeof args.adb === 'string' ? { adb: args.adb } : {}),
289
+ 'adb-capture': true,
290
+ 'clear-logcat': true,
291
+ config: configPath,
292
+ 'command-wait-ms': typeof args['command-wait-ms'] === 'string' ? args['command-wait-ms'] : '250',
293
+ launch: true,
294
+ 'launch-wait-ms': typeof args['launch-wait-ms'] === 'string' ? args['launch-wait-ms'] : '1500',
295
+ 'logcat-lines': typeof args['logcat-lines'] === 'string' ? args['logcat-lines'] : '1000',
296
+ out: outputDir,
297
+ ...(packageName ? { package: packageName } : {}),
298
+ 'profile-session': true,
299
+ 'react-native-debug-host': reactNativeDebugHost,
300
+ 'run-id': profileRunId,
301
+ scenario: path.join(exampleRoot, 'scenarios', 'mobile', profile.scenario),
302
+ serial,
303
+ 'wait-ms': typeof args['wait-ms'] === 'string' ? args['wait-ms'] : '1000',
304
+ }, {
305
+ comparisonLane,
306
+ ...(options.delay ? { delay: options.delay } : {}),
307
+ ...(options.executor ? { executor: options.executor } : {}),
308
+ });
309
+ const profilePointer = {
310
+ healthStatus: typeof result.health.healthStatus === 'string' ? result.health.healthStatus : 'unknown',
311
+ label: profile.label,
312
+ runDir: result.runDir,
313
+ runId: profileRunId,
314
+ scenario: profile.scenario,
315
+ scenarioId: profile.scenarioId,
316
+ verdictStatus: typeof result.verdict.verdictStatus === 'string' ? result.verdict.verdictStatus : 'unknown',
317
+ };
318
+ profiles.push(profilePointer);
319
+ if (!isTrustedProfileRun({ health: result.health, verdict: result.verdict })) {
320
+ failedProfiles.push(profilePointer);
321
+ }
322
+ }
323
+ const skippedInteractionProofs = buildSkippedInteractionProofs({
324
+ failedProfiles,
325
+ requestedRunners: enabledInteractionRunners,
326
+ runIdsByRunner,
327
+ });
328
+ const profileBatchTrusted = failedProfiles.length === 0;
329
+ if (profileBatchTrusted && isEnabledFlag(args['agent-device-proof'])) {
330
+ const agentDeviceCapture = await runAgentDeviceCapture({
331
+ ...(typeof args['agent-device'] === 'string' ? { agentDevicePath: args['agent-device'] } : {}),
332
+ app: packageName,
333
+ ...(options.agentDeviceExecutor ? { executor: options.agentDeviceExecutor } : {}),
334
+ open: true,
335
+ outputDir: path.join(outputDir, '_agent-device-captures', agentDeviceRunId),
336
+ platform: 'android',
337
+ runId: agentDeviceRunId,
338
+ scenario: readJson(path.join(exampleRoot, 'scenarios', 'mobile', 'app-startup.json')),
339
+ serial,
340
+ ...(agentDeviceSession ? { session: agentDeviceSession } : {}),
341
+ ...(agentDeviceSessionMode
342
+ ? { sessionMode: agentDeviceSessionMode }
343
+ : {}),
344
+ waitMs: parsePositiveInteger(args['agent-device-wait-ms'], 1000),
345
+ });
346
+ interactionProofs.push({
347
+ label: 'startup-ui',
348
+ runDir: agentDeviceCapture.runDir,
349
+ runId: agentDeviceRunId,
350
+ runnerId: 'agent-device',
351
+ scenarioId: 'app-startup',
352
+ });
353
+ }
354
+ if (profileBatchTrusted && isEnabledFlag(args['argent-proof'])) {
355
+ const argentBaseArgs = parseArgentBaseArgs(process.env.ASL_ARGENT_BASE_ARGS);
356
+ const argentCapture = await runArgentCapture({
357
+ app: packageName,
358
+ argentCommand: process.env.ASL_ARGENT_BIN || 'argent',
359
+ ...(argentBaseArgs ? { baseArgs: argentBaseArgs } : {}),
360
+ commandTimeoutMs: parsePositiveInteger(process.env.ASL_ARGENT_COMMAND_TIMEOUT_MS, 60_000),
361
+ deviceId: serial,
362
+ ...(options.delay ? { delay: options.delay } : {}),
363
+ ...(options.argentExecutor ? { executor: options.argentExecutor } : {}),
364
+ outputDir: path.join(outputDir, '_argent-captures', argentRunId),
365
+ platform: 'android',
366
+ runId: argentRunId,
367
+ scenario: readJson(path.join(exampleRoot, 'scenarios', 'mobile', 'app-startup.json')),
368
+ });
369
+ interactionProofs.push({
370
+ label: 'startup-ui-argent',
371
+ runDir: argentCapture.runDir,
372
+ runId: argentRunId,
373
+ runnerId: 'argent',
374
+ scenarioId: 'app-startup',
375
+ });
376
+ }
377
+ const comparisons = profileBatchTrusted && isEnabledFlag(args['compare-latest'])
378
+ ? await compareLiveProfilesToLatest({ outputDir, profiles })
379
+ : [];
380
+ const aggregateSummary = await writeLiveProofSummary({
381
+ comparisons,
382
+ interactionProofs,
383
+ outputDir,
384
+ platform: 'android',
385
+ preflightDir: preflight.runDir,
386
+ preflightRunId,
387
+ profiles,
388
+ runId: aggregateRunId,
389
+ skippedInteractionProofs,
390
+ });
391
+ const result = {
392
+ aggregateSummary,
393
+ comparisons,
394
+ interactionProofs,
395
+ outputDir,
396
+ preflightDir: preflight.runDir,
397
+ profiles,
398
+ skippedInteractionProofs,
399
+ };
400
+ if (isEnabledFlag(args['fail-on-regression'])) {
401
+ assertNoRegressedComparisons({
402
+ platformLabel: 'Android',
403
+ result,
404
+ });
405
+ }
406
+ assertAggregatePassed(result);
407
+ return result;
408
+ }
409
+ /**
410
+ * Formats the live proof result for humans and agents.
411
+ *
412
+ * @param {AndroidLiveProofResult} result
413
+ * @returns {string}
414
+ */
415
+ function formatResult(result) {
416
+ return [
417
+ 'Android example live proof passed.',
418
+ `Live proof: ${result.aggregateSummary.summaryPath}`,
419
+ `Preflight: ${result.preflightDir}/agent-summary.md`,
420
+ ...result.profiles.map((profile) => (`${profile.label}: ${profile.runDir}/agent-summary.md`)),
421
+ ...result.interactionProofs.map((proof) => (`${proof.label}: ${proof.runDir}/agent-summary.md`)),
422
+ ...(result.comparisons.length > 0
423
+ ? [
424
+ 'Comparisons:',
425
+ ...result.comparisons.map((comparison) => (comparison.status === 'skipped'
426
+ ? `${comparison.label}: skipped - ${comparison.reason}`
427
+ : `${comparison.label}: ${comparison.status} ${comparison.summaryPath}`)),
428
+ ]
429
+ : []),
430
+ `Artifact root: ${result.outputDir}`,
431
+ ].join('\n');
432
+ }
433
+ /**
434
+ * Runs the example Android live proof CLI.
435
+ *
436
+ * @returns {Promise<void>}
437
+ */
438
+ async function main() {
439
+ const argv = process.argv.slice(2);
440
+ if (hasHelpFlag(argv)) {
441
+ usage(process.stdout);
442
+ return;
443
+ }
444
+ loadAslLocalEnv();
445
+ const args = parseArgs(argv);
446
+ const result = await runExampleAndroidLiveProof(args);
447
+ process.stdout.write(`${formatResult(result)}\n`);
448
+ }
449
+ if (require.main === module) {
450
+ main().catch((error) => {
451
+ console.error(error instanceof Error ? error.message : String(error));
452
+ process.exitCode = 1;
453
+ });
454
+ }
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ type CliArgs = import('./ios-simctl').CliArgs;
3
+ type LiveComparisonResult = import('./live-comparison').LiveComparisonResult;
4
+ type LiveProofSummaryResult = import('./live-proof-summary').LiveProofSummaryResult;
5
+ type IosLiveProofOptions = {
6
+ agentDeviceExecutor?: import('./agent-device').CommandExecutor;
7
+ argentExecutor?: import('./argent').CommandExecutor;
8
+ delay?: (ms: number) => Promise<void>;
9
+ executor?: import('./ios-simctl').CommandExecutor;
10
+ packageRoot?: string;
11
+ };
12
+ type IosInteractionProof = {
13
+ label: string;
14
+ runDir: string;
15
+ runId: string;
16
+ runnerId: string;
17
+ scenarioId: string;
18
+ };
19
+ type IosSkippedInteractionProof = import('./live-proof-summary').LiveProofSkippedInteractionProofPointer;
20
+ type IosLiveProfile = {
21
+ healthStatus?: string;
22
+ label: string;
23
+ runDir: string;
24
+ runId: string;
25
+ scenario: string;
26
+ scenarioId: string;
27
+ verdictStatus?: string;
28
+ };
29
+ type IosLiveProofResult = {
30
+ aggregateSummary: LiveProofSummaryResult;
31
+ comparisons: LiveComparisonResult[];
32
+ interactionProofs: IosInteractionProof[];
33
+ outputDir: string;
34
+ preflightDir: string;
35
+ profiles: IosLiveProfile[];
36
+ skippedInteractionProofs: IosSkippedInteractionProof[];
37
+ };
38
+ type RegressionGateOptions = {
39
+ platformLabel: string;
40
+ result: IosLiveProofResult;
41
+ };
42
+ /**
43
+ * Prints CLI usage.
44
+ *
45
+ * @param {{write: (message: string) => unknown}} [output]
46
+ * @returns {void}
47
+ */
48
+ declare function usage(output?: {
49
+ write: (message: string) => unknown;
50
+ }): void;
51
+ /**
52
+ * Resolves the simulator target used by every iOS proof lane.
53
+ *
54
+ * @param {CliArgs} args
55
+ * @returns {string}
56
+ */
57
+ declare function resolveIosDeviceId(args: CliArgs): string;
58
+ /**
59
+ * Converts a caller-provided run suffix into a path-safe run-id segment.
60
+ *
61
+ * @param {unknown} value
62
+ * @returns {string | null}
63
+ */
64
+ declare function normalizeRunSuffix(value: unknown): string | null;
65
+ /**
66
+ * Applies the optional run suffix while preserving deterministic defaults.
67
+ *
68
+ * @param {string} baseRunId
69
+ * @param {string | null} suffix
70
+ * @returns {string}
71
+ */
72
+ declare function buildLiveRunId(baseRunId: string, suffix: string | null): string;
73
+ /**
74
+ * Builds the comparison lane suffix for enabled interaction proofs.
75
+ *
76
+ * @param {string[]} runnerIds
77
+ * @returns {string}
78
+ */
79
+ declare function buildInteractionComparisonLane(runnerIds: string[]): string;
80
+ /**
81
+ * Reports whether profile evidence is healthy enough to trust sidecar proofs and comparisons.
82
+ *
83
+ * @param {{health: Record<string, unknown>, verdict: Record<string, unknown>}} run
84
+ * @returns {boolean}
85
+ */
86
+ declare function isTrustedProfileRun({ health, verdict, }: {
87
+ health: Record<string, unknown>;
88
+ verdict: Record<string, unknown>;
89
+ }): boolean;
90
+ /**
91
+ * Builds skipped sidecar pointers for requested example runners when the profile batch failed.
92
+ *
93
+ * @param {{failedProfiles: IosLiveProfile[], requestedRunners: string[], runIdsByRunner: Record<string, string>}} options
94
+ * @returns {IosSkippedInteractionProof[]}
95
+ */
96
+ declare function buildSkippedInteractionProofs({ failedProfiles, requestedRunners, runIdsByRunner, }: {
97
+ failedProfiles: IosLiveProfile[];
98
+ requestedRunners: string[];
99
+ runIdsByRunner: Record<string, string>;
100
+ }): IosSkippedInteractionProof[];
101
+ /**
102
+ * Throws after aggregate proof writing when the live proof itself failed.
103
+ *
104
+ * @param {IosLiveProofResult} result
105
+ * @returns {void}
106
+ */
107
+ declare function assertAggregatePassed(result: IosLiveProofResult): void;
108
+ /**
109
+ * Throws after aggregate proof writing when fail-on-regression should gate the run.
110
+ *
111
+ * @param {RegressionGateOptions} options
112
+ * @returns {void}
113
+ */
114
+ declare function assertNoRegressedComparisons({ platformLabel, result, }: RegressionGateOptions): void;
115
+ /**
116
+ * Runs simctl preflight plus all canonical iOS example live profiles.
117
+ *
118
+ * @param {CliArgs} args
119
+ * @param {IosLiveProofOptions} [options]
120
+ * @returns {Promise<IosLiveProofResult>}
121
+ */
122
+ declare function runExampleIosLiveProof(args: CliArgs, options?: IosLiveProofOptions): Promise<IosLiveProofResult>;
123
+ /**
124
+ * Formats the live proof result for humans and agents.
125
+ *
126
+ * @param {IosLiveProofResult} result
127
+ * @returns {string}
128
+ */
129
+ declare function formatResult(result: IosLiveProofResult): string;
130
+ /**
131
+ * Runs the example iOS live proof CLI.
132
+ *
133
+ * @returns {Promise<void>}
134
+ */
135
+ declare function main(): Promise<void>;
136
+ export { assertAggregatePassed, buildLiveRunId, formatResult, assertNoRegressedComparisons, buildSkippedInteractionProofs, buildInteractionComparisonLane, isTrustedProfileRun, main, normalizeRunSuffix, resolveIosDeviceId, runExampleIosLiveProof, usage, };
137
+ export type { IosLiveProofOptions, IosInteractionProof, IosLiveProofResult, IosLiveProfile, };