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