@ucptools/validator 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/.claude/settings.local.json +60 -0
  2. package/.vercel/README.txt +11 -0
  3. package/.vercel/project.json +1 -0
  4. package/dist/cli/index.d.ts +6 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +279 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/compliance/compliance-generator.d.ts +34 -0
  9. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  10. package/dist/compliance/compliance-generator.js +320 -0
  11. package/dist/compliance/compliance-generator.js.map +1 -0
  12. package/dist/compliance/index.d.ts +8 -0
  13. package/dist/compliance/index.d.ts.map +1 -0
  14. package/dist/compliance/index.js +17 -0
  15. package/dist/compliance/index.js.map +1 -0
  16. package/dist/compliance/templates.d.ts +34 -0
  17. package/dist/compliance/templates.d.ts.map +1 -0
  18. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  19. package/dist/compliance/templates.js.map +1 -0
  20. package/dist/compliance/types.d.ts +64 -0
  21. package/dist/compliance/types.d.ts.map +1 -0
  22. package/dist/compliance/types.js +64 -0
  23. package/dist/compliance/types.js.map +1 -0
  24. package/dist/db/index.d.ts +11 -0
  25. package/dist/db/index.d.ts.map +1 -0
  26. package/dist/db/index.js +63 -0
  27. package/dist/db/index.js.map +1 -0
  28. package/dist/db/schema.d.ts +444 -0
  29. package/dist/db/schema.d.ts.map +1 -0
  30. package/dist/db/schema.js +65 -0
  31. package/dist/db/schema.js.map +1 -0
  32. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  33. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  34. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +642 -726
  35. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  36. package/dist/feed-analyzer/index.d.ts +8 -0
  37. package/dist/feed-analyzer/index.d.ts.map +1 -0
  38. package/dist/feed-analyzer/index.js +19 -0
  39. package/dist/feed-analyzer/index.js.map +1 -0
  40. package/dist/feed-analyzer/types.d.ts +204 -0
  41. package/dist/feed-analyzer/types.d.ts.map +1 -0
  42. package/dist/feed-analyzer/types.js +162 -0
  43. package/dist/feed-analyzer/types.js.map +1 -0
  44. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  45. package/dist/generator/index.d.ts.map +1 -0
  46. package/dist/generator/index.js +13 -0
  47. package/dist/generator/index.js.map +1 -0
  48. package/dist/generator/key-generator.d.ts +24 -0
  49. package/dist/generator/key-generator.d.ts.map +1 -0
  50. package/dist/generator/key-generator.js +144 -0
  51. package/dist/generator/key-generator.js.map +1 -0
  52. package/dist/generator/profile-builder.d.ts +15 -0
  53. package/dist/generator/profile-builder.d.ts.map +1 -0
  54. package/dist/generator/profile-builder.js +338 -0
  55. package/dist/generator/profile-builder.js.map +1 -0
  56. package/dist/hosting/artifacts-generator.d.ts +10 -0
  57. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  58. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  59. package/dist/hosting/artifacts-generator.js.map +1 -0
  60. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  61. package/dist/hosting/index.d.ts.map +1 -0
  62. package/dist/hosting/index.js +10 -0
  63. package/dist/hosting/index.js.map +1 -0
  64. package/dist/index.d.ts +18 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +78 -0
  67. package/dist/index.js.map +1 -0
  68. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  69. package/dist/security/index.d.ts.map +1 -0
  70. package/dist/security/index.js +12 -0
  71. package/dist/security/index.js.map +1 -0
  72. package/dist/security/security-scanner.d.ts +10 -0
  73. package/dist/security/security-scanner.d.ts.map +1 -0
  74. package/dist/security/security-scanner.js +541 -0
  75. package/dist/security/security-scanner.js.map +1 -0
  76. package/dist/security/types.d.ts +48 -0
  77. package/dist/security/types.d.ts.map +1 -0
  78. package/dist/security/types.js +21 -0
  79. package/dist/security/types.js.map +1 -0
  80. package/dist/services/directory.d.ts +104 -0
  81. package/dist/services/directory.d.ts.map +1 -0
  82. package/dist/services/directory.js +333 -0
  83. package/dist/services/directory.js.map +1 -0
  84. package/dist/simulator/agent-simulator.d.ts +69 -0
  85. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  86. package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
  87. package/dist/simulator/agent-simulator.js.map +1 -0
  88. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  89. package/dist/simulator/index.d.ts.map +1 -0
  90. package/dist/simulator/index.js +23 -0
  91. package/dist/simulator/index.js.map +1 -0
  92. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +145 -170
  93. package/dist/simulator/types.d.ts.map +1 -0
  94. package/dist/simulator/types.js +18 -0
  95. package/dist/simulator/types.js.map +1 -0
  96. package/dist/types/generator.d.ts +106 -0
  97. package/dist/types/generator.d.ts.map +1 -0
  98. package/dist/types/generator.js +6 -0
  99. package/dist/types/generator.js.map +1 -0
  100. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  101. package/dist/types/index.d.ts.map +1 -0
  102. package/dist/types/index.js +23 -0
  103. package/dist/types/index.js.map +1 -0
  104. package/dist/types/ucp-profile.d.ts +103 -0
  105. package/dist/types/ucp-profile.d.ts.map +1 -0
  106. package/dist/types/ucp-profile.js +45 -0
  107. package/dist/types/ucp-profile.js.map +1 -0
  108. package/dist/types/validation.d.ts +68 -0
  109. package/dist/types/validation.d.ts.map +1 -0
  110. package/dist/types/validation.js +32 -0
  111. package/dist/types/validation.js.map +1 -0
  112. package/dist/validator/index.d.ts +26 -0
  113. package/dist/validator/index.d.ts.map +1 -0
  114. package/dist/validator/index.js +161 -0
  115. package/dist/validator/index.js.map +1 -0
  116. package/dist/validator/network-validator.d.ts +28 -0
  117. package/dist/validator/network-validator.d.ts.map +1 -0
  118. package/dist/validator/network-validator.js +319 -0
  119. package/dist/validator/network-validator.js.map +1 -0
  120. package/dist/validator/rules-validator.d.ts +11 -0
  121. package/dist/validator/rules-validator.d.ts.map +1 -0
  122. package/dist/validator/rules-validator.js +257 -0
  123. package/dist/validator/rules-validator.js.map +1 -0
  124. package/dist/validator/sdk-validator.d.ts +58 -0
  125. package/dist/validator/sdk-validator.d.ts.map +1 -0
  126. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  127. package/dist/validator/sdk-validator.js.map +1 -0
  128. package/dist/validator/structural-validator.d.ts +11 -0
  129. package/dist/validator/structural-validator.d.ts.map +1 -0
  130. package/dist/validator/structural-validator.js +415 -0
  131. package/dist/validator/structural-validator.js.map +1 -0
  132. package/package.json +1 -1
  133. package/publish-output.txt +0 -0
  134. package/CLAUDE.md +0 -109
  135. package/api/analyze-feed.js +0 -140
  136. package/api/badge.js +0 -185
  137. package/api/benchmark.js +0 -177
  138. package/api/directory-stats.ts +0 -29
  139. package/api/directory.ts +0 -73
  140. package/api/generate-compliance.js +0 -143
  141. package/api/generate-schema.js +0 -457
  142. package/api/generate.js +0 -132
  143. package/api/security-scan.js +0 -133
  144. package/api/simulate.js +0 -187
  145. package/api/tsconfig.json +0 -10
  146. package/api/validate.js +0 -1351
  147. package/apify-actor/.actor/actor.json +0 -68
  148. package/apify-actor/.actor/input_schema.json +0 -32
  149. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  150. package/apify-actor/Dockerfile +0 -8
  151. package/apify-actor/README.md +0 -166
  152. package/apify-actor/main.ts +0 -111
  153. package/apify-actor/package.json +0 -17
  154. package/apify-actor/src/main.js +0 -199
  155. package/docs/BRAND-IDENTITY.md +0 -238
  156. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  157. package/drizzle/0000_black_king_cobra.sql +0 -39
  158. package/drizzle/meta/0000_snapshot.json +0 -309
  159. package/drizzle/meta/_journal.json +0 -13
  160. package/drizzle.config.ts +0 -10
  161. package/public/.well-known/ucp +0 -25
  162. package/public/android-chrome-192x192.png +0 -0
  163. package/public/android-chrome-512x512.png +0 -0
  164. package/public/apple-touch-icon.png +0 -0
  165. package/public/brand.css +0 -321
  166. package/public/directory.html +0 -701
  167. package/public/favicon-16x16.png +0 -0
  168. package/public/favicon-32x32.png +0 -0
  169. package/public/favicon.ico +0 -0
  170. package/public/guides/bigcommerce.html +0 -743
  171. package/public/guides/fastucp.html +0 -838
  172. package/public/guides/magento.html +0 -779
  173. package/public/guides/shopify.html +0 -726
  174. package/public/guides/squarespace.html +0 -749
  175. package/public/guides/wix.html +0 -747
  176. package/public/guides/woocommerce.html +0 -733
  177. package/public/index.html +0 -3835
  178. package/public/learn.html +0 -396
  179. package/public/logo.jpeg +0 -0
  180. package/public/og-image-icon.png +0 -0
  181. package/public/og-image.png +0 -0
  182. package/public/robots.txt +0 -6
  183. package/public/site.webmanifest +0 -31
  184. package/public/sitemap.xml +0 -69
  185. package/public/social/linkedin-banner-1128x191.png +0 -0
  186. package/public/social/temp.PNG +0 -0
  187. package/public/social/x-header-1500x500.png +0 -0
  188. package/public/verify.html +0 -410
  189. package/scripts/generate-favicons.js +0 -44
  190. package/scripts/generate-ico.js +0 -23
  191. package/scripts/generate-og-image.js +0 -45
  192. package/scripts/reset-db.ts +0 -77
  193. package/scripts/seed-db.ts +0 -71
  194. package/scripts/setup-benchmark-db.js +0 -70
  195. package/src/api/server.ts +0 -266
  196. package/src/cli/index.ts +0 -302
  197. package/src/compliance/compliance-generator.ts +0 -452
  198. package/src/compliance/index.ts +0 -28
  199. package/src/compliance/types.ts +0 -170
  200. package/src/db/index.ts +0 -28
  201. package/src/db/schema.ts +0 -84
  202. package/src/feed-analyzer/index.ts +0 -34
  203. package/src/feed-analyzer/types.ts +0 -354
  204. package/src/generator/key-generator.ts +0 -124
  205. package/src/generator/profile-builder.ts +0 -402
  206. package/src/index.ts +0 -105
  207. package/src/security/security-scanner.ts +0 -604
  208. package/src/security/types.ts +0 -55
  209. package/src/services/directory.ts +0 -434
  210. package/src/types/generator.ts +0 -140
  211. package/src/types/ucp-profile.ts +0 -140
  212. package/src/types/validation.ts +0 -89
  213. package/src/validator/index.ts +0 -194
  214. package/src/validator/network-validator.ts +0 -417
  215. package/src/validator/rules-validator.ts +0 -297
  216. package/src/validator/structural-validator.ts +0 -476
  217. package/tests/fixtures/non-compliant-profile.json +0 -25
  218. package/tests/fixtures/official-sample-profile.json +0 -75
  219. package/tests/integration/benchmark.test.ts +0 -207
  220. package/tests/integration/database.test.ts +0 -163
  221. package/tests/integration/directory-api.test.ts +0 -268
  222. package/tests/integration/simulate-api.test.ts +0 -230
  223. package/tests/integration/validate-api.test.ts +0 -269
  224. package/tests/setup.ts +0 -15
  225. package/tests/unit/agent-simulator.test.ts +0 -575
  226. package/tests/unit/compliance-generator.test.ts +0 -374
  227. package/tests/unit/directory-service.test.ts +0 -272
  228. package/tests/unit/feed-analyzer.test.ts +0 -517
  229. package/tests/unit/lint-suggestions.test.ts +0 -423
  230. package/tests/unit/official-samples.test.ts +0 -211
  231. package/tests/unit/pdf-report.test.ts +0 -390
  232. package/tests/unit/sdk-validator.test.ts +0 -531
  233. package/tests/unit/security-scanner.test.ts +0 -410
  234. package/tests/unit/validation.test.ts +0 -390
  235. package/vercel.json +0 -34
  236. package/vitest.config.ts +0 -22
@@ -1,941 +1,650 @@
1
- /**
2
- * AI Agent Simulator
3
- * Simulates how an AI agent discovers and interacts with UCP-enabled merchants
4
- *
5
- * The goal is to test real-world functionality, not just spec compliance.
6
- * This proves that a UCP implementation actually works for AI agent commerce.
7
- */
8
-
9
- import type { UcpProfile, UcpCapability, UcpService } from '../types/ucp-profile.js';
10
- import type {
11
- AgentSimulationResult,
12
- SimulationOptions,
13
- SimulationStepResult,
14
- SimulationStepStatus,
15
- DiscoveryFlowResult,
16
- CapabilityInspectionResult,
17
- ServiceInspectionResult,
18
- RestApiSimulationResult,
19
- CheckoutSimulationResult,
20
- PaymentReadinessResult,
21
- OperationTestResult,
22
- DEFAULT_SIMULATION_OPTIONS,
23
- } from './types.js';
24
-
25
- const DEFAULT_TIMEOUT_MS = 30000;
26
- const DEFAULT_FETCH_TIMEOUT_MS = 10000;
27
-
28
- /**
29
- * Normalize capability to extract the name string
30
- * Handles both string format and object format capabilities
31
- */
32
- function getCapabilityName(cap: unknown): string {
33
- if (typeof cap === 'string') {
34
- return cap;
35
- }
36
- if (cap && typeof cap === 'object' && 'name' in cap) {
37
- return String((cap as { name: unknown }).name);
38
- }
39
- return '';
40
- }
41
-
42
- /**
43
- * Get capability object (for accessing schema, version, etc.)
44
- * Returns null for string-only capabilities
45
- */
46
- function getCapabilityObject(cap: unknown): UcpCapability | null {
47
- if (cap && typeof cap === 'object' && 'name' in cap) {
48
- return cap as UcpCapability;
49
- }
50
- return null;
51
- }
52
-
53
- /**
54
- * Step builder helper
55
- */
56
- function createStep(
57
- step: string,
58
- status: SimulationStepStatus,
59
- message: string,
60
- details?: string,
61
- durationMs?: number,
62
- data?: Record<string, unknown>
63
- ): SimulationStepResult {
64
- return { step, status, message, details, durationMs, data };
65
- }
66
-
67
- /**
68
- * Fetch with timeout
69
- */
70
- async function fetchWithTimeout(
71
- url: string,
72
- timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
73
- ): Promise<{ ok: boolean; status?: number; data?: unknown; error?: string }> {
74
- const controller = new AbortController();
75
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
76
-
77
- try {
78
- const response = await fetch(url, {
79
- signal: controller.signal,
80
- headers: {
81
- 'User-Agent': 'UCP-Agent-Simulator/1.0',
82
- 'Accept': 'application/json',
83
- },
84
- });
85
-
86
- clearTimeout(timeoutId);
87
-
88
- if (!response.ok) {
89
- return { ok: false, status: response.status, error: `HTTP ${response.status}` };
90
- }
91
-
92
- const contentType = response.headers.get('content-type') || '';
93
- if (contentType.includes('application/json')) {
94
- const data = await response.json();
95
- return { ok: true, status: response.status, data };
96
- }
97
-
98
- return { ok: true, status: response.status };
99
- } catch (error) {
100
- clearTimeout(timeoutId);
101
- const message = error instanceof Error ? error.message : 'Unknown error';
102
- return { ok: false, error: message };
103
- }
104
- }
105
-
106
- /**
107
- * HEAD request to check endpoint responsiveness
108
- */
109
- async function checkEndpointResponsive(
110
- url: string,
111
- timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
112
- ): Promise<{ ok: boolean; status?: number; error?: string }> {
113
- const controller = new AbortController();
114
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
115
-
116
- try {
117
- const response = await fetch(url, {
118
- method: 'HEAD',
119
- signal: controller.signal,
120
- headers: {
121
- 'User-Agent': 'UCP-Agent-Simulator/1.0',
122
- },
123
- });
124
-
125
- clearTimeout(timeoutId);
126
- // Accept various success codes - we just want to know endpoint exists
127
- return { ok: response.status < 500, status: response.status };
128
- } catch (error) {
129
- clearTimeout(timeoutId);
130
- const message = error instanceof Error ? error.message : 'Unknown error';
131
- return { ok: false, error: message };
132
- }
133
- }
134
-
135
- /**
136
- * Simulate discovery flow - how an AI agent discovers a UCP merchant
137
- */
138
- export async function simulateDiscoveryFlow(
139
- domain: string,
140
- timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
141
- ): Promise<DiscoveryFlowResult> {
142
- const steps: SimulationStepResult[] = [];
143
- let profileUrl: string | undefined;
144
- let profile: UcpProfile | null = null;
145
- const capabilities: string[] = [];
146
- const services: string[] = [];
147
- const transports: string[] = [];
148
-
149
- // Step 1: Try to fetch /.well-known/ucp
150
- const startTime = Date.now();
151
- const urls = [
152
- `https://${domain}/.well-known/ucp`,
153
- `https://${domain}/.well-known/ucp.json`,
154
- ];
155
-
156
- let foundProfile = false;
157
- for (const url of urls) {
158
- const fetchStart = Date.now();
159
- const result = await fetchWithTimeout(url, timeoutMs);
160
- const fetchDuration = Date.now() - fetchStart;
161
-
162
- if (result.ok && result.data && typeof result.data === 'object') {
163
- const data = result.data as Record<string, unknown>;
164
- if (data.ucp) {
165
- profile = data as UcpProfile;
166
- profileUrl = url;
167
- foundProfile = true;
168
- steps.push(createStep(
169
- 'discover_profile',
170
- 'passed',
171
- `Found UCP profile at ${url}`,
172
- `Response time: ${fetchDuration}ms`,
173
- fetchDuration,
174
- { url, responseTime: fetchDuration }
175
- ));
176
- break;
177
- }
178
- }
179
- }
180
-
181
- if (!foundProfile) {
182
- steps.push(createStep(
183
- 'discover_profile',
184
- 'failed',
185
- 'Could not find UCP profile',
186
- `Tried: ${urls.join(', ')}`
187
- ));
188
- return { success: false, steps, capabilities, services, transports };
189
- }
190
-
191
- // Step 2: Parse UCP version
192
- if (profile?.ucp?.version) {
193
- steps.push(createStep(
194
- 'parse_version',
195
- 'passed',
196
- `UCP version: ${profile.ucp.version}`,
197
- undefined,
198
- undefined,
199
- { version: profile.ucp.version }
200
- ));
201
- } else {
202
- steps.push(createStep(
203
- 'parse_version',
204
- 'failed',
205
- 'Missing UCP version',
206
- 'AI agent cannot determine protocol version'
207
- ));
208
- }
209
-
210
- // Step 3: Enumerate services
211
- const serviceEntries = Object.entries(profile?.ucp?.services || {});
212
- if (serviceEntries.length > 0) {
213
- for (const [serviceName, service] of serviceEntries) {
214
- services.push(serviceName);
215
-
216
- // Track transports
217
- if ((service as UcpService).rest) transports.push('rest');
218
- if ((service as UcpService).mcp) transports.push('mcp');
219
- if ((service as UcpService).a2a) transports.push('a2a');
220
- if ((service as UcpService).embedded) transports.push('embedded');
221
- }
222
-
223
- steps.push(createStep(
224
- 'enumerate_services',
225
- 'passed',
226
- `Found ${serviceEntries.length} service(s): ${services.join(', ')}`,
227
- `Available transports: ${[...new Set(transports)].join(', ')}`,
228
- undefined,
229
- { services, transports: [...new Set(transports)] }
230
- ));
231
- } else {
232
- steps.push(createStep(
233
- 'enumerate_services',
234
- 'failed',
235
- 'No services found',
236
- 'AI agent has no entry point for commerce operations'
237
- ));
238
- }
239
-
240
- // Step 4: Enumerate capabilities
241
- const capList = profile?.ucp?.capabilities || [];
242
- if (capList.length > 0) {
243
- for (const cap of capList) {
244
- const capName = getCapabilityName(cap);
245
- if (capName) capabilities.push(capName);
246
- }
247
-
248
- // Check for required checkout capability
249
- const hasCheckout = capabilities.some(c => c && c.includes('checkout'));
250
- const hasOrder = capabilities.some(c => c && c.includes('order'));
251
-
252
- steps.push(createStep(
253
- 'enumerate_capabilities',
254
- 'passed',
255
- `Found ${capList.length} capability/ies: ${capabilities.join(', ')}`,
256
- hasCheckout ? 'Checkout capability present - commerce ready' : 'No checkout capability - limited commerce support',
257
- undefined,
258
- { capabilities, hasCheckout, hasOrder }
259
- ));
260
- } else {
261
- steps.push(createStep(
262
- 'enumerate_capabilities',
263
- 'warning',
264
- 'No capabilities declared',
265
- 'AI agent cannot determine supported operations'
266
- ));
267
- }
268
-
269
- const totalDuration = Date.now() - startTime;
270
-
271
- return {
272
- success: foundProfile && serviceEntries.length > 0,
273
- steps,
274
- profileUrl,
275
- capabilities,
276
- services,
277
- transports: [...new Set(transports)],
278
- };
279
- }
280
-
281
- /**
282
- * Inspect capabilities in detail
283
- */
284
- export async function inspectCapabilities(
285
- profile: UcpProfile,
286
- timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
287
- ): Promise<CapabilityInspectionResult[]> {
288
- const results: CapabilityInspectionResult[] = [];
289
- const capList = profile.ucp?.capabilities || [];
290
-
291
- for (const cap of capList) {
292
- const capName = getCapabilityName(cap);
293
- const capObj = getCapabilityObject(cap);
294
-
295
- let schemaAccessible = false;
296
- let specAccessible = false;
297
-
298
- // Check schema URL (only for object-format capabilities)
299
- if (capObj?.schema) {
300
- const schemaResult = await fetchWithTimeout(capObj.schema, timeoutMs);
301
- schemaAccessible = schemaResult.ok;
302
- }
303
-
304
- // Check spec URL (only for object-format capabilities)
305
- if (capObj?.spec) {
306
- const specResult = await checkEndpointResponsive(capObj.spec, timeoutMs);
307
- specAccessible = specResult.ok;
308
- }
309
-
310
- results.push({
311
- name: capName,
312
- version: capObj?.version || 'unknown',
313
- schemaAccessible,
314
- specAccessible,
315
- isExtension: !!capObj?.extends,
316
- parentCapability: capObj?.extends,
317
- });
318
- }
319
-
320
- return results;
321
- }
322
-
323
- /**
324
- * Inspect services and their transports
325
- */
326
- export async function inspectServices(
327
- profile: UcpProfile,
328
- timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
329
- ): Promise<ServiceInspectionResult[]> {
330
- const results: ServiceInspectionResult[] = [];
331
- const serviceEntries = Object.entries(profile.ucp?.services || {});
332
-
333
- for (const [name, service] of serviceEntries) {
334
- const svc = service as UcpService;
335
- const result: ServiceInspectionResult = {
336
- name,
337
- version: svc.version,
338
- transports: {},
339
- };
340
-
341
- // Check REST transport
342
- if (svc.rest) {
343
- const schemaCheck = svc.rest.schema
344
- ? await fetchWithTimeout(svc.rest.schema, timeoutMs)
345
- : { ok: false };
346
- const endpointCheck = await checkEndpointResponsive(svc.rest.endpoint, timeoutMs);
347
-
348
- result.transports.rest = {
349
- endpoint: svc.rest.endpoint,
350
- schemaAccessible: schemaCheck.ok,
351
- endpointResponsive: endpointCheck.ok,
352
- };
353
- }
354
-
355
- // Check MCP transport
356
- if (svc.mcp) {
357
- const schemaCheck = svc.mcp.schema
358
- ? await fetchWithTimeout(svc.mcp.schema, timeoutMs)
359
- : { ok: false };
360
-
361
- result.transports.mcp = {
362
- endpoint: svc.mcp.endpoint,
363
- schemaAccessible: schemaCheck.ok,
364
- };
365
- }
366
-
367
- // Check A2A transport
368
- if (svc.a2a) {
369
- const agentCardCheck = await fetchWithTimeout(svc.a2a.agentCard, timeoutMs);
370
-
371
- result.transports.a2a = {
372
- agentCard: svc.a2a.agentCard,
373
- agentCardAccessible: agentCardCheck.ok,
374
- };
375
- }
376
-
377
- results.push(result);
378
- }
379
-
380
- return results;
381
- }
382
-
383
- /**
384
- * Simulate REST API interaction
385
- */
386
- export async function simulateRestApi(
387
- profile: UcpProfile,
388
- timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
389
- ): Promise<RestApiSimulationResult> {
390
- const steps: SimulationStepResult[] = [];
391
- const sampleOperations: OperationTestResult[] = [];
392
- let schemaLoaded = false;
393
- let endpointAccessible = false;
394
-
395
- // Find REST service
396
- const shoppingService = profile.ucp?.services?.['dev.ucp.shopping'] as UcpService | undefined;
397
- if (!shoppingService?.rest) {
398
- steps.push(createStep(
399
- 'find_rest_service',
400
- 'skipped',
401
- 'No REST service configured',
402
- 'Merchant may use MCP or A2A transport instead'
403
- ));
404
- return { success: false, steps, schemaLoaded, endpointAccessible, sampleOperations };
405
- }
406
-
407
- // Step 1: Load OpenAPI schema
408
- if (shoppingService.rest.schema) {
409
- const schemaStart = Date.now();
410
- const schemaResult = await fetchWithTimeout(shoppingService.rest.schema, timeoutMs);
411
- const schemaDuration = Date.now() - schemaStart;
412
-
413
- if (schemaResult.ok && schemaResult.data) {
414
- schemaLoaded = true;
415
- const schema = schemaResult.data as Record<string, unknown>;
416
-
417
- steps.push(createStep(
418
- 'load_openapi_schema',
419
- 'passed',
420
- `Loaded OpenAPI schema from ${shoppingService.rest.schema}`,
421
- `Response time: ${schemaDuration}ms`,
422
- schemaDuration,
423
- { schemaUrl: shoppingService.rest.schema }
424
- ));
425
-
426
- // Check for expected paths in schema
427
- const paths = schema.paths as Record<string, unknown> | undefined;
428
- if (paths) {
429
- const pathCount = Object.keys(paths).length;
430
- steps.push(createStep(
431
- 'analyze_schema_paths',
432
- pathCount > 0 ? 'passed' : 'warning',
433
- `Schema defines ${pathCount} operation path(s)`,
434
- pathCount > 0 ? `Paths: ${Object.keys(paths).slice(0, 5).join(', ')}${pathCount > 5 ? '...' : ''}` : undefined
435
- ));
436
- }
437
- } else {
438
- steps.push(createStep(
439
- 'load_openapi_schema',
440
- 'failed',
441
- 'Could not load OpenAPI schema',
442
- schemaResult.error || 'Unknown error'
443
- ));
444
- }
445
- } else {
446
- steps.push(createStep(
447
- 'load_openapi_schema',
448
- 'warning',
449
- 'No schema URL provided',
450
- 'AI agent cannot discover available operations'
451
- ));
452
- }
453
-
454
- // Step 2: Check endpoint responsiveness
455
- const endpointStart = Date.now();
456
- const endpointResult = await checkEndpointResponsive(shoppingService.rest.endpoint, timeoutMs);
457
- const endpointDuration = Date.now() - endpointStart;
458
-
459
- if (endpointResult.ok) {
460
- endpointAccessible = true;
461
- steps.push(createStep(
462
- 'check_endpoint',
463
- 'passed',
464
- `REST endpoint responsive: ${shoppingService.rest.endpoint}`,
465
- `Status: ${endpointResult.status}, Response time: ${endpointDuration}ms`,
466
- endpointDuration
467
- ));
468
- } else {
469
- steps.push(createStep(
470
- 'check_endpoint',
471
- 'failed',
472
- `REST endpoint not accessible: ${shoppingService.rest.endpoint}`,
473
- endpointResult.error || `Status: ${endpointResult.status}`
474
- ));
475
- }
476
-
477
- return {
478
- success: schemaLoaded && endpointAccessible,
479
- steps,
480
- schemaLoaded,
481
- endpointAccessible,
482
- sampleOperations,
483
- };
484
- }
485
-
486
- /**
487
- * Simulate checkout flow capability
488
- */
489
- export async function simulateCheckoutFlow(
490
- profile: UcpProfile,
491
- timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
492
- ): Promise<CheckoutSimulationResult> {
493
- const steps: SimulationStepResult[] = [];
494
- let canCreateCheckout = false;
495
- let checkoutSchemaValid = false;
496
- let orderFlowSupported = false;
497
- let fulfillmentSupported = false;
498
-
499
- const capabilities = profile.ucp?.capabilities || [];
500
-
501
- // Check for checkout capability (handle both string and object formats)
502
- const checkoutCapRaw = capabilities.find(c => getCapabilityName(c).includes('checkout'));
503
- const checkoutCapName = checkoutCapRaw ? getCapabilityName(checkoutCapRaw) : null;
504
- const checkoutCapObj = checkoutCapRaw ? getCapabilityObject(checkoutCapRaw) : null;
505
-
506
- if (checkoutCapName) {
507
- canCreateCheckout = true;
508
- steps.push(createStep(
509
- 'find_checkout_capability',
510
- 'passed',
511
- `Found checkout capability: ${checkoutCapName}`,
512
- checkoutCapObj?.version ? `Version: ${checkoutCapObj.version}` : 'String-format capability'
513
- ));
514
-
515
- // Validate checkout schema (only for object-format capabilities)
516
- if (checkoutCapObj?.schema) {
517
- const schemaResult = await fetchWithTimeout(checkoutCapObj.schema, timeoutMs);
518
- if (schemaResult.ok && schemaResult.data) {
519
- checkoutSchemaValid = true;
520
- const schema = schemaResult.data as Record<string, unknown>;
521
-
522
- // Check for required checkout properties
523
- const properties = (schema.properties || schema.$defs) as Record<string, unknown> | undefined;
524
- const hasCheckoutProps = properties && (
525
- properties.checkout_id ||
526
- properties.items ||
527
- properties.CheckoutSession
528
- );
529
-
530
- steps.push(createStep(
531
- 'validate_checkout_schema',
532
- hasCheckoutProps ? 'passed' : 'warning',
533
- `Checkout schema ${hasCheckoutProps ? 'has expected structure' : 'loaded but structure unclear'}`,
534
- hasCheckoutProps ? 'AI agent can create checkout sessions' : 'Schema may need review'
535
- ));
536
- } else {
537
- steps.push(createStep(
538
- 'validate_checkout_schema',
539
- 'failed',
540
- 'Could not load checkout schema',
541
- schemaResult.error || 'Unknown error'
542
- ));
543
- }
544
- } else {
545
- // String-format capability - no schema to validate, mark as valid for basic functionality
546
- checkoutSchemaValid = true;
547
- steps.push(createStep(
548
- 'validate_checkout_schema',
549
- 'info' as SimulationStepStatus,
550
- 'No schema URL in capability (string format)',
551
- 'Capability declared but schema validation skipped'
552
- ));
553
- }
554
- } else {
555
- steps.push(createStep(
556
- 'find_checkout_capability',
557
- 'failed',
558
- 'No checkout capability found',
559
- 'AI agent cannot create checkout sessions'
560
- ));
561
- }
562
-
563
- // Check for order capability
564
- const orderCapRaw = capabilities.find(c => getCapabilityName(c).includes('order'));
565
- const orderCapName = orderCapRaw ? getCapabilityName(orderCapRaw) : null;
566
- if (orderCapName) {
567
- orderFlowSupported = true;
568
- steps.push(createStep(
569
- 'find_order_capability',
570
- 'passed',
571
- `Found order capability: ${orderCapName}`,
572
- 'AI agent can track order status'
573
- ));
574
- } else {
575
- steps.push(createStep(
576
- 'find_order_capability',
577
- 'warning',
578
- 'No order capability found',
579
- 'Order tracking may not be available'
580
- ));
581
- }
582
-
583
- // Check for fulfillment capability
584
- const fulfillmentCapRaw = capabilities.find(c => getCapabilityName(c).includes('fulfillment'));
585
- const fulfillmentCapName = fulfillmentCapRaw ? getCapabilityName(fulfillmentCapRaw) : null;
586
- if (fulfillmentCapName) {
587
- fulfillmentSupported = true;
588
- steps.push(createStep(
589
- 'find_fulfillment_capability',
590
- 'passed',
591
- `Found fulfillment capability: ${fulfillmentCapName}`,
592
- 'AI agent can track shipping and delivery'
593
- ));
594
- } else {
595
- steps.push(createStep(
596
- 'find_fulfillment_capability',
597
- 'info' as SimulationStepStatus,
598
- 'No fulfillment capability found',
599
- 'Fulfillment tracking not available via UCP'
600
- ));
601
- }
602
-
603
- return {
604
- success: canCreateCheckout && checkoutSchemaValid,
605
- steps,
606
- canCreateCheckout,
607
- checkoutSchemaValid,
608
- orderFlowSupported,
609
- fulfillmentSupported,
610
- };
611
- }
612
-
613
- /**
614
- * Check payment readiness
615
- */
616
- export async function simulatePaymentReadiness(
617
- profile: UcpProfile,
618
- timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
619
- ): Promise<PaymentReadinessResult> {
620
- const steps: SimulationStepResult[] = [];
621
- let handlersFound = 0;
622
- let webhookVerifiable = false;
623
- let signingKeyValid = false;
624
-
625
- // Check payment handlers
626
- const handlers = profile.payment?.handlers || [];
627
- handlersFound = handlers.length;
628
-
629
- if (handlersFound > 0) {
630
- const handlerNames = handlers.map(h => h.name).join(', ');
631
- steps.push(createStep(
632
- 'find_payment_handlers',
633
- 'passed',
634
- `Found ${handlersFound} payment handler(s): ${handlerNames}`,
635
- 'AI agent can initiate payments'
636
- ));
637
-
638
- // Check handler configs
639
- for (const handler of handlers) {
640
- if (handler.config_schema) {
641
- const schemaResult = await checkEndpointResponsive(handler.config_schema, timeoutMs);
642
- steps.push(createStep(
643
- `check_handler_${handler.id}`,
644
- schemaResult.ok ? 'passed' : 'warning',
645
- `Payment handler "${handler.name}" config schema ${schemaResult.ok ? 'accessible' : 'not accessible'}`,
646
- handler.config_schema
647
- ));
648
- }
649
- }
650
- } else {
651
- steps.push(createStep(
652
- 'find_payment_handlers',
653
- 'warning',
654
- 'No payment handlers configured',
655
- 'Payment processing may use external flow'
656
- ));
657
- }
658
-
659
- // Check signing keys for webhook verification
660
- const signingKeys = profile.signing_keys;
661
- if (signingKeys && Array.isArray(signingKeys) && signingKeys.length > 0) {
662
- webhookVerifiable = true;
663
-
664
- // Validate key structure
665
- const validKeys = signingKeys.filter(key =>
666
- key.kty && key.kid && (
667
- (key.kty === 'EC' && key.crv && key.x && key.y) ||
668
- (key.kty === 'RSA' && key.n && key.e)
669
- )
670
- );
671
-
672
- signingKeyValid = validKeys.length > 0;
673
-
674
- steps.push(createStep(
675
- 'check_signing_keys',
676
- signingKeyValid ? 'passed' : 'warning',
677
- `Found ${signingKeys.length} signing key(s), ${validKeys.length} valid`,
678
- signingKeyValid
679
- ? 'AI agent can verify webhook signatures'
680
- : 'Signing keys present but may be incomplete'
681
- ));
682
- } else {
683
- steps.push(createStep(
684
- 'check_signing_keys',
685
- 'warning',
686
- 'No signing keys found',
687
- 'Webhook verification not available'
688
- ));
689
- }
690
-
691
- return {
692
- success: handlersFound > 0 || webhookVerifiable,
693
- steps,
694
- handlersFound,
695
- webhookVerifiable,
696
- signingKeyValid,
697
- };
698
- }
699
-
700
- /**
701
- * Generate recommendations based on simulation results
702
- */
703
- function generateRecommendations(
704
- discovery: DiscoveryFlowResult,
705
- capabilities: CapabilityInspectionResult[],
706
- services: ServiceInspectionResult[],
707
- restApi?: RestApiSimulationResult,
708
- checkout?: CheckoutSimulationResult,
709
- payment?: PaymentReadinessResult
710
- ): string[] {
711
- const recommendations: string[] = [];
712
-
713
- // Discovery issues
714
- if (!discovery.success) {
715
- recommendations.push('Ensure UCP profile is accessible at /.well-known/ucp');
716
- }
717
-
718
- // Service issues
719
- if (discovery.services.length === 0) {
720
- recommendations.push('Add at least one service (e.g., dev.ucp.shopping) to enable commerce');
721
- }
722
-
723
- // Transport issues
724
- if (discovery.transports.length === 0) {
725
- recommendations.push('Configure at least one transport binding (REST, MCP, or A2A)');
726
- }
727
-
728
- // Capability issues
729
- const inaccessibleSchemas = capabilities.filter(c => !c.schemaAccessible);
730
- if (inaccessibleSchemas.length > 0) {
731
- recommendations.push(`Fix inaccessible capability schemas: ${inaccessibleSchemas.map(c => c.name).join(', ')}`);
732
- }
733
-
734
- // REST API issues
735
- if (restApi && !restApi.schemaLoaded) {
736
- recommendations.push('Provide accessible OpenAPI schema for REST service');
737
- }
738
- if (restApi && !restApi.endpointAccessible) {
739
- recommendations.push('Ensure REST endpoint is publicly accessible');
740
- }
741
-
742
- // Checkout issues
743
- if (checkout && !checkout.canCreateCheckout) {
744
- recommendations.push('Add checkout capability (dev.ucp.shopping.checkout) to enable purchases');
745
- }
746
- if (checkout && checkout.canCreateCheckout && !checkout.checkoutSchemaValid) {
747
- recommendations.push('Ensure checkout schema is accessible and valid');
748
- }
749
-
750
- // Payment issues
751
- if (payment && payment.handlersFound === 0) {
752
- recommendations.push('Configure payment handlers in the profile');
753
- }
754
- if (payment && !payment.signingKeyValid) {
755
- recommendations.push('Add valid signing keys (EC or RSA JWK) for webhook verification');
756
- }
757
-
758
- // Add positive note if everything looks good
759
- if (recommendations.length === 0) {
760
- recommendations.push('Profile is well-configured for AI agent commerce!');
761
- }
762
-
763
- return recommendations;
764
- }
765
-
766
- /**
767
- * Calculate overall AI readiness score
768
- */
769
- function calculateScore(
770
- discovery: DiscoveryFlowResult,
771
- capabilities: CapabilityInspectionResult[],
772
- services: ServiceInspectionResult[],
773
- restApi?: RestApiSimulationResult,
774
- checkout?: CheckoutSimulationResult,
775
- payment?: PaymentReadinessResult
776
- ): number {
777
- let score = 0;
778
- const maxScore = 100;
779
-
780
- // Discovery (25 points)
781
- if (discovery.success) score += 15;
782
- if (discovery.services.length > 0) score += 5;
783
- if (discovery.capabilities.length > 0) score += 5;
784
-
785
- // Capabilities (25 points)
786
- const accessibleCapabilities = capabilities.filter(c => c.schemaAccessible);
787
- if (capabilities.length > 0) {
788
- score += Math.round((accessibleCapabilities.length / capabilities.length) * 15);
789
- }
790
- // Bonus for having checkout
791
- if (capabilities.some(c => c.name.includes('checkout'))) score += 10;
792
-
793
- // Services & Transport (25 points)
794
- for (const service of services) {
795
- if (service.transports.rest?.endpointResponsive) score += 10;
796
- if (service.transports.rest?.schemaAccessible) score += 5;
797
- if (service.transports.mcp) score += 5;
798
- if (service.transports.a2a?.agentCardAccessible) score += 5;
799
- }
800
- // Cap at 25
801
- score = Math.min(score, 75);
802
-
803
- // Checkout (15 points)
804
- if (checkout?.canCreateCheckout) score += 8;
805
- if (checkout?.checkoutSchemaValid) score += 4;
806
- if (checkout?.orderFlowSupported) score += 3;
807
-
808
- // Payment (10 points)
809
- if (payment?.handlersFound || 0 > 0) score += 5;
810
- if (payment?.signingKeyValid) score += 5;
811
-
812
- return Math.min(score, maxScore);
813
- }
814
-
815
- /**
816
- * Get grade from score (aligned with all other scoring models)
817
- */
818
- function getGrade(score: number): 'A' | 'B' | 'C' | 'D' | 'F' {
819
- if (score >= 90) return 'A';
820
- if (score >= 80) return 'B';
821
- if (score >= 70) return 'C';
822
- if (score >= 60) return 'D';
823
- return 'F';
824
- }
825
-
826
- /**
827
- * Main simulation entry point
828
- * Simulates a complete AI agent interaction with a UCP-enabled merchant
829
- */
830
- export async function simulateAgentInteraction(
831
- domain: string,
832
- options: SimulationOptions = {}
833
- ): Promise<AgentSimulationResult> {
834
- const startTime = Date.now();
835
- const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
836
- const fetchTimeout = Math.min(timeoutMs / 3, DEFAULT_FETCH_TIMEOUT_MS);
837
-
838
- // Step 1: Discovery flow
839
- const discovery = await simulateDiscoveryFlow(domain, fetchTimeout);
840
-
841
- // Early exit if discovery failed
842
- if (!discovery.success || !discovery.profileUrl) {
843
- const durationMs = Date.now() - startTime;
844
- return {
845
- ok: false,
846
- domain,
847
- simulatedAt: new Date().toISOString(),
848
- durationMs,
849
- overallScore: 0,
850
- grade: 'F' as const,
851
- discovery,
852
- capabilities: [],
853
- services: [],
854
- summary: {
855
- totalSteps: discovery.steps.length,
856
- passedSteps: discovery.steps.filter(s => s.status === 'passed').length,
857
- failedSteps: discovery.steps.filter(s => s.status === 'failed').length,
858
- warningSteps: discovery.steps.filter(s => s.status === 'warning').length,
859
- skippedSteps: discovery.steps.filter(s => s.status === 'skipped').length,
860
- },
861
- recommendations: ['Ensure UCP profile is accessible at /.well-known/ucp or /.well-known/ucp.json'],
862
- };
863
- }
864
-
865
- // Fetch full profile for detailed inspection
866
- const profileResult = await fetchWithTimeout(discovery.profileUrl, fetchTimeout);
867
- const profile = profileResult.data as UcpProfile;
868
-
869
- // Step 2: Inspect capabilities
870
- const capabilities = await inspectCapabilities(profile, fetchTimeout);
871
-
872
- // Step 3: Inspect services
873
- const services = await inspectServices(profile, fetchTimeout);
874
-
875
- // Step 4: REST API simulation (if applicable)
876
- let restApi: RestApiSimulationResult | undefined;
877
- if (!options.skipRestApiTest) {
878
- restApi = await simulateRestApi(profile, fetchTimeout);
879
- }
880
-
881
- // Step 5: Checkout flow simulation
882
- let checkout: CheckoutSimulationResult | undefined;
883
- if (options.testCheckoutFlow !== false) {
884
- checkout = await simulateCheckoutFlow(profile, fetchTimeout);
885
- }
886
-
887
- // Step 6: Payment readiness check
888
- const payment = await simulatePaymentReadiness(profile, fetchTimeout);
889
-
890
- // Collect all steps
891
- const allSteps = [
892
- ...discovery.steps,
893
- ...(restApi?.steps || []),
894
- ...(checkout?.steps || []),
895
- ...payment.steps,
896
- ];
897
-
898
- const durationMs = Date.now() - startTime;
899
-
900
- // Generate recommendations
901
- const recommendations = generateRecommendations(
902
- discovery, capabilities, services, restApi, checkout, payment
903
- );
904
-
905
- // Calculate score
906
- const overallScore = calculateScore(
907
- discovery, capabilities, services, restApi, checkout, payment
908
- );
909
-
910
- return {
911
- ok: discovery.success && (checkout?.success ?? true),
912
- domain,
913
- simulatedAt: new Date().toISOString(),
914
- durationMs,
915
- overallScore,
916
- grade: getGrade(overallScore),
917
- discovery,
918
- capabilities,
919
- services,
920
- restApi,
921
- checkout,
922
- payment,
923
- summary: {
924
- totalSteps: allSteps.length,
925
- passedSteps: allSteps.filter(s => s.status === 'passed').length,
926
- failedSteps: allSteps.filter(s => s.status === 'failed').length,
927
- warningSteps: allSteps.filter(s => s.status === 'warning').length,
928
- skippedSteps: allSteps.filter(s => s.status === 'skipped').length,
929
- },
930
- recommendations,
931
- };
932
- }
933
-
934
- // Export helper functions for testing
935
- export {
936
- fetchWithTimeout,
937
- checkEndpointResponsive,
938
- generateRecommendations,
939
- calculateScore,
940
- getGrade,
941
- };
1
+ "use strict";
2
+ /**
3
+ * AI Agent Simulator
4
+ * Simulates how an AI agent discovers and interacts with UCP-enabled merchants
5
+ *
6
+ * The goal is to test real-world functionality, not just spec compliance.
7
+ * This proves that a UCP implementation actually works for AI agent commerce.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.simulateDiscoveryFlow = simulateDiscoveryFlow;
11
+ exports.inspectCapabilities = inspectCapabilities;
12
+ exports.inspectServices = inspectServices;
13
+ exports.simulateRestApi = simulateRestApi;
14
+ exports.simulateCheckoutFlow = simulateCheckoutFlow;
15
+ exports.simulatePaymentReadiness = simulatePaymentReadiness;
16
+ exports.simulateAgentInteraction = simulateAgentInteraction;
17
+ exports.fetchWithTimeout = fetchWithTimeout;
18
+ exports.checkEndpointResponsive = checkEndpointResponsive;
19
+ exports.generateRecommendations = generateRecommendations;
20
+ exports.calculateScore = calculateScore;
21
+ exports.getGrade = getGrade;
22
+ const DEFAULT_TIMEOUT_MS = 30000;
23
+ const DEFAULT_FETCH_TIMEOUT_MS = 10000;
24
+ /**
25
+ * Normalize capability to extract the name string
26
+ * Handles both string format and object format capabilities
27
+ */
28
+ function getCapabilityName(cap) {
29
+ if (typeof cap === 'string') {
30
+ return cap;
31
+ }
32
+ if (cap && typeof cap === 'object' && 'name' in cap) {
33
+ return String(cap.name);
34
+ }
35
+ return '';
36
+ }
37
+ /**
38
+ * Get capability object (for accessing schema, version, etc.)
39
+ * Returns null for string-only capabilities
40
+ */
41
+ function getCapabilityObject(cap) {
42
+ if (cap && typeof cap === 'object' && 'name' in cap) {
43
+ return cap;
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Step builder helper
49
+ */
50
+ function createStep(step, status, message, details, durationMs, data) {
51
+ return { step, status, message, details, durationMs, data };
52
+ }
53
+ /**
54
+ * Fetch with timeout
55
+ */
56
+ async function fetchWithTimeout(url, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
57
+ const controller = new AbortController();
58
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
59
+ try {
60
+ const response = await fetch(url, {
61
+ signal: controller.signal,
62
+ headers: {
63
+ 'User-Agent': 'UCP-Agent-Simulator/1.0',
64
+ 'Accept': 'application/json',
65
+ },
66
+ });
67
+ clearTimeout(timeoutId);
68
+ if (!response.ok) {
69
+ return { ok: false, status: response.status, error: `HTTP ${response.status}` };
70
+ }
71
+ const contentType = response.headers.get('content-type') || '';
72
+ if (contentType.includes('application/json')) {
73
+ const data = await response.json();
74
+ return { ok: true, status: response.status, data };
75
+ }
76
+ return { ok: true, status: response.status };
77
+ }
78
+ catch (error) {
79
+ clearTimeout(timeoutId);
80
+ const message = error instanceof Error ? error.message : 'Unknown error';
81
+ return { ok: false, error: message };
82
+ }
83
+ }
84
+ /**
85
+ * HEAD request to check endpoint responsiveness
86
+ */
87
+ async function checkEndpointResponsive(url, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
88
+ const controller = new AbortController();
89
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
90
+ try {
91
+ const response = await fetch(url, {
92
+ method: 'HEAD',
93
+ signal: controller.signal,
94
+ headers: {
95
+ 'User-Agent': 'UCP-Agent-Simulator/1.0',
96
+ },
97
+ });
98
+ clearTimeout(timeoutId);
99
+ // Accept various success codes - we just want to know endpoint exists
100
+ return { ok: response.status < 500, status: response.status };
101
+ }
102
+ catch (error) {
103
+ clearTimeout(timeoutId);
104
+ const message = error instanceof Error ? error.message : 'Unknown error';
105
+ return { ok: false, error: message };
106
+ }
107
+ }
108
+ /**
109
+ * Simulate discovery flow - how an AI agent discovers a UCP merchant
110
+ */
111
+ async function simulateDiscoveryFlow(domain, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
112
+ const steps = [];
113
+ let profileUrl;
114
+ let profile = null;
115
+ const capabilities = [];
116
+ const services = [];
117
+ const transports = [];
118
+ // Step 1: Try to fetch /.well-known/ucp
119
+ const startTime = Date.now();
120
+ const urls = [
121
+ `https://${domain}/.well-known/ucp`,
122
+ `https://${domain}/.well-known/ucp.json`,
123
+ ];
124
+ let foundProfile = false;
125
+ for (const url of urls) {
126
+ const fetchStart = Date.now();
127
+ const result = await fetchWithTimeout(url, timeoutMs);
128
+ const fetchDuration = Date.now() - fetchStart;
129
+ if (result.ok && result.data && typeof result.data === 'object') {
130
+ const data = result.data;
131
+ if (data.ucp) {
132
+ profile = data;
133
+ profileUrl = url;
134
+ foundProfile = true;
135
+ steps.push(createStep('discover_profile', 'passed', `Found UCP profile at ${url}`, `Response time: ${fetchDuration}ms`, fetchDuration, { url, responseTime: fetchDuration }));
136
+ break;
137
+ }
138
+ }
139
+ }
140
+ if (!foundProfile) {
141
+ steps.push(createStep('discover_profile', 'failed', 'Could not find UCP profile', `Tried: ${urls.join(', ')}`));
142
+ return { success: false, steps, capabilities, services, transports };
143
+ }
144
+ // Step 2: Parse UCP version
145
+ if (profile?.ucp?.version) {
146
+ steps.push(createStep('parse_version', 'passed', `UCP version: ${profile.ucp.version}`, undefined, undefined, { version: profile.ucp.version }));
147
+ }
148
+ else {
149
+ steps.push(createStep('parse_version', 'failed', 'Missing UCP version', 'AI agent cannot determine protocol version'));
150
+ }
151
+ // Step 3: Enumerate services
152
+ const serviceEntries = Object.entries(profile?.ucp?.services || {});
153
+ if (serviceEntries.length > 0) {
154
+ for (const [serviceName, service] of serviceEntries) {
155
+ services.push(serviceName);
156
+ // Track transports
157
+ if (service.rest)
158
+ transports.push('rest');
159
+ if (service.mcp)
160
+ transports.push('mcp');
161
+ if (service.a2a)
162
+ transports.push('a2a');
163
+ if (service.embedded)
164
+ transports.push('embedded');
165
+ }
166
+ steps.push(createStep('enumerate_services', 'passed', `Found ${serviceEntries.length} service(s): ${services.join(', ')}`, `Available transports: ${[...new Set(transports)].join(', ')}`, undefined, { services, transports: [...new Set(transports)] }));
167
+ }
168
+ else {
169
+ steps.push(createStep('enumerate_services', 'failed', 'No services found', 'AI agent has no entry point for commerce operations'));
170
+ }
171
+ // Step 4: Enumerate capabilities
172
+ const capList = profile?.ucp?.capabilities || [];
173
+ if (capList.length > 0) {
174
+ for (const cap of capList) {
175
+ const capName = getCapabilityName(cap);
176
+ if (capName)
177
+ capabilities.push(capName);
178
+ }
179
+ // Check for required checkout capability
180
+ const hasCheckout = capabilities.some(c => c && c.includes('checkout'));
181
+ const hasOrder = capabilities.some(c => c && c.includes('order'));
182
+ steps.push(createStep('enumerate_capabilities', 'passed', `Found ${capList.length} capability/ies: ${capabilities.join(', ')}`, hasCheckout ? 'Checkout capability present - commerce ready' : 'No checkout capability - limited commerce support', undefined, { capabilities, hasCheckout, hasOrder }));
183
+ }
184
+ else {
185
+ steps.push(createStep('enumerate_capabilities', 'warning', 'No capabilities declared', 'AI agent cannot determine supported operations'));
186
+ }
187
+ const totalDuration = Date.now() - startTime;
188
+ return {
189
+ success: foundProfile && serviceEntries.length > 0,
190
+ steps,
191
+ profileUrl,
192
+ capabilities,
193
+ services,
194
+ transports: [...new Set(transports)],
195
+ };
196
+ }
197
+ /**
198
+ * Inspect capabilities in detail
199
+ */
200
+ async function inspectCapabilities(profile, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
201
+ const results = [];
202
+ const capList = profile.ucp?.capabilities || [];
203
+ for (const cap of capList) {
204
+ const capName = getCapabilityName(cap);
205
+ const capObj = getCapabilityObject(cap);
206
+ let schemaAccessible = false;
207
+ let specAccessible = false;
208
+ // Check schema URL (only for object-format capabilities)
209
+ if (capObj?.schema) {
210
+ const schemaResult = await fetchWithTimeout(capObj.schema, timeoutMs);
211
+ schemaAccessible = schemaResult.ok;
212
+ }
213
+ // Check spec URL (only for object-format capabilities)
214
+ if (capObj?.spec) {
215
+ const specResult = await checkEndpointResponsive(capObj.spec, timeoutMs);
216
+ specAccessible = specResult.ok;
217
+ }
218
+ results.push({
219
+ name: capName,
220
+ version: capObj?.version || 'unknown',
221
+ schemaAccessible,
222
+ specAccessible,
223
+ isExtension: !!capObj?.extends,
224
+ parentCapability: capObj?.extends,
225
+ });
226
+ }
227
+ return results;
228
+ }
229
+ /**
230
+ * Inspect services and their transports
231
+ */
232
+ async function inspectServices(profile, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
233
+ const results = [];
234
+ const serviceEntries = Object.entries(profile.ucp?.services || {});
235
+ for (const [name, service] of serviceEntries) {
236
+ const svc = service;
237
+ const result = {
238
+ name,
239
+ version: svc.version,
240
+ transports: {},
241
+ };
242
+ // Check REST transport
243
+ if (svc.rest) {
244
+ const schemaCheck = svc.rest.schema
245
+ ? await fetchWithTimeout(svc.rest.schema, timeoutMs)
246
+ : { ok: false };
247
+ const endpointCheck = await checkEndpointResponsive(svc.rest.endpoint, timeoutMs);
248
+ result.transports.rest = {
249
+ endpoint: svc.rest.endpoint,
250
+ schemaAccessible: schemaCheck.ok,
251
+ endpointResponsive: endpointCheck.ok,
252
+ };
253
+ }
254
+ // Check MCP transport
255
+ if (svc.mcp) {
256
+ const schemaCheck = svc.mcp.schema
257
+ ? await fetchWithTimeout(svc.mcp.schema, timeoutMs)
258
+ : { ok: false };
259
+ result.transports.mcp = {
260
+ endpoint: svc.mcp.endpoint,
261
+ schemaAccessible: schemaCheck.ok,
262
+ };
263
+ }
264
+ // Check A2A transport
265
+ if (svc.a2a) {
266
+ const agentCardCheck = await fetchWithTimeout(svc.a2a.agentCard, timeoutMs);
267
+ result.transports.a2a = {
268
+ agentCard: svc.a2a.agentCard,
269
+ agentCardAccessible: agentCardCheck.ok,
270
+ };
271
+ }
272
+ results.push(result);
273
+ }
274
+ return results;
275
+ }
276
+ /**
277
+ * Simulate REST API interaction
278
+ */
279
+ async function simulateRestApi(profile, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
280
+ const steps = [];
281
+ const sampleOperations = [];
282
+ let schemaLoaded = false;
283
+ let endpointAccessible = false;
284
+ // Find REST service
285
+ const shoppingService = profile.ucp?.services?.['dev.ucp.shopping'];
286
+ if (!shoppingService?.rest) {
287
+ steps.push(createStep('find_rest_service', 'skipped', 'No REST service configured', 'Merchant may use MCP or A2A transport instead'));
288
+ return { success: false, steps, schemaLoaded, endpointAccessible, sampleOperations };
289
+ }
290
+ // Step 1: Load OpenAPI schema
291
+ if (shoppingService.rest.schema) {
292
+ const schemaStart = Date.now();
293
+ const schemaResult = await fetchWithTimeout(shoppingService.rest.schema, timeoutMs);
294
+ const schemaDuration = Date.now() - schemaStart;
295
+ if (schemaResult.ok && schemaResult.data) {
296
+ schemaLoaded = true;
297
+ const schema = schemaResult.data;
298
+ steps.push(createStep('load_openapi_schema', 'passed', `Loaded OpenAPI schema from ${shoppingService.rest.schema}`, `Response time: ${schemaDuration}ms`, schemaDuration, { schemaUrl: shoppingService.rest.schema }));
299
+ // Check for expected paths in schema
300
+ const paths = schema.paths;
301
+ if (paths) {
302
+ const pathCount = Object.keys(paths).length;
303
+ steps.push(createStep('analyze_schema_paths', pathCount > 0 ? 'passed' : 'warning', `Schema defines ${pathCount} operation path(s)`, pathCount > 0 ? `Paths: ${Object.keys(paths).slice(0, 5).join(', ')}${pathCount > 5 ? '...' : ''}` : undefined));
304
+ }
305
+ }
306
+ else {
307
+ steps.push(createStep('load_openapi_schema', 'failed', 'Could not load OpenAPI schema', schemaResult.error || 'Unknown error'));
308
+ }
309
+ }
310
+ else {
311
+ steps.push(createStep('load_openapi_schema', 'warning', 'No schema URL provided', 'AI agent cannot discover available operations'));
312
+ }
313
+ // Step 2: Check endpoint responsiveness
314
+ const endpointStart = Date.now();
315
+ const endpointResult = await checkEndpointResponsive(shoppingService.rest.endpoint, timeoutMs);
316
+ const endpointDuration = Date.now() - endpointStart;
317
+ if (endpointResult.ok) {
318
+ endpointAccessible = true;
319
+ steps.push(createStep('check_endpoint', 'passed', `REST endpoint responsive: ${shoppingService.rest.endpoint}`, `Status: ${endpointResult.status}, Response time: ${endpointDuration}ms`, endpointDuration));
320
+ }
321
+ else {
322
+ steps.push(createStep('check_endpoint', 'failed', `REST endpoint not accessible: ${shoppingService.rest.endpoint}`, endpointResult.error || `Status: ${endpointResult.status}`));
323
+ }
324
+ return {
325
+ success: schemaLoaded && endpointAccessible,
326
+ steps,
327
+ schemaLoaded,
328
+ endpointAccessible,
329
+ sampleOperations,
330
+ };
331
+ }
332
+ /**
333
+ * Simulate checkout flow capability
334
+ */
335
+ async function simulateCheckoutFlow(profile, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
336
+ const steps = [];
337
+ let canCreateCheckout = false;
338
+ let checkoutSchemaValid = false;
339
+ let orderFlowSupported = false;
340
+ let fulfillmentSupported = false;
341
+ const capabilities = profile.ucp?.capabilities || [];
342
+ // Check for checkout capability (handle both string and object formats)
343
+ const checkoutCapRaw = capabilities.find(c => getCapabilityName(c).includes('checkout'));
344
+ const checkoutCapName = checkoutCapRaw ? getCapabilityName(checkoutCapRaw) : null;
345
+ const checkoutCapObj = checkoutCapRaw ? getCapabilityObject(checkoutCapRaw) : null;
346
+ if (checkoutCapName) {
347
+ canCreateCheckout = true;
348
+ steps.push(createStep('find_checkout_capability', 'passed', `Found checkout capability: ${checkoutCapName}`, checkoutCapObj?.version ? `Version: ${checkoutCapObj.version}` : 'String-format capability'));
349
+ // Validate checkout schema (only for object-format capabilities)
350
+ if (checkoutCapObj?.schema) {
351
+ const schemaResult = await fetchWithTimeout(checkoutCapObj.schema, timeoutMs);
352
+ if (schemaResult.ok && schemaResult.data) {
353
+ checkoutSchemaValid = true;
354
+ const schema = schemaResult.data;
355
+ // Check for required checkout properties
356
+ const properties = (schema.properties || schema.$defs);
357
+ const hasCheckoutProps = properties && (properties.checkout_id ||
358
+ properties.items ||
359
+ properties.CheckoutSession);
360
+ steps.push(createStep('validate_checkout_schema', hasCheckoutProps ? 'passed' : 'warning', `Checkout schema ${hasCheckoutProps ? 'has expected structure' : 'loaded but structure unclear'}`, hasCheckoutProps ? 'AI agent can create checkout sessions' : 'Schema may need review'));
361
+ }
362
+ else {
363
+ steps.push(createStep('validate_checkout_schema', 'failed', 'Could not load checkout schema', schemaResult.error || 'Unknown error'));
364
+ }
365
+ }
366
+ else {
367
+ // String-format capability - no schema to validate, mark as valid for basic functionality
368
+ checkoutSchemaValid = true;
369
+ steps.push(createStep('validate_checkout_schema', 'info', 'No schema URL in capability (string format)', 'Capability declared but schema validation skipped'));
370
+ }
371
+ }
372
+ else {
373
+ steps.push(createStep('find_checkout_capability', 'failed', 'No checkout capability found', 'AI agent cannot create checkout sessions'));
374
+ }
375
+ // Check for order capability
376
+ const orderCapRaw = capabilities.find(c => getCapabilityName(c).includes('order'));
377
+ const orderCapName = orderCapRaw ? getCapabilityName(orderCapRaw) : null;
378
+ if (orderCapName) {
379
+ orderFlowSupported = true;
380
+ steps.push(createStep('find_order_capability', 'passed', `Found order capability: ${orderCapName}`, 'AI agent can track order status'));
381
+ }
382
+ else {
383
+ steps.push(createStep('find_order_capability', 'warning', 'No order capability found', 'Order tracking may not be available'));
384
+ }
385
+ // Check for fulfillment capability
386
+ const fulfillmentCapRaw = capabilities.find(c => getCapabilityName(c).includes('fulfillment'));
387
+ const fulfillmentCapName = fulfillmentCapRaw ? getCapabilityName(fulfillmentCapRaw) : null;
388
+ if (fulfillmentCapName) {
389
+ fulfillmentSupported = true;
390
+ steps.push(createStep('find_fulfillment_capability', 'passed', `Found fulfillment capability: ${fulfillmentCapName}`, 'AI agent can track shipping and delivery'));
391
+ }
392
+ else {
393
+ steps.push(createStep('find_fulfillment_capability', 'info', 'No fulfillment capability found', 'Fulfillment tracking not available via UCP'));
394
+ }
395
+ return {
396
+ success: canCreateCheckout && checkoutSchemaValid,
397
+ steps,
398
+ canCreateCheckout,
399
+ checkoutSchemaValid,
400
+ orderFlowSupported,
401
+ fulfillmentSupported,
402
+ };
403
+ }
404
+ /**
405
+ * Check payment readiness
406
+ */
407
+ async function simulatePaymentReadiness(profile, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
408
+ const steps = [];
409
+ let handlersFound = 0;
410
+ let webhookVerifiable = false;
411
+ let signingKeyValid = false;
412
+ // Check payment handlers
413
+ const handlers = profile.payment?.handlers || [];
414
+ handlersFound = handlers.length;
415
+ if (handlersFound > 0) {
416
+ const handlerNames = handlers.map(h => h.name).join(', ');
417
+ steps.push(createStep('find_payment_handlers', 'passed', `Found ${handlersFound} payment handler(s): ${handlerNames}`, 'AI agent can initiate payments'));
418
+ // Check handler configs
419
+ for (const handler of handlers) {
420
+ if (handler.config_schema) {
421
+ const schemaResult = await checkEndpointResponsive(handler.config_schema, timeoutMs);
422
+ steps.push(createStep(`check_handler_${handler.id}`, schemaResult.ok ? 'passed' : 'warning', `Payment handler "${handler.name}" config schema ${schemaResult.ok ? 'accessible' : 'not accessible'}`, handler.config_schema));
423
+ }
424
+ }
425
+ }
426
+ else {
427
+ steps.push(createStep('find_payment_handlers', 'warning', 'No payment handlers configured', 'Payment processing may use external flow'));
428
+ }
429
+ // Check signing keys for webhook verification
430
+ const signingKeys = profile.signing_keys;
431
+ if (signingKeys && Array.isArray(signingKeys) && signingKeys.length > 0) {
432
+ webhookVerifiable = true;
433
+ // Validate key structure
434
+ const validKeys = signingKeys.filter(key => key.kty && key.kid && ((key.kty === 'EC' && key.crv && key.x && key.y) ||
435
+ (key.kty === 'RSA' && key.n && key.e)));
436
+ signingKeyValid = validKeys.length > 0;
437
+ steps.push(createStep('check_signing_keys', signingKeyValid ? 'passed' : 'warning', `Found ${signingKeys.length} signing key(s), ${validKeys.length} valid`, signingKeyValid
438
+ ? 'AI agent can verify webhook signatures'
439
+ : 'Signing keys present but may be incomplete'));
440
+ }
441
+ else {
442
+ steps.push(createStep('check_signing_keys', 'warning', 'No signing keys found', 'Webhook verification not available'));
443
+ }
444
+ return {
445
+ success: handlersFound > 0 || webhookVerifiable,
446
+ steps,
447
+ handlersFound,
448
+ webhookVerifiable,
449
+ signingKeyValid,
450
+ };
451
+ }
452
+ /**
453
+ * Generate recommendations based on simulation results
454
+ */
455
+ function generateRecommendations(discovery, capabilities, services, restApi, checkout, payment) {
456
+ const recommendations = [];
457
+ // Discovery issues
458
+ if (!discovery.success) {
459
+ recommendations.push('Ensure UCP profile is accessible at /.well-known/ucp');
460
+ }
461
+ // Service issues
462
+ if (discovery.services.length === 0) {
463
+ recommendations.push('Add at least one service (e.g., dev.ucp.shopping) to enable commerce');
464
+ }
465
+ // Transport issues
466
+ if (discovery.transports.length === 0) {
467
+ recommendations.push('Configure at least one transport binding (REST, MCP, or A2A)');
468
+ }
469
+ // Capability issues
470
+ const inaccessibleSchemas = capabilities.filter(c => !c.schemaAccessible);
471
+ if (inaccessibleSchemas.length > 0) {
472
+ recommendations.push(`Fix inaccessible capability schemas: ${inaccessibleSchemas.map(c => c.name).join(', ')}`);
473
+ }
474
+ // REST API issues
475
+ if (restApi && !restApi.schemaLoaded) {
476
+ recommendations.push('Provide accessible OpenAPI schema for REST service');
477
+ }
478
+ if (restApi && !restApi.endpointAccessible) {
479
+ recommendations.push('Ensure REST endpoint is publicly accessible');
480
+ }
481
+ // Checkout issues
482
+ if (checkout && !checkout.canCreateCheckout) {
483
+ recommendations.push('Add checkout capability (dev.ucp.shopping.checkout) to enable purchases');
484
+ }
485
+ if (checkout && checkout.canCreateCheckout && !checkout.checkoutSchemaValid) {
486
+ recommendations.push('Ensure checkout schema is accessible and valid');
487
+ }
488
+ // Payment issues
489
+ if (payment && payment.handlersFound === 0) {
490
+ recommendations.push('Configure payment handlers in the profile');
491
+ }
492
+ if (payment && !payment.signingKeyValid) {
493
+ recommendations.push('Add valid signing keys (EC or RSA JWK) for webhook verification');
494
+ }
495
+ // Add positive note if everything looks good
496
+ if (recommendations.length === 0) {
497
+ recommendations.push('Profile is well-configured for AI agent commerce!');
498
+ }
499
+ return recommendations;
500
+ }
501
+ /**
502
+ * Calculate overall AI readiness score
503
+ */
504
+ function calculateScore(discovery, capabilities, services, restApi, checkout, payment) {
505
+ let score = 0;
506
+ const maxScore = 100;
507
+ // Discovery (25 points)
508
+ if (discovery.success)
509
+ score += 15;
510
+ if (discovery.services.length > 0)
511
+ score += 5;
512
+ if (discovery.capabilities.length > 0)
513
+ score += 5;
514
+ // Capabilities (25 points)
515
+ const accessibleCapabilities = capabilities.filter(c => c.schemaAccessible);
516
+ if (capabilities.length > 0) {
517
+ score += Math.round((accessibleCapabilities.length / capabilities.length) * 15);
518
+ }
519
+ // Bonus for having checkout
520
+ if (capabilities.some(c => c.name.includes('checkout')))
521
+ score += 10;
522
+ // Services & Transport (25 points)
523
+ for (const service of services) {
524
+ if (service.transports.rest?.endpointResponsive)
525
+ score += 10;
526
+ if (service.transports.rest?.schemaAccessible)
527
+ score += 5;
528
+ if (service.transports.mcp)
529
+ score += 5;
530
+ if (service.transports.a2a?.agentCardAccessible)
531
+ score += 5;
532
+ }
533
+ // Cap at 25
534
+ score = Math.min(score, 75);
535
+ // Checkout (15 points)
536
+ if (checkout?.canCreateCheckout)
537
+ score += 8;
538
+ if (checkout?.checkoutSchemaValid)
539
+ score += 4;
540
+ if (checkout?.orderFlowSupported)
541
+ score += 3;
542
+ // Payment (10 points)
543
+ if (payment?.handlersFound || 0 > 0)
544
+ score += 5;
545
+ if (payment?.signingKeyValid)
546
+ score += 5;
547
+ return Math.min(score, maxScore);
548
+ }
549
+ /**
550
+ * Get grade from score (aligned with all other scoring models)
551
+ */
552
+ function getGrade(score) {
553
+ if (score >= 90)
554
+ return 'A';
555
+ if (score >= 80)
556
+ return 'B';
557
+ if (score >= 70)
558
+ return 'C';
559
+ if (score >= 60)
560
+ return 'D';
561
+ return 'F';
562
+ }
563
+ /**
564
+ * Main simulation entry point
565
+ * Simulates a complete AI agent interaction with a UCP-enabled merchant
566
+ */
567
+ async function simulateAgentInteraction(domain, options = {}) {
568
+ const startTime = Date.now();
569
+ const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
570
+ const fetchTimeout = Math.min(timeoutMs / 3, DEFAULT_FETCH_TIMEOUT_MS);
571
+ // Step 1: Discovery flow
572
+ const discovery = await simulateDiscoveryFlow(domain, fetchTimeout);
573
+ // Early exit if discovery failed
574
+ if (!discovery.success || !discovery.profileUrl) {
575
+ const durationMs = Date.now() - startTime;
576
+ return {
577
+ ok: false,
578
+ domain,
579
+ simulatedAt: new Date().toISOString(),
580
+ durationMs,
581
+ overallScore: 0,
582
+ grade: 'F',
583
+ discovery,
584
+ capabilities: [],
585
+ services: [],
586
+ summary: {
587
+ totalSteps: discovery.steps.length,
588
+ passedSteps: discovery.steps.filter(s => s.status === 'passed').length,
589
+ failedSteps: discovery.steps.filter(s => s.status === 'failed').length,
590
+ warningSteps: discovery.steps.filter(s => s.status === 'warning').length,
591
+ skippedSteps: discovery.steps.filter(s => s.status === 'skipped').length,
592
+ },
593
+ recommendations: ['Ensure UCP profile is accessible at /.well-known/ucp or /.well-known/ucp.json'],
594
+ };
595
+ }
596
+ // Fetch full profile for detailed inspection
597
+ const profileResult = await fetchWithTimeout(discovery.profileUrl, fetchTimeout);
598
+ const profile = profileResult.data;
599
+ // Step 2: Inspect capabilities
600
+ const capabilities = await inspectCapabilities(profile, fetchTimeout);
601
+ // Step 3: Inspect services
602
+ const services = await inspectServices(profile, fetchTimeout);
603
+ // Step 4: REST API simulation (if applicable)
604
+ let restApi;
605
+ if (!options.skipRestApiTest) {
606
+ restApi = await simulateRestApi(profile, fetchTimeout);
607
+ }
608
+ // Step 5: Checkout flow simulation
609
+ let checkout;
610
+ if (options.testCheckoutFlow !== false) {
611
+ checkout = await simulateCheckoutFlow(profile, fetchTimeout);
612
+ }
613
+ // Step 6: Payment readiness check
614
+ const payment = await simulatePaymentReadiness(profile, fetchTimeout);
615
+ // Collect all steps
616
+ const allSteps = [
617
+ ...discovery.steps,
618
+ ...(restApi?.steps || []),
619
+ ...(checkout?.steps || []),
620
+ ...payment.steps,
621
+ ];
622
+ const durationMs = Date.now() - startTime;
623
+ // Generate recommendations
624
+ const recommendations = generateRecommendations(discovery, capabilities, services, restApi, checkout, payment);
625
+ // Calculate score
626
+ const overallScore = calculateScore(discovery, capabilities, services, restApi, checkout, payment);
627
+ return {
628
+ ok: discovery.success && (checkout?.success ?? true),
629
+ domain,
630
+ simulatedAt: new Date().toISOString(),
631
+ durationMs,
632
+ overallScore,
633
+ grade: getGrade(overallScore),
634
+ discovery,
635
+ capabilities,
636
+ services,
637
+ restApi,
638
+ checkout,
639
+ payment,
640
+ summary: {
641
+ totalSteps: allSteps.length,
642
+ passedSteps: allSteps.filter(s => s.status === 'passed').length,
643
+ failedSteps: allSteps.filter(s => s.status === 'failed').length,
644
+ warningSteps: allSteps.filter(s => s.status === 'warning').length,
645
+ skippedSteps: allSteps.filter(s => s.status === 'skipped').length,
646
+ },
647
+ recommendations,
648
+ };
649
+ }
650
+ //# sourceMappingURL=agent-simulator.js.map