@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
|
@@ -1,941 +0,0 @@
|
|
|
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
|
-
};
|