@ucptools/validator 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +60 -0
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +279 -0
- package/dist/cli/index.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 +11 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +63 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +444 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +65 -0
- package/dist/db/schema.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} +642 -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 +204 -0
- package/dist/feed-analyzer/types.d.ts.map +1 -0
- package/dist/feed-analyzer/types.js +162 -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/{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 +541 -0
- package/dist/security/security-scanner.js.map +1 -0
- package/dist/security/types.d.ts +48 -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/directory.d.ts +104 -0
- package/dist/services/directory.d.ts.map +1 -0
- package/dist/services/directory.js +333 -0
- package/dist/services/directory.js.map +1 -0
- package/dist/simulator/agent-simulator.d.ts +69 -0
- package/dist/simulator/agent-simulator.d.ts.map +1 -0
- package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
- 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} +145 -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/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 +103 -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 +68 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +32 -0
- package/dist/types/validation.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 +11 -0
- package/dist/validator/rules-validator.d.ts.map +1 -0
- package/dist/validator/rules-validator.js +257 -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 +415 -0
- package/dist/validator/structural-validator.js.map +1 -0
- package/package.json +1 -1
- package/publish-output.txt +0 -0
- 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/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/vercel.json +0 -34
- package/vitest.config.ts +0 -22
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit Tests for UCP Profile Validation Logic
|
|
3
|
-
* Tests the core validation functionality
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect } from 'vitest';
|
|
7
|
-
|
|
8
|
-
// Validation helper functions (extracted from api/validate.js logic)
|
|
9
|
-
const VERSION_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
10
|
-
|
|
11
|
-
function validateVersion(version: string): boolean {
|
|
12
|
-
return VERSION_REGEX.test(version);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function calculateGrade(score: number): string {
|
|
16
|
-
if (score >= 90) return 'A';
|
|
17
|
-
if (score >= 80) return 'B';
|
|
18
|
-
if (score >= 70) return 'C';
|
|
19
|
-
if (score >= 60) return 'D';
|
|
20
|
-
return 'F';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getReadinessLevel(score: number, hasUcp: boolean) {
|
|
24
|
-
if (score >= 90 && hasUcp) {
|
|
25
|
-
return { level: 'ready', label: 'AI Commerce Ready' };
|
|
26
|
-
}
|
|
27
|
-
if (score >= 70 && hasUcp) {
|
|
28
|
-
return { level: 'partial', label: 'Partially Ready' };
|
|
29
|
-
}
|
|
30
|
-
if (hasUcp || score >= 50) {
|
|
31
|
-
return { level: 'limited', label: 'Limited Readiness' };
|
|
32
|
-
}
|
|
33
|
-
return { level: 'not_ready', label: 'Not Ready' };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function hasValue(val: unknown): boolean {
|
|
37
|
-
if (val === null || val === undefined) return false;
|
|
38
|
-
if (typeof val === 'string') return val.trim().length > 0;
|
|
39
|
-
if (Array.isArray(val)) return val.length > 0;
|
|
40
|
-
if (typeof val === 'object') return Object.keys(val).length > 0;
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe('UCP Version Validation', () => {
|
|
45
|
-
it('should accept valid date format YYYY-MM-DD', () => {
|
|
46
|
-
expect(validateVersion('2024-01-15')).toBe(true);
|
|
47
|
-
expect(validateVersion('2025-12-31')).toBe(true);
|
|
48
|
-
expect(validateVersion('2023-06-01')).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should reject invalid formats', () => {
|
|
52
|
-
expect(validateVersion('2024-1-15')).toBe(false); // Single digit month
|
|
53
|
-
expect(validateVersion('2024/01/15')).toBe(false); // Wrong separator
|
|
54
|
-
expect(validateVersion('24-01-15')).toBe(false); // Short year
|
|
55
|
-
expect(validateVersion('2024-01')).toBe(false); // Missing day
|
|
56
|
-
expect(validateVersion('v1.0.0')).toBe(false); // Semver
|
|
57
|
-
expect(validateVersion('1.0')).toBe(false); // Numeric
|
|
58
|
-
expect(validateVersion('')).toBe(false); // Empty
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('Grade Calculation', () => {
|
|
63
|
-
it('should return A for scores >= 90', () => {
|
|
64
|
-
expect(calculateGrade(90)).toBe('A');
|
|
65
|
-
expect(calculateGrade(95)).toBe('A');
|
|
66
|
-
expect(calculateGrade(100)).toBe('A');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should return B for scores 80-89', () => {
|
|
70
|
-
expect(calculateGrade(80)).toBe('B');
|
|
71
|
-
expect(calculateGrade(85)).toBe('B');
|
|
72
|
-
expect(calculateGrade(89)).toBe('B');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should return C for scores 70-79', () => {
|
|
76
|
-
expect(calculateGrade(70)).toBe('C');
|
|
77
|
-
expect(calculateGrade(75)).toBe('C');
|
|
78
|
-
expect(calculateGrade(79)).toBe('C');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should return D for scores 60-69', () => {
|
|
82
|
-
expect(calculateGrade(60)).toBe('D');
|
|
83
|
-
expect(calculateGrade(65)).toBe('D');
|
|
84
|
-
expect(calculateGrade(69)).toBe('D');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should return F for scores < 60', () => {
|
|
88
|
-
expect(calculateGrade(59)).toBe('F');
|
|
89
|
-
expect(calculateGrade(50)).toBe('F');
|
|
90
|
-
expect(calculateGrade(0)).toBe('F');
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('Readiness Level Calculation', () => {
|
|
95
|
-
it('should return ready for high score with UCP', () => {
|
|
96
|
-
const result = getReadinessLevel(95, true);
|
|
97
|
-
expect(result.level).toBe('ready');
|
|
98
|
-
expect(result.label).toBe('AI Commerce Ready');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should return partial for medium score with UCP', () => {
|
|
102
|
-
const result = getReadinessLevel(75, true);
|
|
103
|
-
expect(result.level).toBe('partial');
|
|
104
|
-
expect(result.label).toBe('Partially Ready');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should return limited for low score with UCP', () => {
|
|
108
|
-
const result = getReadinessLevel(55, true);
|
|
109
|
-
expect(result.level).toBe('limited');
|
|
110
|
-
expect(result.label).toBe('Limited Readiness');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should return limited for medium score without UCP', () => {
|
|
114
|
-
const result = getReadinessLevel(60, false);
|
|
115
|
-
expect(result.level).toBe('limited');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should return not_ready for very low score without UCP', () => {
|
|
119
|
-
const result = getReadinessLevel(30, false);
|
|
120
|
-
expect(result.level).toBe('not_ready');
|
|
121
|
-
expect(result.label).toBe('Not Ready');
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('hasValue Helper', () => {
|
|
126
|
-
it('should return false for null and undefined', () => {
|
|
127
|
-
expect(hasValue(null)).toBe(false);
|
|
128
|
-
expect(hasValue(undefined)).toBe(false);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should return false for empty strings', () => {
|
|
132
|
-
expect(hasValue('')).toBe(false);
|
|
133
|
-
expect(hasValue(' ')).toBe(false);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should return true for non-empty strings', () => {
|
|
137
|
-
expect(hasValue('hello')).toBe(true);
|
|
138
|
-
expect(hasValue(' hello ')).toBe(true);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should return false for empty arrays', () => {
|
|
142
|
-
expect(hasValue([])).toBe(false);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should return true for non-empty arrays', () => {
|
|
146
|
-
expect(hasValue([1, 2, 3])).toBe(true);
|
|
147
|
-
expect(hasValue([''])).toBe(true);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should return false for empty objects', () => {
|
|
151
|
-
expect(hasValue({})).toBe(false);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('should return true for non-empty objects', () => {
|
|
155
|
-
expect(hasValue({ key: 'value' })).toBe(true);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should return true for numbers', () => {
|
|
159
|
-
expect(hasValue(0)).toBe(true);
|
|
160
|
-
expect(hasValue(42)).toBe(true);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('should return true for booleans', () => {
|
|
164
|
-
expect(hasValue(true)).toBe(true);
|
|
165
|
-
expect(hasValue(false)).toBe(true);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('UCP Profile Structure Validation', () => {
|
|
170
|
-
interface UcpService {
|
|
171
|
-
version?: string;
|
|
172
|
-
spec?: string;
|
|
173
|
-
rest?: { endpoint?: string };
|
|
174
|
-
mcp?: unknown;
|
|
175
|
-
a2a?: unknown;
|
|
176
|
-
embedded?: unknown;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
interface UcpCapability {
|
|
180
|
-
name?: string;
|
|
181
|
-
version?: string;
|
|
182
|
-
spec?: string;
|
|
183
|
-
schema?: string;
|
|
184
|
-
extends?: string;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
interface UcpProfile {
|
|
188
|
-
ucp?: {
|
|
189
|
-
version?: string;
|
|
190
|
-
services?: Record<string, UcpService>;
|
|
191
|
-
capabilities?: UcpCapability[];
|
|
192
|
-
};
|
|
193
|
-
signing_keys?: unknown[];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function validateProfile(profile: UcpProfile) {
|
|
197
|
-
const issues: { code: string; message: string; severity: string }[] = [];
|
|
198
|
-
|
|
199
|
-
if (!profile || typeof profile !== 'object') {
|
|
200
|
-
issues.push({ code: 'UCP_MISSING_ROOT', message: 'Profile must be a JSON object', severity: 'error' });
|
|
201
|
-
return issues;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (!profile.ucp || typeof profile.ucp !== 'object') {
|
|
205
|
-
issues.push({ code: 'UCP_MISSING_ROOT', message: 'Missing required "ucp" object', severity: 'error' });
|
|
206
|
-
return issues;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const ucp = profile.ucp;
|
|
210
|
-
|
|
211
|
-
// Version validation
|
|
212
|
-
if (!ucp.version) {
|
|
213
|
-
issues.push({ code: 'UCP_MISSING_VERSION', message: 'Missing version field', severity: 'error' });
|
|
214
|
-
} else if (!VERSION_REGEX.test(ucp.version)) {
|
|
215
|
-
issues.push({ code: 'UCP_INVALID_VERSION', message: `Invalid version: ${ucp.version}`, severity: 'error' });
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Services validation
|
|
219
|
-
if (!ucp.services || typeof ucp.services !== 'object') {
|
|
220
|
-
issues.push({ code: 'UCP_MISSING_SERVICES', message: 'Missing services', severity: 'error' });
|
|
221
|
-
} else {
|
|
222
|
-
for (const [name, svc] of Object.entries(ucp.services)) {
|
|
223
|
-
if (!svc.version) {
|
|
224
|
-
issues.push({ code: 'UCP_INVALID_SERVICE', message: `Service "${name}" missing version`, severity: 'error' });
|
|
225
|
-
}
|
|
226
|
-
if (!svc.spec) {
|
|
227
|
-
issues.push({ code: 'UCP_INVALID_SERVICE', message: `Service "${name}" missing spec`, severity: 'error' });
|
|
228
|
-
}
|
|
229
|
-
if (!svc.rest && !svc.mcp && !svc.a2a && !svc.embedded) {
|
|
230
|
-
issues.push({ code: 'UCP_NO_TRANSPORT', message: `Service "${name}" has no transport bindings`, severity: 'warn' });
|
|
231
|
-
}
|
|
232
|
-
if (svc.rest?.endpoint && !svc.rest.endpoint.startsWith('https://')) {
|
|
233
|
-
issues.push({ code: 'UCP_ENDPOINT_NOT_HTTPS', message: 'Endpoint must use HTTPS', severity: 'error' });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Capabilities validation
|
|
239
|
-
if (!ucp.capabilities || !Array.isArray(ucp.capabilities)) {
|
|
240
|
-
issues.push({ code: 'UCP_MISSING_CAPABILITIES', message: 'Missing capabilities array', severity: 'error' });
|
|
241
|
-
} else {
|
|
242
|
-
const capNames = new Set(ucp.capabilities.map((c) => c.name));
|
|
243
|
-
|
|
244
|
-
ucp.capabilities.forEach((cap, i) => {
|
|
245
|
-
if (!cap.name) {
|
|
246
|
-
issues.push({ code: 'UCP_INVALID_CAP', message: `Capability ${i} missing name`, severity: 'error' });
|
|
247
|
-
}
|
|
248
|
-
if (!cap.version) {
|
|
249
|
-
issues.push({ code: 'UCP_INVALID_CAP', message: `Capability ${i} missing version`, severity: 'error' });
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Namespace binding check
|
|
253
|
-
if (cap.name?.startsWith('dev.ucp.')) {
|
|
254
|
-
if (cap.spec && !cap.spec.startsWith('https://ucp.dev/')) {
|
|
255
|
-
issues.push({ code: 'UCP_NS_MISMATCH', message: 'dev.ucp.* spec must be on ucp.dev', severity: 'error' });
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Extension check
|
|
260
|
-
if (cap.extends && !capNames.has(cap.extends)) {
|
|
261
|
-
issues.push({ code: 'UCP_ORPHAN_EXT', message: `Parent "${cap.extends}" not found`, severity: 'error' });
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// Signing keys check for order capability
|
|
266
|
-
const hasOrder = ucp.capabilities.some((c) => c.name === 'dev.ucp.shopping.order');
|
|
267
|
-
if (hasOrder && (!profile.signing_keys || profile.signing_keys.length === 0)) {
|
|
268
|
-
issues.push({ code: 'UCP_MISSING_KEYS', message: 'Order requires signing_keys', severity: 'error' });
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return issues;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
it('should reject profile without ucp object', () => {
|
|
276
|
-
const issues = validateProfile({} as UcpProfile);
|
|
277
|
-
expect(issues.some((i) => i.code === 'UCP_MISSING_ROOT')).toBe(true);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it('should reject profile without version', () => {
|
|
281
|
-
const issues = validateProfile({ ucp: { services: {}, capabilities: [] } });
|
|
282
|
-
expect(issues.some((i) => i.code === 'UCP_MISSING_VERSION')).toBe(true);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('should reject invalid version format', () => {
|
|
286
|
-
const issues = validateProfile({ ucp: { version: 'v1.0', services: {}, capabilities: [] } });
|
|
287
|
-
expect(issues.some((i) => i.code === 'UCP_INVALID_VERSION')).toBe(true);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('should reject service without version', () => {
|
|
291
|
-
const issues = validateProfile({
|
|
292
|
-
ucp: {
|
|
293
|
-
version: '2024-01-01',
|
|
294
|
-
services: { shopping: { spec: 'https://example.com/spec' } },
|
|
295
|
-
capabilities: [],
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
expect(issues.some((i) => i.code === 'UCP_INVALID_SERVICE')).toBe(true);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it('should warn about service without transport', () => {
|
|
302
|
-
const issues = validateProfile({
|
|
303
|
-
ucp: {
|
|
304
|
-
version: '2024-01-01',
|
|
305
|
-
services: { shopping: { version: '1.0', spec: 'https://example.com/spec' } },
|
|
306
|
-
capabilities: [],
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
expect(issues.some((i) => i.code === 'UCP_NO_TRANSPORT')).toBe(true);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it('should reject non-HTTPS endpoints', () => {
|
|
313
|
-
const issues = validateProfile({
|
|
314
|
-
ucp: {
|
|
315
|
-
version: '2024-01-01',
|
|
316
|
-
services: {
|
|
317
|
-
shopping: {
|
|
318
|
-
version: '1.0',
|
|
319
|
-
spec: 'https://example.com/spec',
|
|
320
|
-
rest: { endpoint: 'http://api.example.com' },
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
capabilities: [],
|
|
324
|
-
},
|
|
325
|
-
});
|
|
326
|
-
expect(issues.some((i) => i.code === 'UCP_ENDPOINT_NOT_HTTPS')).toBe(true);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('should accept valid profile', () => {
|
|
330
|
-
const issues = validateProfile({
|
|
331
|
-
ucp: {
|
|
332
|
-
version: '2024-01-01',
|
|
333
|
-
services: {
|
|
334
|
-
shopping: {
|
|
335
|
-
version: '1.0',
|
|
336
|
-
spec: 'https://ucp.dev/spec',
|
|
337
|
-
rest: { endpoint: 'https://api.example.com' },
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
capabilities: [
|
|
341
|
-
{ name: 'dev.ucp.shopping.checkout', version: '1.0', spec: 'https://ucp.dev/cap', schema: 'https://ucp.dev/schema' },
|
|
342
|
-
],
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
const errors = issues.filter((i) => i.severity === 'error');
|
|
347
|
-
expect(errors).toHaveLength(0);
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it('should require signing_keys for order capability', () => {
|
|
351
|
-
const issues = validateProfile({
|
|
352
|
-
ucp: {
|
|
353
|
-
version: '2024-01-01',
|
|
354
|
-
services: { shopping: { version: '1.0', spec: 'https://ucp.dev/spec', rest: {} } },
|
|
355
|
-
capabilities: [{ name: 'dev.ucp.shopping.order', version: '1.0', spec: 'https://ucp.dev/cap', schema: 'https://ucp.dev/schema' }],
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
expect(issues.some((i) => i.code === 'UCP_MISSING_KEYS')).toBe(true);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('should accept order capability with signing_keys', () => {
|
|
362
|
-
const issues = validateProfile({
|
|
363
|
-
ucp: {
|
|
364
|
-
version: '2024-01-01',
|
|
365
|
-
services: { shopping: { version: '1.0', spec: 'https://ucp.dev/spec', rest: {} } },
|
|
366
|
-
capabilities: [{ name: 'dev.ucp.shopping.order', version: '1.0', spec: 'https://ucp.dev/cap', schema: 'https://ucp.dev/schema' }],
|
|
367
|
-
},
|
|
368
|
-
signing_keys: [{ kid: 'key1' }],
|
|
369
|
-
});
|
|
370
|
-
expect(issues.some((i) => i.code === 'UCP_MISSING_KEYS')).toBe(false);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it('should enforce namespace binding for dev.ucp.*', () => {
|
|
374
|
-
const issues = validateProfile({
|
|
375
|
-
ucp: {
|
|
376
|
-
version: '2024-01-01',
|
|
377
|
-
services: {},
|
|
378
|
-
capabilities: [
|
|
379
|
-
{
|
|
380
|
-
name: 'dev.ucp.shopping.checkout',
|
|
381
|
-
version: '1.0',
|
|
382
|
-
spec: 'https://example.com/spec', // Wrong domain
|
|
383
|
-
schema: 'https://ucp.dev/schema',
|
|
384
|
-
},
|
|
385
|
-
],
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
expect(issues.some((i) => i.code === 'UCP_NS_MISMATCH')).toBe(true);
|
|
389
|
-
});
|
|
390
|
-
});
|
package/vercel.json
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 2,
|
|
3
|
-
"buildCommand": null,
|
|
4
|
-
"outputDirectory": "public",
|
|
5
|
-
"cleanUrls": true,
|
|
6
|
-
"rewrites": [
|
|
7
|
-
{ "source": "/api/(.*)", "destination": "/api/$1" }
|
|
8
|
-
],
|
|
9
|
-
"headers": [
|
|
10
|
-
{
|
|
11
|
-
"source": "/(.*)",
|
|
12
|
-
"headers": [
|
|
13
|
-
{ "key": "X-Content-Type-Options", "value": "nosniff" },
|
|
14
|
-
{ "key": "X-Frame-Options", "value": "DENY" },
|
|
15
|
-
{ "key": "X-XSS-Protection", "value": "1; mode=block" }
|
|
16
|
-
]
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"source": "/api/(.*)",
|
|
20
|
-
"headers": [
|
|
21
|
-
{ "key": "Access-Control-Allow-Origin", "value": "*" },
|
|
22
|
-
{ "key": "Access-Control-Allow-Methods", "value": "GET, POST, OPTIONS" },
|
|
23
|
-
{ "key": "Access-Control-Allow-Headers", "value": "Content-Type" }
|
|
24
|
-
]
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"source": "/.well-known/ucp",
|
|
28
|
-
"headers": [
|
|
29
|
-
{ "key": "Content-Type", "value": "application/json" },
|
|
30
|
-
{ "key": "Access-Control-Allow-Origin", "value": "*" }
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
}
|
package/vitest.config.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vitest/config';
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
test: {
|
|
5
|
-
globals: true,
|
|
6
|
-
environment: 'node',
|
|
7
|
-
include: ['tests/**/*.test.ts'],
|
|
8
|
-
coverage: {
|
|
9
|
-
provider: 'v8',
|
|
10
|
-
reporter: ['text', 'html'],
|
|
11
|
-
include: ['src/**/*.ts', 'api/**/*.ts'],
|
|
12
|
-
exclude: ['**/*.d.ts', '**/types/**'],
|
|
13
|
-
},
|
|
14
|
-
setupFiles: ['tests/setup.ts'],
|
|
15
|
-
testTimeout: 30000,
|
|
16
|
-
hookTimeout: 30000,
|
|
17
|
-
},
|
|
18
|
-
// Use CommonJS mode for @ucp-js/sdk due to ESM extensionless import bug
|
|
19
|
-
ssr: {
|
|
20
|
-
noExternal: ['@ucp-js/sdk'],
|
|
21
|
-
},
|
|
22
|
-
});
|